大数据Scala系列之特质, 特质的定义除了使用关键字trait之外,与类定义无异。
特质用来在类之间进行接口或者属性的共享。类和对象都可以继承特质,特质不能被实例化,因此也没有参数。
一旦特质被定义了,就可以使用extends或者with在类中混入特质。
特质的定义:
trait Logger{
//
这是一个抽象方法,特质中未被实现的方法默认是抽象的,不需要
abstract
关键字修饰
def log(msg:String)
}
子类对特质的实现:
class ConsoleLogger
extends Logger{
//
重写抽象方法,不需要
override
def log(msg:String){println(msg)}
}
trait ConsoleLogger{
//
注意与
Java
中接口的不同
def log(msg:String){println(msg)}
}
特质的使用
class SavingAccount
extends Account
with ConsoleLogger{
def withdraw(amount:Double){
if(amount >balance) log("Insufficent funds")
else balance -= amount
}
}
scala自带有Logged特质,但是没有实现
trait Logged{
def log(msg:String){}
}
如果在类定义中使用了该特质
//
该类中,其中的日志信息不会被记录
class SavingAccount
extends Account
with Logged{
def withdraw(amount:Double){
if(amount >balance) log("Insufficent funds")
else balance -= amount
}
}
标准的ConsoleLogger扩展自Logger
class ConsoleLogger
extends Logger{
//
重写抽象方法,不需要
override
def log(msg:String){println(msg)}
}
可以在创建对象的时候,加入该特质:
val acct1= new SavingAccount with ConsoleLogger
这样,创建同一类对象,却可以加入不同的特质
val acct2= new SavingAccount with FileLogger
可以为类或者对象添加多个互相调用的特质,特质的执行顺序,取决于特质被添加的顺序
trait Logged{
def log(msg:String)
}
trait ConsoleLogger
extends Logged{
//
重写抽象方法,不需要
override
def log(msg: String) ={println(msg)}
}
//
给
log
加上时间戳
trait TimestampLogger
extends ConsoleLogger {
override
def log(msg: String) {
super.log(s"${java.time.Instant.now()} $msg")
}
}
//
截断过于冗长的日志信息
trait ShortLogger
extends ConsoleLogger{
val maxLength = 15
override
def log(msg: String) {
super.log(
if(msg.length <=maxLength)msg
else
s"${msg.substring(0,maxLength-3)}...")
}
}
//
定义超类
class Account {
protected
var balance:Double = 0
}
class SavingAccount
extends Account
with ConsoleLogger{
def withdraw(amount:Double){
if(amount >balance) log("Insufficent funds")
else balance = balance - amount
}
}
object test{
def main(args: Array[String]): Unit = {
val acct1 =
new SavingAccount
with ConsoleLogger
with TimestampLogger
with ShortLogger
val acct2 =
new SavingAccount
with ConsoleLogger
with ShortLogger
with TimestampLogger
acct1.withdraw(100.0)
acct2.withdraw(100.0)
}
}
//res:
//ShortLogger
的
log
方法先被执行,然后它的
super.log
调用的是
TimestampLogger
的
log
方法,最后调用
ConsoleLogger
的方法将信息打印出来
2018-06-15T16:50:28.448Z Insufficent ...
//
先是
TimestampLogger
的
log
方法被执行,然后它的
super.log
调用的是
ShortLogger
的
log
方法,最后调用
ConsoleLogger
的方法将信息打印出来
2018-06-15T1...
import scala.collection.mutable.ArrayBuffer
trait Pet {
val name: String
}
class Cat(
val name: String)
extends Pet
class Dog(
val name: String)
extends Pet
val dog =
new Dog("Harry")
val cat =
new Cat("Sally")
val animals = ArrayBuffer.empty[Pet]
animals.append(dog)
animals.append(cat)
animals.foreach(pet => println(pet.name))
// Prints Harry Sally
Mixins用于进行类组合的特质:
abstract
class A {
val message: String
}
class B
extends A {
val message = "I'm an instance of class B"
}
//
此处的特质
C
即为
mixin
trait C
extends A {
def loudMessage = message.toUpperCase()
}
class D
extends B
with C
val d =
new D
println(d.message)
// I'm an instance of class B
println(d.loudMessage)
// I'M AN INSTANCE OF CLASS B
//
注意抽象方法和具体方法的结合
trait Logger {
def log(msg: String)
def info(msg: String) { log("INFO: " + msg) }
def warn(msg: String) { log("WARN: " + msg) }
def severe(msg: String) {log("SEVERE: " + msg)}
}
class Account {
protected
var balance:Double = 0
}
class SavingsAccount
extends Account
with Logger {
def withdraw(amount: Double) {
if (amount > balance) severe("Insufficient funds")
else "you can do this" }
override
def log(msg: String) { println(msg) }
}
object test{
def main(args: Array[String]): Unit = {
val acc =
new SavingsAccount
acc.withdraw(100)
}
}
//result
SEVERE: Insufficient funds
特质中的字段有初始值则就是具体的,否则是抽象的。
trait ShortLogger
extends Logged {
val maxLength = 15
//
具体字段
}
那么继承该特质的子类是如何获得这个字段的呢。Scala是直接将该字段放入到继承该特制的子类中,而不是被继承。例如:
class SavingsAccount
extends Account
with ConsoleLogger
with ShortLogger {
var interest = 0.0
def withdraw(amount: Double) {
if (amount > balance) log("Insufficient funds")
else ...
}
}
特质中的抽象字段在具体的子类中必须被重写:
trait ShortLogger
extends Logged {
val maxLength: Int
//
抽象字段
override
def log(msg: String) {
super.log(
if (msg.length <= maxLength) msg
else msg.substring(0, maxLength - 3) + "...")
}
}
class SavingsAccount
extends Account
with ConsoleLogger
with ShortLogger {
val maxLength = 20
//
不需要写
override
}
特质也是有构造器的,由字段的初始化和其他特质体中的语句构成:
trait FileLogger
extends Logger {
val out =
new PrintWriter("app.log")
//
构造器的一部分
out.println("# " +
new Date().toString)
//
也是构造器的一部分
def log(msg: String) { out.println(msg); out.flush() }
}
这些语句在任何混入了该特质的对象在构造时都会被执行。 构造器的顺序:
class SavingsAccount extends Account with FileLogger with ShortLogger
构造器执行顺序:
1Account (超类)
2 Logger (第一个特质的父特质)
3 FileLogger
4 ShortLogger
5 SavingsAccount
特质不能有构造器参数,每个特质都有一个无参构造器。 这也是特质和类的差别。 例如: 我们要在构造的时候指定log的输出文件:
trait FileLogger
extends Logger {
val filename: String
//
构造器一部分
val out =
new PrintWriter(filename)
//
构造器的一部分
def log(msg: String) { out.println(msg); out.flush() }
}
val acct =
new SavingsAccount
extends Account
with FileLogger("myapp.log")
//error
,特质没有带参数的构造器
//
你也许会想到和前面重写
maxLength
一样,在这里重写
filename:
val acct =
new SavingsAccount
with FileLogger {
val filename = "myapp.log"
//
这样是行不通的
}
FileLogger的构造器先于子类构造器执行。这里的子类其实是一个扩展自SavingsAccount 并混入了FileLogger特质的匿名类。而filename的初始化发生在这个匿名类中,而FileLogger的构造器会先执行,因此new PrintWriter(filename)语句会抛出一个异常。 解决方法是要么使用提前定义或者使用懒值:
val acct =
new {
val filename = "myapp.log"
}
with SavingsAccount
with FileLogger
//
对于类同样:
class SavingsAccount
extends {
val filename = "myapp.log"
}
with Account
with FileLogger {
...
// SavingsAccount
的实现
}
//
或使用
lazy
trait FileLogger
extends Logger {
val filename: String
//
构造器一部分
lazy
val out =
new PrintWriter(filename)
//
构造器的一部分
def log(msg: String) { out.println(msg); out.flush() }
}
特质也可以扩展类,这个类将会自动成为所有混入该特质的超类
trait LoggedException extends Exception with Logged {
def log() { log(getMessage()) }
}
log方法调用了从Exception超类继承下来的getMessage 方法。那么混入该特质的类:
class UnhappyException extends LoggedException {
override def getMessage() = "arggh!"
}
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/69913892/viewspace-2660823/ ,如需转载,请注明出处,否则将追究法律责任。