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@6155fa78scala> s.name
res13: String = jcscala> 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@5ed1f76dscala> s.name
<console>:14: error: value name is not a member of Students.name^scala> s.age
<console>:14: error: value age is not a member of Students.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@6db3cc5bscala> s.name
<console>:14: error: value name is not a member of Students.name^scala> s.age
<console>:14: error: value age is not a member of Students.age

辅助constructor

scala中, 可以给类定义多个辅助constructor, 类似与java中的构造函数重载
辅助constructor之间可以互相调用, 而且必须第一行调用主constructor

class Student{private var name=""private var age=0def 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=2println("this Person Object!")def getEyeNum=eyeNum
}

内部类

scala中, 同样可以在类中定义内部类, 但是与java不同的是, 每个外部类的对象的内部类, 都是不同的类

scala> :paste
// Entering paste mode (ctrl-D to finish)import scala.collection.mutable.ArrayBufferclass 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 Outerscala> val out1 = new Outer
out1: Outer = Outer@5479b0b1scala> val inner1 = out1.getInner("inner1")
inner1: out1.Inner = Outer$Inner@7161bdc3scala> out1.inners+=inner1
res3: out1.inners.type = ArrayBuffer(Outer$Inner@7161bdc3)scala> val out2 = new Outer
out2: Outer = Outer@54c2ca03scala> val inner2 = out2.getInner("inner2")
inner2: out2.Inner = Outer$Inner@747a5354scala> out1.inners+=inner2
<console>:17: error: type mismatch;found   : out2.Innerrequired: out1.Innerout1.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 = 0def 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 bobsrocketspackage 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@5a3a27ebscala> println(jcStu.name)
<console>:13: error: variable name in class Student cannot be accessed in Studentprintln(jcStu.name)
scala> jcStu.name = "jc2"
<console>:12: error: variable name in class Student cannot be accessed in StudentjcStu.name = "jc2"^
<console>:13: error: variable name in class Student cannot be accessed in Studentval $ires1 = jcStu.name

如果定义field是val, 则只会生成getter方法

class Student{val name="jc"
}
scala> val jcStu = new Student
jcStu: Student = Student@47573529scala> jcStu.name
res9: String = jcscala> jcStu.name = "jc2"
<console>:12: error: reassignment to valjcStu.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@4ef55cb9scala> jcStu.name
<console>:13: error: value name is not a member of StudentjcStu.name^scala> jcStu.name = "jc2"
<console>:12: error: value name is not a member of StudentjcStu.name = "jc2"^
<console>:13: error: value name is not a member of Studentval $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@e90dc68scala> jcStu.name
call getName
res11: String = jcscala> 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 = newNameelse 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=0def age_=(newValue: Int){if(newValue>0) myAge = newValueelse print("illegal age!")}def age = myAgedef older(s:Student)={myAge > s.myAge}
}
scala> var jc = new Student
jc: Student = Student@384a1d47scala> jc.age=29
jc.age: Int = 29scala> var jay = new Student
jay: Student = Student@1a5b443bscala> jay.age=38
jay.age: Int = 38scala> jc.older(jay)
res0: Boolean = false

上面例子可以直接访问getter方法, 而下面这个就不可以访问myAge字段, 编译会失败

class Student{private[this] var myAge=0def age_=(newValue: Int){if(newValue>0) myAge = newValueelse print("illegal age!")}def age = myAgedef older(s:Student)={myAge > s.myAge}
}<pastie>:21: error: value myAge is not a member of StudentmyAge > 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 Studentscala> 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=2def 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 = scoreoverride 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 Studentp 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:Stringdef 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:Stringdef valid:Boolean={getName == "jc"}
}class Person(val name:String) extends Valid{println(valid)def getName = name
}

Trait的构造机制

在scala中, trait也是有构造代码的, 也就是在trait中的, 不包含在任何方法中的代码
而继承了trait的类的构造机制如下:

  1. 父类的构造函数执行
  2. Trait的构造函数执行, 多个Trait从左到右依次执行
  3. 构造Trait时会先构造父类Trait, 如果多个Trait继承同一个父Trait, 则父Trait只会构造一次
  4. 所有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:Stringprintln(msg.toString)
}class Personval p = new {val msg:String="init"} with Person with SayHelloclass Person extends { val msg:String = "init"} with SayHello{}//另外一种方式就是使用lazy value
trait SayHello{lazy val msg:String = nullprintln(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)}
}

