閉包與內部類
中英文社區中,比較常見的對閉包的定義是
引用了自由變量的一段代碼或函數,被引用的自由變量和函數(一段代碼)共同存在,即使離開了創造它的環境
內部類
按照我的理解,scala/java中雖然並不存在語法級地支持或是定義,對於閉包而言,一些概念和閉包的概念一致。一般理解scala中的一些概念,我會傾向於從Java開始。
Java中的內部類
在java中,內部類有:
- 成員內部類
- 靜態內部類
- 局部內部類
- 匿名內部類
成員內部類
class Outer1{
private int a1;
private static int s1;
void f1() {
}
class Inner1{
int a2;
void f2(){
//access outer's field,function
int b=a1; //可以直接引用或是Outer1.this.a1;
Outer1.this.f1();
int c=Outer1.s1;
}
}
}
拿以上代碼舉例,成員內部類可以訪問到外部類中的所有字段、方法,包括私有。
內部類的實現均是通過編譯器構造字節碼實現的。上述類經過編譯產生的類大概如下
class Outer1{
private int a1;
private static int s1;
void f1() {
}
static int access$000(Outer1 outer){
return outer.a1;
}
int access$100(){
return a1;
}
}
class Inner1{
int a1;
final Outer1 this$0;
Inner1(Outer1 outer){
this.this$0=outer;
}
void f2(){
int b=Outer1.access$000(this$0);
this$0.f1();
int c=Outer1.access$100();
}
}
可以看到,在外部類中添加了相應的方法,給內部類調用來達到訪問外部類的private成員或是方法的目的。在內部類中,會添加一個this$0的對外部對象的引用。
靜態內部類
靜態內部類並不具有內部類和外部類之間的依賴關系,靜態內部類和在一個文件中寫兩個類沒啥卻別,一般用privat static內部類來隱藏實現。
class SomeInterface{
void function();
}
class SomeInterfaceFactory{
private static class SomeInterfaceImpl implements SomeInterface{
}
static newInstance(){
return new SomeInterfaceImpl()
}
}
局部內部類
內部類可以寫在函數中,除了外部類的變量和方法外,內部類還可以訪問到函數中的局部變量.
class Outer3{
int a1;
void function(){
int used=0;
int notUsed=-1;
class Inner3{
void f2(){
int t1=used;
int t2=a1;
}
}
}
}
上述代碼構造出的類如下:
class Outer3{
int a1;
void function(){
int used=0;
int notUsed=-1;
}
}
class Inner3{
final int val$used; //從這里看出不能對外部變量賦值
final Outer3 this$0;
Inner3(Outer3 outer,int a){
this.this$0=outer;
this.val$used=a
}
void f2(){
int t1=val$used;
int t2=this$0.a1;
}
}
從上面可以看出,局部內部類除了像成員內部類那樣添加了外部對象的引用,還添加了對引用到的局部變量的引用,並且這些屬性會通過構造函數進行初始化。
此外,在Inner3的f2中,不能執行類似used=10
的操作,是因為這些引用是final的,當然,對於對象類型,對象內部還是可以修改的,scala中的局部內部類可以更改執行類似used=10
的操作,就是這個原理。
匿名內部類
匿名內部類和局部內部類沒有太大區別,只是生成的類的類名不含用戶的標識。
class Outer4{
int a1;
void function(){
int used=0;
int notUsed=-1;
Runnable t=new Runnable() {
@Override
public void run() {
int t1=used;
int t2=a1;
}
};
}
}
上述代碼構造出的類如下:
class Outer4{
int a1;
void function(){
int used=0;
int notUsed=-1;
Runnable t=new Outer4$1();
}
}
class Outer4$1 implements java.lang.Runnable{
final int val$used;
final Outer4 this$0;
public void run(){
//...
}
}
總結
除靜態內部類外,java編譯器會做以下事情:
- 在內部類添加字段,類型是外部類,即內部對象持有外部類對象的引用。
- 當內部類訪問到外部類的private的屬性時,編譯器會在外部類中添加相應的getter/setter,內部類用它們對private屬性訪問。
- 對局部內部類和匿名內部類而言,使用到的局部變量會轉換為不可變的成員變量。
Scala中的內部類
scala中,內部類的實現與java基本一致,其中函數實現類似實現AbstractFunction接口的的匿名內部類。
成員內部類
成員內部類與java基本一致,對private屬性的處理稍微有些不同,在scala中,其實所有成員變量均是private的,編譯器自動為其添加相應的getter/setter,因此如果內部類訪問了外部類的私有屬性,則編譯器只需調整相應的getter的訪問權限。
局部內部類
局部內部類也與java基本一致,只是局部內部類中可以修改外部的局部變量。其實現原理也很簡單,在局部變量外再包一層,如int改為IntRef,比如
final int[] data=new int[1]
data[0]=1 //set
int tmp=data[0] //get
匿名內部類
scala中,除了像java那樣定義匿名內部類外,函數實現也是匿名內部類實現。如函數
class Outer4{
private val a1=-1
val f1: Int => Int = (a: Int) => a + 1
val f2: Int => Int = (a: Int) => a + a1
}
會生成類似如下匿名內部類
//對應f1
public final class Outer4$$anonfun$3 extends scala.runtime.AbstractFunction1$mcII$sp implements scala.Serializable {
public static final long serialVersionUID=0L;
public final int apply(int){
//計算邏輯
}
public Outer4$$anonfun$3(Outer4 outer){
//...
}
}
//對應f1
public final class Outer4$$anonfun$3 extends scala.runtime.AbstractFunction1$mcII$sp implements scala.Serializable {
public static final long serialVersionUID=0L;
private final Outer4 $outer;
public final int apply(int){
//計算邏輯
}
public Outer4$$anonfun$3(Outer4 outer){
//...
}
}
從上面的例子來看,最大的不同是外部對象引用的不同,在某些情況下一個匿名內部類可能僅僅是'匿名類',通過測試驗證,發現僅在以下情況下內部類會添加對外部類的引用:
- 內部類訪問了外部類的屬性
- 內部類訪問了外部類的方法
還有一個問題,如果在函數A內部定義了函數B,函數B訪問了函數A的局部變量,則函數B的匿名內部類會添加函數A的匿名內部類的引用嗎?
class Outer4{
val a1=-1
val fa=()=>{
val local=a1
val fb=()=>{
println(local)
}
val fc=()=>{
println(a1)
}
}
}
答案是否定的。
在以上代碼中,fb不會持有外部對象即fa的引用,fb對local引用被當作局部變量處理,
這和上面java例子中Inner3對used變量的訪問一致。
fc會持有外部對象即fa的引用,這是因為fc訪問了a1,相當於fa.outer.a1
.
總結
- scala中內部類的機制與java基本一致。
- scala借助匿名內部類實現函數對象,匿名內部類只有訪問外部對象的方法或字段時才會持有外部對象的引用。
- 2這一點想必是出於對函數或是閉包的支持,如果一個函數對象沒有引用上下文的變量或函數,就持上下文的引用,也就無法形成閉包。因此從這個角度說,java匿名內部類或其他內部類,不是真正意義上的閉包。
版本說明
輪子 | 版本 |
---|---|
java | 1.8 |
scala | 2.11.12 |
spark | 2.2.0 |
參考文檔
閉包
https://github.com/ColZer/DigAndBuried/blob/master/spark/function-closure-cleaner.md