一、爲什麼需要閉包
- 在《Think in java》中有這樣一句話:使用內部類最吸引人的原因是:每個內部類都能獨立地繼承一個(接口的)實現,所以無論外圍類是否已經繼承了某個(接口的)實現,對於內部類都沒有影響。
- 內部類最大的優點就在於它能夠非常好的解決多重繼承的問題
- 閉包的價值在於可以作爲函數對象或者匿名函數,持有上下文數據,作爲第一級對象進行傳遞和保存。
- 閉包廣泛用於回調函數、函數式編程中。
特性:
1、內部類可以用多個實例,每個實例都有自己的狀態信息,並且與其他外圍對象的信息相互獨立。
2、在單個外圍類中,可以讓多個內部類以不同的方式實現同一個接口,或者繼承同一個類。
3、創建內部類對象的時刻並不依賴於外圍類對象的創建。
4、內部類並沒有令人迷惑的“is-a”關係,他就是一個獨立的實體。
5、內部類提供了更好的封裝,除了該外圍類,其他類都不能訪問。
二、JAVA中的閉包
在JAVA中,閉包是通過“接口+內部類”實現,JAVA的內部類也可以有匿名內部類。
1、成員內部類
在JAVA中,內部類可以訪問到外圍類的變量、方法或者其它內部類等所有成員,即使它被定義成private了,但是外部類不能訪問內部類中的變量。這樣通過內部類就可以提供一種代碼隱藏和代碼組織的機制,並且這些被組織的代碼段還可以自由地訪 問到包含該內部類的外圍上下文環境。
public class DemoClass{
private int length =0;
private class InnerClass implements ILog
{
@Override
public void write(String message) {
System.out.println("DemoClass.InnerClass:" + length);
}
}
public ILog logger() {
return new InnerClass();
}
public static void main(String[] args){
DemoClass demoClass = new DemoClass();
demoClass.logger().Write("abc");
// 調用方式一
DemoClass dc = new DemoClass();
InnerClass ic = dc.logger();
ic.Write("abcde");
// 調用方式二
DemoClass.InnerClass innerClass = demoClass.new InnerClass();
innerClass.write("使用.new也可以實例化成員內部類");
}
}
public interface ILog {
void writer(String message);
}
從上可見,InnerClass是定義在DemoClass內部的一個內部類,而且InnerClass還可以是Private。
如何通過this顯式引用外圍類的變量? {外圍類名}.this.{變量名稱}
DemoClass.this.length = message.length();
2、局部內部類
局部內部類是指在方法的作用域內定義的的內部類。
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();
}
}
public interface ILog {
void writer(String message);
}
因爲InnerClass類是定義在logger()方法體之內,所以InnerClass類在方法的外圍是不可見的。
3、匿名內部類
顧名思義,匿名內部類就是匿名、沒有名字的內部類,通過匿名內部類可以更加簡潔的創建一個內部類。
public class DemoClass3 {
private int length =0;
// ILog是另外定義的一個接口
public ILog logger() {
return new ILog() {
@Override
public void write(String message) {
length = message.length();
System.out.println("DemoClass3.AnonymousClass:" + length);
}
};
}
}
public interface ILog {
void writer(String message);
}
由此可見,要創建一個匿名內部類,可以new關鍵字來創建。
-
格式:new 接口名稱(){}
-
格式:new 接口名稱(args…){}
4、靜態內部類
- 非靜態內部類中不允許定義靜態成員
- 外部類的靜態成員不可以直接使用非靜態內部類
- 靜態內部類,不能訪問外部類的實例成員,只能訪問外部類的類成員
public class EmpTest {
private Integer id;
private Integer empLevel;
private String mapingOrderLevel;
//外部類私有的構造方法
private EmpTest(Builder builder) {
setId(builder.id);
setEmpLevel(builder.empLevel);
setMapingOrderLevel(builder.mapingOrderLevel);
}
//對外提供初始化EmpTest類的唯一接口,通過這個方法,獲得內部類的實例
public static Builder newBuilder() {
return new Builder();
}
//靜態內部類:Builder
public static final class Builder {
private Integer id;
private Integer empLevel;
private String mapingOrderLevel;
public Builder() {
}
public Builder id(Integer val) {
id = val;
return this;
}
public Builder empLevel(Integer val) {
empLevel = val;
return this;
}
public Builder mapingOrderLevel(String val) {
mapingOrderLevel = val;
return this;
}
//通過內部類的build方法,實例化外部類,並給其實例各個字段賦值
public EmpTest build() {
return new EmpTest(this);
}
}
public Integer getId() {
return id;
}
// get,set方法省略。。。。。
}
調用:
EmpTest.newBuilder().empLevel(getRandom(5)).empNo("Emp_"+i).id(i).mapingOrderLevel(getRandomChar()).orderNumLimit(getRandom(20)).build()
講解:
- EmpTest中 定義了一個Builder的靜態內部類,還定義了一個newBuilder的公共靜態方法,上邊講過加了static關鍵字,那麼就是類相關,所以我不需要實例化外部類,直接使用外部類就可以調用newBuilder的方法
- Builder的實例在調用各個內部類中的方法,給Builder的字段賦值
- 最後調用build()方法,實例化EmpTest,它的構造方法需要builder的對象,所以將builder的對象傳入,通過builder的字段值給EmpTest的對象賦值
5、final關鍵字
閉包所綁定的本地變量必須使用final修飾符,以表示爲一個恆定不變的數據,創建後不能被更改。
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");
}
}
public interface ILog {
void writer(String message);
}
從例子中可以看到,logger方法接受了一個level參數,以表示要寫的日誌等級,這個level參數如果直接賦給內部類中使用,會導致編譯時錯誤,提示level參數必須爲final,這種機制防止了在閉包共享中變量取值錯誤的問題。解決方法可以像例子一樣在方法體內定義一下新的局部變量,標記爲final,然後把參數level賦值給它:
final int logLevel = level ;
//或者直接參數中添加一個final修飾符:
public ILog logger(final int level {
6、實例初始化
匿名類的實例初始化相當於構造器的作用,但不能重載。
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);
}
};
}
public interface ILog {
void writer(String message);
}
匿名內部類的實例初始化工作可以通過符號 {…} 來標記,可以在匿名內部類實例化時進行一些初始化的工作,但是因爲匿名內部類沒有名稱,所以不能進行重載,如果必須進行重載,只能定義成命名的內部類。
三、閉包的問題。
- 讓某些對象的生命週期加長。
- 讓自由變量的生命週期變長,延長至回調函數執行完畢。
- 閉包共享。
- final關鍵字
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 ,先向列表中創建 i 個匿名內部類new Action(),然後通過for遍歷讀出。
因爲 i 變量在各個匿名內部類中使用,這裏產生了閉包共享,java編譯器會強制要求傳入匿名內部類中的變量添加final
關鍵字,所以這裏final int copy = i;需要做一個內存拷貝,否則編譯不過。(在c#中沒有強制要求會導致列有被遍歷時
始終會取 i 最大值,這是因爲延遲執行引起的)