美文网首页
Java8实战笔记-1

Java8实战笔记-1

作者: 王小杰at2019 | 来源:发表于2019-01-18 17:17 被阅读78次

行为参数化

  •  行为参数化,就是一个方法接受多个不同的行为作为参数,并在内部使用它们,完成不
  • 同行为的能力。
  •  行为参数化可让代码更好地适应不断变化的要求,减轻未来的工作量。
  •  传递代码,就是将新行为作为参数传递给方法。但在Java 8之前这实现起来很啰嗦。为接
  • 口声明许多只用一次的实体类而造成的啰嗦代码,在Java 8之前可以用匿名类来减少。
  •  Java API包含很多可以用不同行为进行参数化的方法,包括排序、线程和GUI处理。

Lambda表达式

  1. lambda
    • 参数列表——这里它采用了 Comparator 中 compare 方法的参数,两个 Apple 。
    • 箭头——箭头 -> 把参数列表与Lambda主体分隔开。
    • Lambda主体——比较两个 Apple 的重量。表达式就是Lambda的返回值了。
lambda
  1. 比较两个苹果重量的例子

先前:

Comparator<Apple> byWeight = new Comparator<Apple>() {
public int compare(Apple a1, Apple a2){
return a1.getWeight().compareTo(a2.getWeight());
}
};

之后(用了Lambda表达式):

Comparator<Apple> byWeight =
(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
  1. lambda好处
  • 匿名——我们说匿名,是因为它不像普通的方法那样有一个明确的名称:写得少而想
    得多!
  • 函数——我们说它是函数,是因为Lambda函数不像方法那样属于某个特定的类。但和方
    法一样,Lambda有参数列表、函数主体、返回类型,还可能有可以抛出的异常列表。
  • 传递——Lambda表达式可以作为参数传递给方法或存储在变量中。
  • 简洁——无需像匿名类那样写很多模板代码
  1. 示例

根据上述语法规则,以下哪个不是有效的Lambda表达式?

  • (1) () -> {}
  • (2) () -> "Raoul"
  • (3) () -> {return "Mario";}
  • (4) (Integer i) -> return "Alan" + i;
  • (5) (String s) -> {"IronMan";}

答案:只有4和5是无效的Lambda。

  • (1) 这个Lambda没有参数,并返回 void 。它类似于主体为空的方法: public void run() {} 。
  • (2) 这个Lambda没有参数,并返回 String 作为表达式。
  • (3) 这个Lambda没有参数,并返回 String (利用显式返回语句)。
  • (4) return 是一个控制流语句。要使此Lambda有效,需要使花括号,如下所示:
  • (Integer i) -> {return "Alan" + i;} 。
  • (5)“Iron Man”是一个表达式,不是一个语句。要使此Lambda有效,你可以去除花括号
  • 和分号,如下所示: (String s) -> "Iron Man" 。或者如果你喜欢,可以使用显式返回语
  • 句,如下所示: (String s)->{return "IronMan";}

 在哪里以及如何使用Lambda

  1. 函数式接口

    函数式接口就是只定义一个抽象方法的接口。你已经知道了Java API中的一些其他函数式接口例如 Comparator 和 Runnable 。 如果你去看看新的Java API,会发现函数式接口带有@FunctionalInterface 的标注

    Lambda表达式允许你直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例(具体说来,是函数式接口一个具体实现的实例)。你用匿名内部类也可以完成同样的事情,只不过比较笨拙

  2. 函数描述符
    函数式接口的抽象方法的签名基本上就是Lambda表达式的签名。我们将这种抽象方法叫作
    函数描述符。例如, Runnable 接口可以看作一个什么也不接受什么也不返回( void )的函数的
    签名,因为它只有一个叫作 run 的抽象方法,这个方法什么也不接受,什么也不返回( void )。

    我们在本章中使用了一个特殊表示法来描述Lambda和函数式接口的签名。 () -> void 代表
    了参数列表为空,且返回 void 的函数。这正是 Runnable 接口所代表的。 举另一个例子, (Apple,
    Apple) -> int 代表接受两个 Apple 作为参数且返回 int 的函数。我们会在3.4节和本章后面的
    表3-2中提供关于函数描述符的更多信息

 环绕执行模式

  1. 记得行为参数化
  2. 使用函数式接口来传递行为
  3. 执行一个行为
  4. 传递 Lambda

@FunctionalInterface
interface RowMap<T> {
    T mapped(ResultSet resultSet) throws SQLException;
}

@Data
@AllArgsConstructor
@NoArgsConstructor
class Teacher {
    String name;
    int age;
}

@Data
@AllArgsConstructor
@NoArgsConstructor
class User {
    String name;
    int age;
}

class QueryUtil {
    public <T> List<T> getList(String sql, RowMap<T> rm) throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.jdbc.Driver");
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "root");
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        ResultSet resultSet = preparedStatement.executeQuery();
        List<T> temp = new ArrayList<>();
        while (resultSet.next()) {
            T mapped = rm.mapped(resultSet);
            temp.add(mapped);
        }
        return temp;
    }

}

