Jdk8新特性(七):JDK8之集合框架(Stream、parallelStream、...)

时间:2021-10-21 00:43:17


7. JDK8之集合框架

7.1. 新增串行流(Stream)

7.1.1. Stream简介

Stream即“流”,通过将集合转换为这么一种叫做 “流”的元素队列,通过声明性⽅方式,能够对集合中的每个元素进行一系列并行或串行的流⽔线操作。

Stream 就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。

而和迭代器又不同的是,Stream 可以并行化操作,迭代器只能命令式地、串行化操作。顾名思义,当使用串行方式去遍历时,每个 item 读完后再读下一个 item。而使用并行去遍历时,数据会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出。Stream 的并行操作依赖于 Java7 中引入的 Fork/Join 框架(JSR166y)来拆分任务和加速处理过程。

Java 8 中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)。Stream API 借助于同样新出现的 Lambda 表达式,极大的提高编程效率和程序可读性。同时它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用 fork/join 并行方式来拆分任务和加速处理过程。通常编写并行代码很难而且容易出错, 但使用 Stream API 无需编写一行多线程的代码,就可以很方便地写出高性能的并发程序。所以说,Java 8 中首次出现的 java.util.stream 是一个函数式语言+多核时代综合影响的产物。

7.1.2. Stream操作

7.1.2.1. Stream操作步骤

Stream操作步骤如下:

 数据元素集合
即原始集合List、 Set、 Map等。

 生成流(Stream)
可以是串行流stream() 或者并行流 parallelStream()。
通过一个数据源获取一个流,例如,List中的stream()方法可以直接返回一个Stream对象。

 中间操作
对流中的数据进行的操作,比如循环处理(map),过滤(filter)、排序、聚合、转换等。

 终端操作
很多流操作本身就会返回一个流,所以多个操作可以直接连接起来,最后统一进⾏收集。

7.1.2.2. map和filter函数

 map方法

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class MapAndFilter {

public static void main(String[] args) {
//1.遍历String集合
List<String> colorList = Arrays.asList("black", "white", "yellow", "red", "grey", "blue");
Stream<String> cStream = colorList.stream();
List<String> resList = cStream.map(color -> "我喜欢"+color).collect(Collectors.toList());
for (String s : resList) {
System.out.print(s+",");// 我喜欢black,我喜欢white,我喜欢yellow,我喜欢red,我喜欢grey,我喜欢blue,
}

//2.遍历JavaBean集合
List<User> userList = Arrays.asList(new User("李四", 13), new User("张三", 23), new User("王五", 88));
Stream<User> uStream = userList.stream();
List<User> uList = uStream.map(user -> {
return new User(user.getName(), user.getAge());
}).collect(Collectors.toList());
for (User u : uList) {
System.out.print(u+",");//User [姓名:李四, 年龄:13],User [姓名:张三, 年龄:23],User [姓名:王五, 年龄:88],
}
}

}

 Filter方法

// 筛选出颜色名长度>4的颜色
List<String> colorList2 = Arrays.asList("black", "white", "yellow", "red", "grey", "blue");
List<String> cList = colorList2.stream()
.filter(color -> color.length() > 4)
.collect(Collectors.toList());
System.out.println(cList);// [black, white, yellow]

7.1.2.3. limit和sorted函数

 limit方法
截断(限制)流,使其最多只包含指定数量的元素。

List<String> colorList = Arrays.asList("black", "white", "yellow", 
"red", "grey", "blue");
List<String> list = colorList.stream().limit(2)
.collect(Collectors.toList());
System.out.println(list);//[black, white]

 sorted方法

(1) Stream sorted()
返回由此流的元素组成的流,根据自然顺序排序。

//按字母自然排序
List<String> colorList = Arrays.asList("black", "white", "yellow", "red", "grey", "blue");
List<String> list = colorList.stream().sorted()
.collect(Collectors.toList());
System.out.println(list);//[black, blue, grey, red, white, yellow]

(2) Stream sorted(Comparator<? super T> comparator)
返回由该流的元素组成的流,根据提供的 Comparator进行排序。

//按长度排序
List<String> colorList = Arrays.asList("black", "white", "yellow", "red", "grey", "blue");
List<String> list = colorList.stream()
.sorted(Comparator.comparing(obj -> obj.length()))
.collect(Collectors.toList());
System.out.println(list);//[red, grey, blue, black, white, yellow]

//方式一:按长度排序,并反转(逆序)
List<String> colorList2 = Arrays.asList("black", "white", "yellow", "red", "grey", "blue");
List<String> list2 = colorList2.stream()
.sorted(Comparator.comparing(obj -> obj.length(), Comparator.reverseOrder()))
.collect(Collectors.toList());
System.out.println(list2);//[yellow, black, white, grey, blue, red]

