scala和java都是可以运行在JVM上, 所以scala和java是可以互相调用, 那么问题来了, 既然已经有java语言存在, 为什么还要发明scala语言. 存在即合理, 所以我就想找下scala的合理之处. 其中一个, 那就是把java的繁琐的语法给尽量去掉. 让程序员敲代码的速度变快, 同时也方便阅读和修改(这有点不太准确, 是因为得看站在什么角度).
基于上面的理论, 并带着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)
}
}