一、方法和构造函数引用
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 只会调用一次, 所以操作流水线对于更多的输入元素会执行更快。 在整合复杂的方法链时, 要记住这一点