探索Scala(2)-- Traits

zxh0發表於2014-10-22

本文記錄我對Scala語言Traits的一些理解。

trait >= interface

Scala語言沒有介面(Interface)的概念,取而代之的,是功能更加強大的Trait。因此,interface在Scala語言裡並不是關鍵字,我們可以自由的使用它,如下面這段程式碼所示:


但是要注意,上面的程式碼雖然是合法的Scala程式碼,能編譯出ScalaObject.class。但是如果想在Java里正常使用這個class的話,就會遇到問題。

沒有具體方法的Trait會被編譯成介面

如果一個Trait沒有定義任何有具體實現的方法,那麼它和介面是等價的。換句話說,如果一個Trait的所有方法(如果有的話)全都是抽象的,那麼Scala會把它編譯成Java介面。比如下面這個沒有任何方法的TraitA

會被編譯成TraitA.class,分析class檔案可以知道,它其實等價於下面的Marker介面:

public interface TraitA {

}
再比如下面這個有兩個抽象方法的TraitB

會被編譯為:

public interface TraitB {
    public int m1();
    public int m2(int arg);
}

有具體方法的Trait會被編譯成兩個class檔案

以下面這個TraitC為例:


編譯之後得到兩個class檔案:TraitC.classTraitC$class.class。分析class檔案可以知道,TraitC.class實際上是一個介面,如下所示:

public interface TraitC {
    public int m3(int arg);
}
方法實現在TraitC$class.class裡,如下所示:

public abstract class TraitC$class {
    public static int m3(TraitC t, int arg) {
        return 1;
    }
    public static void $init$() {
        //return;
    }
}
由此可知:

  1. Trait會被編譯為等價的介面
  2. 如果Trait有具體方法,則這些方法會被複制到相應的$class類裡,並且有下面兩處變動:
    1. 方法變為static
    2. Trait例項被插入到引數列表的最開始

欄位(Fields)

如果Trait定義了欄位呢?比如下面這個TraitD

編譯之後,仍然會得到兩個class檔案,如下所示:

public interface TraitD {
    public int f1();
    public void f1_$eq(int i);
}
public abstract class TraitD$class {
    public static void $init$(TraitD t) {
        t.f1_$eq(1);
    }
}
由此可知:

  1. Trait仍然被編譯成了等價的介面,但var欄位被替換成了一對兒getter/setter方法。需要注意的是,這對getter/setter方法並沒有按照JavaBean風格來命名
  2. 相應的$class類裡只有一個$init$方法

Mix in Traits

下面通過一個類來觀察一下mix in上面提到的Traits之後,會發生什麼:


下面是反編譯之後的MyClass(Java)程式碼:

public class MixedIn implements TraitA, TraitB, TraitC, TraitD {

    private int f1;
    
    public MixedIn() {
        TraitC$class.$init$(this);
        TraitD$class.$init$(this);
    }

    public int f1() {
        return this.f1;
    }
    public void f1_$eq(int val) {
        this.f1 = val;
    }

    public int m1() {
        return 0;
    }
    public int m2(int arg) {
        return arg;
    }

    public int m3(int arg) {
        return TraitC$class.m3(this, arg);
    }

}

分析如下:

  • TraitA沒有定義任何方法,所以只是繼承了介面
  • TraitB有兩個抽象方法(m1、m2),所以我們必須自己實現這兩個方法
  • TraitC有一個具體方法(m3),被Scala編譯器實現
  • TraitD定義了一個欄位,也被編譯器實現
  • 編譯器還生成了一個建構函式,呼叫了TraitC和TraitD的$init$方法



相關文章