1. 程式人生 > 程式設計 >程式設計師如何使程式碼簡潔,Lambda表示式入門之四大引用(下篇)

程式設計師如何使程式碼簡潔,Lambda表示式入門之四大引用(下篇)

享學課堂特邀作者:老顧

轉載請宣告出處!

前言

上一篇介紹了lambda表示式的語法,今天老顧繼續介紹

Lambda型別

Lambda表示式可以被當做是一個Object。Lambda表示式的型別,叫做“目標型別(target type)”。Lambda表示式的目標型別是“函式介面(functional interface)”

有一個介面,如果只有一個顯式宣告的抽象方法,那麼它就是一個函式介面。一般用@FunctionalInterface標註出來(也可以不標)。舉例如下

我們看到最後的Comparator介面,宣告瞭兩個方法,好像不符合函式介面的定義,但Comparator確實是函式介面

。這個是因為equals方法是Object的,所有的介面都會宣告Object的public方法(雖然大多是隱式的)。所以,Comparator顯式的宣告瞭equals不影響它依然是個函式介面。雖然Lambda雖然可以當作是Object型別,但需要顯式轉換才行。我們可以用一個Lambda表示式為一個函式介面賦值:

Runnable r1 = () -> {System.out.println("Hello Lambda!");};
複製程式碼

然後再賦值給一個Object:

Object obj = r1;
複製程式碼

但卻不能這樣幹:

Object obj = () -> {System.out.println("Hello Lambda!"
);}; // ERROR! Object is not a functional interface! 複製程式碼

必須顯式的轉型成一個函式接口才可以:

Object o = (Runnable) () -> { System.out.println("Hello Lambda!"); };
複製程式碼

一個Lambda表示式只有在轉型成一個函式介面後才能被當做Object使用。所以下面這句也不能編譯:

System.out.println( () -> {} ); //錯誤! 目標型別不明
複製程式碼

必須先轉型:

System.out.println( (Runnable)() -> {} ); // 正確
複製程式碼

我們可以定義一個無引數,無返回值的介面,類似Runnable

@FunctionalInterface
public  interface  MyRunnable {
	public  void run();
}
複製程式碼

那下面的寫法,都是正確的

Runnable r1 = () -> {System.out.println("Hello Lambda!");};
MyRunnable r2 = () -> {System.out.println("Hello Lambda!");};
複製程式碼

這說明一個Lambda表示式可以有多個目標型別(函式介面),只要函式匹配成功即可。但需注意一個Lambda表示式必須至少有一個目標型別

Lambda的域以及訪問限制

域即作用域,Lambda表示式中的引數列表中的引數在該Lambda表示式範圍內(域)有效。在作用Lambda表示式內,可以訪問外部的變數:區域性變數、類變數和靜態變數,但操作受限程度不一

1、訪問區域性變數

在Lambda表示式外部的區域性變數會被JVM隱式的編譯成final型別,因此只能訪問外而不能修改。

2、訪問靜態變數和成員變數

在Lambda表示式內部,對靜態變數和成員變數可讀可寫。

內建函式式介面

小夥伴們有沒有發現,如果使用Lambda表示式,還是需要我們自己寫一個介面定義的,其實很多介面無非是入參型別和返回值不一樣而已,所以Java給我們提供了幾個常用的標準函式介面:

Consumer< T >con 消費型介面: void accept(T t);Supplier< T >sup供給型介面 : T get();Function< T,R >fun 函式型介面 : R apply (T t);Predicate< T >:斷言型介面 : boolean test(T t);

1、Consumer消費型介面

介面中的方法為 void accept(T t),1個引數,無返回值。呼叫方要傳入值,而不需要返回,形象比喻成消費型

這個就是對傳入num引數值,進行相關的處理(消費)。到底進行處理,具體就在

(num) -> System.out.println("消費了" + num)
複製程式碼

上面的程式碼中,是一個典型的1個引數,無返回值的消費;如果沒有內建的函式介面,那我們就需要自己定義一個,如:

interface  MyConsumer<T>{
	void doFunction(T t);
}
複製程式碼

我們發現和內建函式介面,沒有什麼區別,就是介面名和方法名稱不一樣而已,其實本質是一樣的,這就是為什麼Java會提供一些內建的函式,這樣可以減少大量的程式碼。

2、Supplier供給型介面

介面中的方法 T get(),無引數,有返回值;不需要對方給引數,而是一直返回給對方,形象定義為供給型介面

上面程式碼就是返回一個新的實體物件。

3、Function函式式介面

介面中的方法 R apply (T t),有引數,有返回值;典型的函式,所以形象比喻成函式型介面

介面實現轉換成大寫的字元。

4、Predicate斷言型介面

介面中方法 boolean test(T t),有引數,返回boolean,是一個條件檢查式方法,比喻成斷言型介面

內建的函式介面,大大的提高開發的效率,減少了開發程式碼

方法引用

方法引用是Lambda表示式的一個簡化寫法,其語法結構為:

ObjectRef::methodName

左邊可以是類名或者例項名,中間是方法引用符號“::”,右邊是相應的方法名。方法引用可以分為三類。

使用前提:Lambda體中呼叫方法的引數列表和返回值型別,要和函式式介面中抽象方法的引數列表和返回值型別保持一致

靜態方法引用

先上一個案例

上面的程式碼中,我們發現Converter匿名類過載方法中,呼叫了ReferenceTest的靜態方法String2Int。再有函式介面的方法引數型別 和 返回值型別,跟String2Int是一樣的,入參為String,返回值為Integer。那我們這個時候可以簡化成

直接用靜態方法進行賦值,是不是很簡潔。

例項方法引用

如果函式式介面的實現恰好可以通過呼叫一個例項的例項方法來實現,那麼就可以使用例項方法引用

物件方法引用

抽象方法的第一個引數型別剛好是例項方法的型別(函式式介面的抽象方法必須要有輸入引數)抽象方法剩餘的引數恰好可以當做例項方法的引數

如果函式式介面的實現能由上面說的例項方法呼叫來實現的話,那麼就可以使用物件方法的引用**(兩個條件都要滿足)**

我們看到第一引數Prod s,和例項物件new Prod是同一個型別;剩餘的引數s1,正好是例項方法fun的引數;這樣就可以簡化成Prod::fun

構造方法引用

如果函式式介面的實現恰好可以通過呼叫一個類的構造方法來實現,那麼就可以使用構造方法引用,語法【類名::new】

上面是無引數的建構函式,再來看看有參建構函式

到這裡,老顧來個方法引用的總結:

總結

Lambda表示式要熟練掌握,是要小夥伴們經常去寫,才能夠運用自如,程式碼雖然比較簡潔,但確實沒有接觸的開發人員看上去,就懵逼了,可讀性不強。還有Stream的知識點,老顧後面再介紹;今天就分享到這裡,謝謝!!!

關注我,還有更多技術乾貨分享~