Java 8 做出了改变。现在我们可以在接口中定义方法,这些方法被称为 defender 方法或默认方法。实现类仍可以提供自己的实现。如果实现类未提供自己的实现的话, defender 方法会被调用。因此, Java 8 中的接口行为更接近于 Scala 中的 trait。但是, Java 8 中的接口与 Scala 中的 trait 仍有不同之处。 Java 8 中的接口只能定义静态字段,而 Scala 中的 trait 则可以定义实例级字段。这意味着 Java 8 中的接口无法管理实例状态。接口实现类必须提供字段以记录状态。这也意味着 defender 方法无法访问接口实现体的状态信息,从而限制了 defender 方法的用途。
Scala和Java一样不允许类从多个超类继承,在Java中类只能扩展 自一个超类,它可以实现多个接口,但接口中只能包含抽象方法,不能包含字段
Scala提供的"特质"类似于java中的接口, 但又有所不同,特质可以同时拥有抽象方法和具体方法,而类可以实现多个特质,同时这也很好的解决了java接口的问题
Scala特质可以如同java接口那样工作
举例:
代码语言:javascript复制trait Logger{
def log(msg:String) // 这是一个抽象方法
}
在scala特质中,不需要将方法声明为abstract ,特质中未被实现的方法默认就是抽象的,同时子类的实现如下:
代码语言:javascript复制class consoleLogger extends Logger{ // 子类要实现某个特质,用extends而非implements
def log(msg:String){ println(msg) } // 重写的时候不需要用override关键字
}
如果我们需要实现多个特质的话, 用with关键字来添加其它的特质
class ConsoleLogger extends Logger with Cloneable with Serializable
Scala类只能有一个超类,但可以有任意数量的特质
特质里面也是可以有具体实现
在scala中,特质中的方法并不需要一定是抽象的,举例来说
代码语言:javascript复制trait consoleLogger{
def log(msg:Stirng){ println(msg) }
}
在上面我们将consoleLogger改为了一个特质,并提供了一个带有实现的方法,该方法时将日志信息输出到控制台中.
具体的使用:
代码语言:javascript复制class SavingAcount extends Account with CosnoleLogger{
def withdraw(amount:Double){
if(amount > balance) log("insufficient funds")
else balance -= amount
}
}
SavingAccount 从ConsoleLogger特质得到了一个具体的log方法实现 (如果是java的话,这样是不行的)
在对象创建时加入特质
在构建单个对象时,也可以添加特质,比如:
代码语言:javascript复制trait Logger{
def log(msg : String){}
}
class SavingAccount extends Account with Logger{
def withdraw(amount:Double){
if(amount > balance) log("balabala")
}
}
在上述中,SavingAccount 中的log是不会输出到控制台的,因为在特质logger中,对于log方法没有任何的逻辑.
但是我们加入混入元素,
代码语言:javascript复制trait ConsoleLogger extends Logger{
override def log(msg:String){ println(msg) }
}
然后可以在构造对象的时候,加入特质:
val acct = new SaveingAccount with ConsoleLogger
然后,当我们在acct对象上调用log方法时,consoleLogger特质的log方法就会被执行
特质的构造顺序
我们可以为类或对象添加多个互相调用的特质,从最后一个开始,这对于需要分阶段加工处理某个值得场景是很有用的
比如:
代码语言:javascript复制trait TimeStampLogger extends Logger {
override def log(msg :String){
super.log(new java.util.Date() " " msg)
}
}
比如:
class SavingAccount extends Account with FileLogger with ShortLogger
那么构造器将按照如下的顺序执行
- 首先调用超类的构造器
- 特质构造器的超类构造器之后,类构造器之前执行
- 特质由左到右被构造
- 每个特质当中,父特质先被构造
- 如果多个特质共有一个父特质,而那个父特质已经被构造,则不会被再次构造
- 所有特质构造完毕,子类被构造
如果上面代码的构造顺序如下:
1:Account(超类)
2:Logger(第一个特质的父特质)
3:FileLogger(第一个特质)
4:ShortLogger(第二个特质) // 此时父特质Logger已经被构造
5:SavingAccount(类)
特质中重写抽象方法
前面我们提到,在类中实现某个特质的方法时,是不需要加override的,但是在特质中,如果要重写父特质的方法时,需要加上override的
比如:
代码语言:javascript复制triait Logger{
def log(msg:String) // 其实这个是一个抽象方法
}
然后另一个时间戳特质来扩展Logger特质
trait TimestampLogger extends Logger{
override def log(msg:String){ // 重写抽象方法
super.log(new Date() " " msg )
}
}
因为在Logger中,log方法时一个抽象方法,那么子特质在扩展父特质的时候,抽象方法要这样来修饰
代码语言:javascript复制abstract override def log(msg:String){
super.log(new Date() "" msg)
}
上面这种做法看上去也是不是很好的,如果我们在TimestampLogger中 用abstract override 修饰了,那么在有类继承了TimestampLogger的时候,就会报出异常.
所以在修饰log的时候,
在特质中字段可以是具体,也可以是抽象的,如果给出了初始值,那么字段就是具体的
代码语言:javascript复制trait TimestampLogger extends Logger{
val time = new Date()
}
混入该特质的类自动获得一个time字段,在特质中的每个具体字段,使用该特质得会获得一个字段与之对应,这些字段不能够被继承,它们只是简单的被加到了子类当中.
比如:
代码语言:javascript复制class Acount extends TimestampLogger{
var balance = 1.1
}
class SavingAccount extends Account{
var interset = 0.0
}
SavingAccount 类按正常的方式继承了这个字段,SavingAccount对象由所有超类的字段,以及任何子类中定义的字段构成.
另外特质中不能有构造器参数,每个特质都有一个无参数的构造器
特质没有构造器参数是特质与类之间的其中一个差别,其它的特质具备类的所有特性,比如具体的和抽象的字段,以及超类
特质扩展类
上面我们看到了特质可以扩展另一个特质,而同时,特质也可以扩展类,这个类将会自动成为所有混入该特质的超类.
比如:
代码语言:javascript复制trait LoggerException extends Exception with Logger{
def log(){ log(getMessage()) }
}
LoggerException 有一个log方法用来记录异常的消息,同时,log方法调用了Exception超类继承下来的getMessage()方法
接下来,在来声明一个来混入该特质得类UnHappyException
代码语言:javascript复制class UnHappyException extends LoggerException{ // 该类扩展自一个特质
override def getMessgae() = "error"
}
在上述代码中,特质的超类Exception自动的成为了类UnHappyException的超类
如果类已经扩展了另一个类,只要那是特质的超类的一个子类即可
比如:
代码语言:javascript复制class UnHappyException extends IOExceptin with LoggerException
上面代码是可以的,因为UnHappyException继承了IOException,而IOException是扩展自Exception
自身类型:
当特质扩展类时,编译器能够确保所有混入该特质的类都认这个类为超类,Scala还有另一套机制可以保证这一点:自身类型(self type)
当特质以如下代码开始定义时:
代码语言:javascript复制this:类型 =>
它便能够被混入指定类型的子类
比如:
代码语言:javascript复制trait LoggerException extends Logger{
this : Exception =>
def log(){ log(getMessage()) }
}
该特质并不扩展自Exception类,而是有一个自身类型Exception,这意味着,它只能被混入Exception的子类
在特质方法中,我们可以调用自身类型的任何方法,比如,log方法中的getMessage()调用时合法的.因为this就是一个exception类型,而下面代码
val h = new Home with LoggerException
上面代码是不合法的,因为Home不是Exception的子类型
同时,特质还可以处理结构类型,这种类型只给出类必须拥有的方法,而不是类的名称,比如:
代码语言:javascript复制trait LoggerException extends Logger{
this : { def getMessage():String } =>
def log() { log(getMessgae()) }
}
这个特质可以被混入任何拥有getMessage方法的类
特质内部的实现
Scala需要将特质翻译为JVM的类和接口, 只有抽象方法的特质被简单的编程了一个Java接口,比如:
代码语言:javascript复制trait Logger{
def log(msg:String)
}
直接被翻译为:
代码语言:javascript复制public interface Logger{
void log(String:msg)
}
如果特质由具体的方法,Scala会帮我们创建出一个伴生类,该伴生类用静态方法存放特质的方法.
比如:
代码语言:javascript复制trait ConsoleLogger extends Logger{
def log(msg:String){ println(msg) }
}
被翻译为:
代码语言:javascript复制public interface ConsoleLogger extends Logger{
void log(String msg)
}
public class ConsoleLogger$class{ // 伴生类
public static void log(Console self,Stirng msg){
println(msg)
}
}
特质中的字段对应到接口中的抽象的getter和setter方法,当某个类实现该特质时,字段被自动加入
比如:
代码语言:javascript复制trait TimeLogger extends Logger{
val time = 1
}
被译为:
代码语言:javascript复制public interface TimeLogger extends Logger{
public abstract int time();
public abstract void weird_profix$time_$sq(int);
}
weird开头的setter方式是需要的,用来初始化该字段,初始化发生在伴生类的一个初始化方法内
代码语言:javascript复制public class TimeLogger$class{
public void $init$(TimeLogger self){
self.weird_prefix$time_$eq(15)
}
}
当特质被混入类的时候,类将会得到一个带有getter和setter的time字段,那个类的构造器会调用初始化方法
如果特质扩展自某个超类,则伴生类并不继承这个超类,该超类会被任何实现该特质的类继承