在JAVA中,閉包是通過“接口+内部類”實作,JAVA的内部類也可以有匿名内部類。
1、内部類。
顧名思義,内部類就是将一個類定義在另一個類的内部。在JAVA中,内部類可以通路到外圍類的變量、方法或者其它内部類等所有成員,即使它被定義成private了,但是外部類不能通路内部類中的變量。這樣通過内部類就可以提供一種代碼隐藏和代碼組織的機制,并且這些被組織的代碼段還可以自由的通路到包含該内部類的外圍上下文環境。
這裡提供了一個例子展示這種機制:
- /JavaClosure/src/innerclass/DemoClass1.java
- public class DemoClass1 {
- private int length =0;
- //private|public
- private class InnerClass implements ILog
- {
- @Override
- public void Write(String message) {
- //DemoClass1.this.length = message.length();
- length = message.length();
- System.out.println("DemoClass1.InnerClass:" + length);
- }
- }
- public ILog logger() {
- return new InnerClass();
- }
- public static void main(String[] args){
- DemoClass1 demoClass1 = new DemoClass1();
- demoClass1.logger().Write("abc");
- //.new
- DemoClass1 dc1 = new DemoClass1();
- InnerClass ic = dc1.new InnerClass();
- ic.Write("abcde");
- }
}
該例子的主要功能是實作一個寫日志的ILog接口,但是該接口的類被定義在DemoClass1這個外圍類中了,而且這個InnerClass内部類還可以通路其外圍類中的私有變量length。
1.1、.new
從上面的例子可見,InnerClass是定義在DemoClass1内部的一個内部類,而且InnerClass還可以是Private。
如何建立這個InnerClass的執行個體? 可以通過外圍類的執行個體進行建立,如:
- DemoClass1 dc1 = new DemoClass1();
- InnerClass ic = dc1.new InnerClass();
- ic.Write("abcde");
1.2、.this
如何通過this顯式引用外圍類的變量?通過此格式進行引用:{外圍類名}.this.{變量名稱}。如:
DemoClass1.this.length = message.length();
2、局部内部類。
局部内部類是指在方法的作用域内定義的的内部類。
- /JavaClosure/src/innerclass/DemoClass2.java
- public class DemoClass2 {
- private int length =0;
- public ILog logger() {
- //在方法體的作用域中定義此局部内部類
- class InnerClass implements ILog
- {
- @Override
- public void Write(String message) {
- length = message.length();
- System.out.println("DemoClass2.InnerClass:" + length);
- }
- }
- return new InnerClass();
- }
- }
因為InnerClass類是定義在logger()方法體之内,是以InnerClass類在方法的外圍是不可見的。
3、匿名内部類。
顧名思義,匿名内部類就是匿名、沒有名字的内部類,通過匿名内部類可以更加簡潔的建立一個内部類。
- /JavaClosure/src/innerclass/DemoClass3.java
- public class DemoClass3 {
- private int length =0;
- public ILog logger() {
- return new ILog() {
- @Override
- public void Write(String message) {
- length = message.length();
- System.out.println("DemoClass3.AnonymousClass:" + length);
- }
- };
- }
- }
由此可見,要建立一個匿名内部類,可以new關鍵字來建立
格式:new 接口名稱(){}
格式:new 接口名稱(args...){}
4、final關鍵字。
閉包所綁定的本地變量必須使用final修飾符,以表示為一個恒定不變的資料,建立後不能被更改。
- /JavaClosure/src/innerclass/DemoClass4.java
- public class DemoClass4 {
- private int length =0;
- public ILog logger(int level) {//final int level
- //final
- final int logLevel = level+1;
- switch(level)
- {
- case 1:
- return new ILog() {
- @Override
- public void Write(String message) {
- length = message.length();
- System.out.println("DemoClass4.AnonymousClass:InfoLog "
- + length);
- System.out.println(logLevel);
- }
- };
- default:
- return new ILog() {
- @Override
- public void Write(String message) {
- length = message.length();
- System.out.println("DemoClass4.AnonymousClass:ErrorLog "
- + length);
- System.out.println(logLevel);
- }
- };
- }
- }
- public static void main(String[] args){
- DemoClass4 demoClass4 = new DemoClass4();
- demoClass4.logger(1).Write("abcefghi");
- }
- }
從例子中可以看到,logger方法接受了一個level參數,以表示要寫的日志等級,這個level參數如果直接賦給内部類中使用,會導緻編譯時錯誤,提示level參數必須為final,這種機制防止了在閉包共享中變量取值錯誤的問題。解決方法可以像例子一樣在方法體内定義一下新的局部變量,标記為final,然後把參數level指派給它:
final int logLevel = level ;
或者直接參數中添加一個final修飾符:
public ILog logger(final int level {
5、執行個體初始化。
匿名類的執行個體初始化相當于構造器的作用,但不能重載。
- /JavaClosure/src/innerclass/DemoClass5.java
- public ILog logger(final int level) throws Exception {
- return new ILog() {
- {
- //執行個體初始化,不能重載
- if(level !=1)
- throw new Exception("日志等級不正确!");
- }
- @Override
- public void Write(String message) {
- length = message.length();
- System.out.println("DemoClass5.AnonymousClass:" + length);
- }
- };
- }
匿名内部類的執行個體初始化工作可以通過符号 {...} 來标記,可以在匿名内部類執行個體化時進行一些初始化的工作,但是因為匿名内部類沒有名稱,是以不能進行重載,如果必須進行重載,隻能定義成命名的内部類。
四、為什麼需要閉包。
閉包的價值在于可以作為函數對象或者匿名函數,持有上下文資料,作為第一級對象進行傳遞和儲存。閉包廣泛用于回調函數、函數式程式設計中。
原生java沒有提供Lambda表達式,不過可以使用嘗試使用Scala的Lambda:
例子1:這個是閉包不?
- scala> var add = (x: Int) => x +1
- scala> add(10)
例子2:
- scala> var more = 1
- scala> var addMore = (x: Int) => x + more
- scala> addMore(10)
五、閉包的問題。
1、讓某些對象的生命周期加長。
讓自由變量的生命周期變長,延長至回調函數執行完畢。
2、閉包共享。
inal關鍵字
- /JavaClosure/src/innerclass/ShareClosure.java
- interface Action
- {
- void Run();
- }
- public class ShareClosure {
- List<Action> list = new ArrayList<Action>();
- public void Input()
- {
- for(int i=0;i<10;i++)
- {
- final int copy = i;
- list.add(new Action() {
- @Override
- public void Run() {
- System.out.println(copy);
- }
- });
- }
- }
- public void Output()
- {
- for(Action a : list){a.Run();}
- }
- public static void main(String[] args) {
- ShareClosure sc = new ShareClosure();
- sc.Input();
- sc.Output();
- }
- }
這個例子建立一個接口清單List<Action> ,先向清單中建立 i 個匿名内部類new Action(),然後通過for周遊讀出。
因為 i 變量在各個匿名内部類中使用,這裡産生了閉包共享,java編譯器會強制要求傳入匿名内部類中的變量添加final
關鍵字,是以這裡final int copy = i;需要做一個記憶體拷貝,否則編譯不過。(在c#中沒有強制要求會導緻列有被周遊時
始終會取 i 最大值,這是因為延遲執行引起的)