天天通訊!第八章《Java高級語法》第12節(jié):Lambda表達式

2022-12-31 14:11:48 來源:51CTO博客

?Lambda 表達式是 JDK8 的一個新特性,它可以定義大部分的匿名內(nèi)部類,從而讓程序員能寫出更優(yōu)雅的Java代碼,尤其在集合的各種操作中可以極大地優(yōu)化代碼結(jié)構(gòu)。


(資料圖)

8.12.1認識Lambda表達式

一個接口的實現(xiàn)類可以被定義為匿名類。經(jīng)過大量實踐,人們發(fā)現(xiàn)定義一個接口的匿名實現(xiàn)類需要做很多“復制名稱”的工作,如圖8-50所示。?

圖8-50 定義接口的匿名實現(xiàn)類?

圖8-50展現(xiàn)了一段代碼,這段代碼中定義了一個接口MyInterface,并且在main()方法中定義了這個接口的匿名實現(xiàn)類。通過觀察圖片不難發(fā)現(xiàn):圖片中方框里面的內(nèi)容實際上都是“復制”而來的,不僅僅復制了接口的名稱,還復制了接口所定義的抽象方法的名稱。由此可以看出:定義一個接口的匿名實現(xiàn)類時,程序員要做很多簡單的復制工作,并把復制而來的內(nèi)容按一定的規(guī)則粘貼到特定位置。這些工作都是沒有任何創(chuàng)造性的,真正有創(chuàng)造性的工作只有編寫匿名類中method()方法的代碼。?

那么,能否減少復制性的工作,以更簡潔的方式來定義匿名類呢?從JDK1.8開始,Java語言提供了一種新的定義匿名類的方式,它被稱為Lambda表達式。使用Lambda表達式創(chuàng)建MyInterface定義匿名實現(xiàn)類的代碼如下。?

()->{System.out.println("method方法被執(zhí)行");};?

在這條語句中,首先出現(xiàn)了一對小括號,這對小括號其實就是匿名類中method()方法的那對小括號,是用來傳遞參數(shù)的。因為method()方法沒有參數(shù),所以小括號中什么都沒寫。小括號的右邊是一個減號和一個大于號,像箭頭一樣,這是 Lambda表達式語法規(guī)定的符號,讀者只要記住它必須出現(xiàn)在小括號后面就可以了。箭頭后面是一對大括號,我們都知道:任何方法都有一對大括號,語句中的這對大括號就是method()方法的那對大括號,程序員在大括號中寫上實現(xiàn)抽象方法的代碼就可以。以上代碼中的輸出語句就是抽象方法的實現(xiàn)代碼。表達式中沒有出現(xiàn)接口的名稱,也沒有出現(xiàn)抽象方法的名稱,所以“復制名稱”的工作全部被去除掉了,在Lambda表達式中只有用于實現(xiàn)抽象方法的語句代碼。我們知道:匿名類在被定義時就必須同時創(chuàng)建出它的對象,所以一個Lambda表達式既表示匿名類的定義,也表示這個匿名類的對象。?

在8.6小節(jié)中曾經(jīng)講過:匿名類都必須是某個接口的實現(xiàn)類或某個父類的子類,但Lambda表達式中沒有接口的名稱,所以編譯器根本不知道這個Lambda表達式所定義的匿名類是哪一個接口的實現(xiàn)類。為了讓編譯器明確的知道Lambda表達式代表哪一個接口的匿名實現(xiàn)類,Java語言規(guī)定:由Lambda表達式所創(chuàng)建匿名類對象必須被一個引用所指向,這樣編譯器根據(jù)引用的類型就能判斷出這個一個Lambda表達式所定義匿名類是哪一個接口的實現(xiàn)類。?

MyInterface myInterface = ()->{System.out.println("method方法被執(zhí)行");};?

