一 定义一个简单的类
1.1定义类,包含field以及方法
classHelloWorld {
private var name = "leo"
def sayHello() { print("Hello, " +name) }
def getName = name
}
1.2创建类的对象,并调用其方法
valhelloWorld = new HelloWorld
helloWorld.sayHello()
print(helloWorld.getName)
1.3定义函数括号问题
定义函数我们如果没有参数,可以不加括号,也可以加括号
但是定义的时候,没加,在调用的时候,就不要加,否则会报错;如果在定义的时候加那我们在调用的时候也加上
classHello{
var name = "nicky"
def hello(){print("Hello,"+name)}
def say = print(name+", welcome toyou!")
def getName = name
}
valhello = new Hello
scala>hello.hello()
Hello,nicky
scala>hello.say
nicky,welcome to you!
scala> hello.say()
<console>:13: error: Unit does not take parameters
hello.say()
这里就是定义函数没加括号,但是调用的时候加括号了,然后就报错
二 字段的get和set方法
2.1定义不带private var field的字段
varname = "icon"
此时,JVM会自动定义为private域,并且提供public的getter和setter方法
classHello {
var name = "nicky";
def hello(password:String){
println("Welcome you to ScalaWorld,"+name+", you password is "+password);
}
def say {
println("Are u "+name+"?")
println("yes!!")
}
}
objectTest {
def main(args:Array[String]):Unit = {
val hello = new Hello
hello.hello("123456")
hello.name = "Claria"
hello.say
}
}
运行结果:
Welcomeyou to Scala World,nicky, you password is 123456
Areu Claria ?
yes!!
2.2显示设置private 修饰变量
如果我们显式将字段定义成private,那么此时默认生成的getter和setter方法也是private的,对于其他类不可见。
classHello {
private var name = "nicky";
def say {
println("Are u "+name+"?")
println("yes!!")
}
}
objectTest {
def main(args:Array[String]):Unit = {
val hello = new Hello
hello.name ="Claria"//编译通不过
hello.say
}
}
2.3字段定义成valfield
如果我们定义字段valfield,那么只会生成get方法,而不会生成set方法,因为 val类型的变量,不能修改其值或者引用
class Hello { val name = "nicky"; def say { println("Are u "+name+" ?") println("yes!!") } } object Test { def main(args:Array[String]):Unit = { val hello = new Hello print(hello.name)//但是可以get hello.name = "belly"//编译报错,不能set hello.say } } |
2.4不生成setter和 getter方法
如果希望既不生成setter函数也不希望生成getter函数
那么可以将字段声明为private[this]
class Hello { private[this] var name = "nicky"; def say { println("Are u "+name+" ?") println("yes!!") } } |
2.5private var field 和private[this]var field 比较
都只是能在本类使用
privatevar field 可以生成getter和setter方法,但是是私有的
private[this]var field 压根儿就没有get和set方法
言外之意,在同一类,privatevar field 可以set,但是private[this]var field 没有set,不能修改字段
2.6自定义get和set方法
如果只是希望简单的get和set方法,按照scala提供的语法规则就可以了,根据需要为field选择合适的修饰符,var、val、private、private[this]
但是,如果希望更好的控制get和set函数,你可以自定义getter和
Setter方法
class Student { private var sname = "bell" //自定义get方法 def name = "your name is " + sname
//自定义set方法 def name_=(newValue: String) { sname = newValue } } |
注意:
在自定义set方法=号左边不能有空格,否则编译报错
not found: value newValue
2.7如果不希望get和set方法暴露,但是希望其他类可以修改某一个字段的值
这时候我们可以将字段定义成private,然后提供一个方法修改该字段
class Student { private var sname = "bell" //自定义get方法 def name = "your name is " + sname
def updateName(sname:String):Unit = { this.sname = sname; } } |
2.8如果不希望当前private 字段被其他类的方法调用时访问,可以使用private[this]
比如,当前类Student,定义一个private字段 sname,如果希望两个实例的name进行比较,private可以实现;但是如果希望不同实例间不能访问该字段,则需要使用private[this]
class Student { private var sname = "bell"
def name = "name is "+sname
def name_=(newVal:String){ sname = newVal }
def access(s:Student):Unit = { sname.equals(s.sname) } }
class Student { private[this] var sname = "bell"
def name = "name is "+sname
def name_=(newVal:String){ sname = newVal }
def access(s:Student):Unit = { //value sname is not a member of com.scala.Student sname.equals(s.sname) } } |
2.9让Scala自动生成java风格的getter和setter方法
给field添加@BeanProperty注解即可
import scala.beans.BeanProperty
class Student { @BeanProperty var name: String = _ } object Test { def main(args:Array[String]):Unit = { val s = new Student s.setName("nicky") print(s.getName) } } |
三 constructor 详解
3.1辅助构造器
在scala我们可以定义多个辅助构造函数,类似于Java构造方法重载
辅助构造方法之间可以相互调用,而且第一行必须调用主构造器
class Student { private var name = ""; private var age = 0;
def this(name:String){ this();//主构造器 this.name = name; }
def this(name:String, age:Int){ this(name); this.age = age; } } |
3.2主构造器
Scala中,主constructor是与类名放在一起的,与java不同。而且类中,没有定义在任何方法或者是代码块之中的代码,就是主constructor的代码,这点感觉没有java那么清晰
3.2.1普通构造参数
class Student(var name:String, var age:Int) { println("you name is "+name+", age is "+age); } |
我们在初始化的时候,必须提供构造参数,否则报错
object Test { def main(args:Array[String]):Unit = { //not enough arguments for constructor Student: (name: String, age: Int) //com.scala.Student. Unspecified value parameters name, age. val s = new Student //提供构造参数就没有问题 val s1 = new Student("nicky",20) } } |
3.2.2为构造参数提供默认值
class Student(var name:String = "郭靖", var age:Int = 25) { println("you name is "+name+", age is "+age); } object Test { def main(args:Array[String]):Unit = { //提供默认的构造参数,就不会报错 val s = new Student //提供构造参数就没有问题 val s1 = new Student("nicky",20) } } |
3.2.3构造参数没有生命变量类型和修饰符
如果构造参数没有生命变量类型和修饰符,诸如name:String,如果这样,如果在类里面有方法使用到了,那么该字段就是private[this]修饰;
如果没有方法用到该字段,则表示没有该字段,只是在初始化阶段使用
class Student(name:String,age:Int) { println("you name is "+name+", age is "+age); } |
四 内部类介绍
Scala也有内部类,但是和Java优点不一样,如果不同的外部类实例的内部类实例是不一样的。
Java实现
import java.util.ArrayList; import java.util.List; public class Classes { class Student { private String name;
public Student(String name) { super(); this.name = name; }
} List<Student> students = new ArrayList<Student>(); public Student getStudent(String name){ return new Student(name); }
public static void main(String[] args) { Classes c1 = new Classes(); Student s1 = c1.getStudent("Nicky"); c1.students.add(s1);
Classes c2 = new Classes(); Student s2 = c2.getStudent("Bell"); c1.students.add(s2); } } |
Scala实现
import scala.collection.mutable.ArrayBuffer class Class { class Student(val name: String) {} val students = new ArrayBuffer[Student] def getStudent(name: String) = { new Student(name) } //产生外部类实例班级一 val c1 = new Class //产生一个班级一的学生nicky val s1 = c1.getStudent("nicky") //把该学生放到班级一的学生集合里面 c1.students += s1 //============================================ //产生外部类实例班级二 val c2 = new Class //产生一个班级二的学生nicky val s2 = c2.getStudent("bell") //把该学生放到班级一的学生集合里面,这时候会报错 //因为班级二的学生只能放到班级二的学生集合里 c1.students += s2 } |
在Java里只要内部类是同一个类型,都可以加入到某一个班级中,而不用区别Student是哪一个Classes的实例
Scala里,即使内部类是同一个类型,也要区别Student属于的Class实例不一样
内部类访问外部类
方式一:外部类名.this.成员
class Classes(val name:String = "全真教") { var age:Int = 0; def this(age:Int){ this(); this.age = age; }
class Student(val name: String) { def info():Unit = { Classes.this.print(); println("Outer Class Name: "+Classes.this.name+"; Inner Class Name: "+name) } }
def print(){ println(this.name+" "+this.age) } } |
方式二 :别名,outrer=>
但是必须放在第一行
class Classes(val name:String = "全真教") { outer => class Student(val name: String) { def info():Unit = { outer.print(); println("Outer Class Name: "+outer.name+"; Inner Class Name: "+name) } } var age:Int = 0; def this(age:Int){ this(); this.age = age; }
def print(){ println(this.name+" "+this.age) } } |
五 面向对象编程之object
5.1object特性
普通class没有提供一些静态成员变量,不能定义所有实例对象共享的东西;然而这些东西,Scala也可以定义,定义在object里的;
第一次调用object的方法时,就会执行object的构造器,而且这个构造器只能在object第一次被调用时执行一次,以后再次调用不会执行了
object通常用于作为单例模式的实现,或者放class的静态成员,比如一些工具方法
objectPerson{
private var eyeNum:Int = 2
print("this is Person object,constructor is exexuting")
def getEyeNum = eyeNum
}
scala>Person.getEyeNum
thisis Person object, constructor is exexutingres4: Int = 2
scala>Person.getEyeNum
res5:Int = 2
5.2伴生对象和伴生类
如果有一个class,还有一个跟class同名的object,那么简称这个object是class的伴生对象;class是object的伴生类
伴生类和伴生对象必须同在一个scala文件
伴生类和伴生对象可以互相访问private字段
/** * 作为Person object的伴生类 */ class Person { var name:String = "" var age:Int = 0 private var address:String = "" private[this] var hobby:String = ""
def this(name:String,age:Int,address:String,hobby:String){ this() this.name = name this.age = age this.address = address this.hobby = hobby } def output = println("Name -->"+name+" Age -->"+age+" address -->" +address+" hobby -->"+hobby+" EyeNum -->"+Person.eyeNum) }
/** * 作为class person的伴生对象 */ object Person{ private var eyeNum:Int = 2; println("这是Person object,构造器开始执行") def getEyeNum = eyeNum } |
5.3object继承
object也可以继承抽象类,并覆盖抽象类中的方法
abstract class Hello(var message:String) { def sayHello(name:String):Unit{} }
object HelloImpl extends Hello("Welcome you"){ override def sayHello(name:String):Unit = { println(message+", "+name); } } |
5.4apply方法
>>apply方法是一个object非常特殊的方法,通常在伴生对象object中实现该方法,并在其中实现构造伴生类的的对象的功能.
>>创建伴生类对象时,通常不会使用new 类名方式,而使用类名()的方式,隐式调用伴生对象的apply方法这样会让创建对象更加简洁.
Array类的伴生对象就实现了接收可变数量参数,并创建一个Array对象的功能
def apply[T: ClassTag](xs: T*): Array[T] = { //根据传递进来的可变数量参数长度初始化数组 val array = new Array[T](xs.length) var i = 0 //遍历数组,并把传递进来的值赋给每一个元素 for (x <- xs.iterator) { array(i) = x; i += 1 } //返回数组对象 array } |
定义自己的伴生类和伴生对象
class Human(val name:String, val age:Int){ println("初始化Human class") def info(){ println(this.name+" "+this.age) } }
object Human { def say():Unit = { println("Human can speak"); }
def apply(name:String,age:Int):Human = { new Human(name,age) }
def main(args:Array[String]):Unit = { val human = Human("郭靖",25) Human.say() human.info() } } |
5.5main方法
我们需要一个执行代码的入口类,在Scala里面,我们也需要定义一个main方法,而且这个main方法只能在object里面
def main(args:Array[String]):Unit = { val human = Human("郭靖",25) Human.say() human.info() } |
除了main方法以外,我们还可以集成App Trait,然后将需要在main方法执行的代码直接作为object的构造器代码,而且用args可以接收传入的参数
object HelloWorld extends App { if (args.length > 0) { print("arguments list: ") for(i <- 0 until args.length ) { print(args(i) +" ") } } else{ print("No any arguments passed!") } } |
AppTrait的工作原理:
App继承自DelayedInitTrait,scalac 命令进行编译时,会把AppTrait的object的构造器代码放到DelayedInitTrait的delayedInit方法执行
5.6object实现枚举功能
Scala没有直接提供类似于Java的枚举功能,如果Scala要实现枚举,需要extendsEnumeration类,并且调用value方法来初始化枚举值
object Season extends Enumeration { val SPRING, SUMMER, AUTUMN, WINTER = Value }
还可以通过Value传入枚举值的id和name,通过id和toString可以获取; 还可以通过id和name来查找枚举值
object Season extends Enumeration { val SPRING = Value(0,"spring") val SUMMER = Value(1,"summer") val AUTUMN = Value(2,"autumn") val WINTER = Value(3,"winter")
def main(args:Array[String]){ for (i <- Season values) { println(i) } } } |
5.7 object 与 class区别
>>关键字不一样
>>object不能接受带参数的构造器;class 可以接收带参数的构造器
>>object可以定义一些类似静态的成员变量,然后所有实例共享;class不能定义静态成员变量
>>object可以定义apply方法实例化class
>>class可以直接访问
六 面向对象之继承
6.1extends
Scala的继承和Java基本差不多,都是用extends关键字;
父类如果是final,则不能被继承;
父类方法或者字段是final,则不能被子类覆盖
6.2override & super
覆盖父类方法或者var|val字段。使用关键字override
调用父类的方法或者字段等使用关键字super
而且子类的valfield还可以覆盖父类的valfield的getter方法;只要在子类中使用override关键字即可
class Person { private var name = "leo" def getName = name } class Student extends Person { private var score = "A" def getScore = score override def getName = "Hi, I'm " + super.getName } |
6.3isInstanceOf 和 asInstanceOf
isInstanceOf[类名]:判断是否是某一个类的实例;
asInstanceOf[类名]:将某一个类的实例强制转换
Java实现
public class Person { String name = null; public Person(String name) {
this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } } public class Student extends Person{ private double score; public Student(String name,double score) { super(name); this.score = score; } public void outPut() { System.err.println(this.name+" "+this.score); } public double getScore() { return score; } public void setScore(double score) { this.score = score; } } public static void main(String[] args) { Person p = new Student("洪七公", 95.5); Student s = null; if (p instanceof Student) { s = (Student) p; s.outPut(); } } |
Scala实现:
class Person(var name:String, var age:Int) { println("Person 主构造器开始工作!") val skin:String = null; def say:Unit = print("说人话"); } class Student(name:String,age:Int, var score:Double) extends Person(name,age){ println("Student 主构造器开始工作!") var classes:String = null; def this(name:String,age:Int,score:Double,classes:String){ this(name,age,score); this.classes = classes; println("Student 辅助构造器开始工作!"); } override val skin:String = "黄色" override def say:Unit = println("讲汉语"); } object Test { def main(args: Array[String]): Unit = { val p:Person = new Student("西门吹雪",25,85,"剑客班"); var s:Student = null; if (p.isInstanceOf[Student]) { s = p.asInstanceOf[Student]; } } } |
注意:
val p:Person = new Student("西门吹雪",25,85,"剑客班");
p.isInstanceOf[Person] = tue ?
其实过程是:
它首先判断是不是Person的实例,不是,然后继续判断是不是Person子类的实例,如果还不是返回false,如果是返回true.
==>所以这里的判断类型不算是很精确的
6.4getClass 和 classOf
我们知道:isInstanceOf只能判断出对象是否是指定类以及其子类的对象,而不能精确判断出,对象就是指定类的对象。
如果要求精确地判断对象就是指定类的对象,那么就只能使用getClass和classOf了
对象.getClass可以精确获取对象的类,classOf[类]可以精确获取类,然后使用==操作符即可判断
scala>class Person
definedclass Person
scala>class Student extends Person
definedclass Student
scala>val p:Person = new Student
p:Person = Student@48d5f34e
scala>p.isInstanceOf[Person]
res3:Boolean = true
scala>p.isInstanceOf[Student]
res4:Boolean = true
scala>p.getClass == classOf[Person]
res5:Boolean = false
scala>p.getClass == classOf[Student]
res6:Boolean = true
6.5使用模式匹配进行类型判断
使用模式匹配,功能性上来说,与isInstanceOf一样,也是判断主要是该类以及该类的子类的对象即可,不是精准判断的
scala>p match {
| case per:Person => println("It'sa Person Object")
| case _ => println("UnknownType!!")
| }
It'sa Person Object
6.6protected
Scala也可以使用protected关键字来修饰字段和方法,protected修饰的字段和方法在子类是不需要通过super去调用的,直接就可以访问
protected[this]:只能在当前子类对象中访问父类的字段和方法,无法通过其他子类对象访问父类的字段和方法
class Human { protected var name: String = "nicky" protected[this] var hobby: String = "game" } class Worker extends Human { def sayHello = println("Hello, " + name) def makeFriends(s: Student) { //s.hobby 就会编译报错,因为不是当前子类,而是父类的其他子类 println("my hobby is " + hobby + ", your hobby is " + s.hobby) } } |
6.7调用父类的构造器
Scala中,每个类可以有一个主constructor和任意多个辅助constructor,而每个辅助constructor的第一行都必须是调用其他辅助constructor或者是主constructor; 因此子类的辅助constructor是一定不可能直接调用父类的constructor的
只能在子类的主constructor中调用父类的constructor,以下这种语法,就是通过子类的主构造函数来调用父类的构造函数
注意!如果是父类中接收的参数,比如name和age,子类中接收时,就不要用任何val或var来修饰了,否则会认为是子类要覆盖父类的field,举个例子:
class Human(var name:String, var age:Int) { def outPut:Unit = { println(this.name + "<<>>" + this.age) } } /* * var name:String, var age:Int 编译报错 * 认为是子类要覆盖父类的name和age属性 */ class Worker(var name:String, var age:Int) extends Human(name,age) {
} |
如果我把变量名改一下:
Woker(var name1:String, var age1:Int) extends Human(name1,age1) {
} |
这样就不会报错,的确,确实可以如此,但是,我们却增加了2个字段,浪费空间
标准写法:
class Worker(name:String,age:Int, var score:Double) extends Human(name,age) { def this(name: String) { this(name, 0, 0) }
def this(name:String,age: Int) { this(name, age, 0) } } |
另外,自定义辅助的构造函数参数类型不能和主构造函数相同,否则认为重复了,会编译报错
6.8匿名内部类
可以定义一个类的没有名称的子类,并直接创建其对象,然后将对象的引用赋予一个变量。这个就是匿名内部类,我们甚至可以将该匿名内部类对象传递给其他函数
匿名内部类在java实现:
new父类构造器(参数列表)|实现接口() {
//匿名内部类的类体部分
}
public abstract class Bird { private String name;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public abstract int fly(); } public interface Animal { public abstract void feed(); public abstract void getType(); } new Bird() { @Override public int fly() { return 1000; } }; Animal animal = new Animal() { @Override public void getType() { System.out.println("飞禽"); } @Override public void feed() { System.out.println("觅食"); } }; } |
Scala实现
class Human(var name:String, var age:Int) { def outPut:Unit = { println(this.name + "<<>>" + this.age) } } object Test { def main(args: Array[String]): Unit = { val h:Human = new Human("杨过",20){ override def outPut:Unit = { println(this.name + "#####" + this.age) } } h.outPut } } |
6.9抽象类和抽象方法
在Scala抽象的定义和java一样:
>>如果在父类中,有某些方法无法立即实现,而需要依赖不同的子来来覆盖,重写实现自己不同的方法实现。此时可以将父类中的这些方法不给出具体的实现,只有方法签名,这种方法就是抽象方法。
>>而一个类中如果有一个抽象方法,那么类就必须用abstract来声明为抽象类,此时抽象类是不可以实例化的
>>但是,在子类中覆盖抽象类的抽象方法时,不需要使用override关键字
>>如果父类子段没有初始化值,那么父类必须是抽象的,而且子类需要重写这个属性,但是不用使用关键字override,否则子类编译会报错
abstract class Animal(name:String) { var age:Int def eat:Unit def sleep(){ println("sleep 8 hours") } } class Lion(name:String) extends Animal(name){ var age:Int = 0 def eat:Unit = { println("狮子吃肉"); } } object Test { def main(args: Array[String]): Unit = { val lion = new Lion("美洲狮"); lion.eat lion.sleep() } } |
七 面向对象编程之Trait
7.1Trait基础知识
7.1.1trait作为接口使用
我们可以将trait作为接口使用,这时候和Java的接口非常类似
特性:
>>trait中可以定义抽象方法,和抽象类定义抽象方法一样,不给出具体实现即可
>>类可以使用extends集成trait,这里和Java有所区别,Scala没有实现的概念,无论是继承类还是trait都用extends
>>类继承之后,必须实现其中的抽象方法,实现不需要override
>>Scala不允许类多继承,但是trait可以多继承,使用with关键字即可
trait HelloTrait { def sayHello(name:String) } trait MakeFriendsTrait { def makeFriends(p: Person) } import com.scala.traits.HelloTrait import com.scala.traits.MakeFriendsTrait
class Person(val name:String) extends HelloTrait with MakeFriendsTrait with Serializable { def sayHello(name:String):Unit = { println("Hello, "+ name) } def makeFriends(p: Person){ println("你好,我是"+name+", 您的名字是"+p.name+"吗?") } } object Test { def main(args: Array[String]): Unit = { val p1:Person = new Person("张无忌") p1.sayHello("周芷若") val p2:Person = new Person("赵敏") p1.makeFriends(p2) } } Hello, 周芷若 你好,我是张无忌, 您的名字是赵敏吗? |
7.1.2trait中定义具体方法
trait中不仅只是可以定义抽象方法,还可以定义具体方法,就像一个包含通用工具方法的东西;相当于时trait混入了类
trait Logger { def log(message: String) = println(message) } class Person(val name:String) extends HelloTrait with MakeFriendsTrait with Logger with Serializable { def sayHello(name:String):Unit = { println("Hello, "+ name) }
def makeFriends(p: Person){ println("你好,我是"+name+", 您的名字是"+p.name+"吗?") log(name+"正在和"+p.name+"交朋友") } } |
7.1.3trait中定义具体字段
Scala中的trait可以定义具体field,此时继承trait的类就自动获得了trait中定义的field;
但是这种获取field的方式与继承class是不同的:如果是继承class获取的field,实际是定义在父类中的;而继承trait获取的field,就直接被添加到了类中
trait Logger { val logLevel:String = "info"; def log(message: String) = println(message) } class Person(val name:String) extends HelloTrait with MakeFriendsTrait with Logger with Serializable { def sayHello(name:String):Unit = { println("Hello, "+ name) }
def makeFriends(p: Person){ println("你好,我是"+name+", 您的名字是"+p.name+"吗?") log("日志级别: "+p.logLevel) log(name+"正在和"+p.name+"交朋友") } } |
7.1.4trait中定义抽象字段
trait可以定义抽象方法,继承trait的类,则必须覆盖抽象字段;但是继承trait的trait,可以不必实现;可以理解为抽象类继承抽象类,然后只有继承抽象类的类才会实现具体的东西
trait SayHello { val msg: String def sayHello(name: String) = println(msg + ", " + name) } class Person(val name: String) extends SayHello { val msg: String = "hello" def makeFriends(p: Person) { sayHello(p.name) println("I'm " + name + ", I want to make friends with you!") } } |
7.2Trait高级知识
7.2.1为实例对象混入trait
trait Logged { def log(msg: String) {} } trait MyLogger extends Logged { override def log(msg: String) { println("log: " + msg) } } class Person(val name: String) extends Logged { def sayHello { println("Hi, I'm " + name) log("sayHello is invoked!") } } object Test { def main(args: Array[String]): Unit = { val p = new Person("金毛狮王") with MyLogger p.sayHello } } |
7.2.2trait调用链
>>首先,有一个parent trait,然后类要继承的所有trait都来继承parent trait
>>每一个trait实现父trait或者重写父trait 需要处于调用链上的方法,每一个方法调用完后,都调用一下super.方法名
>>类中调用多个trait中都有的这个方法时,首先会从最右边的trait的方法开始执行,然后依次往左执行,形成一个调用链条
>>这种特性非常强大,其实就相当于设计模式中的责任链模式的一种具体实现依赖
trait Handler { def handle(data: String) {} } trait DataValidHandler extends Handler { override def handle(data: String) { println("数据校验: "+data); super.handle(data) } } trait SignatureValidHandler extends Handler { override def handle(data: String) { println("检查签名: " + data) super.handle(data) } } |
7.2.3trait中覆盖抽象方法(有何意义)
>>在trait中,是可以覆盖父trait的抽象方法的
>>但是覆盖时,如果使用了super.方法的代码,则无法通过编译。因为super.方法就会去掉用父trait的抽象方法,此时子trait的该方法还是会被认为是抽象的
>>如果要通过编译,就得给子trait的方法加上abstractoverride修饰
trait Logger { def log(msg: String) } trait MyLogger extends Logger { abstract override def log(msg: String) { super.log(msg) } } |
7.2.4混合使用trait的具体方法和抽象方法
>>在trait中,可以混合使用具体方法和抽象方法
>>可以让具体方法依赖于抽象方法,而抽象方法则放到继承trait的类中去实现
>>这种trait其实就是设计模式中的模板设计模式的体现
Java实现:
public abstract class CaffeineBeverage {
/** * * @desc * 模板方法,用来控制泡茶与冲咖啡的流程 * 申明为final,不希望子类覆盖这个方法,防止更改流程的执行顺序 * @return void */ final void prepareRecipe(){ boilWater(); brew(); pourInCup(); addCondiments(); }
/** * @desc * 将brew()、addCondiment()声明为抽象类,具体操作由子类实现 * @return void */ abstract void brew();
abstract void addCondiments();
void boilWater(){ System.out.println("Boiling water..."); }
void pourInCup(){ System.out.println("Pouring into Cup..."); } } public class Coffee extends CaffeineBeverage{
void addCondiments() { System.out.println("Adding Sugar and Milk..."); }
void brew() { System.out.println("Dripping Coffee through filter..."); } } |
Scala实现:
trait Cook { def brew def addCondiments def boilWater(){ println("Boiling water...") } def pourInCup(){ println("Pouring into Cup...") }
def prepareRecipe():Unit = { boilWater() brew pourInCup() addCondiments } } import com.scala.traits.Cook
class Tea extends Cook{ def addCondiments(){ println("Adding Sugar and Milk..."); }
def brew(){ println("Dripping tea through filter..."); } } |
7.2.5trait的构造机制
trait也是有构造代码的概念,我们解读以下整个流程:
>>如果有父类,先构造父类
>>如果父类继承trait,先构造trait
>>如果trait有多个,从左右到右依顺序构造
>>如果trait还有父trait,那么父trait先构造
>>如果多个trait继承自同一个trait,这个trait只会被构造一次
trait Handler { println("Handler Trait 构造函数执行..."); def handle(data: String) {} } trait DataValidHandler extends Handler { println("DataValidHandler Trait 构造函数执行..."); override def handle(data: String) { println("数据校验: "+data); super.handle(data) } } trait SignatureValidHandler extends Handler { println("SignatureValidHandler Trait 构造函数执行..."); override def handle(data: String) { println("检查签名: " + data) super.handle(data) } } class Person(val name: String) extends SignatureValidHandler with DataValidHandler { println("Person 类主构造函数执行...") def sayHello = { println("Hello, " + name); handle(name) } } class Student(name: String) extends Person(name){ println("Student 主构造函数执行..."); } object Test { def main(args: Array[String]): Unit = { val s:Student = new Student("林晚荣") } } |
运行结果:
Handler Trait 构造函数执行...
SignatureValidHandler Trait 构造函数执行...
DataValidHandler Trait 构造函数执行...
Person 类主构造函数执行...
Student 主构造函数执行...
7.2.6trait字段的初始化
默认情况下,Scala中,trait是没有接收参数的构造函数的,这是trait与class的唯一区别,但是如果需求就是要trait能够对field进行初始化,该怎么办呢?只能使用Scala中非常特殊的一种高级特性——提前定义
下面代码会报空指针异常:因为trait先于子类构造,但是trait这会儿msg还没有初始化呢
trait SayHello { val msg: String println(msg.toString) } class Person extends SayHello{ val msg:String = "init"; } object Test { def main(args: Array[String]): Unit = { val p = new Person } } |
方式一:
class Person extends { val msg:String = "init"; } with SayHello |
方式二:使用lazyvalue
trait SayHello { lazy val msg: String = null println(msg.toString) } class Person extends SayHello{ override lazy val msg:String = "init"; } object Test { def main(args: Array[String]): Unit = { val p = new Person } } |
方式三:
val p = new { val msg: String = "init" } with Person with SayHello |
7.2.7让trait继承类
在Scala中,trait也可以继承自class,此时这个class就会成为所有继承该trait的类的父类
class MyUtil { def printMessage(msg: String) = println(msg) }
trait Logger extends MyUtil { def log(msg: String) = printMessage("log: " + msg) }
class Person(val name: String) extends Logger { def sayHello { log("Hi, I'm " + name) printMessage("Hi, I'm " + name) } } |