scala中的trait
特质
是一些字段和行为的集合,可以扩展或混入(mixin)你的类中。
1 trait Car { 2 val brand: String 3 } 4 5 trait Shiny { 6 val shineRefraction: Int 7 }
1 class BMW extends Car { 2 val brand = "BMW" 3 }
通过with
关键字,一个类可以扩展多个特质:
1 class BMW extends Car with Shiny { 2 val brand = "BMW" 3 val shineRefraction = 12 4 }
什么时候应该使用特质而不是抽象类? 如果你想定义一个类似接口的类型,你可能会在特质和抽象类之间难以取舍。这两种形式都可以让你定义一个类型的一些行为,并要求继承者定义一些其他行为。一些经验法则:
- 优先使用特质。一个类扩展多个特质是很方便的,但却只能扩展一个抽象类。
- 如果你需要构造函数参数,使用抽象类。因为抽象类可以定义带参数的构造函数,而特质不行。例如,你不能说
trait t(i: Int) {}
,参数i
是非法的。
你不是问这个问题的第一人。可以查看更全面的答案: stackoverflow: Scala特质 vs 抽象类 , 抽象类和特质的区别, and Scala编程: 用特质,还是不用特质?
在Scala中有一个trait类型,它可以被继承,而且支持多重继承,其实它更像我们熟悉的接口(interface),但它与接口又有不同之处是:
1 //声明一个 trait 2 trait TraitBase{ 3 def add(x:Int,y:Int): Int ={ 4 return x+y 5 } 6 } 7 //TraitTest 继承自 TraitBase 8 class TraitTest extends TraitBase{ 9 //重写父类的方法 10 override def add(x:Int,y:Int): Int ={ 11 return x+y*10 12 } 13 } 14 15 //如果需要调用父类的方法,使用super访问父类 16 class TraitTest extends TraitBase{ 17 override def add(x:Int,y:Int): Int ={ 18 return super.add(x,y)*10 19 } 20 } 21 22 //使用 23 val test = new TraitTest 24 println(test.add(4,5))
对于多重继承,我们使用 with 关键字
1 trait A{ 2 def FA(): Unit ={ 3 println("FA") 4 } 5 } 6 trait B{ 7 def FB(): Unit ={ 8 println("FB") 9 } 10 } 11 class C{ 12 val content=null 13 } 14 //多重继承 15 class D extends C with A with B{ 16 //这里不需要必须实现trait中的方法 17 }
看完了使用,我们看看编译器把trait编译成什么样的java对象了。编译后有两个文件(TraitBase$class.class和TraitBase.class)
先来看看TraitBase.class
1 /**很熟悉吧,interface,看来trait还是跟interface有共性*/ 2 public abstract interface TraitBase 3 { 4 public abstract int add(int paramInt1, int paramInt2); 5 }
再看看TraitBase$class.class
1 /**抽象类 2 它内部都是static的静态方法,但是大家注意 3 每个方法都带有一个参数,TraitBase 对象,通过这个对象,可以访问实际对象中定义的变量和方法 4 */ 5 public abstract class TraitBase$class 6 { 7 public static int add(TraitBase $this, int x, int n) 8 { 9 return x + n; 10 } 11 12 public static void $init$(TraitBase $this) 13 { 14 } 15 }
对于继承自trait的TraitTest,反编译结果如下
1 /**中规中矩的接口继承*/ 2 public class TraitTest implements TraitBase 3 { 4 public int add(int x, int n) 5 { 6 //调用的是TraitBase$class类中的add方法,并传递了自己这个实例对象 7 return TraitBase.class.add(this, x, n); 8 } 9 //初始化 10 public TraitTest() 11 { 12 TraitBase.class.$init$(this); 13 } 14 15 }
属性的访问,假如有下面的Scala代码
1 trait TraitBase{ 2 val content = "this is trait test" 3 } 4 5 class TraitTest extends TraitBase{ 6 def Foo(): Unit ={ 7 println(content) 8 } 9 }
反编译结果
TraitBase.class
1 public abstract interface TraitBase 2 { 3 /**编译器自动增加了两个接口,对content属性赋值和取值*/ 4 5 //写接口,编译器自动命名,就是我们常用的stter 6 public abstract void scala$test$TraitBase$_setter_$content_$eq(String paramString); 7 //读接口,就是我们常用的getter 8 public abstract String content(); 9 }
TraitBase$class.class
1 public abstract class TraitBase$class 2 { 3 //初始化,初始化变量 4 public static void $init$(TraitBase $this) 5 { 6 $this.scala$test$TraitBase$_setter_$content_$eq("this is trait test"); 7 } 8 }
看来trait最终编译为interface和abstract class的两个文件