本來以為Java8的新特性需要3篇文章才能寫完的,但是發(fā)現(xiàn)其實(shí)Stream的接口并不想象的那么多洞辣,今天就可以完結(jié)啦!
流之初體驗(yàn)
首先先定義一個(gè)菜品類:
Dish
public class Dish {
private final String name;
private final boolean vegetarian;
private final int calories;
private final Type type;
public boolean isVegetarian() {
return vegetarian;
}
//省略set,get琅束,toString方法
}
然后創(chuàng)建一個(gè)靜態(tài)方法招盲,并且設(shè)置成類的成員變量低缩,以供測(cè)試。
public static List<Dish> getDishes() {
return Arrays.asList(
new Dish("pork", false, 800, Dish.Type.MEAT),
new Dish("beef", false, 700, Dish.Type.MEAT),
new Dish("chicken", false, 400, Dish.Type.MEAT),
new Dish("french fries", true, 530, Dish.Type.OTHER),
new Dish("rice", true, 350, Dish.Type.OTHER),
new Dish("pizza", true, 550, Dish.Type.OTHER),
new Dish("prawns", false, 300, Dish.Type.FISH),
new Dish("salmon", false, 450, Dish.Type.FISH)
);
}
好了,現(xiàn)在有個(gè)需求咆繁,找出菜品中所有小于400卡路里的食物讳推,并且按照卡路里的大小進(jìn)行排序。
在java8之前玩般,甚至有些人在java8之后银觅,都會(huì)想著借助一個(gè)中間變量保符合要求的菜品,然后排序坏为。
public static List<String> beforeJava8() {
List<Dish> lowCaloricDishes = new ArrayList<>();
for (Dish dish : dishes) {
if (dish.getCalories() < 400) {
lowCaloricDishes.add(dish);
}
}
lowCaloricDishes.sort(Comparator.comparingInt(Dish::getCalories));
// lowCaloricDishes.sort((d1, d2) -> Integer.compare(d1.getCalories(), d2.getCalories()));
List<String> res = new ArrayList<>();
for (Dish dish : lowCaloricDishes) {
res.add(dish.getName());
}
return res;
}
由于前一篇文章講過了方法引用究驴,所以這里就直接用,不過下面一行也有普通的Lambda表達(dá)式的書寫匀伏。
上述寫法有什么問題嗎洒忧,可以發(fā)現(xiàn)lowCaloricDishes
只使用了一次,真就一個(gè)臨時(shí)變量够颠。那能不能跳過創(chuàng)建變量的過程熙侍,你直接把數(shù)據(jù)給我,我經(jīng)過過濾排序后得到想要的呢履磨,就和流水線一樣蛉抓。
public static List<String> afterJava8() {
return dishes.stream()
.filter(dish -> dish.getCalories() < 400)
.sorted(Comparator.comparing(Dish::getCalories))
.map(Dish::getName)
.collect(Collectors.toList());
}
這就是本篇要講的流(Stream)了
流的定義
從支持?jǐn)?shù)據(jù)處理操作的源生成的元素序列
流和集合有點(diǎn)類似,集合是數(shù)據(jù)結(jié)構(gòu)蹬耘,主要的目的是存儲(chǔ)和訪問元素芝雪,而流的主要目的是為了對(duì)元素進(jìn)行一系列的操作。
通俗入門地來講综苔,集合就相當(dāng)于你一部電影下載惩系,流就相當(dāng)于在線觀看。其實(shí)只需要把流想成高級(jí)的集合即可如筛。流有兩個(gè)重要的特點(diǎn):
- 流水線: 很多流本身會(huì)返回一個(gè)流堡牡,這樣多個(gè)流就能鏈接起來和流水線一般。
-
內(nèi)部迭代: 內(nèi)部迭代也就是把迭代封裝起來杨刨,如
collect(Collectors.toList)
晤柄,與之相對(duì)應(yīng)的外部迭代則是for-each
。
值得注意的是妖胀,和迭代器類似芥颈,流只能遍歷一次 ,遍歷完就可以說這個(gè)流消費(fèi)掉了赚抡。
流的構(gòu)建
流的構(gòu)建
流常用的構(gòu)建方式有4種爬坑,其實(shí)要么是借助Stream
類的靜態(tài)方法,要么是借助別人的類的靜態(tài)方法涂臣。
- 由值創(chuàng)建流
- 由數(shù)組創(chuàng)建流
- 由文件生成流
- 由函數(shù)生成流
public static void buildStream() throws IOException {
Stream<String> byValue = Stream.of("java8", "c++", "go");
Stream<Object> empty = Stream.empty();
int[] nums = {1, 2, 3, 4, 5};
IntStream byInts = Arrays.stream(nums);
Stream<String> byFiles = Files.lines(Paths.get(""));
Stream<Integer> byFunction1 = Stream.iterate(0, n -> n * 2);
Stream<Double> byFunction2 = Stream.generate(Math::random);
Stream<String> java = Stream.of("java");
}
流的操作
可以連接起來的流操作稱為中間操作盾计,關(guān)閉流的操作稱為終端操作
通俗地講,返回結(jié)果是流的操作稱為中間操作,放回的不是流的操作稱為終端操作署辉。
通過查找java8接口可以得知到哪些接口是中間操作族铆,哪些接口時(shí)終端操作。由于那些接口描述得太過官方哭尝,估計(jì)我貼了也沒啥人會(huì)仔細(xì)看哥攘,所以想看的直接去官方查閱即可。
流的使用
就按照官網(wǎng)上的java API順序來講述刚夺,小插一句献丑,之前我一直沒有學(xué)流是主要是因?yàn)楦杏X接口會(huì)很多,怎么可能記得了這么多侠姑,其實(shí)這幾天看才發(fā)現(xiàn)真的很少,基本上不用記箩做。
首先構(gòu)建好一個(gè)數(shù)字列表:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 5, 5, 5, 6, 7);
中間操作
中間操作有去重莽红、過濾、截?cái)喟畎睢⒉榭窗灿酢⑻^、排序 燃辖,這些相信大家都能夠明白是什么意思鬼店。
public static void midOperation() {
numbers.stream()
.distinct()
.forEach(System.out::println);
List<Integer> filter = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
numbers.stream()
.limit(3)
.forEach(System.out::println);
numbers.stream()
.peek(integer -> System.out.println("consume operation:" + integer))
.forEach(System.out::println);
List<Integer> skip = numbers.stream()
.skip(2)
.collect(Collectors.toList());
numbers.stream()
.sorted()
.forEach(System.out::println);
}
中間操作之映射(map)
需要單獨(dú)拎出來說的是映射(map) 和扁平化映射(flatMap) ,注意黔龟,這里的map并不是hashmap的那個(gè)map妇智,而是說把什么映射或者說轉(zhuǎn)化成了什么。
public static void midOperation() {
List<String> map = numbers.stream()
.map(Object::toString) //這里就是把int映射成了string
.collect(Collectors.toList());
}
而對(duì)于扁平化映射氏身,現(xiàn)在又有一個(gè)需求巍棱,現(xiàn)在有個(gè)單詞列表如{"hello", "world"},返回里面各不相同的字符蛋欣,也就是要求返回List<String>
航徙。
這還不簡(jiǎn)單,把單詞映射成一個(gè)個(gè)字母陷虎,再去重就好了到踏。
public static void flatMapDemoNoral() {
List<String> words = Arrays.asList("hello", "world");
List<String[]> normal = words.stream()
.map(str -> str.split(""))
.distinct()
.collect(Collectors.toList());
}
雖然確實(shí)也能達(dá)到效果,但是注意映射所用的函數(shù)是split()
尚猿,返回的是String[]
窝稿,因此整個(gè)返回的是List<String[]>
那我映射完后再把每個(gè)String[]
數(shù)組映射成流
public static void flatMapDemoMap() {
List<String> words = Arrays.asList("hello", "world");
List<Stream<String>> usingMap = words.stream()
.map(str -> str.split(""))
.map(Arrays::stream)
.distinct()
.collect(Collectors.toList());
}
雖然摘掉了數(shù)組的帽子,但是返回的卻是List<Stream<String>>
谊路。
flatMap
正是為了解決這種情況的
public static void flatMapDemoFlatMap() {
List<String> words = Arrays.asList("hello", "world");
List<String> usingFlatMap = words.stream()
.map(str -> str.split(""))
.flatMap(Arrays::stream)
.distinct()
.collect(Collectors.toList());
}
可以簡(jiǎn)單的理解讹躯,map是把每個(gè)元素映射成了獨(dú)立的流,而扁平化map是把元素保存了下來,最后映射成了一個(gè)流 潮梯。
查找與匹配
終端操作除了上述寫例子的時(shí)候常用的collect()
和forEach()
還有查找和規(guī)約兩種大的方向骗灶。
因?yàn)闆]啥好說的,直接上代碼就完了:
public static void endOperationFindAndMatch() {
if (dishes.stream().noneMatch(Dish::isVegetarian)) {
System.out.println("所有的菜品都是非素食");
}
if (dishes.stream().allMatch(Dish::isVegetarian)) {
System.out.println("所有的菜品都是素食");
}
if (dishes.stream().anyMatch(Dish::isVegetarian)) {
System.out.println("菜品中至少有一道菜是素食");
}
Optional<Dish> any = dishes.stream()
.filter(meal -> meal.getCalories() <= 1000)
.findAny();
Optional<Dish> first = dishes.stream()
.filter(meal -> meal.getCalories() <= 1000)
.findFirst();
}
歸約(計(jì)算)
對(duì)流的規(guī)約操作的話秉馏,一般有普通操作也就是能直接調(diào)用接口的耙旦,還有一種就是借助reduce()
。
對(duì)于普通操作來說萝究,像求和免都,最大值,最小值這些都是有接口對(duì)應(yīng)的帆竹。
public static void endOperationCalculate() {
long count = dishes.stream()
.filter(meal -> meal.getCalories() <= 1000)
.count();
Optional<Dish> max = dishes.stream()
.max(Comparator.comparingInt(Dish::getCalories));
Optional<Dish> min = dishes.stream()
.min(Comparator.comparing(Dish::getName));
}
但是如果說要求對(duì)元素求和绕娘,就要使用reduce()
一般使用的是可以接受2個(gè)參數(shù),一個(gè)是初始值栽连,一個(gè)是BinaryOprator<T>
來將兩個(gè)元素結(jié)合起來產(chǎn)生的新值险领。
public static void reduceDemo() {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 5, 5, 5, 6, 7);
Integer sum = numbers.stream().reduce(0, Integer::sum);
//所有元素相乘也是比較簡(jiǎn)單
Integer multi = numbers.stream().reduce(0, (a, b) -> a * b);
//還有求最大值
Optional<Integer> max = numbers.stream().reduce(Integer::max);
}
Optional類
上面一直出現(xiàn)有返回值是Optional<T>
,它是一個(gè)容器類秒紧,代表一個(gè)值存在或者不存在绢陌,比如一開始的findAny()
,可能找不到符合條件的菜品熔恢。Java8引入的目的主要是/為了不要返回容易出現(xiàn)問題的null了脐湾。
就說幾個(gè)比較常用的api就好了至于其它的可以上網(wǎng)看下官方API,今天說的API已經(jīng)夠多了
-
isPresent()
將在Optional
包含值的時(shí)候返回true叙淌,否則返回false -
ifPresent(Consumer<T> block)
存在值的時(shí)候會(huì)執(zhí)行給定的代碼塊 -
get()
存在值就返回值秤掌,否則拋出NoSuchElement
異常 -
orElse()
存在值就返回,否則就返回一個(gè)默認(rèn)值
public static void optionalDemo() {
//ifPresent
dishes.stream()
.filter(Dish::isVegetarian)
.findAny()
.ifPresent(dish -> System.out.println(dish.getName()));
//isPresent
boolean isLowCalories= dishes.stream()
.filter(dish -> dish.getCalories() <= 1000)
.findAny()
.isPresent();
//get
Optional<Dish> optional = dishes.stream()
.filter(Dish::isVegetarian)
.findAny();
if (optional.isPresent()) {
Dish dish = optional.get();
}
//orElse
Dish dishNormal = dishes.stream()
.filter(Dish::isVegetarian)
.findAny()
.orElse(new Dish("java", false, 10000, Dish.Type.OTHER));
}
總結(jié)
一樣的凿菩,還是有幾個(gè)小點(diǎn)沒講机杜,比如并行流的部分,收集器部分衅谷,但是這些相對(duì)來說算是比較深入的椒拗。再經(jīng)過兩篇文章后,已經(jīng)盡可能地全面地了解了Java8新特性获黔。消化完后就可以去看《java8實(shí)戰(zhàn)》蚀苛,看書才是學(xué)習(xí)新知識(shí)的重點(diǎn),最后就是應(yīng)用了玷氏。