开发者学堂课程【Scala 核心编程-基础:特质的再说明】学习笔记,与课程紧密联系,让用户快速学习知识。
课程地址:https://developer.aliyun.com/learning/course/609/detail/8991
特质的再说明
一、特质 trait 的再说明
1、Scala 提供了特质(trait),特质可以同时拥有抽象方法和具体方法,一个类可以实现继承多个特质。
(1)思考:特质有抽象方法,又有具体方法,那么特质在底层具体对应什么?它们之间又是怎么调用的?这涉及到特质底层调用原理。
在 mytrait 目录下新建 Create New Scala Class,Name 为 TraitDemo03,Kind 为 Object。打开编辑:
package com.atguigu.chapter08.mytrait
object TraitDemo03 {
def main(args: Array[String]): Unit = {
println("~~~~")
//创建 sheep
val sheep = new Sheep
sheep.sayHi()
sheep.sayHello()
}
}
trait Trait03 {
//抽象方法
def sayHi()
//实现普通方法
def sayHello(): Unit = {
println("say Hel1o~~" )
}
}
class Sheep extends Trait03 {
override def sayHi(): Unit = {
println("小羊say hi~~")
}
}
先写 trait03,然后写抽象方法,再写实现普通方法。再写一个类继承 trait03。这里,看到 Sheep 下出现红色波浪线,报错,因为抽象方法还没有实现。点击 Sheep,选择 Implement methods 实现方法,在弹出的 Select Members to Implement 窗口选择s ayHi0: Unit 点击 OK。然后输出小羊 say hi~~。然后创建一只小羊对象,对 sheep 进行调用。运行结果如下:
~~~~
小羊 say hi~~
say Hello~~
(2)探讨话题:trait Trait03发生了什么?class Sheep extends Trait03发生了什么?
有普通方法和没有普通方法之间原理有变化。
① 没有普通方法时
先注销普通方法 def sayHello(),看运行结果:
~~~~
小羊say hi~~
打开 Java Decompiler,点击左上角文件图标,在 atguigu 目文件录下的 chapter08文件中 mytrait 文件里,找到 Trait03.class,这时,这里只有 Trait03。打开。在 Java Decompiler - Trait03.class 源码中,看到
public abstract interface Trait03
{
public abstract void sayHi ():
}
这里有一个抽象方法 sayHi (),仅是一个接口。
思考:在 TraitDemo03.scala 中的程序 class Sheep extends Trait03里,底层 Sheep 类是怎么和 Trait03关联起来的?
打开 Sheep.scala 的源码,
public class Sheep
implements Trait03
{
public void sayHi ()
{
Predef..
MODULE$
.print1n("小羊say hi~~");
}
}
看到这里和 Java 的原理一样。即,当 Trait 里只有抽象方法时,与 Java 的机制完全一样。
② 有普通方法时
打开普通方法 def sayHello()。可以发现,Java 中不能把实现的方法占为己用。显然,不可以把实现的方法放在接口。实现方法放到机器里是不可以的。
当一个 Trait 有抽象方法和非抽象方法时。一个 Trait 会在底层对应两个类。包对象、伴生对象、和 trait 都是一样的原理。第一个类 Trait03.class 实际上就是接口。还对应 Trait03$class.class,会生成 Trait03$class 的抽象类。抽象类可以实现方法。这时,trait 就有两个,一个是接口,一个抽象类。
把分开过后,class Sheep extends Trait03会变成什么?
当 trait 有接口和抽象类时,class Sheep extends Trait03在底层会直接实现接口,Employment。实现trait Trait03接口。但是,这里面的方法是直接用 Trait03$class 里的方法。所以,当在 Sheep 这个类中要使用 Treat03实现的方法,就直接通过 Trait03$class 抽象类来调。
编译运行后,重新打开 jd-gui.exe,在 Java Decompiler 窗口点击文件图标,在 mytrait 目录下看到两个 Trait03,一个是 Trait03$class.class,另一个是 Trait03.class。
Trait03.class 是接口。打开,
public abstract interface Tr
a
it03
{
pub1
i
c ab
s
tract vo
i
d
s
ayH
i
()
;
public abstract void say
H
e
llo(
)
;
}
看到 Trait03.class 是接口。有两个方法 sayHi ()、sayHello()。
sayHello()虽然是抽象的,但是它把真正实现的方法放在 Trait03$class 中。
打开 Trait03$class.class
public abstract class Trait03$clas
s
{
public static void sayHe11o(Trait03 $this)
{
Predef..
MODULE$
.print1n("say Hello
~~
");
}
public static void $init$ (Trait03 $thi
s
)
{
}
}
看到 sayHe11o(Trait03 $this)方法。sayHe11o()只是在接口里声明了方法,但真正实现方法还是在 Trait03$class中。
打开 Shepp.class 源码,
public class Sheep
implements
Trait03
{
public void sayHe
llo
()
{
Trait03.clas
s
.sayHe11o(this) ;
}
public void sayHi() { Predef..
MODULE$
.print1n("小羊say hi
~
~"); }
public Sheep()
{
Trait03.clasg.$init$(this) :
}
}
看到只实现 Trait03接口。实现 Trait03接口,就会把 sayHello()、 sayHi()方法都实现。
sayHi() 是 Sheep 自己实现的。sayHello()则是做了语法调用。Sheep 把 Trait03接口里的 sayHello()写过来后,直接调用了 Trait03.class 的反编译拿到 sayHe11o(this) 来调用。
(3)示意图理解
示意图解释说明如下:
图中是一段 trait 特质代码。如果没有普通方法程序段,那就只是一个接口。这里有普通方法,会对应生成两个内容。
一个是生成所谓纯的接口,如下图中源程序段;
另一个是生成抽象类,把实现非抽象类的方法放到抽象类里面。如下图:
一个 trait 对应两个文件,Trait03.class 和 Trait03$class.dass
这个方法是怎么使用的?在底层调用。
小羊 sayhi~~代码段对应生成 Sheep.class 文件。
在 Sheep.class 中实现 Trait03的两个方法,sayHi ()和 sayHello()。
将 sayHello()做包装,在实现的时候,通过名字与 Trait03$class.dass 文件进行关联。
Trait03.class.sayHe11o(this) 直接使用了抽象类中的 public static void sayHe11o(Trait03 $this)写的 sayHello。
自己写的小羊 sayhi~~ 会直接对应到 Sheep.class中。
总之,Trait 就是接口和抽象类的组合体。
2、特质中没有实现的方法就是抽象方法。类通过 extends 继承特质,通过 with 可以继承多个特质。
3、所有的 java 接口都可以当做 Scala 特质使用。
案例:演示一个类怎么继承多个特质的语法
trait Logger {
def log(msg: String)
}
class Console extends Logger with Cloneable with Serializable{
def log(msg: String) {
println(msg)
}
}
Console 类实现第一个特质 Logger,又直接使用 Java 中的 Cloneable 和 Serializable 两个接口。