Java8新特性學習(三)- Stream類
背景及介紹
這裏提到Java8的Stream類並不像Java以前版本的InputStream和OutputStream,他們是幾乎不搭邊的兩個類。Stream類常跟集合處理一起使用,算是集合的一個增強。Stream類比前面提到的Optional類更加通用,原因在於Stream類提供更豐富的API,滿足了程序中通用的集合處理方法,且使用lambda方便,較Java7減少較多的代碼。
舉例說明
使用Stream和lambda編碼,可以提高代碼效率,對於過濾取值這種常規操作,相比起java7來實現高效簡單。下面舉例說明,假設我們有一組求職者的簡歷集合,我們需要從中過濾具有3年以上工作經驗,且有高併發技能的Java開發工程師的求職者,得到他們的手機號,進行面試邀約。
public class Resume {
private String name;
private Integer workingAge;
private List<String> skillList;
private String job;
private String phone;
}
//java7
List<String> phoneList = new ArrayList<>();
for (Resume resume : resumeList) {
if (resume.getWorkingAge() < 3) {
continue;
}
if (!resume.getSkillList().contains("高併發")) {
continue;
}
if (!"Java".equals(resume.getJob())) {
continue;
}
phoneList.add(resume.getPhone());
}
//java8
List<String> phoneList = resumeList.stream()
.filter(resume -> resume.getWorkingAge() >= 3)
.filter(resume -> resume.getSkillList().contains("高併發"))
.filter(resume -> "Java".equals(resume.getJob()))
.map(Resume::getPhone).collect(Collectors.toList());
重要方法介紹
java.util.Collection#stream#parallelStream
集合類對象可以直接通過stream()/paralleStream()方法創建串行流和並行流。如
List<String> phoneList = resumeList.stream()
.map(Resume::getPhone).collect(Collectors.toList());
of\generate
of()用來顯式創建串行的Stream對象,有兩個重載方法,一個是元素個數不限,另一個只能傳一個值。基礎數據類型的流,可以擴展使用IntStream、LongStream、DoubleStream.
特別說明:流只能使用一次,不然會拋異常java.lang.IllegalStateException: stream has already been operated upon or closed.
//of()
Stream<Integer> integerStream = Stream.of(1,2,3,4,5,6);
integerStream.filter(i -> i > 4).forEach(System.out::println);
這裏創建的是串行的運行方式,如果想要創建並行的,可以通過Colletion#parallelStream()方法創建。of()方法暫時沒有提供這個特性。
generate()方法可以自己生成控制Stream,這個方法適合用來創建常量的Stream或者隨機元素的Stream。generate()是無限生成流的,需要配合limit()來使用.下面舉例在1000個數字中隨機抽取5個幸運中獎號碼.
//generate()
Stream.generate(() -> new Random().nextInt(1000)).limit(5).forEach(System.out::println);
filter
filter會對Stream中的所有元素進行過濾,會創建一個新的Stream,其元素均符合過濾條件。如文章開頭舉例的簡歷過濾:
//java8
List<String> phoneList = resumeList.stream()
.filter(resume -> resume.getWorkingAge() >= 3)
.filter(resume -> resume.getSkillList().contains("高併發"))
.filter(resume -> "Java".equals(resume.getJob()))
.map(Resume::getPhone).collect(Collectors.toList());
map/flatMap
map和flatMap都可以被用在一個Stream對象上,且都能返回Stream.不同之處在於map操作會對每一個輸入返回一個結果,然而flatMap操作會返回任意個(0個/1個/多個)值.這取決於在每個方法上的操作。
map操作接受一個Function,會將傳入的每個值都調用改Function,然後得到結果後返回。而flatMap操作會對每個參數產生任意數量結果。在Java編程中,返回任意結果可能會在處理時比較麻煩,需要考慮更多的情況,因爲返回值可能是一個值,Array或List。我們可以把flatMap操作可以看成是map操作 + flat操作,首先應用Function得到結果,然後將結果flat出來。而map操作相比flatMap操作則可以視爲沒有最後的flat操作。下面舉例:
@Test
public void testFlatMap() {
List<Integer> intList1 = new ArrayList<>();
intList1.add(1);
intList1.add(2);
List<Integer> intList2 = new ArrayList<>();
intList1.add(3);
intList1.add(4);
List<Integer> list = Stream.of(intList1, intList2).flatMap(List::stream).collect(Collectors.toList());
System.out.println(list);
//[1, 2, 3, 4]
}
再舉例,現在有幾個單詞,想過濾單詞中不重複的字母。map操作無法得到,而flatMap可以得到。
//map
List<Stream<String>> sMap = Arrays.stream(words).map(word -> word.split(""))
.map(Arrays::stream).distinct()
.collect(Collectors.toList());
//flatMap
List<String> sFlatMap = Arrays.stream(words).map(word -> word.split(""))
.flatMap(Arrays::stream).distinct()
.collect(Collectors.toList());
System.out.println(sFlatMap);
//[J, A, V, G, O]
Stream map(Function<? super T, ? extends R> mapper);
Stream flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
collect
collect方法用於將Stream流中的對象轉化還原爲流的結果。如
List<String> asList = stringStream.collect(Collectors.toList());
Map<String, List<Person>> peopleByCity = personStream.collect(Collectors.groupingBy(Person::getCity));
Map<String, Map<String, List<Person>>> peopleByStateAndCity = personStream.collect(Collectors.groupingBy(Person::getState, Collectors.groupingBy(Person::getCity)));
anyMatch\allMatch
anyMatch:檢測是否流中是否有任一元素滿足提供Predicate.
allMatch:檢測是否流中是否所有元素滿足提供Predicate.
boolean anyMatch = Stream.of(1, 2, 3, 4, 5, 6).anyMatch(i -> i > 6);
System.out.println(anyMatch);//false
boolean allMatch = Stream.of(1, 2, 3, 4, 5, 6).allMatch(i -> i < 20);
System.out.println(allMatch);//true
min\max
根據參數Comparator返回流中最大\小的元素,返回類型是Optional。更多Optional信息,請查看
Java8新特性學習(二)- Optional類
Optional<Integer> optionalMin = Stream.of(1, 2, 3, 4, 5, 6).min((a, b) -> a > b ? 1 : -1);
System.out.println(optionalMin.orElse(null));//1
Optional<Integer> optionalMax = Stream.of(1, 2, 3, 4, 5, 6).max(Integer::compareTo);
System.out.println(optionalMax.orElse(null));//6
skip\limit
skip:返回除去前n個元素之外剩餘元素組成的流。如果全部被拋棄,則返回一個空流。
limit:返回最大元素個數不超過n的流
Stream.of(1,2,3,4,5,6).skip(20l).forEach(System.out::print);
Stream.of(1,2,3,4,5,6).limit(3l).forEach(System.out::print);
reduce
reduce是關聯累積函數對流的元素進行縮減,並返回縮減之後的Optional類型的結果。
reduce()提供了3種方式使用
1、Optional<T> reduce(BinaryOperator<T> accumulator);//返回值是Optional
2、T reduce(T identity, BinaryOperator<T> accumulator);//返回值是T,identity一樣的類型
3、<U> U reduce(U identity,
BiFunction<U, ? super T, U> accumulator,
BinaryOperator<U> combiner);
//返回值是U,identity一樣的類型,accumulator累加器,combiner:結合器
舉例使用
System.out.println(Stream.of(1,2,3,4,5,6).reduce( (a, b) -> a+b).orElse(0));
System.out.println(Stream.of(1,2,3,4,5,6).reduce(0, (a, b) -> a+b));
這裏會對Stream中的元素遍歷每一個,進行BinaryOperator的apply操作,並最終返回一個類似合併各元素之後的結果。而我們常用的sum,max,min等操作都是一種特殊的reduce操作。可以看IntStream中min()函數的實現:
public final OptionalInt min() {
return reduce(Math::min);
}
總結
- Stream不是一種數據結構
- Stream並不會自己存儲數據,而是利用原數據進行操作
- Stream只能使用一次,使用多次會拋出異常
- 具有串行和並行能力,簡單高效就能寫出高併發代碼