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());
}
出現如下異常:
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);
}