本文阐述了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.