? ? ? ? 作為一名Java開發(fā)者谢翎,JDK是其必備的開發(fā)工具,如今JDK已經(jīng)更新到了JDK11沐旨,可是最近我發(fā)現(xiàn)很多有過多年Java開發(fā)經(jīng)驗(yàn)的開發(fā)者森逮,居然還在使用JDK6或者更早的版本,或者使用了JDK8磁携,但是對(duì)JDK8及其之后版本的一些新特性完全不了解褒侧,要知道JDK8可是在2013年就正式發(fā)布了。小編自從嘗試了JDK8帶來的快感后谊迄,對(duì)于每次JDK新版本的發(fā)布闷供,總是抱有很大的期待,雖然每次JDK版本的正式發(fā)布统诺,總是帶來不少的新特性和重要改進(jìn)歪脏,不過遺憾的是,JDK8之后版本的新特性對(duì)于一個(gè)著重Java應(yīng)用開發(fā)的開發(fā)者來說感覺并不明顯粮呢,感覺最明顯的還是JDK8婿失,引入的Lambda表達(dá)式和Stream編程。
一啄寡、Lambda表達(dá)式
? ? ? ? Lambda表達(dá)式簡單理解即函數(shù)式編程移怯,其并非Java獨(dú)有,很多編程語言都有这难,如python舟误、c++。 在JDK8之前姻乓,Java是不支持函數(shù)式編程的嵌溢,所謂的函數(shù)編程,即可理解是將一個(gè)函數(shù)(也稱為“行為”)作為一個(gè)參數(shù)進(jìn)行傳遞蹋岩。通常我們提及得更多的是面向?qū)ο缶幊汤挡荩嫦驅(qū)ο缶幊淌菍?duì)數(shù)據(jù)的抽象,而函數(shù)式編程則是對(duì)行為的抽象(將行為作為一個(gè)參數(shù)進(jìn)行傳遞)剪个。 Java 中的 Lambda 表達(dá)式通常使用 (argument) -> (expression) 語法書寫秧骑,例如:
(arg1, arg2...) -> { expression }
(type1 arg1, type2 arg2...) -> { expression }
以下是一些 Lambda 表達(dá)式的例子:
(int a, int b) -> {? return a + b; }
() -> System.out.println("Hello 2019");
(String s) -> { System.out.println(s); }
() -> 30
() -> { return 4 };
通過上面的例子,可以簡單了解一下 Lambda 表達(dá)式的結(jié)構(gòu):
1,一個(gè) Lambda 表達(dá)式可以有零個(gè)或多個(gè)參數(shù)乎折;
2绒疗,參數(shù)的類型既可以明確聲明,也可以根據(jù)上下文來推斷骂澄。例如:(int a)與(a)效果相同吓蘑。
3,參數(shù)需包含在圓括號(hào)內(nèi)坟冲,參數(shù)之間用逗號(hào)分隔磨镶。例如:(a, b) 或 (String a, String b) 或 (int a, String b, float c),空?qǐng)A括號(hào)代表參數(shù)集為空健提。例如:() -> 30琳猫;當(dāng)只有一個(gè)參數(shù),且其類型可推導(dǎo)時(shí)私痹,圓括號(hào)()可省略脐嫂。例如:a -> return a*a;
4侄榴,Lambda 表達(dá)式的主體({}內(nèi)的表達(dá)式)可包含零條或多條語句雹锣,如果 Lambda 表達(dá)式的主體只有一條語句,花括號(hào){}可省略癞蚕。如果 Lambda 表達(dá)式的主體包含一條以上語句蕊爵,則表達(dá)式必須包含在花括號(hào){}中。下面通過代碼來舉幾個(gè)簡單例子桦山。
1攒射,線程的初始化方法
//舊方法:
new Thread(new Runnable() {
@Override
public void run() {
? ? System.out.println("this thread");
}
}).start();
//新方法
new Thread(
() -> System.out.println("this thread")
).start();
2,List循環(huán)轉(zhuǎn)化為Lambda表達(dá)式
//老方法
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
for(Integer n: list) {
? System.out.println(n);
}
//新方法
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
list.forEach(n -> System.out.println(n));
或者
list.forEach(System.out::println);
3恒水,對(duì)象排序中比較器的實(shí)現(xiàn)
//老方法
Comparator<Developer> byName = new Comparator<Developer>() {
? ? @Override
? ? public int compare(Developer o1, Developer o2) {
? ? ? ? return o1.getName().compareTo(o2.getName());
? ? }
};
//新方法
Comparator<Developer> byName =
? ? ? ? (Developer o1, Developer o2)->o1.getName().compareTo(o2.getName());
二会放、Stream API
? ? ? ? Stream API作為 JDK8 的一大亮點(diǎn),它與 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念钉凌。它也不同于 StAX(Streaming API for XML) 對(duì) XML 解析的 Stream咧最,也不是Storm、Spark御雕、Flink等對(duì)大數(shù)據(jù)實(shí)時(shí)處理的 Stream矢沿。JDK8 中的 Stream 是對(duì)集合(Collection)對(duì)象功能的增強(qiáng),它專注于對(duì)集合對(duì)象進(jìn)行各種非常便利酸纲、高效的聚合操作(aggregate operation)捣鲸,或者大批量數(shù)據(jù)操作 (bulk data operation)。Stream API 借助于同樣新出現(xiàn)的 Lambda 表達(dá)式闽坡,極大的提高編程效率和程序可讀性栽惶。同時(shí)它提供串行和并行兩種模式進(jìn)行匯聚操作愁溜,并發(fā)模式能夠充分利用多核處理器的優(yōu)勢(shì),使用 fork/join 并行方式(JDK7引入)來拆分任務(wù)和加速處理過程外厂。通常編寫并行代碼很難而且容易出錯(cuò), 但使用 Stream API 無需編寫一行多線程的代碼冕象,就可以很方便地寫出高性能的并發(fā)程序。所以說酣衷,JDK8 中首次出現(xiàn)的 java.util.stream 是一個(gè)函數(shù)式語言+多核時(shí)代綜合影響的產(chǎn)物交惯。那JDK8中的Stream是什么次泽?Stream 不是集合元素穿仪,它不是數(shù)據(jù)結(jié)構(gòu)并不保存數(shù)據(jù),它是有關(guān)算法和計(jì)算的意荤,它更像一個(gè)高級(jí)版本的 Iterator啊片。原始版本的 Iterator,用戶只能顯式地一個(gè)一個(gè)遍歷元素并對(duì)其執(zhí)行某些操作玖像;高級(jí)版本的 Stream紫谷,用戶只要給出需要對(duì)其包含的元素執(zhí)行什么操作,比如 “過濾掉長度大于 10 的字符串”捐寥、“獲取每個(gè)字符串的首字母”等笤昨,Stream 會(huì)隱式地在內(nèi)部進(jìn)行遍歷,做出相應(yīng)的數(shù)據(jù)轉(zhuǎn)換握恳。Stream 就如同一個(gè)迭代器(Iterator)瞒窒,單向,不可往復(fù)乡洼,數(shù)據(jù)只能遍歷一次崇裁,遍歷過一次后即用盡了,就好比流水從面前流過束昵,一去不復(fù)返拔稳。而和迭代器又不同的是,Stream 可以并行化操作锹雏,迭代器只能命令式地巴比、串行化操作。顧名思義礁遵,當(dāng)使用串行方式去遍歷時(shí)轻绞,每個(gè) item 讀完后再讀下一個(gè) item。而使用并行去遍歷時(shí)榛丢,數(shù)據(jù)會(huì)被分成多個(gè)段铲球,其中每一個(gè)都在不同的線程中處理,然后將結(jié)果一起輸出晰赞。Stream 的并行操作依賴于 Fork/Join 框架來拆分任務(wù)和加速處理過程稼病。下面通過幾個(gè)實(shí)列來了解Stream API的用法选侨。
1,map/flatMap
//老方法
List<Integer> nums = Arrays.asList(1, 2, 3, 4);
List<Integer> squareNums=new ArrayList<>();
for(Integer num:nums)
{
squareNums.add(num*num);
}
//新方法
List<Integer> nums = Arrays.asList(1, 2, 3, 4);
List<Integer> squareNums = nums.stream().map(n -> n * n).collect(Collectors.toList());// map 生成的是個(gè)一對(duì)一映射然走,每個(gè)輸入元素援制,都按照規(guī)則轉(zhuǎn)換成為另外一個(gè)元素。還有一些場景芍瑞,是一對(duì)多映射關(guān)系的晨仑,這時(shí)可以使用 flatMap,有興趣的可以參見后文列舉的參考資料拆檬,這里不詳述洪己。
2,filter
//老方法
Integer[] sixNums = {1, 2, 3, 4, 5, 6};
List<Integer> evensList = new ArrayList<>();
for(Integer num:sixNums)
{if(num%2==0){evens.add(num);}
}
Integer[] evens= new Integer[evensList .size()];
evens = evensList .toArray(evens);
//新方法
Integer[] sixNums = {1, 2, 3, 4, 5, 6};
Integer[] evens =
Stream.of(sixNums).filter(n -> n%2 == 0).toArray(Integer[]::new);
3竟贯,forEach
見前文Lambda表達(dá)式答捕。
4,findFirst
它總是返回 Stream 的第一個(gè)元素屑那,或者空拱镐。這里比較重點(diǎn)的是它的返回值類型:Optional,它可能含有某值持际,或者不包含沃琅。使用它的目的是盡可能避免NullPointerException。
如String strA = " abcd ", strB = null;要求求strA或者strB的長度蜘欲,即實(shí)現(xiàn)
getLength(String text)方法益眉。如下:
public static int getLength(String text) {
// 老方法
// return if (text != null) ? text.length() : -1;
//新方法
return Optional.ofNullable(text).map(String::length).orElse(-1);
};
5,limit/skip
limit 返回 Stream 的前面 n 個(gè)元素芒填;skip 則是扔掉前 n 個(gè)元素呜叫。代碼如下:
public void testLimitAndSkip() {
List<Person> persons = new ArrayList();
for (int i = 1; i <= 10000; i++) {
Person person = new Person(i, "name" + i);
persons.add(person);
}
List<String> personList2 = persons.stream().map(Person::getName).limit(10).skip(3).collect(Collectors.toList());
System.out.println(personList2);
}
private class Person {
public int no;
private String name;
public Person (int no, String name) {
this.no = no;
this.name = name;
}
public String getName() {
return name;
}
}
輸出結(jié)果:
[name4, name5, name6, name7, name8, name9, name10]
6,stored
對(duì) Stream 的排序通過 sorted 進(jìn)行殿衰,它比數(shù)組的排序更強(qiáng)之處在于你可以首先對(duì) Stream 進(jìn)行各類 map朱庆、filter、limit闷祥、skip 甚至 distinct 來減少元素?cái)?shù)量后娱颊,再排序,這能幫助程序明顯縮短執(zhí)行時(shí)間凯砍。
List<Person> persons = new ArrayList();
for (int i = 1; i <= 5; i++) {
Person person = new Person(i, "name" + i);
persons.add(person);
}
List<Person> personList2 = persons.stream().limit(2).sorted((p1, p2) -> p1.getName().compareTo(p2.getName())).collect(Collectors.toList());
System.out.println(personList2);
7箱硕,Match
Stream 有三個(gè) match 方法,從語義上說:
allMatch:Stream 中全部元素符合傳入的 predicate悟衩,返回 true
anyMatch:Stream 中只要有一個(gè)元素符合傳入的 predicate剧罩,返回 true
noneMatch:Stream 中沒有一個(gè)元素符合傳入的 predicate,返回 true
它們都不是要遍歷全部元素才能返回結(jié)果座泳。例如 allMatch 只要一個(gè)元素不滿足條件惠昔,就 skip 剩下的所有元素幕与,返回 false。如:
List<Person> persons = new ArrayList();
persons.add(new Person(1, "name" + 1, 10));
persons.add(new Person(2, "name" + 2, 21));
persons.add(new Person(3, "name" + 3, 34));
persons.add(new Person(4, "name" + 4, 6));
persons.add(new Person(5, "name" + 5, 55));
boolean isAllAdult = persons.stream().
allMatch(p -> p.getAge() > 18);
System.out.println("All are adult? " + isAllAdult);
boolean isThereAnyChild = persons.stream().
anyMatch(p -> p.getAge() < 12);
System.out.println("Any child? " + isThereAnyChild);
輸出結(jié)果:
All are adult? false
Any child? true
? ? ? ? 本文是總結(jié)自己的工作實(shí)戰(zhàn)镇防,拋磚引玉啦鸣,列舉了常見的JDK8 Lambda表達(dá)式和Stream API。如果想深入了解JDK8的特性来氧,可以參看Oracle和IBM官網(wǎng)資料诫给。
1,https://www.oracle.com/technetwork/articles/java/architect-lambdas-part1-2080972.html
2啦扬,https://www.ibm.com/developerworks/cn/java/j-lo-java8streamapi/