2018年第44周-scala入门-面向对象基础语法相关推荐

  1. 【JAVA学习】1、零基础入门Java 基础语法:概念、规则、格式

    [JAVA学习]1.零基础入门Java 基础语法 前言 JAVA开发环境选择 Java 基础概念 Java 标识符 Java修饰符 基础代码模板框架 命名规则 基础规则 后端开发规则 代码格式 前言 ...

  2. Python入门之基础语法

    Python入门之基础语法 行与缩进 #有错误的函数1 def wrong1():print("wrong1")print("这里有一个错误缩进")#有错误的函 ...

  3. 【C++快速入门】基础语法篇

    C++基础语法 C++介绍 cin.cout 函数重载(Overload) 默认参数 extern "C" #pragma once 内联函数(inline function) 内 ...

  4. 复习Java入门与基础语法生活【记录一个咸鱼大学生三个月的奋进生活】002

    复习Java(入门) 既然是入门那就必须讲个故事了:   其实Java是James Gosling为了跨平台而创造的,当时(1991年)SUN公司想在智能家电中大展宏图,结果发现想完成智能家电用C是不 ...

  5. Python基础入门_2基础语法和变量类型

    Python 基础入门系列第二篇,上一篇简单介绍了为什么用 Python,以及安装和配置环境. 这一篇将先介绍基础的语法,包括标识符,即变量名字,然后 Python 特色的缩进规则,注释.保留字等等, ...

  6. Python入门 —— 02基础语法

    基础语法入门学习推荐: 简明 Python 教程 下文仅为入门推荐书籍的补充与重点 多行语句:末尾使用斜杠 (  ) ,将一行分为多行 var = item1 + item2 + item3 注释: ...

  7. Kotlin快速入门:基础语法、类与继承、集合

    参考书籍: <AtomicKotlin>,全英文,读起来慢,但是写得很详细,没有任何语言基础的也能看懂 <第一行代码 Android 第三版>,第二章,有java基础的看一看就 ...

  8. scala学习笔记-基础语法(1)

    Scala与Java的关系 Scala与Java的关系是非常紧密的!! 因为Scala是基于Java虚拟机,也就是JVM的一门编程语言.所有Scala的代码,都需要经过编译为字节码,然后交由Java虚 ...

  9. python输入一个整数和一个字符_【python零基础入门】基础语法之变量、字符串、数字、规则。...

    本文使用版本python3.8.1,编辑器pycharm. 学习指导书<Python编程--从入门到实践> 1.万恶之首"Hello world" 输入: print( ...

最新文章

  1. ETL 工具下载全集 包括 Informatica Datastage Cognos( 持续更新)
  2. SourceTree 教程文档(了解界面)
  3. 对于C#里面的this与base
  4. OpenGL Shadow Mapping阴影贴图的实例
  5. .NET6发布了Preview2,这点超越过去10年!
  6. 温柔又有耐心的男孩最吸引人
  7. CentOS上安装mysql5.5.23
  8. mysql物理删除索引_Oracle与MySQL删除字段时对索引和约束的处理
  9. 云服务售后服务调研,参与问卷送福利!
  10. 微信小程序 — 生成二维码功能
  11. html怎么设置字体为微软雅黑,css如何设置字体为微软雅黑
  12. 2022智源大会议程公开 | 视觉模型论坛
  13. Linux下的Job Control
  14. C++图像处理 -- 图像翻转(镜像)
  15. 对于给定的一个百分比制成绩,输出相应的五分制成绩
  16. 多传感器融合定位(一)——3D激光里程计
  17. 高斯定理证明(HTML)
  18. ISLR读书笔记(1)统计学习简介
  19. Xilinx原语使用方法
  20. Nginx+Tomcat实现负载均衡与动静分离

热门文章

  1. Auto.js Pro安卓免ROOT引流脚本开发系列教程26网易公开课(4)-关注用户
  2. Excel中怎么把经纬度记录转成经纬度数值形式
  3. Oracle Acs资深顾问罗敏 老罗技术核心感悟:11g的数据压缩技术
  4. 推荐这三款亲测好用的ai工具
  5. 微信修改基本配置信息
  6. 不足100克按100克C语言,C语言_第3章.ppt
  7. 报错:SLF4J: Failed to load class “org.slf4j.impl.StaticLoggerBinder“.
  8. 白皮书显示,2022年仅有28.4%企业实现社保基数完全合规,有38.1%企业额外购买补充商业保险 | 美通社头条...
  9. 三相电流滞环跟踪PWM控制
  10. java中私有变量和方法_Java 私有变量和私有方法