2018年第44周-scala入门-面向对象基础语法
基于上面的理论, 并带着java面向对象的思维, 会更好理解scala的面向对象的语法.
这里得有几个共识:
类里面的函数, 叫方法类的字段, 也称为成员变量, 它们对应的英文都是field.
类实例化后, 叫对象(object), 也叫实例.
类里的字段, 方法, 统称为成员.
代码这个东西, 在你眼睛没达到能看清细节之前, 看到的代码, 基本就是个宏观的图片. 宏观的信息很少, 所以需要自己动手, 进入细节, 才获取更多的细节信息.
定义一个简单的类
定义类, 包含field以及方法
class HelloWorld{ private var name="jc" def sayHello(){print("Hello "+name)} def getName=name } //创建类对象, 并调用其方法 val helloWorld = new HelloWorld helloWorld.sayHello() println(helloWorld.getName) //如果定义方法时没有带括号, 则调用方法时不能带括号
主construtor
scala中, 主constructor是与类名放在一起的, 这与java不一样
而且类中 , 主constructor里的代码, 是没有定义在任何方法或者是代码块中, 这点与java对比, 就没那么直观看出那些是主构造方法的代码.
class Student(val name:String, val age:Int){ println("your name is "+name+", your age is "+age) } scala> var s = new Student("jc",29) your name is jc, your age is 29 s: Student = Student@6155fa78 scala> s.name res13: String = jc scala> s.age res14: Int = 29
主constructor中还可以通过使用默认参数, 来给参数默认的值
class Student(val name:String="jc", val age:Int=30){ println("your name is" + name+", your age is "+age ) }
如果主constructor传入的参数没有修饰符, 比如Student(name:String,age:Int), 那么如果类内部有方法使用到这些传入的参数, 则会声明为private[this] name; 否则没有该field, 就只能被constructor代码使用而已. 简单的说就是外部不能访问name和age
class Student(name:String, age:Int){ println("your name is "+name+", your age is "+age) } scala> var s = new Student("jc",29) your name is jc, your age is 29 s: Student = Student@5ed1f76d scala> s.name <console>:14: error: value name is not a member of Student s.name ^ scala> s.age <console>:14: error: value age is not a member of Student s.age ^ class Student(name:String, age:Int){ println("your name is "+name+", your age is "+age) def print={println(name)} } scala> var s = new Student("jc",29) your name is jc, your age is 29 s: Student = Student@6db3cc5b scala> s.name <console>:14: error: value name is not a member of Student s.name ^ scala> s.age <console>:14: error: value age is not a member of Student s.age
辅助constructor
scala中, 可以给类定义多个辅助constructor, 类似与java中的构造函数重载
辅助constructor之间可以互相调用, 而且必须第一行调用主constructor
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 } def print={println(name+", "+age)} }
object
object, 就是class的单个实例, 这里很特别, scala可以直接定义object, 通常在里面放一些静态的field或者method, 那就跟java的静态类很像
第一次调用object的方法时, 就会执行object的constructor, 也就是object内部不在method的代码; 但是object不能定义接受参数的constructor
注意, object的constructor只会在其第一次被调用时执行一次, 以后再次调用就不会再次执行constructor了
object通常用于作为单例模式的实现, 或者放class的静态成员, 比如工具方法
object Person{ private var eyeNum=2 println("this Person Object!") def getEyeNum=eyeNum }
内部类
scala中, 同样可以在类中定义内部类, 但是与java不同的是, 每个外部类的对象的内部类, 都是不同的类
scala> :paste // Entering paste mode (ctrl-D to finish) import scala.collection.mutable.ArrayBuffer class Outer { class Inner(val name:String){} val inners = new ArrayBuffer[Inner] def getInner(name:String) ={ new Inner(name) } } // Exiting paste mode, now interpreting. import scala.collection.mutable.ArrayBuffer defined class Outer scala> val out1 = new Outer out1: Outer = Outer@5479b0b1 scala> val inner1 = out1.getInner("inner1") inner1: out1.Inner = Outer$Inner@7161bdc3 scala> out1.inners+=inner1 res3: out1.inners.type = ArrayBuffer(Outer$Inner@7161bdc3) scala> val out2 = new Outer out2: Outer = Outer@54c2ca03 scala> val inner2 = out2.getInner("inner2") inner2: out2.Inner = Outer$Inner@747a5354 scala> out1.inners+=inner2 <console>:17: error: type mismatch; found : out2.Inner required: out1.Inner out1.inners+=inner2
java内部类的对比:
Outer.java文件
package com.java; import java.util.ArrayList; import java.util.List; public class Outer { public class Inner{ String name; public Inner(String name){ this.name = name; } } public List<Inner> inners = new ArrayList<>(); public Inner getInner(String name){ return new Inner(name); } }
App.java文件
package com.app; import com.java.Outer; public class App { public static void main(String[] args) { Outer out1 = new Outer(); Outer.Inner inner1 = out1.getInner("inner1"); out1.inners.add(inner1); Outer out2 = new Outer(); Outer.Inner inner2 = out2.getInner("inner1"); out1.inners.add(inner2); } }
java和scala的内部类, 提供的价值是, 让你逻辑地组织类, 并且控制它们的访问.
访问修饰符
包, 类, 对象的成员都可以被修饰为private或protected来控制其是否可被访问(调用).
所有修饰符, 无论是java还是scala, 都是用于在编译器发现错误. 所以当不能访问(调用)都会出现编译错误. 所以这里的访问(access), 也可以说是调用, 引用等都没关系, 因为都不会到达运行时才发现错误.
private成员
一个被修饰为private的成员, 只能被其class和object里的成员访问(注意是"和", 后续伴生类概念时会有所体现).
private修饰符的访问限制, 跟java的很像, 但注意还是有不一样.
一样的是: java和scala的private修饰的成员任何其他类不能调用.不一样的是: java内部类是可以调用其外部类的private成员, 外部类也是可以调用内部类的private成员. 而scala不可以
我们先理解上面一句话的"任何其他类".
在java里的"任何其他类"是除class类内部.
内部类是可以调用其外部类的private成员, 外部类也是可以调用内部类的private成员.
//编译通过 package com.java; import java.util.ArrayList; import java.util.List; public class Outer { private String outerName ; class Inner{ private void f(){ System.out.printf(outerName); } } void accesF(){ new Inner("").f(); } }
而在scala里的"任何其他类"包括就真的是任何其类, 内部类也属于任何其他类. 但一点跟java一样的是, 内部类可以访问外部类的private成员.
//编译失败 class Outer { class Inner{ private def f(){println("f")} class InnerMost{ f() //ok } } (new Inner).f() //error: f is not accessible }
下面例子证明修饰符是用于编译期发现错误, 以下都是可以编译通过.
package com.java; import java.util.ArrayList; import java.util.List; public class Outer { private int value ; public boolean compare(Out otherOuter){ return this.value < otherOuter.value } }
class Outer { private var value = 0 def compare(otherOuter:Outer) ={ this.value < otherOuter.value } }
protected成员
在scala, protected修饰的成员和java的就不太一样. java修饰层次更鲜明点, java的protected是包内的类都能够访问.
在scala, protected修饰的成员只能其自身或子类访问.
package p { class Super { protected def f() { println("f") } class inner { (new Super).f() } } class Sub extends Super { f() } class Other { (new Super).f() // 这行编译失败 error: f is not accessible } }
在java里, Other类是可以访问super.f(), 是因为Other和Super都在通过包p下.
public成员
这里又跟java不一样, 在scala里, 没有修饰符的, 默认是public.
public成员, 可以随意调用.
可设置保护范围
scala中的访问修饰符可以指定限定符(qualified).
一个private[X]或protected[X]这样的格式的修饰符, X可以是包, 可以是类, 也可是object.
这样的设置, 可以让你达到Java的protected修饰符的效果(只有本包下可以访问, 哪怕子包也不可以访问), 如我们修改下上面一小节protected成员的例子, 让它可以编译通过:
package p { class Super { protected[p] def f() { println("f") } //指定包名 class inner { (new Super).f() } } class Sub extends Super { f() } class Other { (new Super).f() //这里编译通过了 } }
利用这语法, 可以让你更灵活的表达访问权限, 这是java所不具有的:
package bobsrockets package navigation { private[bobsrockets] class Navigator { protected[navigation] def useStarChart() {} class LegOfJourney { private[Navigator] val distance = 100 } private[this] var speed = 200 } } package launch { import navigation._ object Vehicle { private[launch] val guide = new Navigator } }
Navigator类不修饰为private[bobsrockets], 这样只有包bobsrockets下面的类或对象才能访问.
Vehicle对象是不可以访问Navigator, 因为Vehicle对象是在launch包下, 那怕import navigation包, 也是不可以访问Navigator类
getter与setter
这一节, 感觉不太严谨, 因为都不是java概念中getter和setter, 是硬套进去的概念. 更多是上一节的访问修饰符的概念.
JVM会考虑以下情况getter与setter.
使用了private来修饰的field, 所以生成的getter和setter是private的.
class Student{ private var name="jc" } scala> val jcStu = new Student jcStu: Student = Student@5a3a27eb scala> println(jcStu.name) <console>:13: error: variable name in class Student cannot be accessed in Student println(jcStu.name) scala> jcStu.name = "jc2" <console>:12: error: variable name in class Student cannot be accessed in Student jcStu.name = "jc2" ^ <console>:13: error: variable name in class Student cannot be accessed in Student val $ires1 = jcStu.name
如果定义field是val, 则只会生成getter方法
class Student{ val name="jc" } scala> val jcStu = new Student jcStu: Student = Student@47573529 scala> jcStu.name res9: String = jc scala> jcStu.name = "jc2" <console>:12: error: reassignment to val jcStu.name = "jc2" ^
定义不带private的var field, 此时scala生成的面向JVM的类时, 会定义为private的name字段, 并提供public的getter和setter方法
class Student{ var name="jc" } //调用getter和setter的方法 val jcStu = new Student println(jcStu.name) //其实是调用了getter方法 jcStu.name = "jc2" //其实是调用了setter方法
如果不希望生成setter和getter方法, 则将field生命为private[this]
class Student{ private[this] var name="jc" } scala> val jcStu = new Student jcStu: Student = Student@4ef55cb9 scala> jcStu.name <console>:13: error: value name is not a member of Student jcStu.name ^ scala> jcStu.name = "jc2" <console>:12: error: value name is not a member of Student jcStu.name = "jc2" ^ <console>:13: error: value name is not a member of Student val $ires3 = jcStu.name
getter和setter方法在scala的方法名是name和name_=
class Student{ var nameField="jc" def name={ println("call getName"); nameField} def name_= (newName:String){ println("modify name, old name="+nameField);nameField=newName} } scala> val jcStu = new Student jcStu: Student = Student@e90dc68 scala> jcStu.name call getName res11: String = jc scala> jcStu.name="jc2" modify name, old name=jc call getName jcStu.name: String = jc2 //可以看出, name已经成为方法的名, 而字段name被改为nameField, 感觉getter和setter就是普通的方法, 与nameField关系不大了. 而且与java不一样, getter方法参数不能与字段同名, 不过下面的构造方法可以使用this
自定义getter与setter
class Student{ var nameField="jc" def name={ println("call getName"); nameField} def name_= (newName:String){ println("modify name, old name="+nameField);nameField=newName} } //在上面这个例子里, 其实存在两个getter和setter方法, getter方法分别是nameField和name, setter方法是nameField和和name_ 所以在这里, 我们想控制getter和setter的话, 需要把字段改为private class Student{ private var nameField="jc" def name={ println("call getName"); nameField} def name_= (newName:String){ println("modify name, old name="+nameField);nameField=newName} } //自定义setter方法的时候一定要注意scala的语法限制, 签名, =, 参数不能有空格
仅暴露field的getter方法
如果你不希望field有setter方法, 可以将field定义为val, 但此时就再也不能修改field的值了.
所以如果你仅仅是想暴露getter方法, 但还可以继续修改field的值, 则需要综合使用private以及自定义getter方法.
当定义field是private时, setter和getter都是private, 对外界没有暴露, 自己可以实现修改field值的方法, 自己可以覆盖getter方法
class Student{ private var myName="jc" //自定义setter方法 def updateName(newName:String){ if(newName == "jc2") myName = newName else println("not accept this new name") } //覆盖setter方法 def name_=(newName:String){ myName=newName} def name="your name is " + myName }
上述这例子, 虽然field名已经不是name了, 但可以对客户端来说, 我们的field就是name, 所以就可以呈现出name是个字段
看来scala的getter方法和setter方法的理解, 和java不太一样.
private[this]的使用
如果field使用private来修饰, 那么代表这个field是私有的, 在类的方法中, 可以直接访问类的其他对象的private field.
这种情况下, 如果不希望field被其他对象访问到, 那么可以使用private[this], 意味着对象私有的field, 只有本对象内可以访问到
class Student{ private var myAge=0 def age_=(newValue: Int){ if(newValue>0) myAge = newValue else print("illegal age!") } def age = myAge def older(s:Student)={ myAge > s.myAge } } scala> var jc = new Student jc: Student = Student@384a1d47 scala> jc.age=29 jc.age: Int = 29 scala> var jay = new Student jay: Student = Student@1a5b443b scala> jay.age=38 jay.age: Int = 38 scala> jc.older(jay) res0: Boolean = false
上面例子可以直接访问getter方法, 而下面这个就不可以访问myAge字段, 编译会失败
class Student{ private[this] var myAge=0 def age_=(newValue: Int){ if(newValue>0) myAge = newValue else print("illegal age!") } def age = myAge def older(s:Student)={ myAge > s.myAge } } <pastie>:21: error: value myAge is not a member of Student myAge > s.myAge ^
Java风格的getter和setter方法
scala的getter和setter方法的命名与java的是不同的, scala是field和field_=的方式
如果要让scala自动生成java风格的getter和setter方法, 只要field添加@BeanProperty注解即可
此时会生成4个方法, name:String, name_=(newValue:String):Unit, getName():String, setName(newValue:String):Unit
import scala.reflect.BeanProperty //这是在2.10.*版本, 2.12版本这个类就不在这个包了 scala> :paste // Entering paste mode (ctrl-D to finish) import scala.beans.BeanProperty //这是在2.12.*版本 class Student{ @BeanProperty var name: String = _ } // Exiting paste mode, now interpreting. import scala.beans.BeanProperty defined class Student scala> val s = new Student s: Student = Student@53449d36 //另一种定义类方式 class Student(@BeanProperty var name:String)
伴生对象
如果有一个class, 还有一个与class同名的object, 那么就称这个object是class的伴生对象, class是object的伴生类
伴生类和伴生对象必须存放在一个scala文件中
伴生类和伴生对象, 最大的特点就在于, 互相可以访问private field, 不过需通过对象.访问
class Person(val name:String, val age:Int){ def sayHello=println("Hi, "+name+", I guess you are "+age+" years old! And you must have "+Person.eyeNum+" eyes") } object Person{ private val eyeNum=2 def getEyeNum = eyeNum }
让object继承抽象类
object的功能其实和class类似, 除了不能定义接受参数的constructor之外, 它也可以继承抽象类, 并覆盖抽象类中的方法
abstract class Hello(var message:String){ def sayHello(name:String):Unit } object HelloImpl extends Hello("HELLO"){ override def sayHello(name:String)={ println(message+","+name) } }
apply方法
object中非常重要的一个特殊方法, 就是apply方法
通常在伴生对象中实现apply方法, 并在其中实现构造伴生类的对象的功能
而创建伴生类的对象时, 通常不会使用new Class的方式, 而是使用Class()的方式, 隐式地调用伴生对象的apply方法, 这样会让对象的创建更加简洁.
比如, Array类的伴生对象的apply方法就实现了接受可变参数, 并创建一个Array对象的功能
val a= Array(1,2,3,4,5)
比如, 定义自己的伴生类和伴生对象
class Person(val name:String) object Person{ def apply(name: String) = new Person(name) }
main方法
就跟java一样, 如果要运行一个程序, 就必须有个入口, 就是包含main方法的类; 在scala中, 如果要运行一个应用程序, 那么必须有一个main方法, 作为入口
scala中的main方法定义为def main(args:Array[String]), 而且必须定义在object中
object HelloWorld{ def main(args: Array[String]){ println("Hello World!!!") } }
除了自己实现main方法之外, 还可以继承App Trait, 然后将需要在main方法中允许的代码, 直接作为object的constructor代码; 二期用args可以接受传入的参数
object HelloWorld extends App{ if(args.length>0) println("hello, "+args(0)) else println("Hello World!!") }
用object来实现枚举功能
scala没有直接提供类似于Java中的Enum这样的枚举特性, 如果要实现枚举, 则需要用object继承Enumeration类, 并且调用Value方法来初始化枚举值
object Season extends Enumeration{ val SPRING, SUMEMER, AUTUMN, WINTER = Value } scala> for(ele <- Season.values) println(ele) SPRING SUMEMER AUTUMN WINTER
还可以通过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") } Season(0) Season.withName("spring") scala> for(ele <- Season.values) println(ele) spring summer autumn winter
使用枚举object.values可以遍历枚举值
for(ele <- Season.values) println(ele)
extends
scala中, 让子类继承父类, 跟java一样, 也是使用extends关键字
继承就代表, 子类可以从父类继承父类的field和method, 然后子类可以在自己内部放入父类所有没有的, 子类特有的field和method, 使用继承可以有效复用代码, 玩出更高级的花样时, 那就是设计模式, 不仅仅复用代码, 面向扩展也是很方便, 维护也是很方便, 如你知道是装饰模式, 那么肯定知道有被装饰者和装饰者的存在.
子类可以覆盖父类的field和method, 但是如果父类用final修饰, field和method用final修饰, 则该类是无法被继承, field和method是无法被覆盖的.
class Person{ private var name="jc" def getName= name } class Student extends Person{ private var score = "A" def getSorce = score }
override和super
scala中, 如果子类要覆盖一个父类的非抽象方法, 则必须使用override关键字
override关键字可以帮助我们尽早地发现代码里的错误,在编译器发现错误比运行时发现错误成本要低, 比如: override修饰的父类的方法的方法名我们品写错了, 比如要覆盖的父类方法的参数我没写错了,等等.
此外, 在子类覆盖父类方法之后, 如果我们在子类中就是要调用父类的被覆盖的方法呢? 那就可以使用super关键字, 显式地指定要调用父类的方法
class Person{ private var name= "jc" def getName=name } class Student extends Person{ private var score = "A" def getScore = score override def getName = "Hi, I'm "+super.getName }
override field
scala中, 子类可以覆盖父类的val field, 而且子类的val field还可以覆盖父类的val field的getter方法, 只要子类使用override关键字即可
class Person{ val name:String="Person" def age:Int=0 } class Student extends Person{ override val name:String="jc" override val age:Int=29 }
isInstanceOf和asInstanceOf
如果我们创建了子类的对象, 但是又将其赋值了父类类型的变量, 如果我们有需要将父类类型的变量转为子类类型的变量时, 该怎么做.
首先, 需要使用isInstanceOf判断对象是否是指定类的对象, 如果是的话, 则可以使用asInstanceOf将对象转为指定类型
注意, 如果对象是null, 则isInstanceOf返回false, asInstanceOf返回null
注意, 如果没有isInstanceOf先判断对象是否为指定类的实现, 就直接使用asInstanceOf转换, 可能会出现异常
跟java不一样, java是使用括号强转类型
class Person class Student extends Person val p:Person = new Student var s:Student = null if(p.isInstanceOf[Student]) s = p.asInstanceOf[Student]
getClass和classOf
isInstanceOf只能判断对象是否是指定类以及其子类的对象, 而不能精确判断出, 对象是否是指定类的对象
如果要求精确地判断对象就是指定类的对象, 那么就只能用getClass和classOf了
对象.getClass可以精确获取对象的类, classOf[类]可以精确获取类, 然后使用==操作符即可判断
class Person class Student extends Person val p:Person = new Student p.isInstanceOf[Person] p.getClass == classOf[Person] p.getClass == classOf[Student]
使用模式匹配进行类型判断
这种方式更加简洁明了, 而且代码的可维护性和可扩展性也非常的高
使用模式匹配, 功能性上来说, 与isInstanceOf一样, 也是判断主要是该类以及该类的子类的对象即可, 不是精准判断的
class Person class Student extends Person val p:Person = new Student p match{ case per: Person=> println("it's Person's object") case _=> println("unknow type") }
调用父类的constructor
scala中, 每个类可以有一个主constructor和任意多个辅助constructor, 而每个辅助constructor的第一行都必须是调用其他辅助constructor或者是主constructor; 因此子类的辅助constructor是一定不可能直接调用父类的constructor的.
只能在子类的主constructor中调用父类的constructor, 以下这种语法, 就是通过子类的主constructor调用父类的constructor
注意, 如果是父类中接受的参数, 比如name个age, 子类中接收时, 就不要用任何val和var来修饰了, 否则会认为是子类要覆盖父类的field
class Person(val name:String, val age:Int) class Student(name:String, age:Int, var score:Double) extends Person(name,age){ def this(name:String){ this(name,0,0) } def this(age:Int){ this("jc",age,0) } }
匿名子类
在scala中, 匿名子类是非常常见, 而且非常强大的.
对应着Java的匿名内部类
匿名子类, 可以定一个类的没有名字的子类, 并直接创建其对象, 然后将对象的引用赋予一个变量.之后甚至可以将该匿名子类的对象传递给其他函数.
class Person(protected val name:String){ def sayHello = "Hello, I'm "+name } val p = new Person("jc"){ override def sayHello = "Hi, I'm "+ name } //函数可以指定结构体参数 def greeting(p:Person{def sayHello:String}){ println(p.sayHello) }
抽象类
如果在弗雷中, 有某些方法无法立刻实现, 而需要依赖不同的子类来覆盖, 重写实现自己不同的方法实现. 此时可以将父类中的这些方法不给出具体的实现, 只有方法前面, 这种方法就是抽象方法
而一个类中如果有一个抽象方法, 那么类就必须用abstract来声明为抽象类, 此时抽象类是不可以实例化的
在子类中覆盖抽象类的抽象方法时, 不需要使用override关键字
abstract class Person(val name:String){ def sayHello:Unit } class Student(name:String) extends Person(name){ def sayHello:Unit = println("Hello, "+name) }
抽象field
如果在父类中 ,定义field, 但没有给出初始值, 则此field为抽象field
抽象field意味着, scala会根据自己的规则, 为var或val类型的field生成对应的getter和setter方法, 但是父类中是没有该field的
子类必须覆盖field, 以定义自己的具体field, 并且覆盖抽象field, 不需要使用override关键字
abstract class Person{ val name:String } class Student extends Person{ val name:String = "jc" }
将trait作为接口使用
scala中Trait是一种特殊的概念
首先我们可以将Trait作为接口来使用, 此时的Trait就与java中的接口非常类似了
在Trait中可以定义抽象方法, 就与抽象类中的抽象方法一样, 只要不给出方法的具体实现即可
类可以使用extends关键字继承Trait, 注意, 这里不是implement, 而是extends, 在scala中没有implement的概念, 无论继承类还是Trait, 统一都是extends
类继承Trait后, 必须实现其中的抽象方法, 实现时不需要使用override关键字
scala不支持类进行多继承, 但是支持多重继承Trait, 使用with关键字即可
trait HelloTrait{ def sayHello(name:String) } trait MakeFriendsTrait{ def makeFriends(p: Person) } class Person(val name:String) extends HelloTrait with MakeFriendsTrait with Cloneable with Serializable{ def sayHello(name: String) = println("Hello, "+name) def makeFriends(p: Person) = println("Hello, my name is "+name+", your name is" +p.name) }
在Trait中定义具体方法
scala中的Trait可以不是只定义抽象方法, 还可以定义具体方法, 此时Trait更像是包含了通用的工具方法的东西
有一个专有的名词来形容这种情况, 就是说Trait的功能混入了类
Java8 也支持这一功能, 叫默认方法
举例来说, Trait中可以包含一些很多类都通用的功能方法, 比如日志打印等, spark中就用了Trait来定义通用的日志打印方法
trait Logger{ def log(message: String) = println(message) } class Person(val name:String) extends Logger{ def makeFriends(p:Person){ println("Hi, I'm " + name + ", I'm glad to make friends with you, " + p.name) log("makeFriends method is invoked with parameter Person[name="+p.name+"]") } }
在Trait中定义具体字段
scala中的Trait可以定义具体field, 此时继承Trait的类就自动获得了Trait中定义的field
但是这种获取field的方式与继承class是不同的: 如果是继承clas获取field, 实际是定义在父类中, 而继承Trait获取的field, 就直接被添加到子类中
trait Person{ val eyeNum: Int = 2 } class Student(val name:String) extends Person{ def sayHello = println("Hi, I'm " + name +", I have " + eyeNum +" eyes.") }
在Trait中定义抽象字段
scala中的Trait可以定义抽象的field, 而Trait中的具体方法则可以基于抽象field来编写
但是继承Trait的类, 则必须覆盖抽象field, 提供具体值
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!") } }
为实例混入Trait
有时候我们可以在创建类的对象时, 指定该对象混入某个Trait, 这样, 就只有这个对象混入该Trait的方法, 而类的其他对象则没有
trait Logger{ def log(msg:String) {} } trait MyLogger extends Logger{ override def log(msg:String) {println("log: "+ msg)} } class Person(val name:String) extends Logger{ def sayHello{println("Hi, I'm "+ name); log("sayHello is invoked!")} } val p1 = new Person("jc") p1.sayHello val p2 = new Person("jay") with MyLogger p2.sayHello
Trait调用链
scala中支持让类继承多个Trait后, 依次调用多个Trait中的同一个方法, 只要让多个Trait的同一个方法中, 在最后都执行super.方法 即可
类中调用多个Trait中都有的这个方法是, 首相会从最右边的Trait的方法开始执行, 然后依次往左执行, 形成一个调用链条
这种特性非常强大, 其实就相当于设计模式中的责任链模式的一种具体实现依赖
trait Handler{ def handle(data:String){} } trait DataValidHandler extends Handler{ override def handle(data:String){ println("check data:"+data) super.handle(data) } } trait SignatureValidHandler extends Handler{ override def handle(data: String){ println("check signature:" +data) super.handle(data) } } //注: 不是靠super调用上级产生调用链, 而是Trait的特性 class Person(val name:String) extends SignatureValidHandler with DataValidHandler{ def sayHello={println("Hello, "+name);handle(name)} }
在Trait中覆盖抽象方法
在Trait中, 是可以覆盖父Trait的抽象方法的
但是覆盖时, 如果使用了super.方法的代码, 则无法通过编译. 因为super.方法就会去掉用父Trait的抽相反, 此时子Trait的该方法还是会被认为是抽象的
此时如果要通过编译, 就得给子Trait的方法加上abstract override修饰
trait Logger{ def log(msg:String) } trait MyLogger extends Logger{ abstract override def log(msg:String) {super.log(msg)} }
混合使用Trait的具体方法和抽象方法
在Trait中, 可以混合使用具体方法和抽象方法
可以让具体方法依赖于抽象方法, 而抽象方法则放到继承Trait的类中去实现
这种Trait其实就是设计模式中的模板设计模式的体现
trait Valid{ def getName:String def valid:Boolean={ getName == "jc" } } class Person(val name:String) extends Valid{ println(valid) def getName = name }
Trait的构造机制
在scala中, trait也是有构造代码的, 也就是在trait中的, 不包含在任何方法中的代码
而继承了trait的类的构造机制如下:
- 父类的构造函数执行
- Trait的构造函数执行, 多个Trait从左到右依次执行
- 构造Trait时会先构造父类Trait, 如果多个Trait继承同一个父Trait, 则父Trait只会构造一次
- 所有Trait构造完毕后, 子类的构造函数执行
class Person{println("Person's constructor!")} trait Logger{println("Logger's constructor!")} trait MyLogger extends Logger{println("MyLogger's constructor!")} trait TimeLogger extends Logger{println("TimeLogger's constructor!")} class Student extends Person with MyLogger with TimeLogger{ println("Student's constructor!") }
Trait字段的初始化
在scala中, Trait是没有接收参数的构造函数的, 这是Trait和class的唯一区别, 但是如果需求就是要trait能够对field进行初始化, 该怎么办? 只能使用scala中非常特殊的一种高级特性:提前定义
trait SayHello{ val msg:String println(msg.toString) } class Person val p = new {val msg:String="init"} with Person with SayHello class Person extends { val msg:String = "init"} with SayHello{} //另外一种方式就是使用lazy value trait SayHello{ lazy val msg:String = null println(msg.toString) } class Person extends SayHello{ override lazy val msg:String = "init" }
Trait继承class
在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) } }