//方式二:按长度排序,并反转(逆序)
List<String> colorList3 = Arrays.asList("black", "white", "yellow", "red", "grey", "blue");
List<String> list3 = colorList3.stream()
.sorted(Comparator.comparing(String::length).reversed())
.collect(Collectors.toList());
System.out.println(list3);//[yellow, black, white, grey, blue, red]

7.1.2.4. allMatch和anyMatch函数

 allMatch方法

//allMatch,所有元素均符合,返回true
boolean flag = colorList.stream().allMatch(obj -> obj.length() > 4);
System.out.println(flag);//false
boolean flag2 = colorList.stream().allMatch(obj -> obj.length() > 1);
System.out.println(flag2);//true

 anyMatch方法

//anyMatch,有任意一个元素匹配,返回true
boolean flag3 = colorList.stream().anyMatch(obj -> obj.length() > 4);
System.out.println(flag3);//true
boolean flag4 = colorList.stream().anyMatch(obj -> obj.length() > 6);
System.out.println(flag4);//false

7.1.2.5. max和min函数

List<User> userList = Arrays.asList(new User("李四", 13), new User("张三", 23), new User("王五", 88));

//第一种:求最大、最小值
Optional<User> max = userList.stream()
.max(Comparator.comparingInt(User::getAge));
System.out.println(max.get());//User [姓名:王五, 年龄:88]
Optional<User> min = userList.stream()
.min(Comparator.comparingInt(User::getAge));
System.out.println(min.get());//User [姓名:李四, 年龄:13]

//第二种:求最大、最小值
//Optional<User> max2 = userList.stream()
.max((O1, O2) -> Integer.compare(O1.getAge(), O2.getAge()));
Optional<User> max2 = userList.stream()
.max((O1, O2) -> O1.getAge()-O2.getAge());
System.out.println(max2.get());//User [姓名:王五, 年龄:88]
Optional<User> min2 = userList.stream()
.min((O1, O2) -> O1.getAge()-O2.getAge());
System.out.println(min2.get());//User [姓名:李四, 年龄:13]

7.2. 新增并行流parallelStream

 为什么会有这个并行流?

集合做重复的操作,如果使用串行执行会相当耗时,因此一般会采用多线程来加快, Java8的paralleStream用fork/join框架提供了并发执行能力。

底层原理:
(1) 线程池(ForkJoinPool)维护了一个线程队列。
(2) 可以分割任务,将父任务拆分成子任务,完全贴合分治思想。

List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);

//串行,顺序输出
list.stream().forEach(System.out::print);//12345678910

//并行,乱序输出
list.parallelStream().forEach(System.out::print);//76891045123

 问题
(1) paralleStream并⾏是否一定比Stream串行快?

不一定,数据量少的情况,可能串行更快,ForkJoin会耗性能。

(2) 多数情况下并⾏比串⾏快,是否可以都⽤并行?

不⾏,部分情况会有线程安全问题, parallelStream里面使用的外部变量,比如:集合一定要使用线程安全集合,不然就会引发多线程安全问题。

(1) 非线程安全集合ArrayList:

for (int i = 0; i < 10; i++) {
List<Integer> list2 = new ArrayList<Integer>();
IntStream.range(0, 100).parallel().forEach(list2::add);
System.out.println(list2.size());
}

出现如下异常:

Jdk8新特性(七):JDK8之集合框架(Stream、parallelStream、...)

ArrayList的add()源码:

/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}

(2) 线程安全集合CopyOnWriteArrayList

for (int i = 0; i < 10; i++) {
List<Integer> list2 = new CopyOnWriteArrayList<Integer>();
IntStream.range(0, 100).parallel().forEach(list2::add);
System.out.println(list2.size());
}

CopyOnWriteArrayList的add()源码:

/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}

7.3. 集合的聚合操作(reduce)

 常用方法:Optional reduce(BinaryOperator accumulator)

//1.普通写法
Optional<Integer> opt = Stream.of(1,2,3,4,5)
.reduce(new BinaryOperator<Integer>() {
@Override
public Integer apply(Integer item1, Integer item2) {
return item1+item2;
}
});
if(opt.isPresent()) {
System.out.println(opt.get());//15
}

//2.Lambda写法
int value = Stream.of(1, 2, 3, 4, 5)
.reduce((item1, item2) -> item1 + item2).get();
System.out.println(value);//15

 常用方法:T reduce(T identity, BinaryOperator accumulator)

