學習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
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/502889.html
標籤:Java
上一篇:冪等公共組件
