Java必會-Java8新特性
Stream流
Stream
流分為順序流和並行流,所謂順序流就是按照順序對集合中的元素進行處理,而並行流則是使用多執行緒同時對集合中多個元素進行處理,所以在使用並行流的時候就要注意執行緒安全的問題了。
建立流
- 呼叫集合的
stream()
方法或者parallelStream()
方法建立流。 - Stream 類的靜態
of()
方法建立流。
List< String> createStream = new ArrayList< String>(); // 順序流 Stream< String> stream = createStream.stream(); // 並行流 Stream< String> parallelStream = createStream.parallelStream(); // of()方法建立 Stream< String> stringStream = Stream.of( createStream.toArray(new String[createStream.size()]));
使用流
public static void streamImpl(List< Student> students) {
List< Student> filterStudent = students.stream()
.filter(one -> one.getScore() < 60).collect(Collectors.toList()); // 使用流篩選後轉換為集合
System.out.println(filterStudent);
}
終端操作和中間操作
終端操作會消費 Stream 流,並且會產生一個結果,比如 iterator()
spliterator()
。如果一個 Stream 流被消費過了,那它就不能被重用的。
中間操作會產生另一個流。需要注意的是中間操作不是立即發生的。而是當在中間操作建立的新流上執行完終端操作後,中間操作指定的操作才會發生。流的中間操作還分無狀態操作和有狀態操作兩種。
- 在無狀態操作中,在處理流中的元素時,會對當前的元素進行單獨處理。比如,過濾操作,因為每個元素都是被單獨進行處理的,所有它和流中的其它元素無關。
- 在有狀態操作中,某個元素的處理可能依賴於其他元素。比如查詢最小值,最大值,和排序,因為他們都依賴於其他的元素。
流介面
BaseStream
BaseStream是Stream介面的父介面,是 Stream 流最基礎的介面,它提供了所有流都可以使用的基本功能。 BaseStream
是一個泛型介面,它有兩個型別引數 T
和 S
, 其中 T
指定了流中的元素的型別, S
指定了具體流的型別,由 <S extends BaseStream<T,S>>
可以知道 S
必須為 BaseStream
或 BaseStream
子類,換句話說,就是 S
必須是擴充套件自 BaseStream
的。 BaseStream
繼承了 AutoCloseable
介面,簡化了關閉資源的操作,但是像平時我們操作的集合或陣列,基本上都不會出現關閉流的情況。下面是 BaseStream
介面下定義的方法的相關解釋:
Iterator<T> iterator()
:獲取流的迭代器。Spliterator spliterator()
:獲取流的spliterator
。boolean isParallel()
:判斷一個流是否是並行流,如果是則返回true
,否則返回false
。S sequential()
:基於呼叫流返回一個順序流,如果呼叫流本身就是順序流,則返回其本身。S parallel()
:基於呼叫流,返回一個並行流,如果呼叫流本身就是並行流,則返回其本身。S unordered()
:基於呼叫流,返回一個無序流。S onClose(Runnable closeHandler)
:返回一個新流,closeHandler
指定了該流的關閉處理程式,當關閉該流時,將呼叫這個處理程式。void close()
:從AutoCloseable
繼承來的,呼叫註冊關閉處理程式,關閉呼叫流(很少會被使用到)。
stream().forEach、stream().map、stream().filter、stream().sorted的區別和用法
List<Teacher> list = new ArrayList<>();
list.add(Teacher.builder().age(28).name("李四").build());
list.add(Teacher.builder().age(27).name("張三").build());
list.add(Teacher.builder().age(29).name("王五").build());
//適合只對原資料讀操作
list.stream().forEach(teacher -> {
if ("王五".equals(teacher.getName())) {
Address address = Address.builder().address("南京").build();
teacher.setAddress(address);
}
});
System.out.println("還是老的List:" + list);
//不影響原資料,生成新資料
List<Teacher> listCopy = list.stream().map(teacher -> {
if ("王五".equals(teacher.getName())) {
Address address = Address.builder().address("南京").build();
teacher.setAddress(address);
}
return teacher;
}).collect(Collectors.toList());
System.out.println("產生新的List:" + listCopy);
//過濾
List<Teacher> listFilter = list.stream().filter(
teacher -> AllTypeUtils.isNotEmptyAndNotNull(teacher.getAddress()
)).collect(Collectors.toList());
System.out.println("產生新的List:" + listFilter);
//排序:升序(第一種寫法)
List<Teacher> listSortedAsc1 = list.stream().sorted(Comparator.comparing(Teacher::getAge)
).collect(Collectors.toList());
System.out.println("產生新的List:" + listSortedAsc1);
//排序:升序(第二種寫法)
List<Teacher> listSortedAsc2 = list.stream().sorted((teacher1, teacher2) -> {
return teacher1.getAge().compareTo(teacher2.getAge());
//這種升序排列被編譯器建議寫成:Comparator.comparing(Teacher::getAge)
}
).collect(Collectors.toList());
System.out.println("產生新的List:" + listSortedAsc2);
//排序:降序(第一種寫法)
List<Teacher> listSortedDesc = list.stream().sorted(Comparator.comparing(Teacher::getAge).reversed()
).collect(Collectors.toList());
System.out.println("產生新的List:" + listSortedDesc);
//排序:降序(第二種寫法)
List<Teacher> listSortedDesc2 = list.stream().sorted((teacher1, teacher2) -> {
return teacher2.getAge().compareTo(teacher1.getAge());
}
).collect(Collectors.toList());
System.out.println("產生新的List:" + listSortedDesc2);
Stream 介面
-
Stream filter(Predicate predicate)
:產生一個新流,其中包含呼叫流中滿足predicate
指定的謂詞元素,即篩選符合條件的元素後重新生成一個新的流。(中間操作)List<User> newlist = list.stream().filter(user -> user.getAge() > 20 ).collect(Collectors.toList());
-
Stream map(Function mapper)
,產生一個新流,對呼叫流中的元素應用mapper
,新Stream
流中包含這些元素。(中間操作) -
IntStream mapToInt(ToIntFunction mapper)
:對呼叫流中元素應用mapper
,產生包含這些元素的一個新IntStream
流。(中間操作) -
Stream sorted(Comparator comparator)
:產生一個自然順序排序或者指定排序條件的新流。(中間操作)List<Transaction> tr2011 = transactions.stream() .filter(transaction -> transaction.getYear() == 2011) .sorted(comparing(Transaction::getValue)) .collect(toList()); System.out.println(tr2011);
-
void forEach(Consumer action)
:遍歷了流中的元素。(終端操作)transactions.stream() .map(Transaction::getTrader) // Java 8 中可以通過 `::` 關鍵字來訪問類的構造方法,物件方法,靜態方法。 .filter(trader -> trader.getCity().equals("Milan")) .forEach(trader -> trader.setCity("Cambridge")); System.out.println(transactions);
-
Optional min(Comparator comparator)
和Optional max(Comparator comparator)
:獲得流中最大最小值,比較器可以由自己定義。(終端操作) -
boolean anyMatch(Predicate<? super T> predicate)
:判斷Stream
流中是否有任何符合要求的元素,如果有則返回ture
,沒有返回false
。(終端操作)boolean milanBased = transactions.stream() .anyMatch(transaction -> transaction.getTrader() .getCity() .equals("Milan") ); System.out.println(milanBased);
-
Stream<T> distinct()
,去重操作,將Stream
流中的元素去重後,返回一個新的流。(中間操作)List<String> cities = transactions.stream() .map(transaction -> transaction.getTrader().getCity()) .distinct() .collect(toList()); System.out.println(cities);
stream流的API操作
縮減操作reduce()
最終將流縮減為一個值的終端操作,我們稱之為縮減操作。在上一節中提到的 min(),max()
方法返回的是流中的最小或者最大值,這兩個方法屬於特例縮減操作。而通用的縮減操作就是指的我們的 reduce()
方法了,在 Stream 類中 reduce
方法有三種簽名方法
reduce 方法的三種實現
Optional<T> reduce(BinaryOperator<T> accumulator);
T reduce(T identity, BinaryOperator<T> accumulator);
<U> U reduce(U identity,
BiFunction<U, ? super T, U> accumulator,
BinaryOperator<U> combiner);
reduce的應用
-
Optional
reduce(BinaryOperator accumulator); public static void reduceFirstSign() { List<Integer> list = Arrays.asList(1,2,3,4,5,6); ptional<Integer> count = list.stream().reduce((a, b) -> (a + b)); // 所有元素的和 System.out.println(count.get()); // 21 }
-
T reduce(T identity, BinaryOperator
accumulator); public static void reduceSecondSign() { List<Integer> list = Arrays.asList(1,2,3,4,5,6); Integer count = list.stream().reduce(2, (a, b) -> (a * b)); // 所有元素乘積的兩倍 System.out.println(count); // 1440 }
-
前面兩種前面的一個缺點在於返回的資料都只能和 Stream 流中元素型別一致,但這在某些情況下是無法滿足我們的需求的,比如 Stream 流中元素都是
Integer
型別,但是求和之後數值超過了Integer
能夠表示的範圍,需要使用Long
型別接收,這就用到了我們第三種簽名的reduce()
方法。U reduce(U identity,
BiFunction<U, ? super T, U> accumulator,
BinaryOperator combiner); // 第三個引數表示接收的型別
public static void reduceThirdSign() {
List<Integer> list = Arrays.asList(Integer.MAX_VALUE, Integer.MAX_VALUE);
long count = list.stream().reduce(0L, (a, b) -> (a + b), (a,b) -> 0L);
System.out.println(count);
}
總的來說縮減操作有兩個特點,一是他只返回一個值,二是它是一個終端操作
對映
可能在我們的日常開發過程中經常會遇到將一個集合轉換成另外一個物件的集合,那麼這種操作放到 Stream 流中就是對映操作。對映操作主要就是將一個 Stream 流轉換成另外一個物件的 Stream 流或者將一個 Stream 流中符合條件的元素放到一個新的 Stream 流裡面。
map()
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
map()
方法可以將一個流轉換成另外一種物件的流,其中的 T
是原始流中元素的型別,而 R
則是轉換之後的流中元素的型別
示例
public static void useMap() {
List<Student> students = initData();
double scoreCount = students.stream()
.map(Student::getScore)
.reduce(0.0, (a,b) -> (a + b)); // stream物件的元素轉換為Double型別並求和後輸出
System.out.println(scoreCount);
}
mapToDouble()
mapToDouble和map功能相似,前者相對方便些
double scoreCount = students.stream()
.mapToDouble(Student::getScore)
.sum();
flatMap()
flatMap()
操作能把原始流中的元素進行一對多的轉換,並且將新生成的元素全都合併到它返回的流裡面。
程式碼中 flatMap()
中返回的是一個一個的 String
型別的 Stream 流,它們會被合併到最終返回的 Stream 流(String 型別)中。而後面的 distinct()
則是一個去重的操作, collect()
是收集操作。
public static void useFlatMap() {
List<Student> students = initData();
List<String> course = students.stream().flatMap(one -> one.getCourse().stream()).distinct()
.collect(Collectors.toList());
System.out.println(course);
}
收集操作
從流中收集一些元素,並以集合的方式返回,我們把這種反向操作稱為收集操作。
<R, A> R collect(Collector<? super T, A, R> collector);
其中 R
指定結果的型別, T
指定了呼叫流的元素型別。內部積累的型別由 A
指定。 collector
是一個收集器,指定收集過程如何執行, collect()
方法是一個終端方法。一般情況我們只需要藉助 Collectors
中的方法就可以完成收集操作。
Collectors
類是一個最終類,裡面提供了大量的靜態的收集器方法,藉助他,我們基本可以實現各種複雜的功能了。
Collectors
Collectors
給我們提供了非常豐富的收集器,這裡只列出來了 toList
和 toMap
兩種,其他的可以參考 Collectors
類的原始碼。 toList()
相信您在清單 14 中已經見到了,那麼下面將展示如何將一個使用收集操作將一個 List
集合轉為 Map
。
public final class Collectors {
...
public static <T> Collector<T, ?, List<T>> toList() {
...
}
public static <T, K, U> Collector<T, ?, Map<K,U>> toMap(
Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper) {
...
}
...
}
收集操作將 List 轉 Map
public static void list2Map() {
List<Student> students = initData();
Map<String, Double> collect = students.stream()
.collect(Collectors.toMap(one -> one.getName(),
one -> one.getScore()));
System.out.println(collect);
}
可以看到通過 Stream API 可以很方便地將一個 List
轉成了 Map
,但是這裡有一個地方需要注意。那就是在通過 Stream API 將 List
轉成 Map
的時候我們需要確保 key
不會重複,否則轉換的過程將會直接丟擲異常。
並行流
Stream API 提供了相應的並行流來支援我們並行地運算元組和集合框架,從而高速地執行我們對陣列或者集合的一些操作。
其實建立一個並行流非常簡單,在建立流 部分已經提到過如何建立一個並行流,我們只需要呼叫集合的 parallelStream()
方法就可以輕鬆的得到一個並行流。相信大家也知道多執行緒程式設計非常容易出錯,所以使用並行流也有一些限制,一般來說,應用到並行流的任何操作都必須符合三個約束條件:無狀態、不干預、關聯性。因為這三大約束確保在並行流上執行操作的結果和在順序流上執行的結果是相同的。
在縮減操作部分我們一共提到了三種簽名的 reduce()
方法,其中第三種簽名的 reduce()
方法最適合與並行流結合使用。
清單 16. 第三種簽名方式的 reduce()
方法與並行流結合使用
public interface Stream<T> extends BaseStream<T, Stream<T>> {
...
<U> U reduce(U identity,
BiFunction<U, ? super T, U> accumulator,
BinaryOperator<U> combiner);
...
}
其中 accumulator
被為累加器, combiner
為合成器。 combiner
定義的函式將 accumulator
提到的兩個值合併起來,在之前的例子中我們沒有為合併器設定具體的表示式,因為在那個場景下我們不會使用到合併器。下面我們來看一個例子,並且分析其執行的步驟:
清單 17. 並行流使用場景
public static void main(String[] args) {
List<Integer> list = Arrays.asList(2,2);
Integer result = list.stream().parallel().reduce(2, (a, b) -> (a + b), (a, b) -> (a + b));
System.out.println(result);
}
先使用累加器把 Stream 流中的兩個元素都加 2
後,然後再使用合併器將兩部分的結果相加。最終得到的結果也就是 8
。並行流的使用場景也不光是在這中縮減操作上,比如我會經常使用並行流處理一些複雜的物件集合轉換,或者是一些必須迴圈呼叫的網路請求等等,當然在使用的過程中最需要注意的還是執行緒安全問題。