scala特质

        在这里,你将学习如何使用特质。一个类扩展自一个或多个特质,以便使用这些特质提供的服务。特质可能会要求使用它的类支持某个特定的特性。不过,和Java接口不同,Scala特质可以给出这些特性的缺省实现。

1. Java 接口和 Scala 特质

1.1 Java 接口

在学习Scala特质之前,我们先来复习一下Java接口。

Java接口是一系列方法的声明,是一些方法特征的集合,一个接口只有方法的特征没有方法的实现,因此这些方法可以在不同的地方被不同的类实现,而这些实现可以具有不同的行为(功能)。

Java接口本身没有任何实现,因为Java接口不涉及表象,而只描述public行为,所以Java接口比Java抽象类更抽象化。

Java接口的方法只能是抽象的和公开的,Java接口不能有构造器,Java接口可以有public、静态的和final属性。

java8开始接口里可以有静态方式,用static修饰,但是接口里的静态方法的修饰符只能是public,且默认是publicjava8里,除了可以在接口里写静态方法,还可以写非静态方法,但是必须用default修饰,且只能是public,默认也是public。默认方法可以被继承。但是要注意,如果继承了两个接口里面的默认方法一样的话,那么必须重写。接口可以被实现,但是无法被实例化。

1.2 Scala特质

Scala Trait(特质) 相当于 Java 的接口,实际上它比接口还功能强大。与接口不同的是,它还可以定义属性和方法的实现。

Trait定义的方式与类类似,但它使用的关键字是 trait

Eg

trait A {

val num: Int = 1

}

2. Scala类没有多继承

问题:为什么Scala不支持多重继承呢?

接下来我们看一个例题:

class A{

val id: Int = 01

}

class B {

val id: Int = 02

}

假设可以有:

class C extends A B {

 ...

}

问题:要求返回id时,该返回哪一个呢?

这就引出了菱形继承问题:


 


对于 class A 中的字段, class D B C 都得到了一份,这两个字段怎么得到和被构造呢?这样的情况显然是不合理的。

如果只是把毫不相关的类组装在一起,多继承不会出现问题,但如果这些类具备某些共同的字段或方法,则多继承就会出现问题,即多重继承会产生菱形继承问题。

那么,如何解决这种问题呢?

Java中取而代之的是接口,Scala中则是特质。

一般情况下Scala的类只能够继承一个超类,但是如果是Trait的话就可以继承多个,从结果来看就是实现了多重继承。

 

Scala 中类只能继承一个超类(Java中称为父类),可以扩展任意数量的特质,与Java接口相比,Scala 的特质可以有具体方法和抽象方法; Java 的抽象基类中也有具体方法和抽象方法,但Java的接口不能有具体方法。

 

3. 当做接口使用的特质

 首先,让我们从熟悉的内容开始。Scala特质可以完全像Java接口一样工作。

例子:

trait Logger {

    //abstractmethod, but no abstract declare required

    def log(msg: String)       //没有实现,这是个抽象方法

}

注意:你不需要将抽象方法声明为 abstract特质中未被实现的方法默认就是抽象方法。

子类可以给出实现:

class ConsoleLogger extends Logger {

//在重写特质的抽象方法时不需要给出override关键字

    def log(msg: String){

        println(msg)

    }

}

4. 带有具体实现的特质