public class Around1 {
    public static void main(String[] args) throws SQLException, ClassNotFoundException {
        QueryUtil userDao = new QueryUtil();
        List<User> list = userDao.getList("SELECT * FROM user",
                resultSet ->
                        new User(resultSet.getString("name"), resultSet.getInt("age")
                        )
        );

        System.out.println(JSON.toJSONString(list));
        List<Teacher> teachers = userDao.getList("SELECT * FROM teacher",
                resultSet ->
                        new Teacher(resultSet.getString("name"), resultSet.getInt("age")
                        )
        );
        System.out.println(JSON.toJSONString(teachers));
    }

}


使用函数式接口

  1. predicate
    Predicate<T> T->boolean
    java.util.function.Predicate<T> 接口定义了一个名叫 test 的抽象方法,它接受泛型
    T 对象,并返回一个 boolean 。

  2. Consumer Consumer<T> T->void
    java.util.function.Consumer<T> 定义了一个名叫 accept 的抽象方法,它接受泛型 T
    的对象,没有返回( void )。

  3. Function Function<T,R> T->R
    java.util.function.Function<T, R> 接口定义了一个叫作 apply 的方法,它接受一个
    泛型 T 的对象,并返回一个泛型 R 的对象。

//Predicate T->boolean  
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 67, 8, 9);
List<Integer> collect = list.stream().filter((a -> a > 5)).collect(Collectors.toList());
System.out.println(JSON.toJSONString(collect));

//Consumer  T->void 
list.forEach(a -> {
    System.out.println(a);
});

//接收接收一个T类型,返回一个R类型   T->R 
List<String> collect1 = list.stream().map(a -> a + 1+"").collect(Collectors.toList());
System.out.println(JSON.toJSONString(collect1));

方法引用

Lambda及其等效方法引用的例子

Lambda 等效的方法引用
(Apple a) -> a.getWeight() Apple::getWeight
() -> Thread.currentThread().dumpStack() Thread.currentThread()::dumpStack
(str, i) -> str.substring(i) String::substring
(String s) -> System.out.println(s) System.out::println
  1. 指向静态方法的方法引用(例如 Integer 的 parseInt 方法,写作 Integer::parseInt )。
  2. 指 向 任 意 类 型 实 例 方 法 的 方 法 引 用 ( 例 如 String 的 length 方 法 , 写 作
    String::length )。
  3. 指向现有对象的实例方法的方法引用(假设你有一个局部变量 expensiveTransaction
    用于存放 Transaction 类型的对象,它支持实例方法 getValue ,那么你就可以写 expensive-
    Transaction::getValue )。
appleList.stream().map(Apple::getColor).forEach(System.out::println);
appleList.forEach(System.out::println);

Lambda复合

什么是流

流是Java API的新成员,它允许你以声明性方式处理数据集合(通过查询语句来表达,而不
是临时编写一个实现),就现在来说,你可以把它们看成遍历数据集的高级迭代器。此外,流还可以透明地并行处理,你无需写任何多线程代码了!

其他库:Guava、Apache和lambdaj

为了给Java程序员提供更好的库操作集合,前人已经做过了很多尝试。比如,Guava就是
谷歌创建的一个很流行的库。它提供了 multimaps 和 multisets 等额外的容器类。Apache
Commons Collections库也提供了类似的功能。最后,本书作者Mario Fusco编写的lambdaj受到
函数式编程的启发,也提供了很多声明性操作集合的工具。
如今Java 8自带了官方库,可以以更加声明性的方式操作集合了。

“从支持数据处理操作的源生成的元素序列”。让
我们一步步剖析这个定义。

  • 元素序列——就像集合一样,流也提供了一个接口,可以访问特定元素类型的一组有序
    值。因为集合是数据结构,所以它的主要目的是以特定的时间/空间复杂度存储和访问元
    素(如 ArrayList 与 LinkedList )。但流的目的在于表达计算,比如你前面见到的
    filter 、 sorted 和 map 。集合讲的是数据,流讲的是计算。我们会在后面几节中详细解
    释这个思想。
  • 源——流会使用一个提供数据的源,如集合、数组或输入/输出资源。 请注意,从有序集
    合生成流时会保留原有的顺序。由列表生成的流,其元素顺序与列表一致。
  • 数据处理操作——流的数据处理功能支持类似于数据库的操作,以及函数式编程语言中
    的常用操作,如 filter 、 map 、 reduce 、 find 、 match 、 sort 等。流操作可以顺序执
    行,也可并行执行。
    此外,流操作有两个重要的特点。
  • 流水线——很多流操作本身会返回一个流,这样多个操作就可以链接起来,形成一个大
    的流水线。这让我们下一章中的一些优化成为可能,如延迟和短路。流水线的操作可以
    看作对数据源进行数据库式查询。
  • 内部迭代——与使用迭代器显式迭代的集合不同,流的迭代操作是在背后进行的。
