探索Scala(2)-- Traits

本文記錄我對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$方法



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章