當前時間:2019年 11月 11日,距離 JDK 14 發佈時間(2020年3月17日)還有多少天?java
// 距離JDK 14 發佈還有多少天?
LocalDate jdk14 = LocalDate.of(2020, 3, 17);
LocalDate nowDate = LocalDate.now();
System.out.println("距離JDK 14 發佈還有:"+nowDate.until(jdk14,ChronoUnit.DAYS)+"天");
複製代碼
Java 8
早已經在2014 年 3月 18日發佈,毫無疑問 Java 8
對 Java 來講絕對算得上是一次重大版本更新,它包含了十多項語言、庫、工具、JVM 等方面的新特性。好比提供了語言級的匿名函數,也就是被官方稱爲 Lambda
的表達式語法(外界也稱爲閉包,Lambda
的引入也讓流式操做成爲可能,減小了代碼編寫的複雜性),好比函數式接口,方法引用,重複註解。再好比 Optional
預防空指針,Stearm
流式操做,LocalDateTime
時間操做等。python
在前面的文章裏已經介紹了 Java 8
的部分新特性。c++
這一次主要介紹一下 Lambda 的相關狀況。golang
Lambda
名字來源於希臘字母表中排序第十一位的字母 λ,大寫爲Λ,英語名稱爲 Lambda
。在 Java 中 Lambda
表達式(lambda expression)是一個匿名函數,在編寫 Java 中的 Lambda
的時候,你也會發現 Lambda
不只沒有函數名稱,有時候甚至連入參和返回均可以省略,這也讓代碼變得更加緊湊。面試
上面說了此次是介紹 Lambda
表達式,爲何要介紹函數接口呢?其實 Java 中的函數接口在使用時,能夠隱式的轉換成 Lambda
表達式,在 Java 8
中已經有不少接口已經聲明爲函數接口,如 Runnable、Callable、Comparator 等。shell
函數接口的例子能夠看下 Java 8
中的 Runnable
源碼(去掉了註釋)。express
package java.lang;
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
複製代碼
那麼什麼樣子的接口才是函數接口呢?有一個很簡單的定義,也就是隻有一個抽象函數
的接口,函數接口使用註解 @FunctionalInterface
進行聲明(註解聲明不是必須的,若是沒有註解,也是隻有一個抽象函數,依舊會被認爲是函數接口)。多一個或者少一個抽象函數都不能定義爲函數接口,若是使用了函數接口註解又不止一個抽象函數,那麼編譯器會拒絕編譯。函數接口在使用時候能夠隱式的轉換成 Lambda 表達式。編程
Java 8
中不少有不少不一樣功能的函數接口定義,都放在了 Java 8
新增的 java.util.function
包內。下面是一些關於 Java 8
中函數接口功能的描述。
序號 | 接口 & 描述 |
---|---|
BiConsumer | 表明了一個接受兩個輸入參數的操做,而且不返回任何結果 |
BiFunction | 表明了一個接受兩個輸入參數的方法,而且返回一個結果 |
BinaryOperator | 表明了一個做用於於兩個同類型操做符的操做,而且返回了操做符同類型的結果 |
BiPredicate | 表明了一個兩個參數的boolean值方法 |
BooleanSupplier | 表明了boolean值結果的提供方 |
Consumer | 表明了接受一個輸入參數而且無返回的操做 |
DoubleBinaryOperator | 表明了做用於兩個double值操做符的操做,而且返回了一個double值的結果。 |
DoubleConsumer | 表明一個接受double值參數的操做,而且不返回結果。 |
DoubleFunction | 表明接受一個double值參數的方法,而且返回結果 |
DoublePredicate | 表明一個擁有double值參數的boolean值方法 |
DoubleSupplier | 表明一個double值結構的提供方 |
DoubleToIntFunction | 接受一個double類型輸入,返回一個int類型結果。 |
DoubleToLongFunction | 接受一個double類型輸入,返回一個long類型結果 |
DoubleUnaryOperator | 接受一個參數同爲類型double,返回值類型也爲double 。 |
Function | 接受一個輸入參數,返回一個結果。 |
IntBinaryOperator | 接受兩個參數同爲類型int,返回值類型也爲int 。 |
IntConsumer | 接受一個int類型的輸入參數,無返回值 。 |
IntFunction | 接受一個int類型輸入參數,返回一個結果 。 |
IntPredicate | 接受一個int輸入參數,返回一個布爾值的結果。 |
IntSupplier | 無參數,返回一個int類型結果。 |
IntToDoubleFunction | 接受一個int類型輸入,返回一個double類型結果 。 |
IntToLongFunction | 接受一個int類型輸入,返回一個long類型結果。 |
IntUnaryOperator | 接受一個參數同爲類型int,返回值類型也爲int 。 |
LongBinaryOperator | 接受兩個參數同爲類型long,返回值類型也爲long。 |
LongConsumer | 接受一個long類型的輸入參數,無返回值。 |
LongFunction | 接受一個long類型輸入參數,返回一個結果。 |
LongPredicate | 接受一個long輸入參數,返回一個布爾值類型結果。 |
LongSupplier | 無參數,返回一個結果long類型的值。 |
LongToDoubleFunction | 接受一個long類型輸入,返回一個double類型結果。 |
LongToIntFunction | 接受一個long類型輸入,返回一個int類型結果。 |
LongUnaryOperator | 接受一個參數同爲類型long,返回值類型也爲long。 |
ObjDoubleConsumer | 接受一個object類型和一個double類型的輸入參數,無返回值。 |
ObjIntConsumer | 接受一個object類型和一個int類型的輸入參數,無返回值。 |
ObjLongConsumer | 接受一個object類型和一個long類型的輸入參數,無返回值。 |
Predicate | 接受一個輸入參數,返回一個布爾值結果。 |
Supplier | 無參數,返回一個結果。 |
ToDoubleBiFunction | 接受兩個輸入參數,返回一個double類型結果 |
ToDoubleFunction | 接受一個輸入參數,返回一個double類型結果 |
ToIntBiFunction | 接受兩個輸入參數,返回一個int類型結果。 |
ToIntFunction | 接受一個輸入參數,返回一個int類型結果。 |
ToLongBiFunction | 接受兩個輸入參數,返回一個long類型結果。 |
ToLongFunction | 接受一個輸入參數,返回一個long類型結果。 |
UnaryOperator | 接受一個參數爲類型T,返回值類型也爲T。 |
(上面表格來源於菜鳥教程)
Lambda 的語法主要是下面幾種。
(params) -> expression
(params) -> {statements;}
Lambda 的語法特性。
->
分割 Lambda 參數和處理語句。舉幾個具體的例子, params 在只有一個參數或者沒有參數的時候,能夠直接省略不寫,像這樣。
// 1.不須要參數,沒有返回值,輸出 hello
()->System.out.pritnln("hello");
// 2.不須要參數,返回 hello
()->"hello";
// 3. 接受2個參數(數字),返回兩數之和
(x, y) -> x + y
// 4. 接受2個數字參數,返回兩數之和
(int x, int y) -> x + y
// 5. 兩個數字參數,若是都大於10,返回和,若是都小於10,返回差
(int x,int y) ->{
if( x > 10 && y > 10){
return x + y;
}
if( x < 10 && y < 10){
return Math.abs(x-y);
}
};
複製代碼
經過上面的幾種狀況,已經能夠大體瞭解 Lambda 的語法結構了。
從上面的介紹中已經知道了 Runnable 接口已是函數接口了,它能夠隱式的轉換爲 Lambda 表達式進行使用,經過下面的建立線程並運行的例子看下 Java 8
中 Lambda 表達式的具體使用方式。
/** * Lambda 的使用,使用 Runnable 例子 * @throws InterruptedException */
@Test
public void createLambda() throws InterruptedException {
// 使用 Lambda 以前
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("JDK8 以前的線程建立");
}
};
new Thread(runnable).start();
// 使用 Lambda 以後
Runnable runnable1Jdk8 = () -> System.out.println("JDK8 以後的線程建立");
new Thread(runnable1Jdk8).start();
// 更加緊湊的方式
new Thread(() -> System.out.println("JDK8 以後的線程建立")).start();
}
複製代碼
能夠發現 Java 8
中的 Lambda
碰到了函數接口 Runnable,自動推斷了要運行的 run 方法,不只省去了 run 方法的編寫,也代碼變得更加緊湊。
運行獲得結果以下。
JDK8 以前的線程建立
JDK8 以後的線程建立
JDK8 以後的線程建立
複製代碼
上面的 Runnable 函數接口裏的 run 方法是沒有參數的狀況,若是是有參數的,那麼怎麼使用呢?咱們編寫一個函數接口,寫一個 say
方法接受兩個參數。
/** * 定義函數接口 */
@FunctionalInterface
public interface FunctionInterfaceDemo {
void say(String name, int age);
}
複製代碼
編寫一個測試類。
/** * 函數接口,Lambda 測試 */
@Test
public void functionLambdaTest() {
FunctionInterfaceDemo demo = (name, age) -> System.out.println("我叫" + name + ",我今年" + age + "歲了");
demo.say("金庸", 99);
}
複製代碼
輸出結果。
我叫金庸,我今年99歲了。
複製代碼
方法引用這個概念前面尚未介紹過,方法引用可讓咱們直接訪問類的實例或者方法,在 Lambda 只是執行一個方法的時候,就能夠不用 Lambda
的編寫方式,而用方法引用的方式:實例/類::方法
。這樣不只代碼更加的緊湊,並且能夠增長代碼的可讀性。
經過一個例子查看方法引用。
@Getter
@Setter
@ToString
@AllArgsConstructor
static class User {
private String name;
private Integer age;
}
public static List<User> userList = new ArrayList<User>();
static {
userList.add(new User("A", 26));
userList.add(new User("B", 18));
userList.add(new User("C", 23));
userList.add(new User("D", 19));
}
/** * 測試方法引用 */
@Test
public void methodRef() {
User[] userArr = new User[userList.size()];
userList.toArray(userArr);
// User::getAge 調用 getAge 方法
Arrays.sort(userArr, Comparator.comparing(User::getAge));
for (User user : userArr) {
System.out.println(user);
}
}
複製代碼
獲得輸出結果。
Jdk8Lambda.User(name=B, age=18) Jdk8Lambda.User(name=D, age=19) Jdk8Lambda.User(name=C, age=23) Jdk8Lambda.User(name=A, age=26)
Lambda 帶來了新的遍歷方式,Java 8
爲集合增長了 foreach
方法,它能夠接受函數接口進行操做。下面看一下 Lambda
的集合遍歷方式。
/** * 新的遍歷方式 */
@Test
public void foreachTest() {
List<String> skills = Arrays.asList("java", "golang", "c++", "c", "python");
// 使用 Lambda 以前
for (String skill : skills) {
System.out.print(skill+",");
}
System.out.println();
// 使用 Lambda 以後
// 方式1,forEach+lambda
skills.forEach((skill) -> System.out.print(skill+","));
System.out.println();
// 方式2,forEach+方法引用
skills.forEach(System.out::print);
}
複製代碼
運行獲得輸出。
java,golang,c++,c,python,
java,golang,c++,c,python,
javagolangc++cpython
複製代碼
得益於 Lambda
的引入,讓 Java 8
中的流式操做成爲可能,Java 8
提供了 stream 類用於獲取數據流,它專一對數據集合進行各類高效便利操做,提升了編程效率,且同時支持串行和並行的兩種模式匯聚計算。能充分的利用多核優點。
流式操做如此強大, Lambda
在流式操做中怎麼使用呢?下面來感覺流操做帶來的方便與高效。
流式操做一切從這裏開始。
// 爲集合建立串行流
stream()
// 爲集合建立並行流
parallelStream()
複製代碼
流式操做的去重 distinct
和過濾 filter
。
@Test
public void streamTest() {
List<String> skills = Arrays.asList("java", "golang", "c++", "c", "python", "java");
// Jdk8 以前
for (String skill : skills) {
System.out.print(skill + ",");
}
System.out.println();
// Jdk8 以後-去重遍歷
skills.stream().distinct().forEach(skill -> System.out.print(skill + ","));
System.out.println();
// Jdk8 以後-去重遍歷
skills.stream().distinct().forEach(System.out::print);
System.out.println();
// Jdk8 以後-去重,過濾掉 ptyhon 再遍歷
skills.stream().distinct().filter(skill -> skill != "python").forEach(skill -> System.out.print(skill + ","));
System.out.println();
// Jdk8 以後轉字符串
String skillString = String.join(",", skills);
System.out.println(skillString);
}
複製代碼
運行獲得結果。
java,golang,c++,c,python,java,
java,golang,c++,c,python,
javagolangc++cpython
java,golang,c++,c,
java,golang,c++,c,python,java
複製代碼
流式操做的數據轉換(也稱映射)map
。
/** * 數據轉換 */
@Test
public void mapTest() {
List<Integer> numList = Arrays.asList(1, 2, 3, 4, 5);
// 數據轉換
numList.stream().map(num -> num * num).forEach(num -> System.out.print(num + ","));
System.out.println();
// 數據收集
Set<Integer> numSet = numList.stream().map(num -> num * num).collect(Collectors.toSet());
numSet.forEach(num -> System.out.print(num + ","));
}
複製代碼
運行獲得結果。
1,4,9,16,25,
16,1,4,9,25,
複製代碼
流式操做的數學計算。
/** * 數學計算測試 */
@Test
public void mapMathTest() {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
IntSummaryStatistics stats = list.stream().mapToInt(x -> x).summaryStatistics();
System.out.println("最小值:" + stats.getMin());
System.out.println("最大值:" + stats.getMax());
System.out.println("個數:" + stats.getCount());
System.out.println("和:" + stats.getSum());
System.out.println("平均數:" + stats.getAverage());
// 求和的另外一種方式
Integer integer = list.stream().reduce((sum, cost) -> sum + cost).get();
System.out.println(integer);
}
複製代碼
運行獲得結果。
獲得輸出
最小值:1
最大值:5
個數:5
和:15
平均數:3.0
15
複製代碼
Lamdba
結合函數接口,方法引用,類型推導以及流式操做,可讓代碼變得更加簡潔緊湊,也能夠藉此開發出更增強大且支持並行計算的程序,函數編程也爲 Java 帶來了新的程序設計方式。可是缺點也很明顯,在實際的使用過程當中可能會發現調式困難,測試表示 Lamdba
的遍歷性能並不如 for 的性能高,同事可能沒有學習致使看不懂 Lamdba
等(能夠推薦來看這篇文章)。
文章代碼已經上傳到 github.com/niumoo/jdk-… 。
<完>
我的網站:www.codingme.net
若是你喜歡這篇文章,能夠關注公衆號,一塊兒成長。
關注公衆號回覆資源能夠沒有套路的獲取全網最火的的 Java 核心知識整理&面試資料。