image

集合与流

请注意,和迭代器类似,流只能遍历一次。遍历完之后,我们就说这个流已经被消费掉了

 Stream<Apple> stream = appleList.stream();
        stream.forEach(System.out::println);
        stream.forEach(System.out::println);
        

Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
    at java.util.stream.AbstractPipeline.sourceStageSpliterator(AbstractPipeline.java:279)
    at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)
    at cn.wyj.jdk7.TestFunction.main(TestFunction.java:59)

内部迭代与外部迭代

image

中间操作与终端操作

中间操作

诸如 filter 或 sorted 等中间操作会返回另一个流。这让多个操作可以连接起来形成一个查
询。重要的是,除非流水线上触发一个终端操作,否则中间操作不会执行任何处理——它们很懒。
这是因为中间操作一般都可以合并起来,在终端操作时一次性全部处理。

image

终端操作

端操作会从流的流水线生成结果。其结果是任何不是流的值,比如 List 、 Integer ,甚
至 void 。例如,在下面的流水线中, forEach 是一个返回 void 的终端操作,它会对源中的每道
菜应用一个Lambda。把 System.out.println 传递给 forEach ,并要求它打印出由 menu 生成的
流中的每一个 Dish :
menu.stream().forEach(System.out::println);

使用流

筛选、切片和匹配

//筛选
List<Dish> vegetarianMenu = menu.stream()
.filter(Dish::isVegetarian)
.collect(toList());


//去重
List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
numbers.stream()
.filter(i -> i % 2 == 0)
.distinct()
.forEach(System.out::println);

//截短流
List<Dish> dishes = menu.stream()
.filter(d -> d.getCalories() > 300)
.limit(3)
.collect(toList());

// 跳过
List<Dish> dishes = menu.stream()
.filter(d -> d.getCalories() > 300)
.skip(2)
.collect(toList());

映射

对流中每一个元素应用函数

//如果你要找出每道菜的名称有多长

List<Integer> dishNameLengths = menu.stream()
.map(Dish::getName)
.map(String::length)
.collect(toList());

流的扁平化

使用 flatMap 方法的效果是,各个数组并不是分别映射成一个流,而是映射成流的内容。所
有使用 map(Arrays::stream) 时生成的单个流都被合并起来,即扁平化为一个流。

List<String> uniqueCharacters =
words.stream()
.map(w -> w.split(""))
.flatMap(Arrays::stream)
.distinct()
.collect(Collectors.toList());

查找、匹配和归约

使用数值范围等数值流

从多个源创建流

无限流

相关文章

  • java8 资料收集

    1.跟上Java8 - 带你实战Java8 https://zhuanlan.zhihu.com/java8 王爵...

  • Java8实战笔记-1

    行为参数化  行为参数化,就是一个方法接受多个不同的行为作为参数,并在内部使用它们,完成不 同行为的能力。 ...

  • 新年第一本书

    java8实战下周写总结

  • Java8中的并行流

    此笔记是我在阅读《Java8实战》时的一些记录。 Java8中增加了流(stream)的概念,为数据的处理带来了很...

  • 《Java8 实战》- 读书笔记第一章(02)

    《Java8 实战》- 读书笔记第一章(02) 2018-12-29 22:56 folder Java[http...

  • 表达的梯子20201210

    最近看《Java8 实战》这本书关于Lambda表达式的内容(《Java8 实战》这是一本计算机编程方面的书,其中...

  • Java8实战-中文版.pdf 免费下载

    下载地址:Java8实战-中文版.pdf

  • 2018学习计划

    摘自我的github 一.读书 1.《疯狂java讲义4》 2.《java8实战》 3.《springCloud微...

  • 2018-09-25

    java学习笔记(三) 简单的讲讲行为参数化传递代码,这也是Java8实战的第二章 应对不断变化的需求 在软件工程...

  • 2018-09-11

    Java学习笔记(一) 第一次写作。这几天看了Java8实战,感觉有必要写写!Java 8 发布至今也已经好几年过...

网友评论

      本文标题:Java8实战笔记-1

      本文链接:https://www.haomeiwen.com/subject/qdandqtx.html