在這條語句中,Lambda表達式所創(chuàng)建的匿名實現(xiàn)類對象被一個引用所指向,這個引用的類型是MyInterface,編譯器由引用的類型就能判斷出這個Lambda表達式所定義的匿名類是MyInterface這個接口的匿名實現(xiàn)類。?

如果用Lambda表達式充當某個方法的參數(shù),編譯器根據(jù)參數(shù)的類型也能判斷出Lambda表達式所定義的匿名類是哪一個接口的實現(xiàn)類。?

【例08_32 Lambda表達式當作方法參數(shù)】

MyInterface.java?

public interface MyInterface {  void method();}

Exam08_32.java?

public class Exam08_32 {  public static void call(MyInterface m) {    m.method();  }  public static void main(String[] args) {    call(()->{System.out.println("method方法被執(zhí)行");});  }}

【例08_32】的Exam08_32類定義了一個靜態(tài)方法call()。這個方法的參數(shù)類型是MyInterface,在main()方法中調(diào)用call()方法時,用一個Lambda表達式所創(chuàng)建的匿名類對象作為方法的參數(shù),編譯器根據(jù)call()方法參數(shù)的類型就能判斷出這個匿名類是MyInterface的實現(xiàn)類。?

Lambda表達式中不僅沒有接口的名稱,甚至連方法的名稱也沒有出現(xiàn)。既然沒有方法的名稱,那么編譯器如何判斷Lambda表達式中所定義的方法到底實現(xiàn)了接口中的哪一個抽象方法呢?為了解決這個問題,Java語言規(guī)定:Lambda表達式只能用來定義僅有且僅有一個抽象方法的接口的匿名實現(xiàn)類。如果某個接口僅有一個抽象方法,那么Lambda表達式中的方法所實現(xiàn)的就必然是接口中唯一的那個抽象方法。專業(yè)上,把這種只有一個抽象方法的接口稱為“函數(shù)式接口”。函數(shù)式接口的英文名稱是“Single Abstract Method interfaces”,名稱前三個單詞的首字母是“SAM”,因此專業(yè)上也把函數(shù)式接口稱為SAM接口。?

無論一個接口中是否定義了有實現(xiàn)過程的默認方法和靜態(tài)方法,也無論這個接口中是否定義了屬性,只要這個接口中僅有一個抽象方法,它就是一個函數(shù)式接口,哪怕它唯一的抽象方法是從父接口那里繼承而來的也可以。JDK1.8定義了一個新的注解,叫做 FunctionalInterface,只要把這個注解添加到一個接口的前面,就能檢驗出這個接口是不是函數(shù)式接口。如果這個接口不是函數(shù)式接口,就會出現(xiàn)語法錯誤,如圖8-51所示。?

圖 檢驗函數(shù)式接口?

圖的接口MyInterface上面添加了FunctionalInterface注解,但它實際上定義了兩個抽象方法,所以被檢查出不是函數(shù)式接口并因此導致編譯器報錯。?

函數(shù)式接口中只能定義一個抽象方法,但這條規(guī)定有一個例外情況,那就是:如果接口中的某個抽象方法能夠在Object類中找到它的實現(xiàn)過程,那么這個抽象方法在接口中不占用一個抽象方法名額,如同8-52所示。?

圖函數(shù)式接口的例外情況?

圖8-52中的MyInterface接口定義了method()和hashCode()兩個抽象方法,并且接口上面還添加了FunctionalInterface注解來檢查它是不函數(shù)式接口,但編譯器并沒有因接口定義了兩個抽象方法而報錯,這是因為Object類中定義了一個hashCode()方法,這個hashCode()方法能夠?qū)崿F(xiàn)MyInterface接口中定義的hashCode()抽象方法。很多讀者都會問:為什么函數(shù)式接口中一個抽象方法在Object類中能夠找到實現(xiàn)過程,這個抽象方法就不占用抽象方法的名額呢?因為Java語言所有的類都是Object的子類,Lambda表達式定義的匿名類也不例外,所以這個匿名類能夠繼承Object類中的方法。雖然Lambda表達式只能實現(xiàn)1個抽象方法,但從Object類中繼承來的方法替Lambda表達式實現(xiàn)了接口中的其他抽象方法,這樣的話,在Object類中能夠找到實現(xiàn)過程的抽象方法不需要由Lambda表達式去實現(xiàn),因此它們不占用抽象方法的名額。?

