1.类的定义
class 列名(参数列表){ //构造器:列名(构造参数列表)
成员变量(字段) var /val
成员方法(方法) def,方法的参数都是val
辅助构造器: //辅助构造器:this(构造参数列表),类似Java的构造方法重载,构造器都没有返回值
}
//*************************分割线*************************************
eg:
class Dog(val name: String,var age){ //自动生成类的属性
def cry()=println("wow ...")
}
val dog = new Dog("Harry",2)
dog.name="Wang" //编译错误
dog.age=3 //编译通过
主构造器中,使用 val的定义的变量自动生成 getter,var 定义的变量自动生
成 getter&setter。在 Scala 中,严格来说是没有 getter 和 setter 这个说法,使用了“value”和“value_=”两个方法来分别代替了 getter 和 setter。如下所示。
class Counter(var value:Int)
class Counter{
private var privateValue = 0;//私有变量,外界无法直接访问
def value = privateValue;//定义一个方法,代替 getter
def value_= ( newValue : Int ){// value_= 是方法名字,代替 setter
value = newValue;
} //注意,scala 中默认方法是 public 的
}
代码样列
class Student{
//定义成员变量
var name = "张三"
var age = 19
var gender:String =_ //null
val banji = "1班"
//定义一个辅助构造器
def this(name:String,age:Int)={
//辅助构造器必须从调用其他构造器开始
this()
this.name = name
this.age = age
}
//定义一个辅助构造器2
def this(gender:String){
this("李四",12)
this.gender = gender
}
//定义一个成员方法
def study()={
println("成员方法")
}
}
object Test{
main{ //def main(args: Array[String]): Unit =
val stu1 = new Student()
println(stu1.name)
println(stu1.study())
val stu2 = new Student("张三",20)
println(stu2.name)
println(stu1.study())
stu2.age = 20
println(stu2,age)
println(stu2.banji)
stu2.banji ="2班" //报错
}
}
2 . 类实例化
使用 new 关键字实例化一个类
var p = new Point() //不能省略()
注意 var 和 val 实例的区别,val p修改变量对应对象值的内容,而不能更改对象的引用
3.类的继承
extends 关键字:子类继承父类
override 关键字:子类覆盖父类的方法
代码样例:
class XiaoXueShen extends Student{
override def study()={
print("我爱学习")
}
def play()={
print("我爱打游戏")
}
}
object Text02{
main(
val xiao = new XiaoXueShen
xiao.banji.prt //println(xiao.banji)
xiao.study()
xiao.play()
xiao.isInstanceOf[Student] //true
//isInstanceOf[T]判断是否为某一类型,asInstanceOf[T]进行强制类型转换。
val x =new XiaoXueShen
x.isInstanceOf[Studet] //true
//Scala中的向上转型与向下转型与 Java 一样
)
}
4.抽象类
abstract修饰类,称为抽象类,未实现的方法称为抽象方法。抽象类不允许实例化
与 Java 类似,Scala 一个类中允许存在未实现的方法,此时应该使用 abstract修饰类,称为抽象类,未实现的方法称为抽象方法。抽象类不允许实例化。
与 Java 不同的是,Scala 中的抽象方法不需要使用 abstract 修饰。 当子类重写父类方法时,使用 override 关键字,想要某个类成员不被子类重
写,可以给成员添加 final 修饰符实现。如果整个类都想有子类,直接将类修饰为 final class。
子类中的方法要覆盖父类中的抽象方法,必须写 override。
子类中的属性(val)要覆盖父类中的属性(val),必须写 override。
注意,无论父类是抽象类还是普通类,使用 override 时不允许重写父类中的变量(var)。如下代码所示:
abstract class Parent(var name:String){
var age:Int=0
//省略方法声明
}
//运行错误
class Child(override var name:String,override var age:Int) extends Parent(name)
正确写法:
class Parent(val name:String){
val age:Int=0
//省略方法定义
}
class Child(override val name:String,override val age:Int) extends Parent(name)
代码样例
创建Shape.scala
//定义一个抽象类,使用abstract修饰
abstract class Shape{
def draw():Unit
def write()={
print("我爱写代码")
}
}
创建Circle.scala
class Circle extends Shape{
override def draw():Unit={
print("我爱小电影")
}
}
object Test3{
main{
val circle = new Cricle
circle.draw()
circle.write()
}
}
5.单例对象
5.1 object 与 class
Scala 比 Java 更加面向对象的特点之一是,取消了 static 关键字,因为类方法不是对象的方法。取而代之的是单例对象,具体是使用object代替class即可,object本身及其任何成员的定义与 `class`一致。
本质上,单例对象是一种特殊的类,有且只有一个实例。单例对象在第一次
使用时被创建,单例对象不需要也不能再被实例化,单例对象中的所有成员都可以直接访问。应该注意到 main 方法必须定义在单例对象中。
object HelloWorld {
println("我被创建")
def main(args: Array[String]): Unit = {
println("HelloWorld")
}
println("我被执行")
}
通常也使用更简便的方式创建应用启动单例对象。
object HelloWorld extends App{
println("Hello World")
}
小结:在 Scala 中,类和单例对象的差别在于,单例对象不能带参数,而类可以。因为单例对象无法使用 new 关键字实例化,也就没有办法为它传递实例化参数
eg2:
object Blah {
println("initalizing")
def sum(l:List[Int]):Int=l.sum
def main(args: Array[String]): Unit = {
//单例对象可以通过单例对象名调用
println(Blah.sum(List(1, 2, 3, 4, 5)))
}
}
5.2 伴生
伴生(companion)
1.伴生关系
限制条件:object(单例对象)与 class(普通类)同名且在一个文件中。例
如 Student.scala:
//伴生类
class Student(n:String,a:Int){
private var name = n //私有变量,伴生对象可以访问
private var age = a
}
//伴生对象
object Student{
def apply(n:String,a:Int):Student = new Student(n,a)
main{
val stu = Student("Jason",9) //通过伴生对象的apply()方法创建实列
stu.name.prt
}
}
一旦建立伴生关系,伴生对象与伴生类可相互访问私有成员。伴生对象主要为伴生类提供一种静态成员访问的途径。
2.伴生类
指在伴生关系中的伴生类,在 class(伴生类)中访问 object(伴生对象)的私有属性,使用“object名.成员”。
3.伴生对象
指在伴生关系中的单例对象,在 object(伴生对象)中访问 class(伴生类)的私有属性,必须通过 class实例对象访问。如上面的“stu.name”。
在 Scala 中,apply()方法有着特殊的含义。对于函数来说即调用函数自身。
例如:
val f = (x: Int) => x + 1
f.apply(1)
f(1) //等价于 f.apply(1)
同样的,在单例对象中,以下写法也是 apply()方法的调用。
def apply(n: String, a: Int): Student = new Student(n, a)
val stu=Student("Jason",9) //等价于val stu=Student.apply("Jason",9)
伴生对象可以继承伴生类,此时伴生类中的所有成员变为伴生对象中的静态成员,可通过伴生对象直接访问。如下代码所示。
class Student(n: String, a: Int){ //伴生类
private var name = n
private var age = a
override def toString: String = {
s"name:$n,age:$a "
}
}
//伴生对象
object Student extends Student("Jason",9){
def main(args: Array[String]): Unit = {
println(Student.toString)
}
}
6.特质
特质封装了字段和方法的定义,并可以混入到类中重用。每个类只能继承一个超类,但可以混入多个特质。定义特质使用 trait关键字,其它与类定义相似,
使用 extends 或 with 关键字可将特质混入类中。对象也可以使用 with 混入特质,称为动态混入。
特质像带有具体方法的 Java 接口,在特质中实现一次方法,而不再需要在每个混入特质的类中重新实现。
特质也像抽像类,但抽象类只能继承一次,而特质可以混入多次
1.特质定义不能带有参数
class Point(x:Int,y:Int) //编译通过
trait NoPoint(x:Int,y:Int) //编译错误
代码样例:
//创建一个PET特质
trait Pet{
val name:String
def cry():Unit
}
class Cat() extends Pet{
override val name:String = "李四"
override def cry():Unit={
print("光棍")
}
}
class Dog(val name:String) extends Pet{
override def cry():Unit={
print("狗")
}
}
object Test6 extends App{
val dog = new Dog("李四")
print(dog.name)
dog.cry()
val cat =new Cat
print(cat.name)
cat.cry()
}
2.特质对 super 的动态调用
class Root{
def hello(){
println("Hello,Root!")
}
}
class SubA extends Root{
override def hello() {
super.hello() //静态调用
println("Hello,SubA!")
}
}
trait D extends Root{ //Root 是类,只能混入到 Root 及其子类中
def traitHello(){
super.hello(); //动态调用
}
}
在 SubA 中,super 的调用是静态绑定的,父类 Root 的 hello()将被调用。而在 D 中,super 的调用是动态绑定的,即在定义特质 D 的时候,super 还不确定,直到特质被混入到具体类的时候才确定。
val a=new Root with D //D 被混入 Root 时,super 为目标类 Root
a.hello() //调用 Root 类的 hello()
又如:
val a=new SubA with D //D 被混入 SubA 时,super 为目标类 SubA
a.hello() //调用 SubA 类的 hello()
3.特质的构造顺序
特质也可以有构造器,由字段的初始化和其他特质体中的语句构成。这些语
句在任何混入该特质的对象在构造时都会被执行。构造器的执行顺序:
1)调用超类的构造器;
2)特质构造器在超类构造器之后、类构造器之前执行;
3)特质由左到右被构造;
4)每个特质当中,父特质先被构造;
5)如果多个特质共有一个父特质,父特质不会被重复构造
6)所有特质被构造完毕,子类被构造。
object MixinTrait extends App {
val d = new D
println(d.message)
println(d.mes_toUpper)
}
//定义一个类
abstract class A{
val message:String
}
//B继承抽象类A
class B extends A{
override val message: String = "this is B"
}
//定义一个特质C,继承A
trait C extends A{
def mes_toUpper=message.toUpperCase()
}
class D extends B with C
5.特质线性化
线性化给了在多个特质中 super 被解析的顺序。假设有一个类 Cat,继承自
超类 Animal 以及两个特质 Furry 和 FourLegged。 FourLegged 又扩展了另一个
特质 HasLegs:
class Animal
trait Furry extends Animal
trait HasLegs extends Animal
trait FourLegged extends HasLegs
class Cat extends Animal with Furry with FourLegged
类 Cat 的继承层级和线性化次序展示在下图。继承次序使用传统的 UML
标注指明:白色箭头表明继承,箭头指向超类型。黑色箭头说明线性化次序,箭
头指向 super 调用解决的方向
Cat 的线性化次序为:Cat>>FourLegged>>HasLegs>>Furry>>Animal。具体计算
方法:如果 C extends c1 with c2 with c3,则:
lin(C) = C >> lin(c3) >> lin(c2) >> lin(c1)
线性化第一个类是自己,这里“>>”意思是 “串接并去掉重复项, 右侧胜
出”,如上面的 Cat 类:
class Cat extends Animal with Furry with FourLegged
lin(Cat) = Cat>>lin(FourLegged)>>lin(Furry)>>lin(Animal)
= Cat>>(FourLegged>>HasLegs)>>(Furry>>Animal)>>(Animal)
= Cat>>FourLegged>>HasLegs>>Furry>>Animal
6.特质调用链
Scala 中支持让类或对象混入多个 trait 后,可依次调用多个 trait 中的同一个
方法,只要让多个 trait 中的同一个方法,在最后都依次执行 super 关键字即可。
类中调用多个 trait 中都有的这个方法时,首先会从最右边的 trait 的方法开始执
行,然后依次往左执行,形成一个调用链。如下所示。
object TraitDemo2 {
def main(args: Array[String]): Unit = {
val t1 = new TraitTest with TraitA with TraitB
t1.log("msg...")
}
}
trait TraitSuper{
def log(msg:String){
println("TraitSuper 被调用")
println(msg)
}
}
trait TraitA extends TraitSuper{
override def log(msg:String){
println("TraitA 被调用")
super.log("TraitA: "+msg)
}
}
trait TraitB extends TraitSuper{
override def log(msg:String){
println("TraitB 被调用")
super.log("TraitB: "+msg)
}
}
class TraitTest {}
输出结果:
TraitB 被调用
TraitA 被调用
TraitSuper 被调用
TraitA: TraitB: msg...
小结:越靠近后面的特质越优先起作用,当调用带混入的类的方法时,最右侧特质的方法首先被调用。如果那个方法调用了 super,则它调用其左侧特质的方法
内部类
一个类可以作为另一个类的成员,称为内部类
Scala内部类绑定到外部类的对象实例
class Graph {
class Node {
var connectedNodes: List[Node] = Nil
def connectTo(node: Node) {
if (connectedNodes.find(node.equals).isEmpty) {
connectedNodes = node :: connectedNodes
}
}
}
var nodes: List[Node] = Nil
def newNode: Node = {
val res = new Node
nodes = res :: nodes
res
}
}
val g: Graph = new Graph
val n1: g.Node = g.newNode
val n2: g.Node = g.newNode
n1.connectTo(n2) // legal
val h: Graph = new Graph
val n3: h.Node = h.newNode
n1.connectTo(n3) // illegal!
7.依赖注入
1 .自身类型(self-type)也称自类型:this:T=> 其中,this 也可替换为 self 或其它不是关键字的别名。自身类型在特质和类中使用具不同含义。
1.trait 中的自身类型
在特质中表示指定了可以混入的类的超类,换句话说,当特质指定自身类型时,可以保证它只能混入该类的子类。示例:
trait A {
this: B=> //指定了特质 A 可以混入 B 的子类,使得 A 拥有 B 的方法
}
class B
class C extends B with A //编译通过
class D extends A //编译错误
2.class 中的自身类型
在类中的自身类型使得类抽象化,该类在实例化时必须混入指定的特质。
class A{
this:B=> //A 在实例化时必须混入 B 或 B 子特质,且不含抽象成员
}
trait B{
val a:Int
}
trait C extends B{
override val a: Int = 0
}
trait D
//实例化 A
val a1=new A with B //编译错误,B 中含有抽象成员
val a2=new A with C //编译通过
val a3=new A with D //编译错误,D 不是 B 的子类型
2 动态混入特质
格式: self=> //注意与自身类型写法区别,this:Type=> 自身类型,表示该类实例化时必须混入相应特质或子特质,self是this的别名
其中“self”可以是除 this 外的任何非关键字,作用与 this 等价。
class A {
self => // this 别名
val x = 2
def foo = self.x + this.x
}
代码样例:动态混入
class Drawing {
//实现注入
self:Shaping=>
def start():Unit=draw()
}
trait Shaping{
def draw():Unit
}
trait DrawSquare extends Shaping{
override def draw(): Unit = {println("drawing a square")}
}
trait DrawTriangle extends Shaping{
override def draw(): Unit = {println("drawing a triangle")}
}
object Test08 extends App {
val d1 = new Drawing with DrawSquare
d1.start()
val d2 = new Drawing with DrawTriangle
d2.start()
}
依赖注入
依赖注入所做的事情就是通过构造器或 setter 方法将依赖对象(当前对象所
依赖的对象)注入到当前对象中,实现组件间的解耦。关键点是依赖对象的创建
由第三方完成。
在 Scala 中可以通过自身类型实现依赖注入。下面示例是自身类型在依赖注
入中的应用。
trait Logger {
def log(msg: String)
}
trait Auth {
auth: Logger => // 别名 auth
def act(msg: String) {
//调用被依赖的对象 Logger 的 log 方法
auth.log(msg)
}
}
object DI extends Auth with Logger {
//重写父类的方法
override def log(msg: String) = println(msg);
}
object DependencyInjection {
def main(args: Array[String]) {
// DI 为静态对象,执行结果:dependency injection
DI.log("dependency injection")
}
}
8、样例类
使用 case 修饰符的类称为样例类(case class)。 case 修饰符可以让 Scala 编
译器自动为类添加一些便捷设定。
case class Student(name:String,age:Int) //定义样例类
val stu=Student("Jason",19) //创建样例类的实例,无需 new 关键字
println(stu.name) //访问对象属性
1.自动生成类的 getter 和 setter 访问器
1)构造器中的参数默认获得“val”前缀,只生成 getter
2)可手动显式对参数指定“var”前缀,同时生成 getter&setter
2.自动生成类的伴生对象(单例对象)
在单例对象中实现 apply()方法,如下所示:
def apply(n: String, a: Int): Student = new Student(n, a)
所以可以通过类名“Student("Jason",19)”创建 Student 对象,这也是样例类
实例常用创建方式。
另外实现了一些其它的方法:toString()、equals()、copy()、hashCode()、unapply()
等。特别地 unapply()也称为提取器,在后面的模式匹配详细介绍。
3.样例类与普通的区别:
1)样例类通常用于描述不可变的数据,数据完全依赖构造参数。如果一个
对象在内部执行有状态计算,或者表现出其他类型的复杂行为,那么它应该是一
个普通类。
2)样例类默认可用伴生对象方式创建实例,普通类需要定义 apply()。
3)样例类默认支持模式匹配,普通类需要定义 unapply()。
总的来说,样例类本质还是方便我们创建、操作的普通类而已,可以说编译
器以极小的转换为我们带来了极大的便利,代价便是使用 case 修饰符以及类和
对象会变得稍微大一些。变大的原因是产生了附加的方法以及对每个构造器参数
添加了隐含的前缀,不过样例类最大的好处还是支持模式匹配
//样例类和枚举
创建CaseClassDemo.scala
object CaseClassDemo {
def main(args: Array[String]): Unit = {
val zhangsan = Student("zhangsan",18)
println(zhangsan.age)
}
}
case class Student(name:String,age:Int)
创建Weekday.scala
object Weekday extends Enumeration {
//赋值
//枚举值从0开始计数的
val Mon,Tue,Wen,Thu,Fri,Sat,Sun=Value
}
object Test09{
def main(args: Array[String]): Unit = {
println(Weekday.Mon)
println(Weekday.Mon.id)
Weekday.values.foreach(println)
}
}