trait Logger {

def log(msg:String)  // 抽象方法

def printAny(k: Any){ // 具体方法

println("具体实现")

}

让特质混有具体行为有一个弊端. 当特质改变时,所有混入该特质的类都必须重新编译。

5.   类可以通过 extends 关键字继承特质,如果需要的特质不止一个,通过 with 关键字添加额外特质

例: Class A extends B with C with D {…}

6. 带有特质的对象

Scala可以在创建对象时添加特质,这是Java接口所不具备的特性。

特质可以将对象原本没有的方法与字段加入对象中,如果特质和对象改写了同一超类的方法,则排在右边的先被执行.

// Feline 猫科动物

abstract class Feline {

def say()

}

trait Tiger extends Feline {

// 在特质中重写抽象方法,需要在方法前添加 abstract override 2个关键字

abstract override def say() = println("")

def king() = println("I'm king of here")

}

class Cat extends Feline {

override def say() = println("")

}

object Test extends App {//APP

val feline = new Cat with Tiger

feline.say  // Cat Tiger 都与 say 方法,调用时从右往左调用, Tiger 在叫

feline.king // 可以看到即使没有 cat 中没有 king 方法, Tiger 特质也能将自己的方法混入 Cat

}

/*output    I'm king of here*/

7. 特质的叠加

就像Java Class可以实现多个接口一样,Scala Class也可以叠加多个特质,一般来说,特质从最后一个开始被处理。这对于需要分阶段加工处理某个值的场景很有用.

假设有如下的Logger trait定义,分别有3trait扩展自Logger

trait Logger {   

    def log(msg: String){ }

}

//给日志消息添加时间戳

trait TimestampLogger extends Logger {   

    overridedef log(msg: String) { super.log(new Date() + “ “ + msg)}

}

Trait ShortLogger extends Logger {   

    val maxLength =15

overridedeflog(msg: String){

    super.log(if(msg.length<= maxLength) msg else msg.substring(0,

    maxLength-3)+ “…” )

}

}

 

上述log方法都将修改过的msg传递给super.log.

实际上,super.log调用的是特质层级中的下一个特质,具体是哪一个,取决于特质添加的顺序。一般来说,特质从最后一个开始被处理。

8. 特质中的字段

8.1 具体字段

特质中的字段可以是具体的也可以是抽象的. 如果给出了初始值那么字段就是具体的.

trait  Ability {

val run ="running" // 具体字段

}

8.2 初始化特质抽象字段

特质中未被初始化的字段在具体的子类中必须被重写。

trait  Ability {

val swim: String         // 抽象字段

def ability(msg:String) = println(msg + swim)  // 方法用了swim字段

}

class Cat extends Ability {

val swim ="swimming"

 }

这种提供特质参数的方式在临时构造某种对象很有利,很灵活,按需定制.

 

9. 特质的构造顺序

和类一样,特质也可以有构造器,由字段的初始化和其他特质体中的语句构成。

trait FileLogger extends Logger

      val out =new PrintWriter(“app.log”)//这是特质构造器的一部分

      out.println(“#”+newDate().Tostring) //这也是特质构造器的一部分

deflog(msg: String){ out.println(msg);out.flush() }

 

这些语句在任何混入该特质的对象在构造时都会被执行。

构造器以如下顺序执行:

1.   调用超类的构造器;

2.   特质构造器在超类构造器之后、类构造器之前执行;

3.   特质由左到右被构造;

4.   每个特质当中,父特质先被构造;

5.   如果多个特质共有一个父特质,父特质不会被重复构造

6.   所有特质被构造完毕,子类被构造。

 

举例考虑一下下面这个类构造器将按什么顺序执行:

trait Logger{}  

trait ShortLogger extends logger{}  

trait TimestampLogger extends logger{}  

class Account{}  

class SavingsAccount extends Account with FileLogger with ShortLogger

 

构造器将按如下顺序执行:

1.   Account(超类)

2.   Logger(第一个特质的父特质)

3.   FileLogger(第一个特质)

4.   ShortLogger(第二个特质)

5.   SavingsAccount(类)


特质不能有构造器参数. 每个特质都有一个无参构造器. 值得一提的是,缺少构造器参数是特质与类唯一不相同的技术差别. 除此之外,特质可以具有类的所有特性,比如具体的和抽象的字段,以及超类。


特质背后的实现: Scala通过将 trait 翻译成 JVM 的类和接口关于通过反编译的方式查看 Scala 特质的背后工作方式可以参照 Scala 令人着迷的类设计中介绍的方法,有兴趣的可以看看.

注:本文内容多摘取自快学scala

猜你喜欢

转载自blog.csdn.net/liu68686868/article/details/80307804