以上講解的所有例子中,Lambda表達式定義的匿名類都被當作了接口的實現(xiàn)類,那么,Lambda表達式定義的匿名類能不能被當作抽象類的子類呢?Java語言規(guī)定:Lambda表達式只能用來定義函數(shù)式接口的匿名實現(xiàn)類,因此由Lambda表達式所定義的匿名類不能被當作抽象類的子類。?

同樣,使用引用來指向用Lambda表達式創(chuàng)建的匿名類對象時,引用的類型只能是函數(shù)式接口,否則會引起語法錯誤,如圖8-53所示。?

圖8-53 引用類型不匹配導致語法錯誤?

按理說Object是所有類的父類,使用Object類型的引用可以指向任何對象,包括匿名類對象,那么為什么圖片中的語句為什么會出錯呢?前文講過,Java語言中Lambda表達式只能用來定義函數(shù)式接口的匿名實現(xiàn)類,而編譯器就是根據(jù)引用的類型來判斷Lambda表達式定義的匿名類是哪一個接口的實現(xiàn)類,圖中用Object類型的引用指向Lambda表達式創(chuàng)建的匿名類對象,這會讓編譯器認為這個Lambda表達式定義的是Object的匿名子類而不是某個函數(shù)式接口的實現(xiàn)類,因此編譯器會報錯。?

如果函數(shù)式接口中的抽象方法使用throws關(guān)鍵字聲明了異常,用Lambda表達式定義這個接口的匿名實現(xiàn)類時不需要在小括號后面再次用throws關(guān)鍵字聲明異常,否則就會出現(xiàn)語法錯誤。之所以不用在Lambda表達式中再次聲明異常,是因為Lambda表達式被發(fā)明出來就是為了提供一種最簡潔的定義匿名實現(xiàn)類的語法格式。這種語法格式遵循一個基本原則:如果能從函數(shù)式接口中找到的信息,都可以不出現(xiàn)在Lambda表達式中。因此,函數(shù)式接口中的抽象方法已經(jīng)聲明了異常,Lambda表達式中就不需要再次聲明這些異常。?

8.12.2簡化Lambda表達式

Lambda表達式的本質(zhì)其實就是用來定義函數(shù)式接口匿名實現(xiàn)類的一種語法格式,這種語法格式相比傳統(tǒng)的創(chuàng)建匿名類對象的語法格式要簡潔很多。在某些情況下,Lambda表達式還可以寫的更簡潔,本小節(jié)就來梳理一下Lambda表達式的各種簡化形式的寫法。?

1. 省略參數(shù)類型?

如果函數(shù)式接口的抽象方法中定義了參數(shù),那么在用Lambda表達式定義匿名實現(xiàn)類時可以省略定義方法的參數(shù)類型。?

【例08_33 省略定義參數(shù)類型】

NewInterface.java?

public interface NewInterface {  void method(int x);}

Exam08_33.java?

public class Exam08_33 {  public static void main(String[] args) {    NewInterface n1 = (int x)->{System.out.println("定義了參數(shù)類型");};    NewInterface n2 = (x)->{System.out.println("沒有定義參數(shù)類型");};    n1.method(1);    n2.method(2);  }}

【例08_33】中,函數(shù)式接口NewInterface的抽象方法method()定義了1個參數(shù),在main()方法中由Lambda表達式定義的匿名實現(xiàn)類在實現(xiàn)method()抽象方法時采用了兩種書寫形式,其中第二種書寫形式只是定義出了參數(shù)的名稱,并沒有定義參數(shù)的類型。【例08_33】的運行結(jié)果如圖8-54所示。?

圖8-54【例08_33】運行結(jié)果?

