學習Lambda的理由 絕大多數公司代碼的主流風格。 大數據量下處理集合效率高,優秀的高併發解決。 代碼的可讀性增強。 消滅嵌套地獄。>形狀的if或者for再也不用寫了。 為了瞭解Lambda表達式,我們必須瞭解什麼是函數式介面,這是Lambda表達式得以實現的依據。 在java中,函數式介面指註解 ...
學習Lambda的理由
- 絕大多數公司代碼的主流風格。
- 大數據量下處理集合效率高,優秀的高併發解決。
- 代碼的可讀性增強。
- 消滅嵌套地獄。>形狀的if或者for再也不用寫了。
為了瞭解Lambda表達式,我們必須瞭解什麼是函數式介面,這是Lambda表達式得以實現的依據。
在java中,函數式介面指註解了@FunctionalInterface(非必須)的介面。
函數式介面具有一下特性:
- 介面中有且僅有一個抽象方法(必須)。
除卻以上性質與普通的介面沒有區別。
由以上定義,那麼問題來了,為什麼要使用函數式介面?
讓我們先看一個介面的實現案例。
// 傳統的介面實現方式
interface MyInterface {
void test();
}
class MyInterfaceImpl implements MyInterface{
public void test() {
// 各種處理
}
}
class Client {
public void process() {
MyInterface mif = new MyInterfaceImpl();
mif.test();
}
}
目前看上去我們的實現是沒有什麼問題的,無外乎定義一個介面,然後定義介面的實現類,在客戶端調用的過程中父類引用子類對象。。。
但是,如果這麼定義的話,意味著實現類是可以復用的。實際上應該反過來思考,就因為需要復用實現類,我們才這麼定義介面與實現。
有基礎的小伙伴肯定反應過來我想說的問題:如果一個介面的實現根據業務需求在項目中指調用兩三次,並且每次的實現方式還不同,我需要為了這三個不一樣的實現分別寫三個不同的實現類嗎?
其實沒有必要,由此引出接下來的內容:匿名內部類實現介面。
// 匿名內部類的介面實現方式
interface MyInterface {
void test();
}
class Client {
public void process() {
MyInterface mif = new MyInterface() {
@Override
public void test() {
// 各種處理
}
};
mif.test();
}
/*
其實不難看出,對於只有一個抽象方法的介面,匿名內部類的實現方式比較冗餘
首先new MyInterface(){}並不是非寫不可,
@Override,public,void,test也同樣。
可能有同學看到這裡已經蒙了:這人在說啥?我接下來解釋。
介面只有一個抽象方法也就是說沒有重載,所以我們通過明確寫出方法的參數列(類型也不需要,可推定)
已經方法體就可以確定重寫的是哪個方法,也就省去了@Override public void test。
又因為變數聲明瞭一個介面類型所以就知道需要實例化的對象介面也就省去了new MyInterface(){}
至此代碼變為了以下這種形式:
MyInterface mif = () -> { /* 各種處理 */ };
*/
}
雖然上述的實現方式可以讓我們比較方便的實現需求頻繁變動且復用需求低的介面,但人類是非常懶的,為了讓我們實現上述功能的同時寫更少的代碼,技術人員發明瞭lambda[1]表達式。
接下來,我們來看看上述實現使用lambda的效果。
// lambda的介面實現方式
interface MyInterface {
void test();
}
class Client {
public void process() {
MyInterface mif = () -> { /* 各種處理 */ };
mif.test();
}
}
代碼量肉眼可見的減少,接下來解釋為什麼可以這麼寫。
MyInterface mif = () -> { /* 各種處理 */ };
- ():介面抽象方法的形式參數。
- ->:箭頭標記,固定寫法。
- {}:實現的方法體。
lambda的省略規則
// 方法參數類型可以省略
(a, b) -> { /* 各種處理 */ };
// 方法體只有一行代碼時,{}、;、return可省略(必須同時省略)
(a, b) -> /* 各種處理 */;
// 方法參數只有一個時,()可以省略
a -> /* 各種處理 */;
// 方法無參時,()不可省略
() -> /* 各種處理 */;
仔細觀察上述的所有寫法可以瞭解到lambda表達式與傳統匿名內部類的實現方式有一個本質的區別:有且僅有一個實現的抽象方法。這也是為什麼函數式介面只能定義一個抽象方法的原因。
補充
-
lambda基本可以認為是匿名內部類的語法糖[2](不太準確)
但lambda與匿名內部類在原理上有一個區別:-
匿名內部類會生成class文件
-
lambda不會生成class文件
-
-
lambda有著延遲執行的特點
public Main { public static void main(String args[]) { String str1 = "你"; String str2 = "是"; String str3 = "誰?"; // lambda的實現方式 Test.doTest(true, () -> { System.out.println("滿足條件時執行"); return "lambda實現:"+str1+str2+str3; }); // 匿名內部類的實現方式(同樣有延遲執行的特點) Test.doTest(false, new Test() { @Override public void test() { System.out.println("滿足條件時執行"); return "匿名內部類實現:"+str1+str2+str3; } }); // 常規寫法 doTest(false, str1+str2+str3); /* 滿足條件時執行 lambda實現:你是誰? */ /* 通常情況下會使用常規寫法,因為理解簡單,但存在一個問題, 可以看出flag=false時,確實沒有輸出str,但str卻提前拼接好, 導致性能的浪費。 為此使用lambda(推薦)或者匿名內部類的形式,將字元串的拼接 延遲到判定條件之後。 */ } public static void doTest(boolean flag, String str) { if (flag) { System.out.println(str); } } } interface Test { String test(); static void doTest(boolean flag, Test t) { if (flag) { System.out.println(t.test()); } } }
核心函數式介面
Supplier
方法簽名:T get()
作用:供應商介面。生成T類型的數據。
public class Main {
public static void main(String[] args) {
int nums[] = {1, 23, 135, 534, 6245, 16254, 3547345};
Integer res = test(() -> {
int max = -1;
for (int num : nums) {
max = Math.max(max, num);
}
return max;
});
System.out.println("最大值:" + res); // 最大值:3547345
}
public static <T> T test(Supplier<T> s) {
return s.get();
}
}
Consumer
方法簽名:void accept(T t)
作用:消費者介面。使用T類型的數據。
// 格式化列印
public class Main {
public static void main(String[] args) {
String strs[] = {"張三 男", "李四 男", "小紅 女"};
test(strs, (arr) -> {
for (String str : arr) {
String s[] = str.split(" ");
System.out.println(s[0] + ":" + s[1]);
}
});
/*
張三:男
李四:男
小紅:女
*/
}
public static <T> void test(T t, Consumer<T> c) {
c.accept(t);
}
}
當想將上面的數據正向與逆向輸出兩遍怎麼辦?
// 正向與逆向格式化列印, 使用andThen
public class Main {
public static void main(String[] args) {
String strs[] = {"張三 男", "李四 男", "小紅 女"};
Consumer<String[]> c1 = (arr) -> {
System.out.println("------正序輸出------");
for (String str : arr) {
String s[] = str.split(" ");
System.out.println(s[0] + ":" + s[1]);
}
};
Consumer<String[]> c2 = (arr) -> {
System.out.println("------逆序輸出------");
for (int i = arr.length - 1; i >= 0; i --) {
String s[] = arr[i].split(" ");
System.out.println(s[0] + ":" + s[1]);
}
};
test(strs, c1.andThen(c2));
/*
------正序輸出------
張三:男
李四:男
小紅:女
------逆序輸出------
小紅:女
李四:男
張三:男
*/
}
public static <T> void test(T t, Consumer<T> c) {
c.accept(t);
}
}
由此看出兩種寫法等效,並且可以看出andThen可以鏈接兩個Consumer的處理變為一個處理,或者說一起處理。當需要鏈接的Consumer數量不定時,有非常大的作用。傳入的參數只需如下即可。
// c1,c2,c3,c4,c5均為Consumer實例
// 如此一來就可以連續執行c1,c2,c3,c4,c5的處理了
test(strs, c1.andThen(c2).andThen(c3).andThen(c4).andThen(c5));
Predicate
方法簽名:boolean test(T t)
作用:斷言介面。封裝判斷語句。
其實Predicate就是將條件語句打包成一個類,減少編程時傳參的麻煩,同時使得條件語句也可以延遲執行。
public class Main {
public static void main(String[] args) {
int arr[] = {1, 23, 135, 534, 6245, 16254, 3547345};
test(arr, (t) -> {
if (t > 12523) return true;
return false;
});
/*
16254
3547345
*/
}
public static void test(int arr[], Predicate<Integer> pre) {
for (int s : arr) {
// 用pre.test(s)來代替條件語句
if (pre.test(s)) {
System.out.println(s);
}
}
}
}
default方法
方法簽名:Predicate<T> and(Predicate<? super T> other)
作用:返回當前斷言 && 入參斷言。
滿足當前斷言和入參斷言時,返回true。
public static void main(String[] args) {
int arr[] = {1, 23, 135, 534, 6245, 16254, 3547345};
Predicate<Integer> pre1 = (t) -> {
if (t > 12523) return true;
return false;
};
Predicate<Integer> pre2 = (t) -> {
if (t < 300000) return true;
return false;
};
// 等價於 t > 12523 && t < 300000 的條件
test(arr, pre1.and(pre2));
/*
16254
*/
}
public static void test(int arr[], Predicate<Integer> pre) {
for (int s : arr) {
if (pre.test(s)) {
System.out.println(s);
}
}
}
方法簽名:Predicate<T> or(Predicate<? super T> other)
作用:返回當前斷言 || 入參斷言。
滿足當前斷言或者入參斷言時,返回true。
public static void main(String[] args) {
int arr[] = {1, 23, 135, 534, 6245, 16254, 3547345};
Predicate<Integer> pre1 = (t) -> {
if (t > 12523) return true;
return false;
};
Predicate<Integer> pre2 = (t) -> {
if (t < 300000) return true;
return false;
};
// 等價於 t > 12523 && t < 300000 的條件
test(arr, pre1.or(pre2));
/*
1
23
135
534
6245
16254
3547345
*/
}
public static void test(int arr[], Predicate<Integer> pre) {
for (int s : arr) {
if (pre.test(s)) {
System.out.println(s);
}
}
}
方法簽名:Predicate<T> negate()
作用:返回當前斷言的取反。
public static void main(String[] args) {
int arr[] = {1, 23, 135, 534, 6245, 16254, 3547345};
Predicate<Integer> pre1 = (t) -> {
if (t < 12523) return true;
return false;
};
Predicate<Integer> pre2 = (t) -> {
if (t > 300000) return true;
return false;
};
// 等價於 t > 12523 && t < 300000 的條件
test(arr, pre1.or(pre2).negate());
/*
16254
3547345
*/
}
public static void test(int arr[], Predicate<Integer> pre) {
for (int s : arr) {
if (pre.test(s)) {
System.out.println(s);
}
}
}
其他方法
static <T> Predicate<T> isEqual(Object targetRef) {
return null == targetRef ? Objects::isNull : (object) -> {
return targetRef.equals(object);
};
}
static <T> Predicate<T> not(Predicate<? super T> target) {
Objects.requireNonNull(target);
return target.negate();
}
- isEqual:傳入一個對象,當對象引用null時,返回isNull方法引用的Predicate,否則返回equals的Predicate
- not:返回目標Predicate的negate
Function
方法簽名:R apply(T var1)
作用:將T類型轉為R類型,返回。
public class Main {
public static void main(String[] args) {
String str = "13523";
Integer i = test(str, (t) -> {
return Integer.parseInt(str);
});
System.out.println("i的value: " + i); // i的value: 13523
}
public static <T, R> R test(T t, Function<T, R> f) {
return f.apply(t);
}
}
default方法
方法簽名:<V> Function<T, V> andThen(Function<? super R, ? extends V> after)
作用:將兩個Function結合,返回。
先根據當前Function執行類型轉換,然後再根據入參Function執行類型轉換。
public class Main {
public static void main(String[] args) {
Function<String, Integer> f1 = (t) -> {
return Integer.parseInt(t);
};
Function<Integer, String> f2 = (t) -> {
return String.valueOf(t);
};
String str = "13523";
String str1 = test(str, f1.andThen(f2));
System.out.println("str1的value: " + str1); // str1的value: 13523
/*
String -> Integer -> String
*/
}
public static <T, R> R test(T t, Function<T, R> f) {
return f.apply(t);
}
}
方法簽名:<V> Function<V, R> compose(Function<? super V, ? extends T> before)
作用:將兩個Function結合,返回。
與andThen剛好相反,先根據入參Function執行類型轉換,然後再根據當前Function執行類型轉換。
public class Main {
public static void main(String[] args) {
Function<String, Integer> f1 = (t) -> {
return Integer.parseInt(t);
};
Function<Integer, String> f2 = (t) -> {
return String.valueOf(t);
};
Integer i = 13523;
Integer i1 = test(i, f1.compose(f2));
System.out.println("i1的value: " + i1);
/*
Integer -> String -> Integer
*/
}
public static <T, R> R test(T t, Function<T, R> f) {
return f.apply(t);
}
}
其他方法
static <T> Function<T, T> identity() {
return (t) -> {
return t;
};
}
- identity:返回一個和輸入類型相同的輸出類型。
總結
Lambda的特點
- 語法只關註參數與方法體
- 可推導即可省略
Lambda的作用
- 優化部分匿名內部類的寫法。(介面有且僅有一個抽象方法)
本文來自博客園,作者:buzuweiqi,轉載請註明原文鏈接:https://www.cnblogs.com/buzuweiqi/p/16584590.html