//1.普通写法
Integer val = Stream.of(1,2,3,4,5)
.reduce(100, new BinaryOperator<Integer>() {
@Override
public Integer apply(Integer item1, Integer item2) {
return item1+item2;
}
});
System.out.println(val);//115

//2.Lambda写法
Integer val2 = Stream.of(1,2,3,4,5)
.reduce(100, (item1, item2) -> item1+item2);
System.out.println(val2);//115

练习:求最大值

//1.普通写法
Integer v = Stream.of(12,70,32).max(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1-o2;
}
}).get();
System.out.println(v);//70

//2.Lambda写法一
Integer v1 = Stream.of(12,70,32).max((o1,o2)-> o1-o2).get();
System.out.println(v1);//70

//3.Lambda写法二
Integer v2 = Stream.of(12,70,32).reduce((item1,item2)->
item1>item2 ? item1:item2).get();
System.out.println(v2);//70

7.4. 集合的遍历操作(foreach)

注意:
(1) 不能修改包含外部变量的值。
(2) 不能用break或return或continue等关键词结束或跳过循环。

List<User> userList = Arrays.asList(new User("李四", 13), new User("张三", 23), new User("王五", 88));
userList.forEach(obj -> {
System.out.println(obj);
});
/*
User [姓名:李四, 年龄:13]
User [姓名:张三, 年龄:23]
User [姓名:王五, 年龄:88]
*/

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
list.stream().forEach(System.out::print);// 12345678910

list.stream().forEach(obj -> {
System.out.print(obj + ",");// 1,2,3,4,5,6,7,8,9,10,
});

7.5. JDK8收集器和集合统计

7.5.1. JDK8收集器(collector)

 collect()方法的作用

一个终端操作,用于对流中的数据进行归集操作, collect方法接受的参数是一个Collector。

有两个重载方法,在Stream接口里面。

//重载方法⼀
<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);

 Collector的作用
就是收集器,也是一个接口,它的工具类Collectors提供了很多工厂方法。

 Collectors 的作用
工具类,提供了很多常见的收集器的实现。

// 1.遍历String集合
List<String> colorList = Arrays.asList("black", "white", "yellow", "red", "grey", "blue");
Stream<String> cStream = colorList.stream();
List<String> resList = cStream.map(color -> "我喜欢" + color).collect(Collectors.toList());
for (String s : resList) {
System.out.print(s + ",");// 我喜欢black,我喜欢white,我喜欢yellow,我喜欢red,我喜欢grey,我喜欢blue,
}

Collectors.toList()源码:

/**
* Returns a {@code Collector} that accumulates the input elements
* into a new {@code List}.
*
* @param <T> the type of the input elements
* @return a {@code Collector} which collects all the input elements into a
* {@code List}, in encounter order
*/
public static <T>
Collector<T, ?, List<T>> toList() {
return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add,
(left, right) -> { left.addAll(right); return left; },CH_ID);
}

ArrayList​​::​​​new,创建一个ArrayList作为累加器。
List::add,对流中元素的操作就是直接添加到累加器中
reduce操作,对子任务归集结果addAll,后一个子任务的结果直接全部添加到前一个子任务结果中。
CH_ID 是一个unmodifiableSet集合。
Collectors.toMap()
Collectors.toSet()
Collectors.toCollection() :用户自定义的实现Collection的数据结构收集
Collectors.toCollection(LinkedList::new)
Collectors.toCollection(CopyOnWriteArrayList::new)
Collectors.toCollection(TreeSet::new)


7.5.2. 集合统计函数

7.5.2.1. 拼接函数(joining)

List<String> colorList = Arrays.asList("black", "white", "yellow", "red", "grey", "blue");
String str = colorList.stream().collect(Collectors.joining("_", "$", "^"));
System.out.println(str);//$black_white_yellow_red_grey_blue^

Collectors.joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix) 源码:

/**
* Returns a Collector that concatenates the input elements,
* separated by the specified delimiter, with the specified prefix and
* suffix, in encounter order.
*
* @param delimiter the delimiter to be used between each element
* @param prefix the sequence of characters to be used at the beginning
* of the joined result
* @param suffix the sequence of characters to be used at the end
* of the joined result
* @return A {@code Collector} which concatenates CharSequence elements,
* separated by the specified delimiter, in encounter order
*/
public static Collector<CharSequence, ?, String> joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix) {
return new CollectorImpl<>(
() -> new StringJoiner(delimiter, prefix, suffix),
StringJoiner::add, StringJoiner::merge,
StringJoiner::toString, CH_NOID);
}

7.5.2.2. 分组函数(partitioningBy)