從8-54可以看出,即使Lambda表達式中沒有定義參數(shù)的類型,方法也依然能夠運行成功。那么,編譯器是如何判斷調(diào)用方法時傳入的參數(shù)是否合法呢?在調(diào)用方法時,編譯器會檢查每一個傳入方法的實際參數(shù)能否與函數(shù)式接口中抽象方法定義的參數(shù)類型相匹配,以此來判斷實際參數(shù)的合法性。需要注意:書寫Lambda表達式時,要省略參數(shù)類型就全部省略,要不就干脆都不省略,不能只省略其中的一部分。?

2. 省略小括號?

Lambda表達式中有一對小括號,這對小括號中書寫了方法的參數(shù)。如果函數(shù)式接口中的抽象方法有且僅有一個參數(shù),在這種情況下,書寫Lambda表達式時如果省略了參數(shù)類型,也可以同時省略小括號。因此,如果使用Lambda表達式定義NewInterface的匿名實現(xiàn)類可以采用如下形式:?

NewInterface n = x->{System.out.println("省略小括號");};?

以上代碼中的Lambda表達式省略了參數(shù)x外圍的小括號。需要注意:省略小括號的兩個條件是:一、抽象方法本身有且僅有一個參數(shù)。二、書寫Lambda表達式時省略了參數(shù)的類型。如果沒有省略參數(shù)類型,那么小括號也不能被省略。?

3. 省略大括號及return關(guān)鍵字?

在Lambda表達式中,如果大括號中只有一條語句,那么這對大括號以及大括號外面的分號是可以省略的,因此可以用以下形式的Lambda表達式來定義NewInterface的匿名實現(xiàn)類。?

NewInterface n = x->System.out.println("省略大括號");?

如果Lambda表達式所實現(xiàn)的函數(shù)式接口中的抽象方法是一個有返回值的方法,那么在省略大括號時必須把return關(guān)鍵字一同省略掉。例如NewInterface中的method()抽象方法被定義為如下形式:?

void method(int x);?

在這種情況下,可以把Lambda表達式定義為:?

NewInterface n = x->1;?

這個Lambda表達式表示method()方法的返回值為1,它等同于:?

NewInterface n = x->{return 1;};?

本例中方法的返回值是一個常量,實際上,方法的返回值還可以是一個變量,也可以是一個表達式,甚至可以是一個方法的運行結(jié)果。總之,只要是能被當作方法的返回值的東西,都可以寫在“->”的右邊。但是要注意:如果Lambda表達式在實現(xiàn)抽象方法時需要用到多條語句,那么大括號以及return關(guān)鍵字都不能省略。讀者可以仿照以上幾個例子嘗試用各種簡化形式來書寫Lambda表達式。?

8.12.3方法引用

有些Lambda表達式所定義的匿名類在實現(xiàn)抽象方法的過程中只是簡單的調(diào)用了另一個方法,除此之外并沒有做其他操作。凡是具有這種特征的匿名類,都可以用一種更簡潔的Lambda表達式來定義,這種簡潔的定義形式被稱為“方法引用”。請看下面的【例08_34】:?

【例08_34 靜態(tài)方法引用】

QuoteInterface1.java

public interface QuoteInterface1 {  int method(int x);}

Exam08_34.java?

