【版權(quán)聲明】未經(jīng)博主同意,謝絕轉(zhuǎn)載?。ㄕ?qǐng)尊重原創(chuàng),博主保留追究權(quán))
https://blog.csdn.net/m0_69908381/article/details/130522535
出自【進(jìn)步*于辰的博客】
啟發(fā)博文:《Lambda表達(dá)式超詳細(xì)總結(jié)》(轉(zhuǎn)發(fā))。
這是我系統(tǒng)學(xué)習(xí)Lambda表達(dá)式時(shí)參考的文章。在下文中,我會(huì)引用這篇博文中的一些資源,如:圖片、闡述。因?yàn)槲艺J(rèn)為那位前輩總結(jié)的很好,我再盡力做也最多如此了。如有侵權(quán),請(qǐng)與我聯(lián)系!
參考筆記一,P33.8、P43.5;筆記三,P33.1。
注:掌握Lambda表達(dá)式,這兩個(gè)知識(shí)是基礎(chǔ):
- 匿名內(nèi)部類,幫助在于理解“ 變式 \color{red}{變式} 變式”,因?yàn)樽畛蹰_發(fā)Lambda表達(dá)式的目的之一是簡(jiǎn)化匿名內(nèi)部類;
- 泛型,用于Lambda表達(dá)式的“ 擴(kuò)展 \color{red}{擴(kuò)展} 擴(kuò)展”。
1、關(guān)于Lambda優(yōu)化匿名內(nèi)部類
1.1 內(nèi)部類
java中內(nèi)部類分為成員內(nèi)部類、靜態(tài)內(nèi)部類、局部?jī)?nèi)部類和匿名內(nèi)部類。
class TestInnerClass {
// 成員內(nèi)部類
class MemberInner {}
// 靜態(tài)內(nèi)部類
static class StaticInner {}
public static void main(String[] args) {
// 局部?jī)?nèi)部類
class LocalInner {}
// 匿名內(nèi)部類
Runnable run = new Runnable() {
public void run() {}
};
new Thread(run).start();
}
}
與Lambda表達(dá)式相關(guān)的只有匿名內(nèi)部類。
1.2 匿名內(nèi)部類
示例:
Runnable runa = new Runnable() {
public void run() {
sout "Hello!! CSDN";
}
};
new Thread(runa).start();
等同于:
class MyRunnable implements Runnable {
public void run() {
sout "Hello!! CSDN";
}
}
class TestThread {
public static void main(String[] args) {
Runnable runa = new MyRunnable();
new Thread(runa).start();
}
}
匿名內(nèi)部類一般有一個(gè)特點(diǎn): 一次性 \color{red}{一次性} 一次性,故這種寫法既冗余,又 L O W LOW LOW。
使用 L a m b d a 進(jìn)行優(yōu)化: \color{red}{使用Lambda進(jìn)行優(yōu)化:} 使用Lambda進(jìn)行優(yōu)化:
Runnable runa = () -> {
sout "Hello!! CSDN";
};
new Thread(runa).start();
有什么變化?是不是省去了run()
的聲明部分。
繼續(xù)優(yōu)化。
new Thread(() -> {
sout "Hello!! CSDN";
}).start();
這就是 “將函數(shù)如參數(shù)般傳遞” \color{red}{“將函數(shù)如參數(shù)般傳遞”} “將函數(shù)如參數(shù)般傳遞”。大家先初步了解,繼續(xù)看。。。
題外話:
題外話:
題外話:
此項(xiàng)的第一個(gè)示例中的引用runa
,指向 Runnable 接口的匿名內(nèi)部類(上轉(zhuǎn)),而下文所述的() -> {}
和三種引用都可以說是對(duì)匿名內(nèi)部類的簡(jiǎn)化,但其已不是匿名內(nèi)部類,其類型是Lambda
(一種類型)。
我暫未找到相關(guān)資料,就自己驗(yàn)證??聪率鍪纠?。
Predicate<Integer> ser1 = new Predicate<Integer>() {
@Override
public boolean test(Integer a) {
return a.equals(0);
}
};
Class z1 = ser1.getClass();
System.out.println(z1);// class Test2$1
System.out.println(z1.getSimpleName());// ""
System.out.println(z1.getEnclosingClass());// class Test2
Supplier<Integer> ser2 = () -> {
return 0;
};
z1 = ser2.getClass();
System.out.println(z1);// class Test2$$Lambda$1/1791741888
System.out.println(z1.getSimpleName());// Test2$$Lambda$1/1791741888
System.out.println(z1.getEnclosingClass());// null
具體說明簡(jiǎn)單又啰嗦,就不贅述,大家只要了解getSimpleName()
和getEnclosingClass()
這兩個(gè)方法就明白了。如果大家感興趣,可以看看我對(duì)Class<T>
類的解析。
2、關(guān)于Lambda的優(yōu)化規(guī)范
2.1 準(zhǔn)備
從:
Runnable runa = new Runnable() {
public void run() {
sout "Hello!! CSDN";
}
};
new Thread(runa).start();
優(yōu)化為:
Runnable runa = () -> {
sout "Hello!! CSDN";
};
new Thread(runa).start();
難道可以隨便寫?當(dāng)然不是。那規(guī)范是什么?
在上個(gè)例子中,大家有沒有注意一個(gè)問題?
\color{grey}{在上個(gè)例子中,大家有沒有注意一個(gè)問題?}
在上個(gè)例子中,大家有沒有注意一個(gè)問題?
在示例中,有沒有run()
的聲明?沒有。因此,Lambda只能優(yōu)化只有一個(gè)抽象方法的接口的匿名內(nèi)部類,這類接口稱之為
函數(shù)式接口
\color{green}{函數(shù)式接口}
函數(shù)式接口(注解是@FunctionalInterface
)。
舉個(gè)例:定義java.lang.Number
抽象類的匿名內(nèi)部類。
這是Number抽象類的源碼。
可見,有4個(gè)抽象方法。因此,創(chuàng)建 Number 類的匿名內(nèi)部類,必須重寫這4個(gè)抽象方法。即:
new Number() {
@Override
public int intValue() {
return 0;
}
@Override
public long longValue() {
return 0;
}
......
};
如這般,就無法使用 Lambda 進(jìn)行優(yōu)化。
2.2 規(guī)范
Lambda 的基礎(chǔ)語法:
() -> {}
。
->
的左邊是圓括號(hào),對(duì)應(yīng)匿名內(nèi)部類重寫的唯一抽象方法的參數(shù)列表;右邊是花括號(hào),對(duì)應(yīng)方法體。
參數(shù)列表: \color{red}{參數(shù)列表:} 參數(shù)列表:
- 若無參,則必須是
()
,即:() -> {}
; - 若只有1個(gè)參數(shù)
xx
,則可以是(xx)
或xx
,即:(xx) -> {}
或xx -> {}
;(注:xx
無類型,其名稱任意) - 若有2個(gè)參數(shù) a 和 b,則必須是
(a, b)
,即:(a, b) -> {}
; - 舉一反三。
方法體:
\color{red}{方法體:}
方法體:
由于方法體不用關(guān)注方法的聲明,故只注意一些
省略規(guī)范
省略規(guī)范
省略規(guī)范。
1、 若只有一條語句,可省略分號(hào)和花括號(hào)。
示例:
new Thread(() -> sout "Hello!! CSDN").start();
// 等同于:
new Thread(() -> {
sout "Hello!! CSDN";
}).start();
run()
無參。
2、 若方法有返回值,且只有一條語句時(shí),可省略分號(hào)、花括號(hào)和return
。
示例:
interface SelfInterface {
double getTrigonometric(double angle);
}
class TestSelf {
public static void main(String[] args) {
SelfInterface service = xx -> Math.sin(xx);
// 等同于:
SelfInterface service = xx -> {
return Math.sin(xx);
};
double radian = service.getRadian(10);
sout radian;// 打印:-0.5440211108893698
}
}
2.3 補(bǔ)充說明
以上例為例:
兩個(gè)問題:
\color{grey}{兩個(gè)問題:}
兩個(gè)問題:
- JVM如何知道圖中紅框部分是
getTrigonometric()
的方法體? - JVM是如何知道參數(shù)
xx
的類型是double
?(因?yàn)?code>Math.sin()的形參類型是 double)
下圖是Math.sin()
的API截圖:
解釋:
\color{red}{解釋:}
解釋: (以下闡述轉(zhuǎn)載自博文《Lambda表達(dá)式超詳細(xì)總結(jié)》)
因?yàn)镴VM可以通過上下文推斷出為何接口實(shí)現(xiàn)抽象方法,即 接口推斷 \color{purple}{接口推斷} 接口推斷;以及推斷出所實(shí)現(xiàn)的相應(yīng)抽象方法的參數(shù)列表(包括形參類型),即 類型推斷 \color{brown}{類型推斷} 類型推斷。
簡(jiǎn)言之,Lambda表達(dá)式依賴于上下文環(huán)境。
3、Java內(nèi)置函數(shù)式接口
3.1 四大核心函數(shù)式接口
(此表格引用自啟發(fā)博文。)
函數(shù)式接口 | 參數(shù)類型 | 返回值類型 | 說明 |
---|---|---|---|
Consumer<T> 消費(fèi)型接口 |
T | void | 對(duì)類型為T 的對(duì)象應(yīng)用操作,包含方法:void accept(T t)
|
Supplier<T> 供給型接口 |
無 | T | 返回類型為T 的對(duì)象,包含方法:T get()
|
Function<T, R> | T | R | 對(duì)類型為T 的對(duì)象應(yīng)用操作,并返回類型為R 的對(duì)象,包含方法:R apply(T t)
|
Predicate<T> 斷定型接口 |
T | boolean |
斷定類型為T 的對(duì)象是否滿足某約束,并返回boolean 結(jié)果,包含方法:boolean test(T t)
|
使用示例:
1、消費(fèi)型接口 Consumer<T>
:
Consumer<String> service1 = str -> sout str;
service1.accept("Hello!! CSDN");// 打?。篐ello!! CSDN
方法:void
accept(T t)
。
2、供給型接口 Supplier<T>
:
Supplier<Integer> service2 = () -> (int)(Math.random() * 100);// 獲取0~100的隨機(jī)整數(shù)
sout service2.get();// 打印:66
方法:T get()
。
3、函數(shù)型接口 Function<T,R>
:
Function<String, Integer> service3 = str -> str.length();
sout service3.apply("I love China!!");// 打?。?4
方法:R apply(T t)
。
4、斷定型接口 Predicate<T>
:
Integer i1 = 10;
Predicate<Integer> service4 = xx -> i1.equals(xx);
sout service4.test(10);// 打印:true
sout service4.test(20);// 打?。篺alse
方法:boolean test(T t)
。
3.2 其他函數(shù)式接口
(此表格引用自啟發(fā)博文。)
函數(shù)式接口 | 參數(shù)類型 | 返回值類型 | 說明 |
---|---|---|---|
BiFunction<T, U, R> | T, U | R | 對(duì)類型為T 、U 的對(duì)象應(yīng)用操作,返回類型為R 的對(duì)象,包含方法:R apply(T t, U u)
|
UnaryOperator<T> (Function<T> 的子接口) |
T | T | 對(duì)類型為T 的參數(shù)進(jìn)行一元運(yùn)算,并返回類型為T 的結(jié)果,包含方法:T aaply(T t)
|
BinaryOperator<T> (BiFunction<T, U, R>的子接口) |
T, T | T | 對(duì)類型為T 的參數(shù)進(jìn)行二元運(yùn)算,并返回類型為T 的結(jié)果,包含方法:T apply(T t1, T t2)
|
BiConsumer<T, U> | T, U | void | 對(duì)類型為T 、U 的對(duì)象應(yīng)用操作,包含方法:void accept(T t, U u)
|
ToIntFunction<T> 、ToLongFunction<T> 、ToDoubleFunction<T>
|
T | int、long、double | 分別計(jì)算 int、long、double 的函數(shù) |
IntFunction<R> 、LongFunction<R> 、DoubleFunction<R>
|
int、long、double | R | 參數(shù)分別為 int、long、double 的函數(shù) |
使用示例:
1、函數(shù)型接口 BiFunction<T, U, R>
:
BiFunction<Character[], Character, Integer> service5 = (charArr, c) -> Arrays.binarySearch(charArr, c);
sout service5.apply(new Character[]{65, 66, 67}, 'B');// 打印:1
方法:R apply(T t, U u)
。
關(guān)于binarySearch()
,詳情可見Arrays類的第2.2項(xiàng)。
2、函數(shù)型接口 UnaryOperator<T>
:
UnaryOperator<String> service6 = str -> str.trim().split("!")[1].trim().toUpperCase();
sout service6.apply(" Hello KiTi! I am 小白 of csdn ");// 打?。篒 AM 小白 OF CSDN
方法:T apply(T t)
。
3、函數(shù)型接口 BinaryOperator<T>
:
BinaryOperator<List<Character>> service7 = (list1, list2) -> {
Collections.copy(list2, list1);
return list2;
};
sout service7.apply(Arrays.asList('C', 'h'), Arrays.asList('#', '#', 'i', 'n', 'a'));// 打?。篬C, h, i, n, a]
方法:T apply(T t1, T t2)
。
關(guān)于copy()
,詳情可見Collections類的第2.10項(xiàng);關(guān)于asList()
,詳情可見 Arrays 類的第2.1項(xiàng)。
4、消費(fèi)型接口 BiConsumer<T,R>
:
BiConsumer<char[], Character>service8 = (charArr, c) -> {
Arrays.fill(charArr, c);
sout Arrays.toString(charArr);// 打?。篬#, #, #, #, #]
};
service8.accept(new char[]{'進(jìn)', '步', '*', '于', '辰'}, '#');
方法:void accept(T t, U u)
。
關(guān)于fill()
,詳情可見 Arrays 類的第2.8項(xiàng)。
5、未知型接口 To[Int/Long?Double]Function<T>
:
// ToIntFunction<T>
ToIntFunction<List> service9= list -> list.size();
int size = service9.applyAsInt(Arrays.asList(true, 3, 2, 1, "yes", 'f', 'i', 'r', 'e'));// 結(jié)果:9
// ToLongFunction<T>
ToLongFunction<Integer> service10 = n-> {
long startTime = System.nanoTime();
while ((n--) > 0) {}
long endTime = System.nanoTime();
return endTime - startTime;
};
long time = service10.applyAsLong(100000);// 結(jié)果:6486400ms
// ToDoubleFunction<T>
ToDoubleFunction<Double> service11 = radian -> Math.toDegrees(radian);
double angle = service11.applyAsDouble(Math.PI);// 結(jié)果:180.0
方法:int/long/double applyAs[Int/Long/Double]
。
6、函數(shù)型接口 [Int/Long/Double]Function<R>
:
// IntFunction<R>
IntFunction<String> service12 = length -> {
StringBuffer builder = new StringBuffer(10);
while ((length--) > 0) {
int x = (int) (Math.random() * 10);// 獲取0~9的隨機(jī)數(shù)
builder.append(x);
}
return builder.toString();
};
String code = service12.apply(6);// 結(jié)果:695821
// LongFunction<R>
LongFunction<Date> service13 = timeStamp -> new Date(timeStamp);
Date current = service13.apply(System.currentTimeMillis());// 結(jié)果:Tue May 09 22:17:26 CST 2023
// DoubleFunction<R>
DoubleFunction<Long> service14 = originN -> Math.round(originN);
long round = service14.apply(10.5);// 結(jié)果:11
方法:R apply(int/long/double i)
。
3.3 其他函數(shù)式接口補(bǔ)充
函數(shù)式接口 | 參數(shù)類型 | 返回值類型 | 說明 |
---|---|---|---|
斷定型接口 `BiPredicate<T, U> | T, U | boolean | 確定類型分別為T/U的對(duì)象是否滿足某約束,并返回 boolean 值。包含方法:boolean test(T t, U u)
|
使用示例:
1、斷定型接口 `BiPredicate<T,U>:
BiPredicate<String, String > service1 = (t, u) -> t.equals(u);
sout service1.test("abc", "abc");// 打印:true
sout service1.test("abc", "123");// 打?。篺alse
方法:boolean test(T t, U u)
。
3.4 示例補(bǔ)充說明
大家在看上面的示例時(shí),肯定想吐槽:“你寫的那些方法體,很多都是多此一舉?!?br> 以實(shí)用性的角度來說的確是,例如:sout str
、str.length
、xx > 0? true: false
,直接調(diào)用相應(yīng)方法不香么?還用 Lambda 轉(zhuǎn)個(gè)彎實(shí)現(xiàn)。
那我為何還這樣寫?
\color{grey}{那我為何還這樣寫?}
那我為何還這樣寫?
因?yàn)槲矣X得使用 Lambda 的核心思維在于
靈活、擴(kuò)展、通用
\color{red}{靈活、擴(kuò)展、通用}
靈活、擴(kuò)展、通用。因此,我寫那些示例的初衷是
“任意舉例、簡(jiǎn)單易懂”
\color{green}{“任意舉例、簡(jiǎn)單易懂”}
“任意舉例、簡(jiǎn)單易懂”,目的不在于實(shí)現(xiàn)何種功能,而是闡述 Lambda 的使用。
4、Lambda表達(dá)式的三種引用
先言:
\color{red}{先言:}
先言:
以下對(duì)于引用的示例,我會(huì)盡量用上文中Java內(nèi)置函數(shù)式接口及其所舉示例進(jìn)行
“演變”
“演變”
“演變”舉例,從而降低大家閱讀代碼的成本。
4.1 方法引用
4.1.1 概述
先說結(jié)論: \color{red}{先說結(jié)論:} 先說結(jié)論:
方法引用中所使用的 “缺省參數(shù)列表” \color{red}{“缺省參數(shù)列表”} “缺省參數(shù)列表”必須與抽象方法的參數(shù)列表相同,返回值類型也必須相同。
何為“缺省參數(shù)列表”? \color{grey}{何為“缺省參數(shù)列表”?} 何為“缺省參數(shù)列表”?
“缺省參數(shù)列表”指方法引用中已指定參數(shù)與所引用方法的參數(shù)列表相比較的缺失參數(shù)部分。
這是我自定義的概念,看著有點(diǎn)繞口,我會(huì)在示例中舉例說明。
方法引用格式: \color{green}{方法引用格式:} 方法引用格式:
-
對(duì)象 :: 成員方法名
; -
類 :: 類方法名
; -
類 :: 成員方法名
。
4.1.2 說明示例
4.1.2.1 boolean equals(String str)
為 String 類的成員方法boolean equals(String str)
定義方法引用。
以此例為基礎(chǔ)進(jìn)行舉例。
BiPredicate<String, String > service1 = (t, u) -> t.equals(u);
演變辦法一:
// 格式:類 :: 成員方法名
BiPredicate<String, String> service = String::equals;
sout service.test("csdn", "bilibili");// 打印:false
sout service.test("csdn", "csdn");// 打?。簍rue
為什么選擇 B i P r e d i c a t e 接口實(shí)現(xiàn)方法引用? \color{red}{為什么選擇 BiPredicate 接口實(shí)現(xiàn)方法引用?} 為什么選擇BiPredicate接口實(shí)現(xiàn)方法引用?
-
equals()
的返回值類型為 boolean,則此抽象方法的返回值類型也必須是 boolean; -
equals()
是成員方法,一共需要2個(gè)變量,方法引用為String::equals
,由于未指定任何變量,故缺省2個(gè)變量。而這兩個(gè)變量必須由抽象方法的參數(shù)列表提供,故參數(shù)列表必須且僅有兩個(gè)參數(shù),且類型要與方法引用的類型相同; -
equals()
所需的兩個(gè)變量,類型都是 String,則抽象方法的兩個(gè)參數(shù)的類型只能是 String。故<T>
和<U>
的 類型實(shí)參 \color{green}{類型實(shí)參} 類型實(shí)參為 String。
演變辦法二:
// 格式:對(duì)象 :: 成員方法名
String str = "csdn";
Predicate<String> service = str::equals;
sout service.test("bilibili");// 打?。篺alse
sout service.test("csdn");// 打?。簍rue
為什么選擇 P r e d i c a t e 接口實(shí)現(xiàn)方法引用? \color{red}{為什么選擇 Predicate接口實(shí)現(xiàn)方法引用?} 為什么選擇Predicate接口實(shí)現(xiàn)方法引用?
-
equals()
的返回值類型為 boolean,則此抽象方法的返回值類型也必須是 boolean; -
equals()
是成員方法,一共需要2個(gè)變量,方法引用為str::equals
,由于已指定一個(gè)變量,故缺省1個(gè)變量。而這兩個(gè)變量必須由抽象方法的參數(shù)列表提供,故參數(shù)列表必須且僅有一個(gè)參數(shù),且類型要與方法引用的類型相同; -
equals()
所需的兩個(gè)變量,類型都是 String,則抽象方法的參數(shù)的類型只能是 String。故<T>
的 類型實(shí)參 \color{green}{類型實(shí)參} 類型實(shí)參為 String。
4.1.2.2 void fill()
為 Arrays 類的靜態(tài)方法void fill()
定義方法引用。
以此例為基礎(chǔ)進(jìn)行舉例。
BiConsumer<char[], Character>service8 = (charArr, c) -> {
Arrays.fill(charArr, c);
sout Arrays.toString(charArr);// 打印:[#, #, #, #, #]
};
service8.accept(new char[]{'進(jìn)', '步', '*', '于', '辰'}, '#');
演變:
// 格式:類 :: 靜態(tài)方法名
BiConsumer<char[], Character> service8 = Arrays::fill;
char[] charArr = new char[]{'進(jìn)', '步', '*', '于', '辰'};
service8.accept(charArr, '#');
sout Arrays.toString(charArr);// 打?。篬#, #, #, #, #]
為什么選擇 B i C o n s u m e r 接口實(shí)現(xiàn)方法引用? \color{red}{為什么選擇 BiConsumer接口實(shí)現(xiàn)方法引用?} 為什么選擇BiConsumer接口實(shí)現(xiàn)方法引用?
-
fill()
無返回值,accept()
也無返回值,故匹配; -
fill()
是類方法,需要兩個(gè)變量,方法引用是Arrays::fill
,由于未指定任何變量,故缺省2個(gè)變量。而這兩個(gè)變量必須由抽象方法的參數(shù)列表提供,故參數(shù)列表必須且僅有兩個(gè)參數(shù),且類型要與方法引用的類型相同; -
fill()
的第1個(gè)參數(shù)類型為基本數(shù)據(jù)類型數(shù)組,為char[]
;第2個(gè)參數(shù)類型為基本數(shù)據(jù)類型,為char
。這2個(gè)參數(shù)都由accept()
提供,故<T>
的 類型實(shí)參 \color{green}{類型實(shí)參} 類型實(shí)參為char[]
,<R>
的 類型實(shí)參 \color{blue}{類型實(shí)參} 類型實(shí)參為 Character,
留言:
\color{purple}{留言:}
留言:
泛型的類型實(shí)參只能是類,為何這里可以是char[]
(數(shù)組),歡迎各位博友在評(píng)論區(qū)討論??!
4.2 構(gòu)造器引用
4.2.1 概述
格式:
\color{green}{格式:}
格式:類 :: new
。
說明:
\color{purple}{說明:}
說明:
顧名思義,“構(gòu)造器引用”的作用就是實(shí)例化,即返回實(shí)例。
例如:為實(shí)體類Users
創(chuàng)建構(gòu)造器引用,則構(gòu)造器引用固定為Users :: new
,即返回一個(gè) Users 實(shí)例。
約束:
\color{brown}{約束:}
約束:
抽象方法的參數(shù)列表決定了匹配哪個(gè)構(gòu)造方法,即構(gòu)造器引用等同于構(gòu)造方法。
4.2.2 示例
實(shí)體類。
class Users {
private Integer id;
private String[] hobby;
public Users() {
}
public Users(Integer id) {
this.id = id;
}
public Users(String[] hobby) {
this.hobby = hobby;
}
public Users(Integer id, String[] hobby) {
this.id = id;
this.hobby = hobby;
}
@Override
public String toString() {
return "Users{" +
"id=" + id +
", hobby=" + Arrays.toString(hobby) +
'}';
}
}
測(cè)試。
Supplier<Users> service1 = Users::new;
Users user1 = service1.get();
sout user1;// 打?。篣sers{id=null, hobby=null}
Function<Integer, Users> service2 = Users::new;
Users user2 = service2.apply(1001);
sout user2;// 打?。篣sers{id=1001, hobby=null}
Function<String[], Users> service3 = Users::new;
Users user3 = service3.apply(new String[]{"編程", "Game"});
sout user3;// 打?。篣sers{id=null, hobby=[編程, Game]}
BiFunction<Integer, String[], Users> service4 = Users::new;
Users user4 = service4.apply(1002, new String[]{"java", "cf"});
sout user4;// 打?。篣sers{id=1002, hobby=[java, cf]}
4.3 數(shù)組引用
格式:
\color{orange}{格式:}
格式:類型[] :: new
。
說明:
\color{dark}{說明:}
說明:
與構(gòu)造器引用同理。不過,數(shù)組引用返回的是數(shù)組。(我暫不知如何使用數(shù)組引用創(chuàng)建非空數(shù)組)
示例:
Function<Integer, Integer[]> service1 = Integer[]::new;
Integer[] arr = service1.apply(5);
sout Arrays.toString(arr);// 打?。篬null, null, null, null]
5、Lambda表達(dá)式的作用域
以下闡述轉(zhuǎn)載自博文《Lambda表達(dá)式超詳細(xì)總結(jié)》。
Lambda表達(dá)式可以看作是匿名內(nèi)部類實(shí)例化的對(duì)象,Lambda表達(dá)式對(duì)變量的訪問限制和匿名內(nèi)部類一樣。因此Lambda表達(dá)式可以訪問局部變量、局部引用,靜態(tài)變量和成員變量。
5.1 引用局部常量
規(guī)定在Lambda表達(dá)式中只能引用由final
修飾的局部變量,即局部常量,包括局部基本類型常量和局部引用類型常量。
5.1.1 引用局部基本類型常量
double d1 = 10.2;-------------------------------------------A
// final double d1 = 10.2;----------------------------------------B
UnaryOperator<Double> service = d -> Math.floor(d + d1);----C
// d1 = 5.1;------------------------------------------------D
sout service.apply(5.9);// 打?。?6.0
d1
定義為變量(A),可當(dāng)引用于 Lambda 中時(shí)(C),會(huì)隱式轉(zhuǎn)為常量,但當(dāng)為d1
賦值時(shí)(D),這種“隱式轉(zhuǎn)換”功能會(huì)失效,d1
仍為變量,則C會(huì)編譯報(bào)錯(cuò)。
若將d1
顯式定義為常量(B),則C可編譯通過,但由于常量不可修改,D將會(huì)編譯報(bào)錯(cuò)。
5.1.2 引用局部引用類型常量
示例1:
String subStr = "csdn";
Predicate<String> service = str -> str.contains(subStr);
sout service.test("csdn, bilibili, 博客園");// 打?。簍rue
// subStr = "bili";
此示例與上文中【引用局部基本類型常量】的示例同理。
示例2:
List list = new ArrayList();
list.add(2023);
list.add("年");
list.add(5.12);
Supplier<Integer> service = () -> list.size();
sout service.get();// 打印:3
list.add(true);
sout service.get();// 打?。?
執(zhí)行list.add(true)
是對(duì)list
進(jìn)行了修改,按照上面的結(jié)論,這個(gè)示例是編譯報(bào)錯(cuò)的??蓪?shí)際上編譯通過。為什么?難道上面的結(jié)論有紕漏??
在后面加上這么一條代碼試試:
list = new ArrayList();
這樣就編譯報(bào)錯(cuò)了。大家看出來了吧。。。
結(jié)論:
\color{red}{結(jié)論:}
結(jié)論:
由 Lambda 引用的局部常量不可修改,指的是不可修改引用指向。
5.2 引用成員變量、類變量
public class TestReference {
String originStr1 = "csdn,bilibili,博客園";
static String originStr2 = "csdn,bilibili,博客園";
public static void main(String[] args) {
Supplier<TestReference> service1 = TestReference::new;
TestReference t1 = service1.get();
Supplier<String[]> service2 = () -> t1.originStr1.split(",");
String[] arr1 = service2.get();-----------A
sout Arrays.toString(arr1);// 打?。篬csdn, bilibili, 博客園]
t1.originStr1 = "";-----------------------B
Supplier<String[]> service3 = () -> originStr2.split(",");
String[] arr2 = service3.get();-----------C
sout Arrays.toString(arr2);// 打印:[csdn, bilibili, 博客園]
originStr2 = "";--------------------------D
}
}
B、D處分別修改成員變量originStr1
與類變量originStr2
,都編譯通過??梢?,Lambda 不限制對(duì)成員變量和類變量的引用。
留言:
\color{brown}{留言:}
留言:
至于 Lambda 有沒有如上文中【局部常量】般將成員變量或類變量隱式轉(zhuǎn)為常量,暫未可知。不過,我覺得沒有隱式轉(zhuǎn)換,因?yàn)锽、D處編譯通過。
5.3 引用成員常量、類常量
以上述【引用成員變量、類變量】的示例為基礎(chǔ),在成員變量originStr1
和類變量originStr2
的定義前加上final
,即:
final String originStr1 = "csdn,bilibili,博客園";
final static String originStr2 = "csdn,bilibili,博客園";
則A、C處都編譯通過,說明,Lambda 不限制對(duì)成員常量和類常量的引用;而B、D處都編譯報(bào)錯(cuò)。這是常量本身的性質(zhì),與 Lambda 無關(guān)。
5.4 限制訪問局部變量的原因
具體原因,那位前輩已經(jīng)總結(jié)得很全面,我就不班門弄斧了,詳述請(qǐng)查閱博文《Lambda表達(dá)式超詳細(xì)總結(jié)》(轉(zhuǎn)發(fā))的第8.3項(xiàng)。
6、最后
本文中的示例是為了方便大家理解、以及闡述 Lambda 表達(dá)式的運(yùn)用而簡(jiǎn)單舉出的,不一定有實(shí)用性。示例很多,不過,我所舉的示例都是 “以簡(jiǎn)為宗旨” \color{green}{“以簡(jiǎn)為宗旨”} “以簡(jiǎn)為宗旨”,重心不在于使用 Lambda 表達(dá)式編寫多么強(qiáng)大的功能,而在于盡量擴(kuò)展對(duì) Lambda 表達(dá)式的使用,讓大家能夠更透徹地理解它的格式、規(guī)范、限制等。
P
S
:
\color{blue}{PS:}
PS:
這是我迄今為止寫過的內(nèi)容最多的一篇文章,超1.5萬字,我都有點(diǎn)佩服我自己。
當(dāng)然,這個(gè)內(nèi)容量與大神們動(dòng)則幾萬、十幾萬的大作相比,不值一提(我不是謙虛,類如幾萬、十幾萬的大作,那完全是上了另一個(gè)層面的文章了,用“論文”形容更貼切)。不過,我還是挺有成就感的且受益匪淺!文章來源:http://www.zghlxwxcb.cn/news/detail-448849.html
本文完結(jié)。文章來源地址http://www.zghlxwxcb.cn/news/detail-448849.html
到了這里,關(guān)于關(guān)于對(duì)【java中的Lambda表達(dá)式】的理解與簡(jiǎn)述的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!