List<String> colorList = Arrays.asList("black", "white", "yellow", "red", "grey", "blue");
Map<Boolean, List<String>> result = colorList.stream()
.collect(Collectors.partitioningBy(obj -> obj.length() > 4));
for (Map.Entry<Boolean,List<String>> map : result.entrySet()) {
System.out.println(map.getKey()+":"+map.getValue());
}
/*
false:[red, grey, blue]
true:[black, white, yellow]
*/
//Lambda遍历map
List<String> colorList2 = Arrays.asList("black", "white", "yellow", "red", "grey", "blue");
Map<Boolean, List<String>> map = colorList2.stream()
.collect(Collectors.partitioningBy(obj -> obj.length() > 4));
map.keySet().forEach(key -> {
System.out.println(key+":"+map.get(key));
});
/*
false:[red, grey, blue]
true:[black, white, yellow]
*/

7.5.2.3. 分组函数(groupingBy)和分组统计(counting)

 Collectors.groupingBy:分组

List<Student> stuList = Arrays.asList(new Student("广东", 23), new Student("广东", 24), new Student("广东", 23),new Student("北京", 22), new Student("北京", 20), new Student("北京", 20), new Student("海南", 25));
Map<String, List<Student>> maps = stuList.stream()
.collect(Collectors.groupingBy(obj -> obj.getProvince()));
// 1.普通遍历
for (Map.Entry<String, List<Student>> map : maps.entrySet()) {
System.out.println(map.getKey()+":"+map.getValue());
}
/*
广东:[Student [province=广东, age=23], Student [province=广东, age=24], Student [province=广东, age=23]]
海南:[Student [province=海南, age=25]]
北京:[Student [province=北京, age=22], Student [province=北京, age=20], Student [province=北京, age=20]]
*/

//2.Lambda遍历(一)
maps.entrySet().forEach(obj -> {
System.out.println(obj.getKey()+":"+obj.getValue());
});
/*
广东:[Student [province=广东, age=23], Student [province=广东, age=24], Student [province=广东, age=23]]
海南:[Student [province=海南, age=25]]
北京:[Student [province=北京, age=22], Student [province=北京, age=20], Student [province=北京, age=20]]
*/

//3.Lambda遍历(二)
maps.forEach((key, value) -> {
System.out.println(key+":");
value.forEach(obj -> {
System.out.println(obj);
});
});
/*
广东:
Student [province=广东, age=23]
Student [province=广东, age=24]
Student [province=广东, age=23]
海南:
Student [province=海南, age=25]
北京:
Student [province=北京, age=22]
Student [province=北京, age=20]
Student [province=北京, age=20]
*/

 Collectors.counting():分组统计

List<Student> stuList2 = Arrays.asList(new Student("广东", 23), new Student("广东", 24), new Student("广东", 23),new Student("北京", 22), new Student("北京", 20), new Student("北京", 20), new Student("海南", 25));
Map<String, Long> maps2 = stuList2.stream().collect(Collectors
.groupingBy(Student::getProvince, Collectors.counting()));
maps2.entrySet().forEach(obj -> {
System.out.println(obj.getKey()+":"+obj.getValue());
});
/*
广东:3
海南:1
北京:3
*/

7.5.2.4. summarizing 统计相关函数

List<Student> stuList3 = Arrays.asList(new Student("广东", 23), new Student("广东", 24), new Student("广东", 23),new Student("北京", 22), new Student("北京", 20), new Student("北京", 20), new Student("海南", 25));
IntSummaryStatistics summaryStatistics = stuList3.stream()
.collect(Collectors.summarizingInt(Student::getAge));
System.out.println("平均值: " + summaryStatistics.getAverage());//平均值: 22.428571428571427
System.out.println("人数: " + summaryStatistics.getCount());//人数: 7
System.out.println("最大值: " + summaryStatistics.getMax());//最大值: 25
System.out.println("最小值: " + summaryStatistics.getMin());//最小值: 20
System.out.println("总和: " + summaryStatistics.getSum());//总和: 157

源码:

/**
* Returns a {@code Collector} which applies an {@code int}-producing
* mapping function to each input element, and returns summary statistics for the resulting values.
*
* @param <T> the type of the input elements
* @param mapper a mapping function to apply to each element
* @return a {@code Collector} implementing the summary-statistics reduction
*
* @see #summarizingDouble(ToDoubleFunction)
* @see #summarizingLong(ToLongFunction)
*/
public static <T>
Collector<T, ?, IntSummaryStatistics> summarizingInt(ToIntFunction<? super T> mapper) {
return new CollectorImpl<T, IntSummaryStatistics, IntSummaryStatistics>(
IntSummaryStatistics::new,
(r, t) -> r.accept(mapper.applyAsInt(t)),
(l, r) -> { l.combine(r); return l; }, CH_ID);
}