目录
一, 方法和构造函数引用
二, 允许在接口中有默认方法实现
三, 时间日期 API
四, 内置函数式接口
- Predicate
- Function
- Optional
- Streams
- Filter
- Sorted
- Map
- Match
- Count
- Reduce
- Parallel Streams
五, 处理顺序
一, 方法和构造函数引用
- Converter<String, Integer> converter = Integer::valueOf;
- Integer converted = converter.convert("123");
- System.out.println(converted); // 123
Java 8 允许你通过:: 关键字获取方法或者构造函数的的引用. 上面的例子就演示了如何引用一个静态方法. 而且, 我们还可以对一个对象的方法进行引用:
- class Something {
- String startsWith(String s) {
- return String.valueOf(s.charAt(0));
- }
- }
- Something something = new Something();
- Converter<String, String> converter = something::startsWith;
- String converted = converter.convert("Java");
- System.out.println(converted); // "J"
让我们看看如何使用:: 关键字引用构造函数. 首先我们定义一个示例 bean, 包含不同的构造方法:
- class Person {
- String firstName;
- String lastName;
- Person() {}
- Person(String firstName, String lastName) {
- this.firstName = firstName;
- this.lastName = lastName;
- }
- }
接下来, 我们定义一个 person 工厂接口, 用来创建新的 person 对象:
- interface PersonFactory<P extends Person> {
- P create(String firstName, String lastName);
- }
然后我们通过构造函数引用来把所有东西拼到一起, 而不是像以前一样, 通过手动实现一个工厂来这么做.
- PersonFactory<Person> personFactory = Person::new;
- Person person = personFactory.create("Peter", "Parker");
我们通过 Person::new 来创建一个 Person 类构造函数的引用. Java 编译器会自动地选择合适的构造函数来匹配 PersonFactory.create 函数的签名, 并选择正确的构造函数形式
二, 允许在接口中有默认方法实现
- interface Formula {
- default double sqrt(int a) {
- return Math.sqrt(a);
- }
- }
- Formula formula = new Formula() {
- @Override
- public double calculate(int a) {
- return sqrt(a * 100);
- }
- };
三, 时间日期 API
Timezones,LocalTime
四, 内置函数式接口
1.Predicate
Predicate 是一个布尔类型的函数, 该函数只有一个输入参数. Predicate 接口包含了多种默认方法, 用于处理复杂的逻辑动词
- Predicate<String> predicate = (s) -> s.length()> 0;
- predicate.test("foo"); // true
- predicate.negate().test("foo"); // false
- Predicate<Boolean> nonNull = Objects::nonNull;
- Predicate<Boolean> isNull = Objects::isNull;
- Predicate<String> isEmpty = String::isEmpty;
- Predicate<String> isNotEmpty = isEmpty.negate();
- 2.Function
Function 接口接收一个参数, 并返回单一的结果. 默认方法可以将多个函数串在一起 ( compse, andThen)
- Function<String, Integer> toInteger = Integer::valueOf;
- Function<String, String> backToString =
- toInteger.andThen(String::valueOf);
- backToString.apply("123"); // "123"
- 3.Optional
Optional 不是一个函数式接口, 而是一个精巧的工具接口, 用来防止 NullPointerException 产生. 这个概念在下一节会显得很重要, 所以我们在这里快速地浏览一下 Optional 的工作原理. Optional 是一个简单的值容器, 这个值可以是 null, 也可以是 nonnull. 考虑到一个方法可能会返回一个 non-null 的值, 也可能返回一个空值. 为了不直接返回 null, 我们在 Java 8 中就返回一个 Optional.
- Optional<String> optional = Optional.of("bam");
- optional.isPresent(); // true
- optional.get(); // "bam"
- optional.orElse("fallback"); // "bam"
- optional.ifPresent((s) -> System.out.println(s.charAt(0))); //"b"
- 4.Streams
java.util.Stream 表示了某一种元素的序列, 在这些元素上可以进行各种操作. Stream 操作可以是中间操作, 也可以是完结操作. 完结操作会返回一个某种类型的值, 而中间操作会返回流对象本身, 并且你可以通过多次调用同一个流操作方法来将操作结果串起来 ( 就像 StringBuffer 的 append 方法一样) . Stream 是在一个源的基础上创建出来的, 例如 java.util.Collection 中的 list 或者 set( map 不能作为 Stream 的源) . Stream 操作往往可以通过顺序或者并行两种方式来执行. 我们先了解一下序列流. 首先, 我们通过 string 类型的 list 的形式创
建示例数据:
- List<String> stringCollection = new ArrayList<>();
- stringCollection.add("ddd2");
- stringCollection.add("aaa2");
- stringCollection.add("bbb1");
- stringCollection.add("aaa1");
- stringCollection.add("bbb3");
- stringCollection.add("ccc");
- stringCollection.add("bbb2");
- stringCollection.add("ddd1");
Java 8 中的 Collections 类的功能已经有所增强, 你可以之直接通过调用 Collections.stream() 或者
Collection.parallelStream() 方法来创建一个流对象. 下面的章节会解释这个最常用的操作.
5.Filter
Filter 接受一个 predicate 接口类型的变量, 并将所有流对象中的元素进行过滤. 该操作是一个中间操作, 因此它允许我们在返回结果的基础上再进行其他的流操作 ( forEach) . ForEach 接受一个 function 接口类型的变量, 用来执行对每一个元素的操作. ForEach 是一个中止 r 操作 它不返回流, 所以我们不能再调用其他的流操作.
- stringCollection
- .stream()
- .filter((s) -> s.startsWith("a"))
- .forEach(System.out::println);
- // "aaa2", "aaa1"
- 6.Sorted
Sorted 是一个中间操作, 能够返回一个排过序的流对象的视图. 流对象中的元素会默认按照自然顺序进行排序, 除非你自己指定一个 Comparator 接口来改变排序规则.
- stringCollection
- .stream()
- .sorted()
- .filter((s) -> s.startsWith("a"))
- .forEach(System.out::println);
- // "aaa1", "aaa2"
一定要记住, sorted 只是创建一个流对象排序的视图, 而不会改变原来集合中元素的顺序. 原来 string 集合中的元素顺序是没有改变的.
- System.out.println(stringCollection);
- // ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1
- 7.Map
map 是一个对于流对象的中间操作, 通过给定的方法, 它能够把流对象中的每一个元素对应到另外一个对象上. 下面的例子就演示了如何把每个 string 都转换成大写的 string. 不但如此, 你还可以把每一种对象映射成为其他类型. 对于带泛型结果的流对象, 具体的类型还要由传递给 map 的泛型方法来决定.
- stringCollection
- .stream()
- .map(String::toUpperCase)
- .sorted((a, b) -> b.compareTo(a))
- .forEach(System.out::println);
- // "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1"
- 8.Match
匹配操作有多种不同的类型, 都是用来判断某一种规则是否与流对象相互吻合的. 所有的匹配操作都是终结操作, 只返回一个 boolean 类型的结果.
- boolean anyStartsWithA =
- stringCollection
- .stream()
- .anyMatch((s) -> s.startsWith("a"));
- System.out.println(anyStartsWithA); // true
- boolean allStartsWithA =
- stringCollection
- .stream()
- .allMatch((s) -> s.startsWith("a"));
- System.out.println(allStartsWithA); // false
- boolean noneStartsWithZ = stringCollection
- .stream()
- .noneMatch((s) -> s.startsWith("z"));
- System.out.println(noneStartsWithZ); // true
- 9.Count
Count 是一个终结操作, 它的作用是返回一个数值, 用来标识当前流对象中包含的元素数量.
- long startsWithB =
- stringCollection
- .stream()
- .filter((s) -> s.startsWith("b"))
- .count();
- System.out.println(startsWithB); // 3
- 10.Reduce
该操作是一个终结操作, 它能够通过某一个方法, 对元素进行削减操作. 该操作的结果会放在一个 Optional 变量里返回.
- Optional<String> reduced =
- stringCollection
- .stream()
- .sorted()
- .reduce((s1, s2) -> s1 + "#" + s2);
- reduced.ifPresent(System.out::println);
- // "aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2"
- 11.Parallel Streams
Parallel Streams 像上面所说的, 流操作可以是顺序的, 也可以是并行的. 顺序操作通过单线程执行, 而并行操作则通过多线程执行. 下面的例子就演示了如何使用并行流进行操作来提高运行效率, 代码非常简单. 首先我们创建一个大的 list, 里面的元素都是唯一的:
- int max = 1000000;
- List<String> values = new ArrayList<>(max);
- for (int i = 0; i <max; i++) {
- UUID uuid = UUID.randomUUID();
- values.add(uuid.toString());
- }
流可以并行执行, 在大量输入元素上可以提升运行时的性能. 并行流使用公共的 ForkJoinPool , 由 ForkJoinPool.commonPool() 方法提供. 底层线程池的大小最大为五个线程 - 取决于 CPU 的物理核数.
- Arrays.asList("a1", "a2", "b1", "c2", "c1")
- .parallelStream()
- .filter(s -> {
- System.out.format("filter: %s [%s]\n",
- s, Thread.currentThread().getName());
- return true;
- })
- .map(s -> {
- System.out.format("map: %s [%s]\n",s, Thread.currentThread().getName());
- return s.toUpperCase();
- })
- .sorted((s1, s2) -> {
- System.out.format("sort: %s <> %s [%s]\n",s1, s2, Thread.currentThread().getName());
- return s1.compareTo(s2);
- })
- .forEach(s -> System.out.format("forEach: %s[%s]\n",s,Thread.currentThread().getName()));
输出结果如下:
- filter: c2 [ForkJoinPool.commonPool-worker-3]
- filter: c1 [ForkJoinPool.commonPool-worker-2]
- map: c1 [ForkJoinPool.commonPool-worker-2]
- filter: a2 [ForkJoinPool.commonPool-worker-1]
- map: a2 [ForkJoinPool.commonPool-worker-1]
- filter: b1 [main]
- map: b1 [main]
- filter: a1 [ForkJoinPool.commonPool-worker-2]
- map: a1 [ForkJoinPool.commonPool-worker-2]
- map: c2 [ForkJoinPool.commonPool-worker-3]
- sort: A2 <> A1 [main]
- sort: B1 <> A2 [main]
- sort: C2 <> B1 [main]
- sort: C1 <> C2 [main]
- sort: C1 <> B1 [main]
- sort: C1 <> C2 [main]
- forEach: A1 [ForkJoinPool.commonPool-worker-1]
- forEach: C2 [ForkJoinPool.commonPool-worker-3]
- forEach: B1 [main]
- forEach: A2 [ForkJoinPool.commonPool-worker-2]
- forEach: C1 [ForkJoinPool.commonPool-worker-1]
就像你看到的那样, 并行流使用了所有公共的 ForkJoinPool 中的可用线程来执行流式操作. 在连续的运行中输出可能有所不同, 因为所使用的特定线程是非特定的.
sort 只在主线程上串行执行. 实际上, 并行流上的 sort 在背后使用了 Java8 中新的方法 Arrays.parallelSort() .
五, 处理顺序
数据流操作要么是衔接操作, 要么是终止操作. 衔接操作返回数据流, 所以我们可以把多个衔接操作不使用分号来链接到一起. 终止操作无返回值, 或者返回一个不是流的结果. 在上面的例子中, filter , map 和 sorted 都是衔接操作, 而 forEach 是终止操作
衔接操作的一个重要特性就是延迟性. 观察下面没有终止操作的例子:
- Stream.of("d2", "a2", "b1", "b3", "c")
- .filter(s -> {
- .System.out.println("filter:" + s);
- .return true;
- });
执行这段代码时, 不向控制台打印任何东西. 这是因为衔接操作只在终止操作调用时被执行. 让我们通过添加终止操作 forEach 来扩展这个例子:
- Stream.of("d2", "a2", "b1", "b3", "c")
- .filter(s -> {
- System.out.println("filter:" + s);
- return true;
- })
- .forEach(s -> System.out.println("forEach:" + s));
执行这段代码会得到如下输出:
- filter: d2
- forEach: d2
- filter: a2
- forEach: a2
- filter: b1
- forEach: b1
- filter: b3
- forEach: b3
- filter: c
- forEach: c
结果的顺序可能出人意料. 原始的方法会在数据流的所有元素上, 一个接一个地水平执行所有操作. 但是每个元素在调用链上垂直移动. 第一个字符串 "d2" 首先经过 filter 然后是 forEach , 执行完后才开始处理第二个字符串 "a2" .
这种行为可以减少每个元素上所执行的实际操作数量, 就像我们在下个例子中看到的那样:
- Stream.of("d2", "a2", "b1", "b3", "c")
- .map(s -> {
- System.out.println("map:" + s);
- return s.toUpperCase();
- })
- .anyMatch(s -> {
- System.out.println("anyMatch:" + s);
- return s.startsWith("A");
- });
- // map: d2
- // anyMatch: D2
- // map: a2
- // anyMatch: A2
只要提供的数据元素满足了谓词, anyMatch 操作就会返回 true . 对于第二个传递 "A2" 的元素, 它的结果为真. 由于数据流的链式调用是垂直执行的, map 这里只需要执行两次. 所以 map 会执行尽可能少的次数, 而不是把所有元素都映射一遍.
下面的例子由两个衔接操作 map 和 filter , 以及一个终止操作 forEach 组成. 让我们再来看看这些操作如何执行:
- Stream.of("d2", "a2", "b1", "b3", "c")
- .map(s -> {
- System.out.println("map:" + s);
- return s.toUpperCase();
- })
- .filter(s -> {
- System.out.println("filter:" + s);
- return s.startsWith("A");
- })
- .forEach(s -> System.out.println("forEach:" + s));
- // map: d2
- // filter: D2
- // map: a2
- // filter: A2
- // forEach: A2
- // map: b1
- // filter: B1
- // map: b3
- // filter: B3
- // map: c
- // filter: C
就像你可能猜到的那样, map 和 filter 会对底层集合的每个字符串调用五次, 而 forEach 只会调用一次. 如果我们调整操作顺序, 将 filter 移动到调用链的顶端, 就可以极大减少操作的执行次数:
- Stream.of("d2", "a2", "b1", "b3", "c")
- .filter(s -> {
- System.out.println("filter:" + s);
- return s.startsWith("a");
- })
- .map(s -> {
- System.out.println("map:" + s);
- return s.toUpperCase();
- })
- .forEach(s -> System.out.println("forEach:" + s));
- // filter: d2
- // filter: a2
- // map: a2
- // forEach: A2
- // filter: b1
- // filter: b3
- // filter: c
现在, map 只会调用一次, 所以操作流水线对于更多的输入元素会执行更快. 在整合复杂的方法链时, 要记住这一点
来源: https://blog.csdn.net/JiShuiSanQianLi/article/details/122440121