閉包在很多語言中都存在,例如C++,C#。閉包允許我們創建函數指針,並把它們作為參數傳遞,Java編程語言提供了接口的概念,接口中可以定義抽象方法,接口定義了API,並希望用戶或者供應商來實現這些方法,很多時候並不是為一些接口創建獨立的實現類,我們通過寫一個匿名的內部類來寫一個內聯的接口實現,匿名內部類使用相當的廣泛,匿名內部類最常見的場景就是事件處理器了,其次匿名內部類還被用於多線程中,寫匿名內部類而不是創建Runable\Callable接口的實現類。
一個匿名內部類就是一個內聯的給定接口的實現,這個實現類的對象作為參數傳遞給一個方法,然后這個方法將在內部調用傳遞過來的實現類的方法,這種接口叫做回調接口,這個方法叫做回調方法。
匿名內部類很多地方都在使用,在使用的同時存在一些問題
-
復雜
這些類代碼的層級看起來很亂很復雜,稱作Vertical Problem -
不能訪問封裝類的非final成員
this關鍵字變得很迷惑,如果一個匿名類有一個與其封裝類相同的成員名稱,內部類會覆蓋外部的成員變量,在這種情況下,外部成員在匿名類內部是不可見的,甚至不能通過this來訪問。
實例說明
public void test() { String variable = "Outer Method Variable"; new Thread(new Runnable() { String variable = "Runnable Class Member"; public void run() { String variable = "Run Method Variable"; System.out.println("->" + variable); System.out.println("->" + this.variable); } }).start(); } 輸出
->Run Method Variable ->Runnable Class Member
這個例子很好的說明了上面的兩個問題,而Lambda表達式幾乎解決上面的所有問題,我們討論Lambda表達式之前,讓我們來看看Functional Interfaces
-
Funcational Interfaces
一個只有單個方法的接口,這代表了這個方法的契約。
The Single method cal 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> interface Runnable{void run();} > interface Foo {boolean equals(Object obj);} > interface extends Foo{ int compare(String s1,String s2)} > interface Comparetor{ boolean equals(Object obj); int compare(T t1,T t2); } > interface Foo( int m(); Object clone();
大多數回調接口都是Functional Interfaces,例如Runable,Callable,Comparetor等等,以前被稱作SAM(Single Abstract Method) -
Lambda
- Lambda表達式實際上就是匿名類,只不過他們的結構更輕量,Lambda表達式看起來像方法。他們有一個正式的參數列表和這參數的塊體表達式。針對上面的例子進行Lambda改造。
public class TestLambdaExpression{
public String variable ="Class level variable";
public static void main(){
new TestLambdaExpression().test();
}
public void test() { String variable = "Method local Variable"; new Thread(() { public void run() -> { System.out.println("->" + variable); System.out.println("->" + this.variable); } }).start(); }
} 輸出
->Method local Variable ->Class level variable
可以比較一些使用Lambda表達式和使用匿名內部類的區別,我們可以清楚的看出,使用Lambda表達式的方式寫匿名內部類解決了變量可見性的問題,Lambda表達式不允許創建覆蓋變量。 Lambda表達式的語法包括一個參數列表和“->”,為什么選擇這樣的語法形式,因為目前C#和Scala中通常都是這樣的,也算是遵循了Lambda的通用寫法,這樣的語法基本上解決了匿名類的復雜性,同時也顯得非常的靈活,目標接口類型不是一個表達式的一部分。編譯器會幫助推斷Lambda expression的類型與周圍的環境,Lambda表達式必須有一個目標類型,而它們可以適配任意可能的目標類型,當然類型是一個接口的時候,下面的條件必須滿足,才能編譯正確。接口應該是一個Funcational interface
表達式的參數數量和類型必須與Functional interface中聲明的一致
拋出的異常表達式必須兼容function interface中方法的拋出異常聲明
返回值類型必須兼容function interface 中方法的返回值類型
由於編譯器可以通過目標類型的聲明中得知參數類型和個數,所以在Lambda表達式中可以省略類型的聲明。
例如
Comparetor c = (s1,s2) –> s1.comparteToIgnoreCase(s2);
而且,如果目標類型中聲明的方法只有一個參數,那么小括號也可以不寫,例如
ActionListenr listenr = event –> event.getWhen();
一個很明顯的問題來了,為什么Lambda不需要指定方法名呢?因為Lambda expression只能用戶Functional interface,而functional interface 只有一個方法。當我們確定一個functional interface來創建Lambda表達式的時候,編譯器可以感知functional interface中的方法的簽名,並且檢查給定的表達式是否匹配。Lambda表達式的語法是上下文相關的,但並非在Java8中才出現,Java 7中添加了diamond operators也有這個概念,通過上下文推斷類型。
void invoke (Runable r) {r.run()}
Futrue invoke (Callable c) {return c.compute()}
Future s = invoke (() –> "done");//這個Lambda Expression顯然是調用了帶有futrue的這個接口
-
方法引用
方法引用被用作引用一個方法而不調用它,Lambda表達式允許我們定義一個匿名的方法,並將它作為Functional Inteface的一個實例。方法引用跟Lambda Expression很想,它們都需要一個目標類型,但是不同的方法引用不提供方法的實現,它們引用一個已經存在的類或者對象的方法。
1、System::getProperty
2、"abc"::length
3、String::length
4、super::toString
5、Arraylist::new
這里引用了一個新的操作符"::"(雙冒號)。目標引用或者說接收者被放在提供者和分隔符的后面,這形成了一個表達式,它能夠引用一個方法。下面通過一個例子來了解這個操作符。
這是一個Employee數組的排序程序
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,
-
構造方法引用
先看看實例
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
這看起來有點神奇吧,這個接口和這個類除了接口中聲明的方法的返回值是MyClass類型的,沒有任何關系。這個例子又激起了我心中的另一個問題:怎樣實例化一個帶參數的構造方法引用?看看下面的程序: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
-
Default Method
Java8中將會引入一個叫做默認方法的概念,早期的Java版本的接口擁有非常的嚴格的接口,接口包含了一些抽象方法的聲明,所有非抽象的實現類必須要提供所有這些抽象方法的實現,甚至是這些方法沒有用或者不合適出現在一些特殊的實現類中。在即將到來的Java 版本中,允許我們在接口中定義方法的默認實現。
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
在這個例子中,ParentInterface 定義了兩個方法,一個是正常的,一個是有默認實現的,子接口只是簡單的反了過來,給第一個方法添加了默認實現,給第二個方法移除了默認實現。
設想一個類繼承了類 C ,實現了接口 I ,而且 C 有一個方法,而且跟I中的一個提供默認方法的方法是重載兼容的。在這種情況下,C中的方法會優先於I中的默認方法,甚至C中的方法是抽象的時候,仍然是優先的。
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
第二個例子是,實現了兩個不同的接口,但是兩個接口中都提供了相同的具有默認實現的方法的聲明。在這種情況下,編譯器將會搞不清楚怎么回事,實現類必須選擇兩個的其中一個實現。這可以通過如下的方式來使用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