Lambda表达式
Lambda表达式允许我们将函数当成参数传递给某个方法,或者把代码本身当作数据处理。这一特性在许多语言都有支持,如pyhton, js等。Jdk1.8开始,Java也支持Lambda表达式。
最简单的Lambda表达式可由逗号分隔的参数列表、->符号和语句块组成,例如:
1 | Arrays.asList("a", "b", "d").forEach(e -> System.out.println(e)); |
上面的参数e类型由编译器推导得出,也可显式指定:
1 | Arrays.asList("a", "b", "d").forEach((String e) -> System.out.println(e)); |
对于复杂语句块,可以使用花括号扩起来:
1 | Arrays.asList("a", "b", "d").forEach(e -> { |
Lambda可以引用类成员和局部变量,会将这些变量隐式得转换成final,如以下代码则会报错:
1 | String t = "a"; |
去掉 t=”b”; 则可正常运行。
Lambda表达式有返回值,返回值的类型也由编译器推理得出。如果Lambda表达式中的语句块只有一行,则可以不用使用return语句,下列两个代码片段效果相同:
1 | Arrays.asList("a", "b", "d").sort((e1, e2) -> e1.compareTo(e2)); |
函数式接口
在Java中,Lambda表达式则是一种折中的实现方式。Java的Lambda表达式依赖与函数接口,函数接口指的是只有一个抽象方法的接口,
这样的接口可以隐式转换为Lambda表达式。
还是从最简单的Lambda表达式开始:
1 | Arrays.asList("a", "b", "d").forEach(e -> System.out.println(e)); |
跳转至 Iterable 接口看看是怎么实现的:
1 | public interface Iterable<T> { |
这里forEach方法接收了一个Consumer的参数,Consumer 则是一个函数接口:
1 | @FunctionalInterface |
@FunctionalInterface 注解显式的声明了 Consumer 是个函数式接口,它仅有一个抽象方法accept();
需要注意的是,默认方法和静态方法不会破坏函数式接口的定义,因此上面的代码是合法的;
这使得其可以被转换为Lamda表达式,正如上面的示例。
JDK 1.8新增的函数接口在java.util.function包下:
- Consumer< T> 代表了接受一个输入参数并且无返回的操作。 函数: void accept(T t);
1 | public interface Iterable<T> { |
- Function< T, R> 接受一个输入参数,返回一个结果。 函数: R apply(T t);
1 |
|
- Predicate< T> 接受一个输入参数,返回一个布尔值结果。 函数: boolean test(T t);
1 | static class Person { |
- Supplier< T> 无参数,返回一个结果。 函数: T get();
1 |
|
- UnaryOperator< T> extends Function< T, T> 接受一个参数为类型T,返回值类型也为T。
1 | public class AtomicReference<V> implements java.io.Serializable { |
- BiConsumer< T, U> 代表了一个接受两个输入参数的操作,并且不返回任何结果。 函数: void accept(T t, U u);
1 | public class ConcurrentHashMap<K,V> extends AbstractMap<K,V> |
- BiFunction< T, U, R> 代表了一个接受两个输入参数的方法,并且返回一个结果。 函数: R apply(T t, U u);
1 | public class HashMap<K,V> extends AbstractMap<K,V> |
- BinaryOperator< T> extends BiFunction< T,T,T> 代表了一个作用于于两个同类型操作符的操作,并且返回了操作符同类型的结果。
1 | public class AtomicReference<V> implements java.io.Serializable { |
- BiPredicate< T, U> 代表了一个两个参数的boolean值方法。 函数: boolean test(T t, U u);
1 | BiPredicate<Integer, Integer> largeThan = (x, y) -> x > y; |
基础接口为以上,其他接口都是带类型的,如IntConsumer,其接收参数为int;ObjDoubleConsumer,接收一个obj和一个double。
除开 java.util.function 包下的接口,其它常见函数接口如下:
java.lang.Runnable
java.util.concurrent.Callable
java.util.Comparator
方法引用
方法引用通过方法的名字来指向一个方法。使用符号 :: 。 这可以使得代码更为紧凑。
如:
1 | Arrays.asList("a", "b", "d").forEach(System.out::println); |
再如:
有一个 Car 类。
1 | static class Car { |
- 构造器引用:它的语法是Class::new,或者更一般的Class< T >::new
1 | Car car = Car.create(Car::new); |
- 静态方法引用 Class::static_method
1 | List<Car> cars = Arrays.asList(car); |
- 特定类的任意方法引用 Class::method
1 | cars.forEach(Car::repair); |
- 特定对象的方法引用 instance::method
1 | Car lead = Car.create(Car::new); |
Stream
Java 8 中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)。Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的。它更像一个高级版本的 Iterator,不可以重复遍历里面的数据,像水一样,流过了就一去不复返。它和普通的 Iterator 不同的是,它可以并行遍历,普通的 Iterator 只能是串行,在一个线程中执行。
当使用串行方式去遍历时,每个 item 读完后再读下一个 item。而使用并行去遍历时,数据会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出。Stream 的并行操作依赖于 Java7 中引入的 Fork/Join 框架来拆分任务和加速处理过程。
Stream 的另外一大特点是,数据源本身可以是无限的。
流的构成
流的元素类型: Stream< T>, IntStream, LongStream, DoubleStream。
流计算由以下三部分构成: 流来源,0或多个中间操作,终止操作。
流的来源(stream source)
- Colloection.stream() or parallelStream() 使用一个集合的元素创建一个流。
1
2List list = Arrays.asList("a", "b", "d");
Stream stream = list.stream(); - Arrays.stream(T[] array) 使用数组创建流。
1
2String[] array = {"a", "b", "d"};
Stream stream = Arrays.stream(array); - Stream.of(T t) 使用单个元素创建一个流。
1
Stream stream = Stream.of(Integer.valueOf("123"));
- Stream.of(T… values) 使用多个元素创建一个流。
1
Stream stream = Stream.of(Integer.valueOf("123"), Double.valueOf("233.0"));
- Stream.empty() 创建一个空流。
- Stream.iterate(final T seed, final UnaryOperator< T> f) 创建一个包含序列 first, f(first), f(f(first)), … 的无限流
1
Stream stream = Stream.iterate(1, e -> e * 2); // 2^N
- Stream.generate(Supplier
s) 使用一个生成器函数创建一个无限流。 1
Stream stream = Stream.generate(Math::random);
- IntStream.range(int startInclusive, int endExclusive) 创建一个由下限到上限之间的元素组成的 IntStream。
1
IntStream intStream = IntStream.range(1,10); // 1-9
- Random.ints() 创建由随机数构成的无限流。
1
IntStream intStream = new Random().ints();
- 其他: BufferedReader.lines(); BitSet.stream(); Stream.chars(); Files.list(Path dir);
Pattern.splitAsStream(CharSequence input); JarFile.stream(); etc..
中间操作(intermediate operation)
中间操作负责将一个流转换为另一个流。一个流可以后面跟随零个或多个中间操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是 lazy 的,就是说,仅仅调用到这类方法,并没有真正开始流的遍历。
在对于一个 Stream 进行多次中间操作,每次都对 Stream 的每个元素进行转换,而且是执行多次,这样时间复杂度就是 N(转换次数)个 for 循环里把所有操作都做掉的总和吗?其实不是这样的,转换操作都是 lazy 的,多个中间操作只会在终止操作的时候融合起来,一次循环完成。我们可以这样简单的理解,Stream 里有个操作函数的集合,每次转换操作就是把转换函数放入这个集合中,在终结操作的时候循环 Stream 对应的集合,然后对每个元素执行所有的函数。
中间操作包括 filter()(选择与条件匹配的元素)、map()(根据函数来转换元素)、distinct()(删除重复)、limit()(在特定大小处截断流)和 sorted()。
中间操作可进一步划分为无状态和有状态操作。
无状态操作(比如 filter() 或 map())可独立处理每个元素,而有状态操作(比如 sorted() 或 distinct())可合并以前看到的影响其他元素处理的元素状态。
filter(Predicate< T>) 与预期匹配的流的元素
1
2Person.createRoster().stream().filter(person -> person.getGender() == Person.Sex.MALE
&& person.getAge() >= 18).collect(Collectors.toList()); // 查找18岁以上的男性map(Function< T, U>) 将提供的函数应用于流的元素的结果
1
Person.createRoster().stream().map(person -> person.getAge()).collect(Collectors.toSet()); // 获取所有人的年龄集合
flatMap(Function< T, Stream< U>> 将提供的流处理函数应用于流元素后获得的流元素
1
2
3
4
5
6String[] words = new String[]{"abc","abc","abc"};
Arrays.stream(words)
.map(word -> word.split(""))
.flatMap(Arrays::stream) // 将三个String[]流元素使用Arrays.stream(T..)处理获得String流
.distinct()
.forEach(System.out::print);distinct() 去除重复元素,实际使用了ConcurrentHashMap< T, Boolean>进行操作,所以是根据hashcode进行去重的。(参见DistinctOps类)
1
2
3
4
5
6String[] words = new String[]{"abc","abc","abc"};
Arrays.stream(words)
.map(word -> word.split(""))
.flatMap(Arrays::stream) // 将三个String[]流元素使用Arrays.stream(T..)处理获得String流
.distinct() //去重
.forEach(System.out::print); //输出abcsorted() OR sorted(Comparator<? super T>) 对流元素进行排序。
1
2
3
4
5
6
7String[] words = new String[]{"cbad","def"};
Arrays.stream(words)
.map(word -> word.split(""))
.flatMap(Arrays::stream)
.distinct()
.sorted()
.forEach(System.out::print); // abcdeflimit(long) 截断至所提供长度的流元素
1
new Random().ints().limit(10).forEach(System.out::println); //输出10个随机数
skip(long) 丢弃了前 N 个元素的流元素
1
Stream.iterate(1, e -> e * 2).limit(10).skip(5).forEach(System.out::println); // 2^0 - 2^9 然后丢弃前5,即2^5 - 2^9
peek() 主要为debug使用,给出流操作结果。
1
2
3
4
5
6Stream.of("one", "two", "three", "four")
.filter(e -> e.length() > 3)
.peek(e -> System.out.println("Filtered value: " + e))
.map(String::toUpperCase)
.peek(e -> System.out.println("Mapped value: " + e))
.collect(Collectors.toList());
终结操作(terminal operation)
一个流只能有一个终结操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。终结操作的执行,才会真正开始流的遍历,并且会生成一个结果,或者一个side effect。
forEach(Consumer<? extends T> action) / forEachOrdered(Consumer<? super T> action) 将提供的操作应用于流的每个元素。
1
new Random().ints().limit(10).forEach(System.out::println); //输出10个随机数
toArray() 使用流的元素创建一个数组。
1
new Random().ints().limit(10).toArray();
reduce(…) 将流的元素聚合为一个汇总值。
- Optional< T> reduce(BinaryOperator< T> accumulator); 未定义初始值,从而第一次执行的时候第一个参数的值是Stream的第一个元素,第二个参数是Stream的第二个元素。
- T reduce( T identity, BinaryOperator< T> accumulator); 定义了初始值,从而第一次执行的时候第一个参数的值是初始值,第二个参数是Stream的第一个元素。
- < U> U reduce(U identity,BiFunction< U, ? super T, U> accumulator,BinaryOperator< U> combiner); combiner的作用在于合并每个线程的result得到最终结果。
1 | System.out.println("10个随机数之和:" |
collect(…) 将流的元素聚合到一个汇总结果容器中。
< R> R collect(Supplier< R> supplier,BiConsumer< R, ? super T> accumulator,BiConsumer< R, R> combiner);
< R, A> R collect(Collector<? super T, A, R> collector);
1 | List<String> stringList = Stream.of("one", "two", "three", "four") |
min(Comparator<? super T>) 通过比较符返回流的最小元素。
1
2
3
4
5
6String[] words = new String[]{"cbad","def"};
System.out.println(Arrays.stream(words)
.map(word -> word.split(""))
.flatMap(Arrays::stream)
.distinct()
.min((e1, e2) -> e2.compareTo(e1)).get()); // 这里逆序,返回fmax(Comparator<? super T>) 通过比较符返回流的最大元素。
1
2
3
4
5
6String[] words = new String[]{"cbad","def"};
System.out.println(Arrays.stream(words)
.map(word -> word.split(""))
.flatMap(Arrays::stream)
.distinct()
.max((e1, e2) -> e2.compareTo(e1)).get()); // 这里逆序,返回acount() 返回流的大小。
1
2
3
4
5
6String[] words = new String[]{"cbad","def"};
System.out.println(Arrays.stream(words)
.map(word -> word.split(""))
.flatMap(Arrays::stream)
.distinct()
.count()); // 输出6anyMatch(Predicate<? super T>) / allMatch(Predicate<? super T>) / noneMatch(Predicate<? super T>) 返回流的任何/所有元素是否与提供的预期相匹配。
1
2String[] words = new String[]{"cbad","def"};
System.out.println(Arrays.stream(words).allMatch(string -> string.contains("d"))); // truefindFirst() 返回流的第一个元素(如果有)。保证是第一个元素。
1
2
3
4
5
6
7
8
9
10@Test
public void createStream_whenFindFirstResultIsPresent_thenCorrect() {
List<String> list = Arrays.asList("A", "B", "C", "D");
Optional<String> result = list.stream().findFirst();
assertTrue(result.isPresent());
assertThat(result.get(), is("A"));
}findAny() 返回流的任何元素(如果有)。不保证是第一个元素。
1
2
3
4
5
6
7
8
9@Test
public void createStream_whenFindAnyResultIsPresent_thenCorrect() {
List<String> list = Arrays.asList("A","B","C","D");
Optional<String> result = list.stream().findAny();
assertTrue(result.isPresent());
assertThat(result.get(), anyOf(is("A"), is("B"), is("C"), is("D")));
}
Collectors
终结操作中 collect 方法有两种入参,一是接收三个方法参数,二是接收一个Collector方法。而Collectors类中则内置了常用Collector。
- toCollection(Supplier< C> collectionFactory) 自定义集合类收集元素
1 | List<String> linkedListResult = Arrays.asList("cbad","def").stream().collect(Collectors.toCollection(LinkedList::new)); |
- toList() 转ArrayList
1 | List<String> arrayListResult = Arrays.asList("cbad","def").stream().collect(Collectors.toList()); |
- toSet() 转hashSet
1 | Person.createRoster() |
- joining() / joining(CharSequence delimiter) / joining(CharSequence delimiter,CharSequence prefix,CharSequence suffix) 拼接
1 | String[] words = new String[]{"cbad","def"}; |
- mapping(Function<? super T, ? extends U> mapper, Collector<? super U, A, R> downstream) 在聚合前先对元素进行操作
1 | Stream.of("cbad","def").collect(Collectors.mapping(x -> x.toUpperCase(), Collectors.joining(","))); //CBAD,DEF |
- collectingAndThen(Collector< T,A,R> downstream, Function< R,RR> finisher) 对聚合后的数据进行处理
1 | Map<Person.Sex, Person> oldestPeople = Person.createRoster().stream() |
counting() 统计输入元素数量
minBy(Comparator<? super T> comparator) maxBy(Comparator<? super T> comparator) 获取最小/大元素
summingInt(ToIntFunction<? super T> mapper) / summingLong / summingDouble 求和
1 | Person.createRoster().stream().collect(Collectors.summingInt(Person::getAge)); |
- averagingInt(ToIntFunction<? super T> mapper) / averagingLong / averagingDouble 求平均
1 | Person.createRoster().stream().collect(Collectors.averagingInt(Person::getAge)); |
- reducing(…) 将流的元素聚合为一个汇总值。
- Collector< T, ?, Optional< T>> reducing(BinaryOperator< T> op)
- Collector< T, ?, T> reducing(T identity, BinaryOperator< T> op)
- Collector< T, ?, U> reducing(U identity,Function<? super T, ? extends U> mapper,BinaryOperator< U> op)
1 | System.out.println( |
groupingBy(…) / groupingByConcurrent(…) 分组聚合
Collector< T, ?, Map<K, List< T>>> groupingBy(Function<? super T, ? extends K> classifier)
Collector< T, ?, Map< K, D>> groupingBy(Function<? super T, ? extends K> classifier,Collector<? super T, A, D> downstream)
< T, K, D, A, M extends Map< K, D>> Collector< T, ?, M> groupingBy(Function<? super T, ? extends K> classifier,Supplier< M> mapFactory,Collector<? super T, A, D> downstream)
Collector< T, ?, ConcurrentMap< K, List< T>>> groupingByConcurrent(Function<? super T, ? extends K> classifier)
Collector< T, ?, ConcurrentMap< K, D>> groupingByConcurrent(Function<? super T, ? extends K> classifier,Collector<? super T, A, D> downstream)
< T, K, A, D, M extends ConcurrentMap< K, D>>
Collector< T, ?, M> groupingByConcurrent(Function<? super T, ? extends K> classifier,Supplier< M> mapFactory,Collector<? super T, A, D> downstream)
1 | Map<Person.Sex, List<Person>> peopleGroupBySex = Person.createRoster().stream() |
- partitioningBy(…) 分类为是否符合给定条件的两类
- Collector< T, ?, Map< Boolean, List< T>>> partitioningBy(Predicate<? super T> predicate)
- Collector< T, ?, Map< Boolean, D>> partitioningBy(Predicate<? super T> predicate, Collector<? super T, A, D> downstream)
1 | Map<Boolean, List<Person>> peopleGroupBySex = Person.createRoster().stream() |
toMap(…) / toConcurrentMap(…)
Collector< T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,Function<? super T, ? extends U> valueMapper)
Collector< T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,Function<? super T, ? extends U> valueMapper,BinaryOperator< U> mergeFunction)
Collector< T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,Function<? super T, ? extends U> valueMapper,BinaryOperator< U> mergeFunction,Supplier< M> mapSupplier)
1 | Map<Integer, Person> people = Person.createRoster().stream() |
- Collector< T, ?, IntSummaryStatistics> summarizingInt(ToIntFunction<? super T> mapper) / summarizingLong / summarizingDouble
获得统计信息 最大值,最小值,平均数,总数,总和1
2
3
4Optional.ofNullable(
roster.stream().
collect(Collectors.summarizingInt(Person::getAge)))
.ifPresent(System.out::println); //IntSummaryStatistics{count=20, sum=107, min=0, average=5.350000, max=9}