public class Exam08_34 {    public static void main(String[] args) {        //傳統(tǒng)方式定義匿名實現(xiàn)類        QuoteInterface1 q1 = new QuoteInterface1() {            @Override            public int method(int x) {                return Math.abs(x);            }        };        //Lambda表達式定義匿名實現(xiàn)類        QuoteInterface1 q2 = x->Math.abs(x);        //靜態(tài)方法引用定義匿名實現(xiàn)類        QuoteInterface1 q3 = Math::abs;        System.out.println(q1.method(-1));        System.out.println(q2.method(-2));        System.out.println(q3.method(-3));//①    }}

【例08_34】中定義了一個函數(shù)式接口QuoteInterface1,接口中定義了抽象方法method()。在main()方法中,以不同的形式創(chuàng)建了3個QuoteInterface1接口的匿名實現(xiàn)類。匿名類沒有名稱,為了方便表述每個匿名類,此處暫時以指向匿名類對象的引用名稱來指代這3個匿名類,所以在下面的表述中,定義在main()方法中的這3個匿名類分別被稱為q1,q2和q3。?

q1是以傳統(tǒng)方式定義的匿名類,q1在實現(xiàn)method()方法過程中,只是調(diào)用了Math類的靜態(tài)方法abs(),并把它的運算結(jié)果作為方法返回值,除此之外再無其他操作。q1的定義過程可以被簡化為q2,因此,q2與q1這兩個類實現(xiàn)抽象方法的過程實際上是相同的,只是寫法不同而已。既然匿名類在實現(xiàn)抽象方法的過程只是調(diào)用其他方法,沒有其他操作,那么只要能夠明確的指出被調(diào)用的方法是什么,就已經(jīng)完成了對抽象方法的實現(xiàn)。按照這種思想,Java語言提供了一種Lambda表達式的新寫法,這種寫法能夠以最簡潔的方式指出被調(diào)用的方法是什么,所以這種簡潔的寫法被稱為“方法引用”。本例中的q1和q2這兩個匿名類實現(xiàn)抽象方法的方式?jīng)Q定了它們都可以簡化為方法引用的形式。由于被調(diào)用的方法又可以分為普通方法、靜態(tài)方法和構(gòu)造方法,所以方法引用也可以分為很多種類。本例中,實現(xiàn)抽象方法過程中調(diào)用的abs()是靜態(tài)方法,所以要按“靜態(tài)方法引用”的形式來書寫Lambda表達式。本例中,以方法引用定義的匿名類是q3,它是q1和q2的簡化版本,q3的定義代碼是:?

Math::abs;?

q3的定義代碼展現(xiàn)了靜態(tài)方法引用的書寫格式,這種格式與靜態(tài)方法的調(diào)用格式非常相似,只是把調(diào)用方法時的點號(.)改成了雙冒號(::)。雙冒號左邊的“Math”表示實現(xiàn)抽象方法時調(diào)用的靜態(tài)方法屬于Math類,而雙冒號右邊的“abs”則表明方法的名稱是abs。但q3的定義代碼沒有體現(xiàn)出在調(diào)用abs()方法時要向它傳遞什么參數(shù)。那么虛擬機在執(zhí)行abs()方法時應該給把什么參數(shù)傳遞給abs()方法呢?Java語言規(guī)定:執(zhí)行代碼時要“原封不動”的把匿名類方法的所有參數(shù)傳遞給表達式中的方法。?

這條規(guī)定有些不好理解,此處以【例08_34】中的代碼為例進行解釋。在【例08_34】中,q3定義了QuoteInterface1的匿名實現(xiàn)類,在匿名類中實現(xiàn)了method()抽象方法,匿名類所實現(xiàn)的method()方法就是規(guī)定中所說的“匿名類方法”。在實現(xiàn)抽象方法的過程中,又調(diào)用了Math類的abs()方法,這個abs()方法當然會出現(xiàn)在Lambda表達式中,因此abs()方法就是規(guī)定中所說的“表達式中的方法”。語句①在執(zhí)行method()方法時,給method()方法傳遞的參數(shù)是-3,虛擬機就會把這個參數(shù)原封不動的再傳遞給abs()方法。這條規(guī)定使得表達式中的方法有了明確的參數(shù)來源以及接收方式,所以在q3的定義代碼中只需要指明被調(diào)用的方法屬于哪一個類以及它的名稱是什么就可以,無需指明要給方法傳遞哪些參數(shù)。此外,還需要強調(diào):“原封不動”的傳遞參數(shù),不僅僅是不能改變參數(shù)的值,還不能改變順序。假設傳遞給某個匿名類方法的參數(shù)是x、y、z,那么把這些參數(shù)傳遞給表達式方法時也要按照x、y、z的順序傳遞。各位讀者可以自行運行【例08_34】,以此來體驗以方法引用形式定義的匿名類對象是如何接收并傳遞參數(shù)的。?

如果Lambda表達式定義的匿名類在實現(xiàn)抽象方法時僅調(diào)用了一個構(gòu)造方法,這種情況下也可以把匿名類的定義簡化為方法引用形式。因為被調(diào)用的是一個構(gòu)造方法,所以這種方法引用被稱為“構(gòu)造方法引用”,構(gòu)造方法引用的格式為:?

類名::new

下面的【例08_35】展示了如何使用構(gòu)造方法引用定義函數(shù)式接口的匿名實現(xiàn)類。?

【例08_35 構(gòu)造方法引用】

QuoteInterface2.java?

public interface QuoteInterface2 {  Integer method(int x);}

Exam08_35.java?

public class Exam08_35 {  public static void main(String[] args) {    //傳統(tǒng)方式定義匿名實現(xiàn)類    QuoteInterface2 q1 = new QuoteInterface2() {      @Override      public Integer method(int x) {        return new Integer(x);      }    };    //Lambda表達式定義匿名實現(xiàn)類    QuoteInterface2 q2 = x  ->new Integer(x) ;    //構(gòu)造方法引用定義匿名實現(xiàn)類    QuoteInterface2 q3 = Integer::new;    System.out.println(q1.method(1));    System.out.println(q2.method(2));    System.out.println(q3.method(3));//①  }}

【例08_35】也是用3種不同方式定義了函數(shù)式接口QuoteInterface2的匿名實現(xiàn)類。q1和q2這兩個匿名類的結(jié)構(gòu)特征決定了它們都可以簡化為方法引用的形式。本例中的q3就是用方法引用的形式定義的匿名類,它是q1和q2的簡化版本,q3的定義代碼是:?

Integer::new?

雙冒號右邊的“new”表示要調(diào)用構(gòu)造方法,而雙冒號左邊的“Integer”表示method()方法中調(diào)用的是Integer類的構(gòu)造方法。按照規(guī)定,語句①在執(zhí)行method()方法時,會把傳遞給method()方法的參數(shù)原封不動的傳遞給Integer類的構(gòu)造方法,這樣構(gòu)造方法就能創(chuàng)建出一個Integer類的對象,這個對象正是method()方法的返回值。?

如果匿名類在實現(xiàn)抽象方法時調(diào)用的是一個普通方法,并且沒有做其他操作,這種情況下也可以用方法引用的形式定義匿名類,這種方法引用被稱為“實例方法引用”,實例方法引用的格式為:?

對象名::方法名

下面的【例08_36】展示了如何使用實例方法引用定義函數(shù)式接口的匿名實現(xiàn)類。?

【例08_36 實例方法引用】

QuoteInterface3.java?

public interface QuoteInterface3 {  int method(String substr,int start);}

Exam08_36.java?

public class Exam08_36 {    public static void main(String[] args) {        String str = new String("abcdefabc");        // 傳統(tǒng)方式定義匿名實現(xiàn)類        QuoteInterface3 q1 = new QuoteInterface3() {            @Override            public int method(String substr,int start) {                return str.indexOf(substr, start);            }        };        // Lambda表達式定義匿名實現(xiàn)類        QuoteInterface3 q2 = (substr,start)->str.indexOf(substr, start);        //實例方法引用定義匿名實現(xiàn)類        QuoteInterface3 q3 = str::indexOf;        System.out.println(q1.method("abc",5));        System.out.println(q2.method("abc",5));        System.out.println(q3.method("abc",5));    }}

與前兩個例子一樣,【例08_36】也是用三種不同方式定義了函數(shù)式接口QuoteInterface3的匿名實現(xiàn)類。在匿名類實現(xiàn)抽象方法時都只調(diào)用了字符串對象str的indexOf()方法,并把方法的計算結(jié)果作為返回值。此處需要講解一下indexOf()方法,這個方法的作用是尋找當前字符串對中參數(shù)所指定的子字符串處于什么位置。第一個參數(shù)就是要尋找的那個子字符串,第二個參數(shù)用來指定從哪個位置開始尋找。例如:?

str.indexOf("abc",5);?

這條語句表示在str這個字符串對象中尋找“abc”的位置,并且是從下標為5的位置開始尋找。需要注意:子字符串在整個字符串中的位置是從0而不是1開始計數(shù)的。字符串str的值是“abcdefabc”,所以“str.indexOf("abc",5);”的運行結(jié)果為6。?

本例中在以方法引用形式定義的匿名實現(xiàn)類是q3,它的定義代碼“str::indexOf”就表示調(diào)用str這個對象的indexOf()方法。當然,在執(zhí)行method()方法時,還是會把傳遞給method()方法的參數(shù)原封不動的傳遞給indexOf()方法。?

仔細觀察【例08_36】的程序代碼可以發(fā)現(xiàn):無論q1、q2、q3在執(zhí)行method()方法時傳遞了怎樣的參數(shù),都是在“abcdefabc”這個特定的字符串中尋找子字符串的位置,如果希望實現(xiàn)從任意字符串中尋找子字符串該怎么辦呢?這種情況下就需要把字符串當作方法的參數(shù)傳入到method()方法中,因此實現(xiàn)method()方法時要按照如下方式編寫代碼:?

public int method(String str,String substr,int start) {?    return str.indexOf(substr, start);?}?

以這種方式定義的method()方法把字符串str當作參數(shù),在方法中尋找str的某個子字符串的位置。在調(diào)用method()方法時,可以任意指定str參數(shù)的值,這樣就能實現(xiàn)在任意字符串中尋找子字符串的位置。實現(xiàn)了這種形式的method()方法的匿名類也可以用方法引用來定義,這種方法引用稱為“對象方法引用”。對象方法引用的格式為:?

類名::方法名

下面的【例08_37】展示了如何用對象方法引用定義函數(shù)式接口的匿名實現(xiàn)類:?

【例08_37 對象方法引用】

QuoteInterface4.java?

public interface QuoteInterface4 {  int method(String str,String substr,int start);}

Exam08_37.java?

public class Exam08_37 {    public static void main(String[] args) {        // 傳統(tǒng)方式定義匿名實現(xiàn)類        QuoteInterface4 q1 = new QuoteInterface4() {            @Override            public int method(String str,String substr,int start) {                return str.indexOf(substr, start);            }        };        // Lambda表達式定義匿名實現(xiàn)類        QuoteInterface4 q2 = (str,substr,start)->str.indexOf(substr, start);        //對象方法引用定義匿名實現(xiàn)類        QuoteInterface4 q3 = String::indexOf;        System.out.println(q1.method("abcdefabc","abc",5));        System.out.println(q2.method("abcdefabc","abc",5));        System.out.println(q3.method("abcdefabc","abc",5));    }}

【例08_37】也是用三種不同方式定義了函數(shù)式接口QuoteInterface4的匿名實現(xiàn)類。q1和q2這兩個匿名類的定義代碼都很容易理解,下面重點解釋一下q3的定義代碼是什么意思。q3的定義代碼是:?

String::indexOf?

雙冒號的左邊是“String”,這表示匿名類在實現(xiàn)抽象方法過程中要調(diào)用String類中定義的一個方法。雙冒號的右邊是“indexOf”,表示要調(diào)用String類中定義的indexOf()方法,但indexOf()方法并不是一個靜態(tài)方法,必須通過一個String類的對象才能調(diào)用到這個方法,而q3的定義代碼中并沒有指明這個對象是誰。Java語言規(guī)定:如果使用對象方法引用定義匿名類,那么這個匿名類在實現(xiàn)抽象方法時,會通過傳遞給匿名類方法的第一個參數(shù)對象去調(diào)用方法,并把匿名類方法的剩余參數(shù)原封不動的傳遞給表達式中的方法。這條規(guī)定也不太好理解,此處以q3調(diào)用method()方法的語句為例進行講解。q3調(diào)用method()方法的語句為:?

q3.method("abcdefabc","abc",5);?

以上語句中,method()方法就是匿名類方法。可以看到,語句中傳遞給匿名類方法的第一個參數(shù)是“abcdefabc”,這個參數(shù)就是一個字符串對象。按照規(guī)定,在method()方法中會通過“abcdefabc”這個字符串對象去調(diào)用方法。那么會調(diào)用哪一個方法呢?q3的定義代碼已經(jīng)明確指出要調(diào)用的是indexOf()方法。在調(diào)用indexOf()方法時,會把匿名類方法的剩余參數(shù)傳遞給它,匿名類方法的剩余參數(shù)是“abc”和5,所以傳遞給indexOf()方法的兩個參數(shù)就是“abc”和5。這樣的話,method()方法中所執(zhí)行的語句實際上就是:?

"abcdefabc".indexOf("abc",5);?

經(jīng)過分析可以看出:對象方法引用所定義的匿名類,在實現(xiàn)抽象方法的過程中,并不是“原封不動”的把匿名類方法的所有參數(shù)傳遞給表達式中的方法,而只是把除第一個參數(shù)以外的剩余參數(shù)傳遞給表達式中的方法。?

對象方法引用和靜態(tài)方法引用的寫法有點類似,格式都是“類名::方法名”,編譯器區(qū)分這兩種方法引用的依據(jù)是檢查雙冒號后面的方法是一個普通方法還是一個靜態(tài)方法。判斷出方法引用的類別后就能采取不同的策略對方法引用定義的匿名類進行編譯。?

很多讀者都會問:在執(zhí)行method()方法時會調(diào)用它第一個參數(shù)對象的方法,而第一參數(shù)的類型已經(jīng)被定義為String,為什么還要在定義q3時在雙冒號的左邊寫上String這個類的名稱呢?原因就是第一個參數(shù)有可能使用到泛型,而泛型在編譯階段都會被擦拭掉,在這種情況下編譯器必須明確第一個參數(shù)到底是什么類型的對象,所以必須要在雙冒號的左邊寫上類名。請看下面的【例08_38】?

【例08_38 方法引用定義泛型接口實現(xiàn)類】

QuoteInterface5.java?

public interface QuoteInterface5 {?    int method(T str,String substr,int start);?}?

Exam08_38.java?

public class Exam08_38 {?    public static void main(String[] args) {?        QuoteInterface5 q = String::indexOf;?        System.out.println(q.method("abcdefabc","abc",5));?    }?}?

在【例08_38】中,函數(shù)式接口是一個泛型接口,接口中抽象方法第一個參數(shù)的類型被定義為泛型。雖然泛型在編譯階段會被擦拭,但對象方法引用已經(jīng)明確的指出了indexOf()方法被定義在String中,所以無需通過第一個參數(shù)的類型去判斷indexOf()方法的來源。?

本小節(jié)講解了方法引用的相關(guān)知識。方法引用本質(zhì)上也是一種Lambda表達式,它以最簡潔的形式指出了匿名類在實現(xiàn)抽象方法時要調(diào)用哪一個方法。但并非所有的Lambda表達式都能簡化為方法引用的形式,讀者在把一個普通的Lambda表達式簡化為方法引用的時候一定要仔細觀察它是否符合簡化條件。

本文字版教程還配有更詳細的視頻講解,小伙伴們可以??點擊這里??觀看。

標簽: 抽象方法 靜態(tài)方法 構(gòu)造方法

上一篇:最新:#yyds干貨盤點# LeetCode程序員面試金典:迷路的機器人
下一篇:第八章《Java高級語法》第7節(jié):枚舉