天天看点

trait底层原理分析trait中的变量是抽象变量trait中的变量是实体变量, 子类改写trait中有可执行语句调用变量, 子类会改写变量trait中有可执行语句调用变量, 子类会改写变量

本文阐述了Scala语言中, trait的底层实现原理.

trait中的变量是抽象变量

************--------------------------trait(1):抽象变量---------------------------**************

trait Property{
  var name:String
  val value:String
}


class Test1 extends Property{
  override var name = "Test1"  //override可加可不加;
  override val value = "Test2" //override可加可不加;
}
           

Java里的实现:

public abstract interface Property
{
  public abstract String name();


  public abstract void name_$eq(String paramString);


  public abstract String value();
}


public class Test1
  implements Property
{
  private String name = "Test1";
  private final String value = "Test2";

  public String name()
  {
    return this.name; } 
  public void name_$eq(String x$1) { this.name = x$1; } 
  public String value() { return this.value; }

}
           

结论:

1 完全和java相等的代码; Property是一个interface, name和value都在子类Test1里面定义, 且name有存取方法,value只有取方法; 

2 也可看出, trait里面也可以定义var变量;

trait中的变量是实体变量, 子类改写

************--------------------------trait(2): var的override---------------------------**************

trait Property{
  var name:String = "Property"
  val value:String = "OOO"
}

class Test1 extends Property{
  override var name = "Test1"  //会报错:overriding variable name in trait Property of type String; variable name cannot override a mutable variable;
  override val value = "Test2"
  //val value = "Test2"  //不加override会报错;
}
           

看来trait中非抽象的var不能在子类里重新赋值;

trait中有可执行语句调用变量, 子类会改写变量

************--------------------------trait(3): trait里面有可执行语句---------------------------**************

trait Property{
  val name:String = "name";
  val value:String = "value";
  println("123")
}


class Test1 extends Property{
  override val name = "Test1"
  override val value = "Test2"
}
           

转化为Java的语句:

public abstract class Property$class
{
  public static void $init$(Property $this)
  {
    Predef..MODULE$.println("123");
    $this.Property$_setter_$name_$eq("Property");
    $this.Property$_setter_$value_$eq("OOO");
  }
}


public abstract interface Property
{
  public abstract void Property$_setter_$name_$eq(String paramString);


  public abstract void Property$_setter_$value_$eq(String paramString);


  public abstract String name();


  public abstract String value();
}


public class Test1
  implements Property
{
  private final String name;
  private final String value;


  public void Property$_setter_$name_$eq(String x$1)
  {
  }


  public void Property$_setter_$value_$eq(String x$1)
  {
  }


  public String name()
  {
    return this.name; } 
  public String value() { return this.value; }




  public Test1()
  {
    Property.class.$init$(this);
    this.name = "Test1";
    this.value = "Test2";
  }
}
           

这时的改动就大了: 

  1 由于trait里面多了可执行语句(无论是println还是变量赋值),而非单纯的抽象变量声明, 所以多出了Property$class;

2 Test1()的构造函数里面会先调用Property$class的init(), 等于会先执行trait里的语句!

3 由于trait里面有对变量定义的语句,所以trait等于多了对2个变量的存取方法,且这4个方法需要子类实现;

4 由于Test1对name和value进行了override, 所以等于Test1重写了name和value的存取方法;

5 结论是: 如果在trait里面想对name和value进行读取,是读不到的,因为子类Test1用空方法重写了变量, 而且在调用init()方法后,才会为2个变量赋值;

Test1() -> trait()的执行语句 -> Test1对变量的赋值语句;

结论:

1 trait里面是否有可执行语句, 决定了: 是否会在子类Test1()构造方法里调用trait的可执行语句;

2 trait里面是否对变量赋值(即是否为抽象类型), 决定了: 变量是否有"写访问方法";

3 子类Test1里面是否有override val name = "Test1"重写语句, 决定了: 是否会重写trait里面的"写访问方法"; 

4 当trait里面是抽象类型时, 子类里的override修饰符可以不写;

5 trait里面如果有可执行语句,那么会在子类构造器的其他语句执行前执行, 这样, trait里定义的变量, 在执行trait的语句时就不会正确赋值;

trait中有可执行语句调用变量, 子类会改写变量

************--------------------------trait(4): 使用with改善上面的问题---------------------------**************

class Test1 extends {
  val name = "Test1xxxx"
  val value = "Test2"
} with Property


用jad工具可看到:
	jad -p /Users/umeng/workspace_scala/20160210/out/production/20160210/Test1.class


public class Test1
    implements Property
{


    public String name()
    {
        return name;
    }


    public String value()
    {
        return value;
    }


    public Test1()
    {
        String name = "Test1xxxx";
        this.name = name;
        String value = "Test2";
        this.value = value;
        super();
        Property.class.$init$(this);
    }


    private final String name;
    private final String value;
}
           

可看到,会先设置name和value,然后才会调用父类构造方法,然后才会调用init(); 这样在调用Property的执行语句时, name和value就已经设置好了;

见<<scala编程>>P270.