詳解Java的閉包
在2013年將發(fā)布的 JavaSE8 中將包含一個(gè)叫做 Lambda Project 的計(jì)劃,在今年6月份的 JSR-335 草案中有描述。
JSR-335 將閉包引入了 Java 。閉包在現(xiàn)在的很多流行的語言中都存在,例如 C++、C# 。閉包允許我們創(chuàng)建函數(shù)指針,并把它們作為參數(shù)傳遞。在這篇文章中,我們將粗略的看一遍Java8的特性,并介紹Lambda表達(dá)式。而且我將試著放一些樣例程序來解釋一些概念和語法。
Java 編程語言給我們提供了接口的概念,接口里可以定義抽象的方法。接口定義了 API,并希望用戶或者供應(yīng)商來實(shí)現(xiàn)這些方法。很多時(shí)候,我們并不為一些接口創(chuàng)建獨(dú)立的實(shí)現(xiàn)類,我們通過寫一個(gè)匿名內(nèi)部類來寫一個(gè)內(nèi)聯(lián)的接口實(shí)現(xiàn)。
匿名類使用的非常廣泛。匿名內(nèi)部類使用的最常見的場(chǎng)景就是事件處理器了。其次匿名內(nèi)部類還常被用在多線程的程序中,我們通常寫匿名內(nèi)部類,而不是創(chuàng)建 Runnable/Callable 接口的實(shí)現(xiàn)類。
就像我們討論的一樣,一個(gè)匿名類就是一個(gè)內(nèi)聯(lián)的給定的接口的實(shí)現(xiàn)。通常我們將這個(gè)實(shí)現(xiàn)類的對(duì)象作為參數(shù)傳遞給一個(gè)方法,然后這個(gè)方法將在內(nèi)部調(diào)用傳遞過來的實(shí)現(xiàn)類的方法。故這種接口叫做回調(diào)接口,這些方法叫做回調(diào)方法。
雖然匿名類到處都在使用,但是他們還是有很多問題。第一個(gè)主要問題是復(fù)雜。這些類讓代碼的層級(jí)看起來很亂很復(fù)雜,也稱作 Vertical Problem 。第二,他們不能訪問封裝類的非 final 成員。this 這個(gè)關(guān)鍵字將變得很有迷惑性。如果一個(gè)匿名類有一個(gè)與其封裝類相同的成員名稱,內(nèi)部變量將會(huì)覆蓋外部的成員變量,在這種情況下,外部的成員在匿名類內(nèi)部將是不可見的,甚至不能通過 this 關(guān)鍵字來訪問。因?yàn)?this 關(guān)鍵字值得是匿名類對(duì)象本身而不是他的封裝類的對(duì)象。
public void anonymousExample() { String nonFinalVariable = "Non Final Example"; String variable = "Outer Method Variable"; new Thread(new Runnable() { String variable = "Runnable Class Member"; public void run() { String variable = "Run Method Variable"; //Below line gives compilation error. //System.out.println("->" + nonFinalVariable); System.out.println("->" + variable); System.out.println("->" + this.variable); } }).start(); }
輸出是:
->Run Method Variable ->Runnable Class Member
這個(gè)例子很好的說明了我上面所說的這個(gè)問題,而 Lambda 表達(dá)式幾乎解決了匿名內(nèi)部類帶來的所有問題。在我們進(jìn)一步探討 lambda 表達(dá)式之前,讓我們來看一看 Functional Interfaces。
Functional Interfaces
Functional Interfaces 是一個(gè)只有單個(gè)方法的接口,這代表了這個(gè)方法契約。
上面的定義中的只有一個(gè)實(shí)際上并沒有那么簡(jiǎn)單。這段有些不懂,請(qǐng)讀者查看原文(The ‘Single' method can exist in the form of multiple abstract methods that are inherited from superinterfaces. But in that case the inherited methods should logically represent a single method or it might redundantly declare a method that is provided by classes like Object, e.g. toString.)
下面的例子清楚的展示了怎樣理解 Functional Interfaces 的概念。
interface Runnable { void run(); } // Functional interface Foo { boolean equals(Object obj); } // Not functional; equals is already an implicit member interface Bar extends Foo {int compare(String o1, String o2); } // Functional; Bar has one abstract non-Object method interface Comparator { boolean equals(Object obj); int compare(T o1, T o2); } // Functional; Comparator has one abstract non-Object method interface Foo {int m(); Object clone(); } // Not functional; method Object.clone is not public interface X { int m(Iterable arg); } interface Y { int m(Iterable arg); } interface Z extends X, Y {} // Functional: two methods, but they have the same signature
大多數(shù)回調(diào)接口都是 Functional Interfaces。例如 Runnable,Callable,Comparator 等等。以前被稱作 SAM(Single Abstract Method)
Lambda 表達(dá)式
我們上邊說過,匿名類的一個(gè)主要問題是是代碼的層級(jí)看起來很亂,也就是 Vertical Problem 了,Lamdba 表達(dá)式實(shí)際上就是匿名類,只不過他們的結(jié)構(gòu)更輕量,更短。Lambda 表達(dá)式看起來像方法。他們有一個(gè)正式的參數(shù)列表和這些參數(shù)的塊體表達(dá)。
(String s)-> s.lengh; () -> 43; (int x, int y) -> x + y;
上面的例子的意思是,第一個(gè)表達(dá)式接收一個(gè) String 變量作為參數(shù),然后返回字符串的長(zhǎng)度。第二個(gè)不帶任何參數(shù),并返回43。最后,第三個(gè)接受兩個(gè)整數(shù) x 和 y ,并返回其和。
在看了許多文字后,終于,我可以給出第一個(gè) Lambda 表達(dá)式的例子了,這個(gè)例子運(yùn)行在 JavaSE8 的預(yù)覽版下:
public class FirstLambdaExpression { public String variable = "Class Level Variable"; public static void main(String[] arg) { new FirstLambdaExpression().lambdaExpression(); } public void lambdaExpression(){ String variable = "Method Local Variable"; String nonFinalVariable = "This is non final variable"; new Thread (() -> { //Below line gives compilation error //String variable = "Run Method Variable" System.out.println("->" + variable); System.out.println("->" + this.variable); }).start(); } }
輸出是:
->Method Local Variable ->Class Level Variable
你可以比較一些使用 Lambda 表達(dá)式和使用匿名內(nèi)部類的區(qū)別。我們可以清楚的說,使用 Lambda 表達(dá)式的方式寫匿名類解決了變量可見性的問題。你可以看一下代碼中的注釋, Lambda 表達(dá)式不允許創(chuàng)建覆蓋變量。
通常的 Lambda 表達(dá)式的語法包括一個(gè)參數(shù)列表,箭頭關(guān)鍵字"->"最后是主體。主體可以是表達(dá)式(單行語句)也可以是多行語句塊。如果是表達(dá)式,將被計(jì)算后返回,如果是多行的語句塊,就看起來跟方法的語句塊很相似了,可以使用 return 來指定返回值。break 和 continue 只能用在循環(huán)內(nèi)部。
為什么選擇這個(gè)特殊的語法形式呢,因?yàn)槟壳?C# 和 Scala 中通常都是這種樣式,也算是 Lambda 表達(dá)式的通用寫法。這樣的語法設(shè)計(jì)基本上解決了匿名類的復(fù)雜性。但是與此同時(shí)他也是非常靈活的,例如,如果方法體是單個(gè)表達(dá)式,大括號(hào)和 return 語句都是不需要的。表達(dá)式的結(jié)果就是作為他自己的返回值。這種靈活性可以保持代碼簡(jiǎn)潔。
Lambda 表達(dá)式用作匿名類,因此他們可以靈活運(yùn)用在其他模塊或在其他 Lambda 表達(dá)式(嵌套的 Lambda 表達(dá)式)。
//Lambda expression is enclosed within methods parameter block. //Target interface type is the methods parameter type. String user = doSomething(() -> list.getProperty(“propName”); //Lambda expression is enclosed within a thread constructor //target interface type is contructors paramter i.e. Runnable new Thread (() -> { System.out.println("Running in different thread"); }).start();
如果你仔細(xì)看看 lambda 表達(dá)式,您將看到,目標(biāo)接口類型不是一個(gè)表達(dá)式的一部分。編譯器會(huì)幫助推斷 lambda 表達(dá)式的類型與周圍環(huán)境。
Lambda 表達(dá)式必須有一個(gè)目標(biāo)類型,而他們可以適配任意可能的目標(biāo)類型。當(dāng)目標(biāo)類型是一個(gè)接口的時(shí)候,下面的條件必須滿足,才能編譯正確:
- 接口應(yīng)該是一個(gè) functional interface
- 表達(dá)式的參數(shù)數(shù)量和類型必須與 functional interface 中聲明的一致
- 返回值類型必須兼容 functional interface 中方法的返回值類型
- 拋出的異常表達(dá)式必須兼容 functional interface 中方法的拋出異常聲明
由于編譯器可以通過目標(biāo)類型的聲明中得知參數(shù)類型和個(gè)數(shù),所以在 Lambda 表達(dá)式中,可以省略參數(shù)類型聲明。
Comparator c = (s1, s2) -> s1.compareToIgnoreCase(s2);
而且,如果目標(biāo)類型中聲明的方法只接收一個(gè)參數(shù)(很多時(shí)候都是這樣的),那么參數(shù)的小括號(hào)也是可以不寫的,例如:
ActionListenr listenr = event -> event.getWhen();
一個(gè)很明顯的問題來了,為什么 Lambda 表達(dá)式不需要一個(gè)指定的方法名呢?
答案是:Lambda 表達(dá)式只能用于 functional interface ,而 functional interface 只有一個(gè)方法。
當(dāng)我們確定一個(gè) functional interface 來創(chuàng)建 Lambda 表達(dá)式的時(shí)候,編譯器可以感知 functional interface 中方法的簽名,并且檢查給定的表達(dá)式是否匹配。
這種靈活的語法幫助我們避免了使用匿名類的 Vertical Problem ,而且不會(huì)帶來 Horizontal Problem(單行語句非常長(zhǎng))。
Lambda 表達(dá)式的語法是上下文相關(guān)的,但是這些并不是第一次出現(xiàn)。Java SE 7添加的diamond operators 也有這個(gè)概念,通過上下文推斷類型。
void invoke(Runnable r) {r.run()} void Future invoke(Callable r) {return c.compute()} //above are two methods, both takes parameter of type functional interface Future s = invoke(() ->"Done"); //Which invoke will be called?
上面問題的答案是調(diào)用接收Callable參數(shù)的方法。在這種情況下編譯器會(huì)通過不同參數(shù)類型的重載解決。當(dāng)有不止一個(gè)適用的重載方法,編譯器也檢查lambda表達(dá)式與相應(yīng)的目標(biāo)類型的兼容性。簡(jiǎn)單的說,上面的invoke方法期望一個(gè)返回,但是只有一個(gè)invoke方法具有返回值。
Lambda表達(dá)式可以顯式的轉(zhuǎn)換為指定的目標(biāo)類型,只要跟對(duì)應(yīng)的類型兼容。看一下下面的程序,我實(shí)現(xiàn)了三種Callable,而且都將其轉(zhuǎn)換為Callable類型。
public class FirstSightWithLambdaExpressions { public static void main(String[] args) { List list = Arrays.asList( (Callable)()->"callable 1", (Callable) ()->"callable 2", (Callable) ()->"callable 3"); ExecutorService e = Executors.newFixedThreadPool(2); List futures = null; try { futures = e.invokeAll(list); new FirstSightWithLambdaExpressions().dumpList(futures); } catch (InterruptedException | ExecutionException e1) { e1.printStackTrace(); } e.shutdown(); } public void dumpList(List list) throws InterruptedException, ExecutionException { for (Future future : list) { System.out.println(future.get()); } } }
正如我們前面討論的一樣,匿名類不能訪問周圍環(huán)境中非final的變量。但是Lambda表達(dá)式里就沒有這個(gè)限制。
目前,該定義的 functional interfaces 只適用于接口。我試著對(duì)一個(gè)只有一個(gè)抽象方法的抽象類創(chuàng)建一個(gè) lambda 表達(dá)式,但出了一個(gè)編譯錯(cuò)誤。按照 jsr - 335,未來版本的 lambda 表達(dá)式可能支持 Functional Classes。
方法引用
方法引用被用作引用一個(gè)方法而不調(diào)用它。
Lambda 表達(dá)式允許我們定義一個(gè)匿名的方法,并將它作為 Functional interface 的一個(gè)實(shí)例。方法引用跟 Lambda 表達(dá)式很像,他們都需要一個(gè)目標(biāo)類型,但是不同的是方法引用不提供方法的實(shí)現(xiàn),他們引用一個(gè)已經(jīng)存在的類或者對(duì)象的方法。
System::getProperty "abc"::length String::length super::toString ArrayList::new
上面的語句展示了方法和構(gòu)造函數(shù)的引用的通用語法。這里我們看到引入了一個(gè)新的操作符“::'(雙冒號(hào))。我尚不清楚確切名稱為這個(gè)操作符,但是 JSR 指它作為分隔符,維基百科頁面是指它作為一個(gè)范圍解析操作符。作為我們的參考,本教程的范圍內(nèi),我們將簡(jiǎn)單地將它作為分隔符。
目標(biāo)引用或者說接收者被放在提供者和分隔符的后面。這形成了一個(gè)表達(dá)式,它能夠引用一個(gè)方法。在最后聲明上述代碼,該方法的名字是“new”。這個(gè)表達(dá)式引用的是 ArrayList 類的構(gòu)造方法(下一節(jié)再討論構(gòu)造方法的引用)
再進(jìn)一步了解這個(gè)之前,我想讓你看一看方法引用的強(qiáng)大之處,我創(chuàng)建了一個(gè)簡(jiǎn)單的 Employee 數(shù)組的排序程序。
import java.util.Arrays; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; public class MethodReference { public static void main (String[] ar){ Employee[] employees = {new Employee("Nick"), new Employee("Robin"), new Employee("Josh"), new Employee("Andy"), new Employee("Mark")}; System.out.println("Before Sort:"); dumpEmployee(employees); Arrays.sort(employees, Employee::myCompare); System.out.println("After Sort:"); dumpEmployee(employees); } public static void dumpEmployee(Employee[] employees){ for(Employee emp : Arrays.asList(employees)){ System.out.print(emp.name+", "); } System.out.println(); } } class Employee { String name; Employee(String name) { this.name = name; } public static int myCompare(Employee emp1, Employee emp2) { return emp1.name.compareTo(emp2.name); } }
輸出是:
Before Sort: Nick, Robin, Josh, Andy, Mark, After Sort: Andy, Josh, Mark, Nick, Robin,
輸出沒什么特別,Employee 是一個(gè)非常簡(jiǎn)單的類,只有一個(gè) name 屬性。靜態(tài)方法 myCompare 接收兩個(gè) Employee 對(duì)象,返回他們名字的比較。
在 main 方法中我創(chuàng)建了一個(gè)不同的 employee 的數(shù)組,并且將它連同一個(gè)方法引用表達(dá)式( Employee::myCompare )傳遞給了 Arrays.sort 方法。
等一下,如果我們看 Javadoc 你會(huì)發(fā)現(xiàn) sort 方法的第二個(gè)參數(shù)是 Comparator 類型的,但是我們卻傳遞了 Employee 的一個(gè)靜態(tài)方法引用。重要的問題就在這了,我既沒有讓 Employee 實(shí)現(xiàn) Comparable 接口,也沒有寫一個(gè)獨(dú)立的 Comparator 類,但是輸出確實(shí)沒有任何問題。
讓我們來看一看這是為什么。 Arrays.sort 方法期望一個(gè) Comparator 的實(shí)例,而這個(gè) Comparator 是一個(gè) functional interface ,這就意味著他只有一個(gè)方法,就是 compare 了。這里我們同樣惡意傳一個(gè) Lambda 表達(dá)式,在這個(gè)表達(dá)式中提供 compare 方法的實(shí)現(xiàn)。但是在我們的里中,我們的 Employee 類已經(jīng)有了一個(gè)自己的比較方法。只是他們的名字是不一樣的,參數(shù)的類型、數(shù)量,返回值都是相同的,這里我們就可以創(chuàng)建一個(gè)方法引用,并將它傳遞給 sort 作為第二個(gè)參數(shù)。
當(dāng)有多個(gè)相同的名稱的方法的時(shí)候,編譯器會(huì)根據(jù)目標(biāo)類型選擇最佳的匹配。為了搞明白,來看一個(gè)例子:
public static int myCompare(Employee emp1, Employee emp2) { return emp1.name.compareTo(emp2.name); } //Another method with the same name as of the above. public static int myCompare(Integer int1, Integer int2) { return int1.compareTo(int2); }
我創(chuàng)建了兩個(gè)不同的數(shù)組,用作排序。
Employee[] employees = {new Employee("Nick"), new Employee("Robin"), new Employee("Josh"), new Employee("Andy"), new Employee("Mark")}; Integer[] ints = {1 , 4, 8, 2, 3, 8, 6};
現(xiàn)在,我執(zhí)行下面的兩行代碼
Arrays.sort(employees, Employee::myCompare); Arrays.sort(ints, Employee::myCompare);
這里,兩行代碼中的方法引用聲明都是相同的(Employee::myCompare),唯一不同的是我們傳入的數(shù)組,我們不需要傳遞一個(gè)含糊不清的標(biāo)記用以知名那個(gè)方法作為方法引用,編譯器會(huì)幫助我們檢查第一個(gè)參數(shù),并且智能的找到合適的方法。
不要被靜態(tài)方法誤導(dǎo)了哦,我們還可以創(chuàng)建實(shí)例方法的引用。對(duì)于靜態(tài)方法我們使用類名::方法名來寫方法引用,如果是實(shí)例方法的引用,則是對(duì)象::方法名。
上面的例子已經(jīng)是相當(dāng)不錯(cuò)的了,但是我們不必為整型的比較單獨(dú)寫一個(gè)方法,因?yàn)镮nteger已經(jīng)實(shí)現(xiàn)了Comparable并且提供了實(shí)現(xiàn)方法compareTo。所以我們直接使用下面這一行就行了:
Arrays.sort(ints, Integer::compareTo);
看到這里,你是否覺得有點(diǎn)迷惑?沒有?那我來讓你迷惑一下
這里, Integer 是一個(gè)類名(而不是一個(gè)像 new Integer() 一樣的實(shí)例),而 compareTo 方法卻是 Integer 類的成員方法(非靜態(tài)).如果你仔細(xì)看了我上面的描述就會(huì)知道,成員方法的方法引用::之前應(yīng)該是對(duì)象,但是為什么這里的語句確實(shí)合法的。
答案是:這種類型的語句允許使用在一些特定的類型中。Integer是一個(gè)數(shù)據(jù)類型,而對(duì)于數(shù)據(jù)類型來說,這種語句是允許的。
如果我們將 Employee 的方法 myCompare 變成非靜態(tài)的,然后這樣使用:Employee::myCompare,就會(huì)出編譯錯(cuò)誤:No Suitable Method Found。
構(gòu)造方法引用
構(gòu)造方法引用被用作引用一個(gè)構(gòu)造方法而不實(shí)例化指定的類。
構(gòu)造方法引用是 JavaSE 8 的一個(gè)新的特性。我們可以構(gòu)造一個(gè)構(gòu)造方法的引用,并且將它作為參數(shù)傳遞給目標(biāo)類型。
當(dāng)我們使用方法引用的時(shí)候,我們引用一個(gè)已有的方法使用他們。同樣的,在使用構(gòu)造方法引用的時(shí)候,我們創(chuàng)建一個(gè)已有的構(gòu)造方法的引用。
上一節(jié)中我們已經(jīng)看到了構(gòu)造方法引用的語法類名::new,這看起來很像方法引用。這種構(gòu)造方法的引用可以分配給目標(biāo) functional interfaces 的實(shí)例。一個(gè)類可能有多個(gè)構(gòu)造方法,在這種情況下,編譯器會(huì)檢查 functional interfaces 的類型,最終找到最好的匹配。
對(duì)我來說寫出第一個(gè)構(gòu)造方法引用的程序有些困難,雖然我理解了他的語法,但是我卻不知道怎么使用它,以及它有什么用。最后,我花費(fèi)了很久的努力,終于“啊,找到了...”,看看下面的程序吧。
public class ConstructorReference { public static void main(String[] ar){ MyInterface in = MyClass::new; System.out.println("->"+in.getMeMyObject()); } } interface MyInterface{ MyClass getMeMyObject(); } class MyClass{ MyClass(){} }
輸出是:
->com.MyClass@34e5307e
這看起來有點(diǎn)神奇是吧,這個(gè)接口和這個(gè)類除了接口中聲明的方法的返回值是 MyClass 類型的,沒有任何關(guān)系。
這個(gè)例子又激起了我心中的另一個(gè)問題:怎樣實(shí)例化一個(gè)帶參數(shù)的構(gòu)造方法引用?看看下面的程序:
public class ConstructorReference { public static void main(String[] ar){ EmlpoyeeProvider provider = Employee::new; Employee emp = provider.getMeEmployee("John", 30); System.out.println("->Employee Name: "+emp.name); System.out.println("->Employee Age: "+emp.age); } } interface EmlpoyeeProvider{ Employee getMeEmployee(String s, Integer i); } class Employee{ String name; Integer age; Employee (String name, Integer age){ this.name = name; this.age = age; } }
輸出是:
->Employee Name: John ->Employee Age: 30
在看完這篇文章之前,讓我們?cè)賮砜匆豢碕avaSE8中的最酷的一個(gè)特性--默認(rèn)方法(Default Methods)
默認(rèn)方法(Default Methods)
JavaSE8 中將會(huì)引入一個(gè)叫做默認(rèn)方法的概念。早起的 Java 版本的接口擁有非常嚴(yán)格的接口,接口包含了一些抽象方法的聲明,所有非抽象的實(shí)現(xiàn)類必須要提供所有這些抽象方法的實(shí)現(xiàn),甚至是這些方法沒有用或者不合適出現(xiàn)在一些特殊的實(shí)現(xiàn)類中。在即將到來的Java 版本中,允許我們?cè)诮涌谥卸x方法的默認(rèn)實(shí)現(xiàn)。廢話不多說,看下面:
public class DefaultMethods { public static void main(String[] ar){ NormalInterface instance = new NormalInterfaceImpl(); instance.myNormalMethod(); instance.myDefaultMethod(); } } interface NormalInterface{ void myNormalMethod(); void myDefaultMethod () default{ System.out.println("-> myDefaultMethod"); } } class NormalInterfaceImpl implements NormalInterface{ @Override public void myNormalMethod() { System.out.println("-> myNormalMethod"); } }
輸出是:
-> myDefaultMethod
上面的接口中聲明了兩個(gè)方法,但是這個(gè)接口的實(shí)現(xiàn)類只實(shí)現(xiàn)了其中一個(gè),因?yàn)?myDefaultMethod 使用 default 修飾符標(biāo)記了,而且提供了一個(gè)方法塊用作默認(rèn)實(shí)現(xiàn)。通用的重載規(guī)則在這里仍然生效。如果實(shí)現(xiàn)類實(shí)現(xiàn)了接口中的方法,調(diào)用的時(shí)候?qū)⑹钦{(diào)用類中的方法,否則,默認(rèn)實(shí)現(xiàn)將被調(diào)用。
集成父接口的接口可以增加、改變、移除父接口中的默認(rèn)實(shí)現(xiàn)。
interface ParentInterface{ void initiallyNormal(); void initiallyDefault () default{ System.out.println("-> myDefaultMethod"); } } interface ChildInterface extends ParentInterface{ void initiallyNormal() default{ System.out.println("now default - > initiallyNormal"); } void initiallyDefault (); //Now a normal method }
在這個(gè)例子中,ParentInterface 定義了兩個(gè)方法,一個(gè)是正常的,一個(gè)是有默認(rèn)實(shí)現(xiàn)的,子接口只是簡(jiǎn)單的反了過來,給第一個(gè)方法添加了默認(rèn)實(shí)現(xiàn),給第二個(gè)方法移除了默認(rèn)實(shí)現(xiàn)。
設(shè)想一個(gè)類繼承了類 C ,實(shí)現(xiàn)了接口 I ,而且 C 有一個(gè)方法,而且跟I中的一個(gè)提供默認(rèn)方法的方法是重載兼容的。在這種情況下,C中的方法會(huì)優(yōu)先于I中的默認(rèn)方法,甚至C中的方法是抽象的時(shí)候,仍然是優(yōu)先的。
public class DefaultMethods { public static void main(String[] ar){ Interfaxe impl = new NormalInterfaceImpl(); impl.defaultMethod(); } } class ParentClass{ public void defaultMethod() { System.out.println("->ParentClass"); } } interface Interfaxe{ public void defaultMethod() default{ System.out.println("->Interfaxe"); } } class NormalInterfaceImpl extends ParentClass implements Interfaxe{}
輸出是:
->ParentClass
第二個(gè)例子是,我的類實(shí)現(xiàn)了兩個(gè)不同的接口,但是兩個(gè)接口中都提供了相同的具有默認(rèn)實(shí)現(xiàn)的方法的聲明。在這種情況下,編譯器將會(huì)搞不清楚怎么回事,實(shí)現(xiàn)類必須選擇兩個(gè)的其中一個(gè)實(shí)現(xiàn)。這可以通過如下的方式來使用 super 來搞定。
public class DefaultMethods { public static void main(String[] ar){ FirstInterface impl = new NormalInterfaceImpl(); impl.defaultMethod(); } } interface FirstInterface{ public void defaultMethod() default{ System.out.println("->FirstInterface"); } } interface SecondInterface{ public void defaultMethod() default{ System.out.println("->SecondInterface"); } } class NormalInterfaceImpl implements FirstInterface, SecondInterface{ public void defaultMethod(){ SecondInterface.super.defaultMethod(); } }
輸出是:
->SecondInterface
現(xiàn)在,我們已經(jīng)看完了 Java 閉包的介紹。這個(gè)文章中,我們接觸到了 Functional Interfaces 和 Java Closure ,理解了 Java 的 Lambda 表達(dá)式,方法引用和構(gòu)造方法引用。而且我們也寫出了 Lambda 表達(dá)式的 Hello World 例子。
JavaSE8 很快就要到來了,我將很高興的擁抱這些新特性,也許這些新特性還是有些迷惑不清,但是我相信,隨著時(shí)間的推移,會(huì)變得越來越好。
相關(guān)文章
Java服務(wù)限流算法的6種實(shí)現(xiàn)
服務(wù)限流是指通過控制請(qǐng)求的速率或次數(shù)來達(dá)到保護(hù)服務(wù)的目的,本文主要介紹了Java服務(wù)限流算法的6種實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2023-05-05java處理數(shù)據(jù)庫不支持的emoji表情符問題解決
這篇文章主要介紹了java處理數(shù)據(jù)庫不支持的emoji表情符問題解決,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-09-09java正則表達(dá)式獲取指定HTML標(biāo)簽的指定屬性值且替換的方法
下面小編就為大家?guī)硪黄猨ava正則表達(dá)式獲取指定HTML標(biāo)簽的指定屬性值且替換的方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-12-12JAVA裝飾者模式(從現(xiàn)實(shí)生活角度理解代碼原理)
裝飾者模式可以動(dòng)態(tài)地給一個(gè)對(duì)象添加一些額外的職責(zé)。就增加功能來說,Decorator模式相比生成子類更為靈活。這篇文章主要介紹了JAVA裝飾者模式的相關(guān)資料,需要的朋友可以參考下2016-12-12Netty分布式高性能工具類recycler的使用及創(chuàng)建
這篇文章主要為大家介紹了Netty分布式高性能工具類recycler的使用和創(chuàng)建,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2022-03-03利用IDEA工具修改Maven多模塊項(xiàng)目標(biāo)識(shí)包名全過程記錄
當(dāng)我們?yōu)榧追椒?wù)提供軟件開發(fā)服務(wù)時(shí),需要按照甲方的要求去修改軟件的標(biāo)識(shí),對(duì)于Maven項(xiàng)目來說就對(duì)應(yīng)著groupId,一般地寫對(duì)方公司的域名,如com.example,接下來通過本文給大家分享IDEA修改Maven多模塊項(xiàng)目標(biāo)識(shí)包名,感興趣的朋友一起看看吧2022-09-09Spring中的@EnableScheduling定時(shí)任務(wù)注解
這篇文章主要介紹了Spring中的@EnableScheduling注解,@EnableScheduling是 Spring Framework 提供的一個(gè)注解,用于啟用 Spring 的定時(shí)任務(wù)功能,通過使用這個(gè)注解,可以在 Spring 應(yīng)用程序中創(chuàng)建定時(shí)任務(wù),需要的朋友可以參考下2024-01-01