Java 之 lambda 表达式(二)---- Stream 操作 API
目录
一. 前言
二. Stream 创建
2.1. 使用集合来创建 Stream
2.2. 使用数组创建 Stream
2.3. 由值创建 Stream
2.4. 由函数创建无限流 Stream
2.5. 代码示例
三. Stream 操作
3.1. 中间型操作
3.1.1. filter()
3.1.2. map()
3.1.3. mapToInt()、mapToLong()、mapToDouble()
3.1.4. flatMap()
3.1.5. peek()
3.1.6. skip()
3.1.7. limit()
3.1.8. concat()
3.1.9. distinct()
3.1.10. sorted()
3.2. 终结型操作
3.2.1. count()
3.2.2. max()、min()
3.2.3. findFirst()、findAny()
3.2.4. anyMatch()、noneMatch() 和 allMatch()
3.2.5. reduce()
3.2.6. collect()
3.3. Collector 收集
3.3.1. toList()、toSet()、toCollection()
3.3.2. toMap()、toConcurrentMap()
3.3.3. mapping()、collectingAndThen()
3.3.4. groupingBy()
3.3.5. partitioningBy()
3.3.6. joining()
3.3.7. 统计相关 counting()、maxBy()、minBy()、averagingDouble()、summingInt()、summarizingInt()
3.3.8. reducing()
一. 前言
Stream,就是 JDK8 依托于函数式编程特性为集合类库做的一个类库,它其实就是 JDK 提供的函数式接口的最佳实践。它能让我们通过 lambda 表达式更简明扼要的以流水线的方式去处理集合内的数据,可以很轻松的完成诸如:过滤、分组、收集、归约这类操作。关于 lambda 的整体介绍请参见《Java 之 lambda 表达式(一)》。
Stream 特点:
1. Stream 自己不会存储元素;
2. Stream 不会改变源对象,Stream 操作会返回一个新的Stream;
3. Stream 操作是延迟执行的,这意味着等到获取结果时,Stream 才会执行。
Stream 操作的三个步骤:
1. Stream 创建,由一个数据源如集合、数组来获取一个Stream;
2. 中间操作:一个中间操作链,对数据源的数据进行处理;
3. 终止操作:执行中间操作链并产生结果。
Stream 的操作步骤示例图:
二. Stream 创建
2.1. 使用集合来创建 Stream
Java8 中的 Collection 接口被扩展,提供了两个获取Stream的方法:
// 返回一个顺序流
default Stream<R> stream();
// 返回一个并行流
default Stream<E> parallelStream();
2.2. 使用数组创建 Stream
Java8 中的 Arrays 的静态方法 stream() 可以获取流:
static <T> Stream<T> stream(T[] array);
static IntStream<T> stream(int[] array);
static LongStream<T> stream(long[] array);
static DoubleStream<T> stream(double[] array);
2.3. 由值创建 Stream
使用 Stream 的静态方法 of 来创建一个流,该方法可以接收任意数量的参数:
static<T> Stream<T> of(T... values)
2.4. 由函数创建无限流 Stream
可以使用 Stream 的静态方法 iterate() 和 generate() 创建无限流:
public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
public static<T> Stream<T> generate(Supplier<T> s)
2.5. 代码示例
public void test01() {
// 1.通过Collection系列集合提供的stream()或parallelStream()获取
ArrayList<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();
// 2.通过Arrays中的静态方法stream()获取
String[] strings=new String[10];
Stream<String> stream2 = Arrays.stream(strings);
// 3.通过Stream类中的静态方法of()获取
Stream<String> stream3 = Stream.of(strings);
// 4.创建无限流
// 4.1迭代
Stream<Integer> stream4 = Stream.iterate(0, x-> x + 2);
// 4.2生成
Stream<Double> stream5 = Stream.generate(() -> Math.random());
}
三. Stream 操作
Stream 的操作大致分为两类:中间型操作和终结型操作。其中转换型操作又分为有状态和无状态两类。有状态是本次的结果需要依赖于前面的处理结果,而无状态则是不依赖。简单来讲就是无状态方法可以互相调换位置,而有状态方法不能调换位置。
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何处理,而是在终止操作时一次性全部处理,称为“惰性求值”。
惰性求值即 Java8 的 Stream 操作。惰性求值操作的结果也是 Stream,惰性求值可以像建造者模式一样链式使用,最后再使用及早求值得到最终结果。
3.1. 中间型操作
中间型操作就是返回值依旧是 Stream 类型的方法。API 如下:
API | 功能说明 | 无状态操作 |
---|---|---|
filter() | 按照条件过滤符合要求的元素, 返回新的stream流。 | 是 |
map() | 将已有元素转换为另一个对象类型,一对一逻辑,返回新的stream流 | 是 |
mapToInt() | 返回一个IntStream,将一个流中的元素转换为 int 类型。 | 是 |
mapToLong() | 返回一个LongStream,将一个流中的元素转换为 long 类型。 | 是 |
mapToDouble() | 返回一个DoubleStream,将一个流中的元素转换为 double 类型。 | 是 |
flatMap() | 将已有元素转换为另一个对象类型,一对多逻辑,即原来一个元素对象可能会转换为1个或者多个新类型的元素,返回新的stream流 | 是 |
flatMapToInt() | 返回一个IntStream,该结果包含将流中的每个元素替换为通过将提供的映射函数应用于每个元素而生成的映射流的内容而得到的结果。 | 是 |
flatMapToLong() | 返回一个LongStream,该结果包含将流中的每个元素替换为通过将提供的映射函数应用于每个元素而生成的映射流的内容而得到的结果。 | 是 |
flatMapToDouble() | 返回一个DoubleStream,该结果包含将流中的每个元素替换为通过将提供的映射函数应用于每个元素而生成的映射流的内容而得到的结果。 | 是 |
peek() | 对stream流中的每个元素进行逐个遍历处理,返回处理后的stream流 | 是 |
limit() | 仅保留集合前面指定个数的元素,返回新的stream流 | 否 |
skip() | 跳过集合前面指定个数的元素,返回新的stream流 | 否 |
concat() | 将两个流的数据合并起来为1个新的流,返回新的stream流 | 否 |
distinct() | 对Stream中所有元素进行去重,返回新的stream流 | 否 |
sorted() | 对stream中所有的元素按照指定规则进行排序,返回新的stream流 | 否 |
takeWhile() | JDK9新增,传入一个断言参数当第一次断言为false时停止,返回前面断言为true的元素。 | 否 |
dropWhile() | JDK9新增,传入一个断言参数当第一次断言为false时停止,删除前面断言为true的元素。 | 否 |
3.1.1. filter()
Stream<T> filter(Predicate<? super T> predicate); 起过滤筛选的作用,内部就是Predicate接口。
public class TestCase {
public static void main(String[] args) {
List<Student> studentList = new ArrayList<>(3);
studentList.add(new Student("流华追梦1", 22, 175));
studentList.add(new Student("流华追梦2", 40, 180));
studentList.add(new Student("流华追梦3", 50, 185));
List<Student> list = studentList.stream()
.filter(stu -> stu.getStature() < 180)
.collect(Collectors.toList());
System.out.println(list);
}
}
// 输出结果:
[Student{name='流华追梦1', age=22, stature=175}]
3.1.2. map()
<R> Stream<R> map(Function<? super T, ? extends R> mapper); 映射函数,实现转换功能,内部就是Function接口。
public class MapDemo {
public static void main(String[] args) {
List<Student> studentList = new ArrayList<>(3);
studentList.add(new Student("流华追梦1", 22, 175));
studentList.add(new Student("流华追梦2", 40, 180));
studentList.add(new Student("流华追梦3", 50, 185));
List<String> list = studentList.stream().map(student -> student.getName())
.collect(Collectors.toList());
// 方法引用写法
// List<String> list = studentList.stream().map(Student::getName)
// .collect(Collectors.toList());
System.out.println(list);
}
}
// 输出结果:
[流华追梦1, 流华追梦2, 流华追梦3]
3.1.3. mapToInt()、mapToLong()、mapToDouble()
mapToInt()、mapToLong()、mapToDouble() 方法的功能和 map() 方法一样,只不过返回的结果已经没有泛型,已经明确是 int、long、double 类型的流了。
public class MapToDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<String>() {{
add("1");
add("2");
add("3");
}};
// mapToInt
List<Integer> intList = list.stream()
.mapToInt(s -> Integer.valueOf(s))
// 一定要有 mapToObj,因为 mapToInt 返回的是 IntStream,因为已经确定是 int 类型了,所以没有泛型的,
// 而 Collectors.toList() 强制要求有泛型的流,所以需要使用 mapToObj方法返回有泛型的流
.mapToObj(s -> s)
.collect(Collectors.toList());
System.out.println(intList);
// mapToLong
List<Long> longList = list.stream()
.mapToLong(s -> Long.valueOf(s))
.mapToObj(s -> s)
.collect(Collectors.toList());
System.out.println(longList);
// mapToDouble
List<Double> doubleList = list.stream()
.mapToDouble(s -> Double.valueOf(s))
.mapToObj(s -> s)
.collect(Collectors.toList());
System.out.println(doubleList);
}
}
// 运行结果:
[1, 2, 3]
[1, 2, 3]
[1.0, 2.0, 3.0]
3.1.4. flatMap()
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper); 将多个Stream 合并为一个 Stream,用于将原有二维结构扁平化。
public class FlatMapDemo {
public static void main(String[] args) {
List<Student> studentList1 = new ArrayList<>();
studentList1.add(new Student("流华追梦1", 22, 175));
studentList1.add(new Student("流华追梦2", 40, 180));
studentList1.add(new Student("流华追梦3", 50, 185));
List<Student> studentList2 = new ArrayList<>();
studentList2.add(new Student("流华追梦4", 25, 183));
studentList2.add(new Student("流华追梦5", 48, 176));
List<List<Student>> studentList = new ArrayList<>();
studentList.add(studentList1);
studentList.add(studentList2);
List<Student> list = studentList.stream().flatMap(subList -> subList.stream()).collect(Collectors.toList());
System.out.println(list);
}
}
// 运行结果:
[Student{name='流华追梦1', age=22, stature=175}
, Student{name='流华追梦2', age=40, stature=180}
, Student{name='流华追梦3', age=50, stature=185}
, Student{name='流华追梦4', age=25, stature=183}
, Student{name='流华追梦5', age=48, stature=176}
]
3.1.5. peek()
Stream<T> peek(Consumer<? super T> action); 对 Stream 流中的每个元素进行逐个遍历处理,返回 Stream 流。
public class PeekDemo {
public static void main(String[] args) {
List<Student> studentList = new ArrayList<>();
studentList.add(new Student("流华追梦1", 22, 175));
studentList.add(new Student("流华追梦2", 40, 180));
studentList.add(new Student("流华追梦3", 50, 185));
List<Student> list = studentList.stream()
.peek(student -> System.out.println(student.getName()))
.collect(Collectors.toList());
System.out.println(list);
}
}
// 运行结果:
流华追梦1
流华追梦2
流华追梦3
[Student{name='流华追梦1', age=22, stature=175}
, Student{name='流华追梦2', age=40, stature=180}
, Student{name='流华追梦3', age=50, stature=185}
]
3.1.6. skip()
Stream<T> skip(long n); 跳过集合前面指定个数的元素,返回新的 Stream 流,若流中的元素不足,则返回空流。与 limit 互补。
public class SkipDemo {
public static void main(String[] args) {
List<Student> studentList = new ArrayList<>();
studentList.add(new Student("流华追梦1", 22, 175));
studentList.add(new Student("流华追梦2", 40, 180));
studentList.add(new Student("流华追梦3", 50, 185));
List<Student> list = studentList.stream()
.skip(1)
.collect(Collectors.toList());
System.out.println(list);
}
}
// 运行结果:
流华追梦2
流华追梦3
[Student{name='流华追梦2', age=40, stature=180}
, Student{name='流华追梦3', age=50, stature=185}
]
3.1.7. limit()
Stream<T> limit(long maxSize); 仅保留集合前面指定个数的元素,返回新的stream流,使其元素不超过给定数量。
public class LimitDemo {
public static void main(String[] args) {
List<Student> studentList = new ArrayList<>();
studentList.add(new Student("流华追梦1", 22, 175));
studentList.add(new Student("流华追梦2", 40, 180));
studentList.add(new Student("流华追梦3", 50, 185));
List<Student> list = studentList.stream()
.filter(student -> student.getStature() > 175)
.limit(1)
.collect(Collectors.toList());
System.out.println(list);
}
}
// 运行结果:
流华追梦2
[Student{name='流华追梦2', age=40, stature=180}
]
3.1.8. concat()
<T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b); 将两个流的数据合并起来为1个新的流,返回新的 Stream 流。
public class ConcatDemo {
public static void main(String[] args) {
List<Student> studentList1 = new ArrayList<>();
studentList1.add(new Student("流华追梦1", 22, 175));
studentList1.add(new Student("流华追梦2", 40, 180));
studentList1.add(new Student("流华追梦3", 50, 185));
List<Student> studentList2 = new ArrayList<>();
studentList2.add(new Student("流华追梦4", 25, 183));
studentList2.add(new Student("流华追梦5", 48, 176));
List<Student> list = Stream.concat(studentList1.stream(), studentList2.stream()).collect(Collectors.toList());
System.out.println(list);
}
}
// 运行结果:
[Student{name='流华追梦1', age=22, stature=175}
, Student{name='流华追梦2', age=40, stature=180}
, Student{name='流华追梦3', age=50, stature=185}
, Student{name='流华追梦4', age=25, stature=183}
, Student{name='流华追梦5', age=48, stature=176}
]
3.1.9. distinct()
Stream<T> distinct(); 对 Stream 中所有元素进行去重,返回新的 Stream流。
示例1:
public static void DistinctDemo() {
List<String> list = new ArrayList<String>() {{
add("1");
add("2");
add("2");
add("5");
add("3");
}};
List<String> strList = list.stream()
.distinct()
.collect(Collectors.toList());
System.out.println(strList);
}
运行结果:
[1, 2, 5, 3]
示例2:
public static void DistinctDemo() {
List<Student> studentList = new ArrayList<>();
studentList.add(new Student("流华追梦1", 22, 175));
studentList.add(new Student("流华追梦2", 40, 180));
studentList.add(new Student("流华追梦3", 50, 185));
studentList.add(new Student("流华追梦1", 22, 183));
studentList.add(new Student("流华追梦2", 48, 176));
// 根据 name 进行去重
List<Student> distinctUsers1 = studentList.stream()
.filter(distinctByKey(Student::getName))
.collect(Collectors.toList());
System.out.println(distinctUsers1);
// 根据 name + age 进行去重
List<Student> distinctUsers2 = studentList.stream()
.filter(distinctByKey(item -> item.getName() + item.getAge()))
.collect(Collectors.toList());
System.out.println(distinctUsers2);
}
// 运行结果:
[{"流华追梦1", 22, 175}, {"流华追梦2", 40, 180}, {"流华追梦3", 50, 185}]
[{"流华追梦1", 22, 175}, {"流华追梦2", 40, 180}, {"流华追梦3", 50, 185}, {"流华追梦2", 48, 176}]
3.1.10. sorted()
sorted() 方法提供了排序的功能,并且允许我们自定义排序。
public static void SortedDemo() {
List<Student> studentList = new ArrayList<>();
studentList.add(new Student("流华追梦1", 22, 175));
studentList.add(new Student("流华追梦2", 40, 180));
studentList.add(new Student("流华追梦3", 50, 185));
studentList.add(new Student("流华追梦4", 22, 183));
studentList.add(new Student("流华追梦5", 48, 176));
studentList.sort((v1, v2)-> v1.getAge() - v2.getAge());
// studentList.sort(Comparator.comparingInt(Student::getAge));
// studentList.sort(new Comparator<Student>() {
// @Override
// public int compare(Student o1, Student o2) {
// return o1.getAge() - o2.getAge();
// }
// });
System.out.println(studentList);
}
// 运行结果:
[{"流华追梦1", 22, 175}, {"流华追梦4", 25, 183}, {"流华追梦2", 48, 176}, {"流华追梦5", 48, 176}, {"流华追梦3", 50, 185}]
3.2. 终结型操作
终结型操作与中间型相反,返回值是非 Stream 类型的。API 如下:
API | 功能说明 |
count() | 返回stream处理后最终的元素个数 |
max() | 返回stream处理后的元素最大值 |
min() | 返回stream处理后的元素最小值 |
findFirst() | 找到第一个符合条件的元素时则终止流处理 |
findAny() | 找到任何一个符合条件的元素时则退出流处理,这个对于串行流时与findFirst相同,对于并行流时比较高效,任何分片中找到都会终止后续计算逻辑 |
anyMatch() | 返回一个boolean值,类似于isContains(),用于判断是否有符合条件的元素 |
allMatch() | 返回一个boolean值,用于判断是否所有元素都符合条件 |
noneMatch() | 返回一个boolean值, 用于判断是否所有元素都不符合条件 |
collect() | 将流转换为指定的类型,通过Collectors进行指定 |
reduce() | 将一个Stream中的所有元素反复结合起来,得到一个结果 |
toArray() | 将流转换为数组 |
iterator() | 将流转换为Iterator对象 |
foreach() | 无返回值,对元素进行逐个遍历,然后执行给定的处理逻辑 |
3.2.1. count()
long count(); 统计功能,一般都是结合 filter() 使用,先筛选出我们需要的再统计即可。
public class CountDemo {
public static void main(String[] args) {
List<Student> studentList = new ArrayList<>(3);
studentList.add(new Student("流华追梦1", 22, 175));
studentList.add(new Student("流华追梦2", 40, 180));
studentList.add(new Student("流华追梦3", 50, 185));
long count = studentList.stream().filter(s1 -> s1.getAge() < 45).count();
System.out.println("年龄小于45岁的人数是:" + count);
}
}
// 输出结果:
年龄小于45岁的人数是:2
3.2.2. max()、min()
Optional<T> max(Comparator<? super T> comparator); 和 Optional<T> min(Comparator<? super T> comparator); 在集合中求最大值和最小值。
public class MaxMinDemo {
public static void main(String[] args) {
List<Student> studentList = new ArrayList<>(3);
studentList.add(new Student("流华追梦1", 22, 175));
studentList.add(new Student("流华追梦2", 40, 180));
studentList.add(new Student("流华追梦3", 50, 185));
Optional<Student> max = studentList.stream()
.max(Comparator.comparing(Student::getAge));
Optional<Student> min = studentList.stream()
.min(Comparator.comparing(Student::getAge));
// 判断是否有值
max.ifPresent(System.out::println);
min.ifPresent(System.out::println);
}
}
// 输出结果:
Student{name='流华追梦3', age=50, stature=185}
Student{name='流华追梦1', age=22, stature=175}
3.2.3. findFirst()、findAny()
Optional<T> findFirst(); 和 Optional<T> findAny(); 返回流中的第一个元素和任意元素。
public class FindDemo {
public static void main(String[] args) {
List<Student> studentList = new ArrayList<>(3);
studentList.add(new Student("流华追梦1", 22, 175));
studentList.add(new Student("流华追梦2", 40, 180));
studentList.add(new Student("流华追梦3", 50, 185));
Optional<Student> student = studentList.stream().filter(s1 -> s1.getAge() < 60).findFirst();
System.out.println("年龄小于60岁的第1个人是:" + stduent.get().getName());
Optional<Student> student = studentList.stream().filter(s1 -> s1.getAge() < 60).findAny();
System.out.println("年龄小于60岁的任1个人是:" + stduent.get().getName());
}
}
// 输出结果:
年龄小于60岁的第1个人是:流华追梦1
年龄小于60岁的任1个人是:流华追梦1
3.2.4. anyMatch()、noneMatch() 和 allMatch()
boolean anyMatch(Predicate<? super T> predicate); 检查是否至少匹配一个元素。
boolean allMatch(Predicate<? super T> predicate); 检查是否匹配所有元素。
boolean noneMatch(Predicate<? super T> predicate); 检查是否没有匹配所有元素。
public class MatchDemo {
public static void main(String[] args) {
List<Student> studentList = new ArrayList<>(3);
studentList.add(new Student("流华追梦1", 22, 175));
studentList.add(new Student("流华追梦2", 40, 180));
studentList.add(new Student("流华追梦3", 50, 185));
boolean anyMatched = studentList.stream().anyMatch(s1 -> s1.getAge() > 40);
System.out.println("有年龄大于40岁的人吗:" + anyMatched);
boolean allMatched = studentList.stream().allMatch(s1 -> s1.getAge() > 40);
System.out.println("年龄全部大于40岁吗:" + allMatched);
boolean noneMatched = studentList.stream().noneMatch(s1 -> s1.getAge() > 40);
System.out.println("年龄全部没有大于40岁的人吗:" + noneMatched);
}
}
// 输出结果:
有年龄大于40岁的人吗:true
年龄全部大于40岁吗:false
年龄全部没有大于40岁的人吗:false
3.2.5. reduce()
T reduce(T identity, BinaryOperator<T> accumulator); 可以实现从一组值中生成一个值。
public class ReduceSumDemo {
public static void main(String[] args) {
Integer reduce = Stream.of(1, 2, 3, 4, 5).reduce(0, Integer::sum);
System.out.println(reduce);
}
}
// 输出结果:
15
3.2.6. collect()
<R, A> R collect(Collector<? super T, A, R> collector); 将流转换为 List,Set 对应 toSet(),Map对应 toMap() 等。
public class TestCase {
public static void main(String[] args) {
List<Student> studentList = Stream.of(
new Student("流华追梦1", 22, 175),
new Student("流华追梦2", 40, 180),
new Student("流华追梦3", 50, 185)).collect(Collectors.toList());
System.out.println(studentList);
}
}
输出结果:
[Student{name='流华追梦1', age=22, stature=175},
Student{name='流华追梦2', age=40, stature=180},
Student{name='流华追梦3', age=50, stature=185}]
3.3. Collector 收集
Collector 接口中的方法的实现决定了如何对流执行收集操作(如收集到 List、Set、Map)。Collectors 实用类提供了很多静态方法,可以方便地创建常用的收集器实例,具体方法如下表:
方法 | 返回类型 | 作用 |
---|---|---|
toList | List<T> | 把流中元素收集到 List |
toSet | Set<T> | 把流中元素收集到 Set |
toCollection | Collection<T> | 把流中元素收集到创建的集合 |
toMap | Map<K,U> | 把流中元素收集到 Map |
toConcurrentMap | ConcurrentMap<K,U> | 把流中元素收集到 ConcurrentMap |
mapping | Collector<T, ?, R> | 首先对流中的每个元素进行映射,即类型转换,然后再将新元素以给定的Collector进行归纳。 类似与Stream的map方法。 |
counting | Long | 计算流中元素的个数 |
summingInt | Integer | 对流中元素的整数属性求和 |
summingLong | Long | 对流中元素的Long属性求和 |
summingDouble | Double | 对流中元素的Double属性求和 |
averagingInt | Double | 计算流中元素Integer属性的平均值 |
averagingLong | Long | 计算流中元素Long属性的平均值 |
averagingDouble | Double | 计算流中元素Double属性的平均值 |
summarizingInt | Collector<T, ?, IntSummaryStatistics> | 收集流中Integer属性的统计值。如平均值 |
summarizingLong | Collector<T, ?, LongSummaryStatistics> | 收集流中Long属性的统计值。如平均值 |
summarizingDouble | Collector<T, ?, DoubleSummaryStatistics> | 收集流中Double属性的统计值。如平均值 |
joining | Collector<CharSequence, ?, String> | 连接流中每个字符串 |
maxBy | Collector<T, ?, Optional<T>> | 根据比较器选择最大值 |
minBy | Collector<T, ?, Optional<T>> | 根据比较器选择最小值 |
reducing | 归约产生的类型 | 从一个作为累加器的初始值开始,利用 BinaryOperator 与流中元素逐个结合,从而归约成单个值 |
collectingAndThen | 置换函数返回的类型 | 包裹另一个收集器,对其结果置换的函数 |
groupingBy | Map<K, List<T>> | 根据某属性值对流分组,属性为K,结果为V |
partitioningBy | Map<Boolean, List<T>> | 根据 true 或 false 进行分区 |
3.3.1. toList()、toSet()、toCollection()
将流转换为其他形式,收集到新的集合上,包括Collectors.toList()、Collectors.toSet()、Collectors.toCollection(集合对象)。
@Test
public void test01() {
List<Student> studentList = new ArrayList<>();
studentList.add(new Student("流华追梦1", 22, 175));
studentList.add(new Student("流华追梦2", 40, 180));
studentList.add(new Student("流华追梦3", 50, 185));
studentList.add(new Student("流华追梦4", 22, 183));
studentList.add(new Student("流华追梦5", 48, 176));
List<String> list = studentList.stream().map(Student::getName).collect(Collectors.toList());
list.forEach(System.out::println);
System.out.println("-----------------");
Set<String> set = studentList.stream().map(Student::getName).collect(Collectors.toSet());
set.forEach(System.out::println);
System.out.println("-----------------");
HashSet<String> hashSet = studentList.stream().map(Student::getName).collect(Collectors.toCollection(HashSet::new));
hashSet.forEach(System.out::println);
}
// 运行结果:
流华追梦1
流华追梦2
流华追梦3
流华追梦4
流华追梦5
-----------------
流华追梦3
流华追梦2
流华追梦5
流华追梦4
流华追梦1
-----------------
流华追梦3
流华追梦2
流华追梦5
流华追梦4
流华追梦1
3.3.2. toMap()、toConcurrentMap()
toMap() 方法是根据给定的键生成器和值生成器生成的键和值保存到一个 map 中返回,键和值的生成都依赖于元素,可以指定出现重复键时的处理方案和保存结果的 map。
toConcurrentMap() 的使用与 toMap() 基本一致, 只不过 toConcurrentMap() 用于处理并发请求,它生成的 map 是 ConcurrentHashMap。
toMap() 有三个重载的方法:
public static <T, K, U>
Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper) {
return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new);
}
public static <T, K, U>
Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction) {
return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new);
}
public static <T, K, U, M extends Map<K, U>>
Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction,
Supplier<M> mapSupplier) {
BiConsumer<M, T> accumulator
= (map, element) -> map.merge(keyMapper.apply(element),
valueMapper.apply(element), mergeFunction);
return new CollectorImpl<>(mapSupplier, accumulator, mapMerger(mergeFunction), CH_ID);
}
三个重载的方法,最终都是调用第三个方法来实现,第一个方法中默认指定了 key 重复的处理方式和 map 的生成方式;而第二个方法默认指定了 map 的生成方式,用户可以自定义 key 重复的处理方式。
Map<Integer, Student> map1 = stream.collect(Collectors.toMap(Student::getId, v->v));
Map<Integer, String> map2 = stream.collect(Collectors.toMap(Student::getId, Student::getName, (a, b)->a));
Map<Integer, String> map3 = stream.collect(Collectors.toMap(Student::getId, Student::getName, (a, b)->a, HashMap::new));
3.3.3. mapping()、collectingAndThen()
mapping() 对流中的每个元素进行映射,即类型转换,然后再将新元素以给定的Collector进行归纳。 类似与Stream的map方法。
collectingAndThen:在归纳动作结束之后,对归纳的结果进行再处理。
public void mappingDemo() {
List<Student> studentList = new ArrayList<>();
studentList.add(new Student("流华追梦1", 22, 175));
studentList.add(new Student("流华追梦2", 40, 180));
studentList.add(new Student("流华追梦3", 50, 185));
studentList.add(new Student("流华追梦4", 22, 183));
studentList.add(new Student("流华追梦5", 48, 176));
String nameByAge = studentList.stream().collect(Collectors.mapping(Student::getName, Collectors.joining(",", "[", "]")));
System.out.println(nameByAge);
nameByAge = studentList.stream().map(student -> student.getName()).collect(Collectors.joining(",", "[", "]"));
System.out.println(nameByAge);
Integer size = studentList.collect(Collectors.collectingAndThen(Collectors.mapping(Student::getAge, Collectors.toList()), o -> o.size()));
}
运行结果:
[流华追梦1,流华追梦2,流华追梦3,流华追梦4,流华追梦5]
[流华追梦1,流华追梦2,流华追梦3,流华追梦4,流华追梦5]
3.3.4. groupingBy()
根据某属性值对流分组,属性为K,结果为V。
public void groupDemo() {
List<Student> studentList = new ArrayList<>();
studentList.add(new Student("流华追梦1", 22, 175));
studentList.add(new Student("流华追梦2", 40, 180));
studentList.add(new Student("流华追梦3", 50, 185));
studentList.add(new Student("流华追梦4", 22, 183));
studentList.add(new Student("流华追梦5", 48, 176));
Map<Integer, String> nameByAgeMap = studentList.stream().collect(
Collectors.groupingBy(Student::getAge, Collectors.mapping(Student::getName, Collectors.joining(",", "[", "]"))));
nameByAgeMap.forEach((k, v) -> System.out.println("Age:" + k + " Students: " + v));
}
// 运行结果:
Age:48 Students: [流华追梦5]
Age:50 Students: [流华追梦3]
Age:22 Students: [流华追梦1,流华追梦4]
Age:40 Students: [流华追梦2]
3.3.5. partitioningBy()
该方法将流中的元素按照给定的校验规则的结果分为两个部分,放到一个map中返回,map的键是Boolean类型,值为元素的集合。
public static <T>
Collector<T, ?, Map<Boolean, List<T>>> partitioningBy(Predicate<? super T> predicate) {
return partitioningBy(predicate, toList());
}
public static <T, D, A>
Collector<T, ?, Map<Boolean, D>> partitioningBy(Predicate<? super T> predicate,
Collector<? super T, A, D> downstream) {
......
}
从上面的重载方法中可以看出,partitioningBy 与 groupingBy 类似, 只不过partitioningBy 生成的map的key的类型限制只能是Boolean类型。
public void partitionDemo() {
List<Student> studentList = new ArrayList<>();
studentList.add(new Student("流华追梦1", 22, 175));
studentList.add(new Student("流华追梦2", 40, 180));
studentList.add(new Student("流华追梦3", 50, 185));
studentList.add(new Student("流华追梦4", 22, 183));
studentList.add(new Student("流华追梦5", 48, 176));
Map<Boolean, List<Student>> m4 = stream.collect(Collectors.partitioningBy(stu -> stu.getAge() > 40));
Map<Boolean, Set<Student>> m5 = stream.collect(Collectors.partitioningBy(stu -> stu.getAge() > 60, Collectors.toSet()));
}
3.3.6. joining()
连接流中每个字符串。
Stream<String> stream = Stream.of("1", "2", "3", "4", "5", "6");
String s = stream.collect(Collectors.joining(",", "prefix", "suffix"));
// 运行结果:
prefix1,2,3,4,5,6suffix
3.3.7. 统计相关 counting()、maxBy()、minBy()、averagingDouble()、summingInt()、summarizingInt()
数组统计相关方法,包括 counting()、maxBy()、minBy()、averagingDouble()、summingInt()、summarizingInt() 等方法。
public void test01() {
List<Student> studentList = new ArrayList<>();
studentList.add(new Student("流华追梦1", 22, 175));
studentList.add(new Student("流华追梦2", 40, 180));
studentList.add(new Student("流华追梦3", 50, 185));
studentList.add(new Student("流华追梦4", 22, 183));
studentList.add(new Student("流华追梦5", 48, 176));
// 总数
Long sum= studentList.stream().collect(Collectors.counting());
System.out.println(sum);
// 平均值
Double averageAge = studentList.stream().collect(Collectors.averagingDouble(Student::getAge));
System.out.println(averageAge);
// 某属性的综合
Integer ageSum = studentList.stream().collect(Collectors.summingInt(Student::getAge));
System.out.println(ageSum);
// 最大值
Optional<Student> maxAgeEmployee = studentList.stream().collect(Collectors.maxBy((e1, e2) -> e1.getAge().compareTo(e2.getAge())));
System.out.println(maxAgeEmployee.get());
// 最小值
Optional<Student> minAgeEmployee = studentList.stream().collect(Collectors.minBy((e1, e2) -> e1.getAge().compareTo(e2.getAge())));
System.out.println(minAgeEmployee.get());
// 所有数据
IntSummaryStatistics totalData = studentList.stream().collect(Collectors.summarizingInt(Student::getAge));
System.out.println(totalData);
}
// 运行结果:
5
36.4
182
Student{name='流华追梦3', age=50, stature=185}
Student{name='流华追梦1', age=22, stature=175}
IntSummaryStatistics{count=5, sum=182, min=22, average=36.400000, max=50}
3.3.8. reducing()
reducing() 方法有三个重载方法,其实是和 Stream 里的三个 reduce() 方法对应的,二者是可以替换使用的,作用完全一致,也是对流中的元素做统计归纳作用。
public static <T> Collector<T, ?, Optional<T>> reducing(BinaryOperator<T> op) {
......
}
public static <T> Collector<T, ?, T> reducing(T identity, BinaryOperator<T> op) {
......
}
public static <T, U> Collector<T, ?, U> reducing(U identity,Function mapper, BinaryOperator<U> op) {
......
}
List<String> list2 = Arrays.asList("123","456","789","qaz","wsx","edc");
Optional<Integer> optional = list2.stream().map(String::length).collect(Collectors.reducing(Integer::sum));
System.out.println("reducing1: " + optional.get());
Integer sum1 = list2.stream().map(String::length).collect(Collectors.reducing(0, Integer::sum));
System.out.println("reducing2: " + sum1);
Integer sum2 = list2.stream().limit(4).collect(Collectors.reducing(0, String::length, Integer::sum));
System.out.println("reducing2: " + sum2);
// 运行结果:
reducing1: 18
reducing2: 18
reducing3: 18