8.1 静态属性和静态方法
8.1.1 静态属性-提出问题
有一群小孩在玩堆雪人,不时有新的小孩加入,请问如何知道现在共有多少人在玩?请使用面向对象的思想,编写程序解决

8.1.2 基本介绍
-Scala中静态的概念-伴生对象
Scala语言是完全面向对象(万物皆对象)的语言,所以并没有静态的操作(即在Scala中没有静态的概念)。但是为了能够和Java语言交互(因为Java中有静态概念),就产生了一种特殊的对象来模拟类对象,我们称之为类的伴生对象。这个类的所有静态内容都可以放置在它的伴生对象中声明和调用
8.1.3 伴生对象的快速入门
- object boke_demo01 {
-
- def main(args: Array[String]): Unit = {
-
- println(ScalaPerson.sex) //true 在底层等价于 ScalaPerson$.MODULE$.sex()
- ScalaPerson.sayHi() //在底层等价于 ScalaPerson$.MODULE$.sayHi()
- }
- }
-
- //说明
- //1. 当在同一个文件中,有 class ScalaPerson 和 object ScalaPerson
- //2. class ScalaPerson 称为伴生类,将非静态的内容写到该类中
- //3. object ScalaPerson 称为伴生对象,将静态的内容写入到该对象(类)
- //4. class ScalaPerson 编译后底层生成 ScalaPerson类 ScalaPerson.class
- //5. object ScalaPerson 编译后底层生成 ScalaPerson$类 ScalaPerson$.class
- //6. 对于伴生对象的内容,我们可以直接通过 ScalaPerson.属性 或者方法
-
- //伴生类
- class ScalaPerson { //
- var name: String = _
- }
-
- //伴生对象
- object ScalaPerson { //
- var sex: Boolean = true
-
- def sayHi(): Unit = {
- println("object ScalaPerson sayHI~~")
- }
- }
-对快速入门的案例的源码分析

8.1.4 伴生对象的小结
1) Scala中伴生对象采用object关键字声明,伴生对象中声明的全是“静态”内容,可以通过伴生对象名称直接调用
2) 伴生对象对应的类称之为伴生类,伴生对象的名称应该和伴生类名一致
3) 伴生对象中的属性和方法都可以通过伴生对象名(类名)直接调用访问
4) 从语法角度来讲,所谓的伴生对象其实就是类的静态方法和成员的集合
5) 从技术角度来讲,Scala还是没有生成静态的内容,只不过是将伴生对象生成了一个新的类,实现属性和方法调用[反编译看源码]
6) 从底层原理看,伴生对象实现静态特性是依赖 public static final MOUDLE$ 实现的
7) 伴生对象的声明应该和伴生类的声明在同一个源码文件中(如果不在同一个文件中会运行错误),但是如果没有伴生类,也就没有所谓的伴生对象了,所以放在哪里就无所谓了
8) 如果 class A 独立存在,那么A就是一个类,如果 Object A 独立存在,那么A就是一个“静态”性质的对象[即类对象],在 Object A 中声明的属性和方法可以通过 A.属性和A.方法 来实现调用
9) 当一个文件中,存在半生类和伴生对象时,文件的图标会发生变化
8.1.5 最佳实践-使用伴生对象完成小孩堆雪人游戏
设计一个var total Int 表示总人数,我们在创建一个小孩时,就把total加1,并且total是所有对象共享的就ok了,使用伴生对象来解决
- object boke_demo01 {
-
- def main(args: Array[String]): Unit = {
- //创建三个小孩
- val child0 = new Child("铁蛋")
- val child1 = new Child("狗蛋")
- val child2 = new Child("熊大")
- Child.joinGame(child0)
- Child.joinGame(child1)
- Child.joinGame(child2)
- Child.showNum()
- }
- }
-
- class Child(cName: String) {
- var name = cName
- }
-
- object Child {
- //统计共有多少小孩的属性
- var totalChildNum = 0
-
- def joinGame(child: Child): Unit = {
- printf("%s 小孩加入了游戏\n", child.name)
- //totalChildNum 加1
- totalChildNum += 1
- }
-
- def showNum(): Unit = {
- printf("当前有%d小孩玩游戏\n", totalChildNum)
- }
- }
8.1.6 伴生对象-apply方法
在伴生对象中定义apply方法,可以实现:类名(参数)方式来创建对象实例
- object boke_demo01 {
-
- def main(args: Array[String]): Unit = {
- val list = List(1, 2, 5)
- println(list)
-
- val pig = new Pig("狗蛋")
-
- //使用apply方法来创建对象
- val pig2 = Pig("铁蛋") //自动 apply(pName: String)
- val pig3 = Pig() // 自动触发 apply()
-
- println("pig2.name=" + pig2.name) //小黑猪
- println("pig3.name=" + pig3.name) //匿名猪猪
- }
- }
-
- //案例演示apply方法.
- class Pig(pName: String) {
- var name: String = pName
- }
-
- object Pig {
- //编写一个apply
- def apply(pName: String): Pig = new Pig(pName)
-
- def apply(): Pig = new Pig("匿名")
- }
8.2 单例对象
这个部分将在Scala设计模式专题进行介绍
8.3 接口
8.3.1 回顾Java接口
-声明接口
interface接口名
-实现接口
class 类名 implements 接口1,接口2
-Java接口的使用小结
1) 在Java中,一个类可以实现多个接口
2) 在Java中,接口之间支持多继承
3) 接口中属性都是常量
4) 接口中的方法都试抽象的
8.3.2 Scala接口的介绍
1) 从面向对象来看,接口并不属于面向对象的范畴,Scala是纯面向对象的语言,在Scala中,没有接口
2) Scala语言中,采用特质trait(特征)来代替接口的概念,也就是说,多个类具有相同的特质(特征)时,就可以将这个特质(特征)独立出来,采用关键字trait声明。理解trait等价于(interface+abstract class)
3) Scala继承特质(trait)的示意图

8.3.3 trait的声明
trait 特质名 {
trait 体
}
1) trait 命名 一般首字母大写 Cloneable,Serializable
object T1 extends Serializable {
}
Serializable:就是Scala的一个特质
-在Scala中,Java中的接口可以当做特质使用
- object boke_demo01 {
-
- def main(args: Array[String]): Unit = {
-
- }
- }
-
- //trait Serializable extends Any with java.io.Serializable
- //在scala中,java的接口都可以当做trait来使用(如上面的语法)
- object T1 extends Serializable {
-
- }
-
- object T2 extends Cloneable {
-
- }
8.3.4 Scala中trait的使用
一个类具有某种特质(特征),就意味着这个类满足了这个特质(特征)的所有要素,所以在使用时,也采用了extends关键字,如果有多个特质或存在父类,那么需要采用with关键字连接
1) 没有父类
class 类名 extends 特质1 with 特质2 with 特质3...
2) 有父类
class 类名 extends 父类 with 特质1 with 特质2 with 特质3...
8.4 特质(trait)
8.4.1 特质的快速入门案例
Scala引入trait特质,第一可以替代Java的接口,第二也是对单继承机制的一种补充

8.4.2 案例代码
- object boke_demo01 {
-
- def main(args: Array[String]): Unit = {
- val c = new C()
- val f = new F()
- c.getConnect() // 连接mysql数据库...
- f.getConnect() // 连接oracle数据库..
- }
- }
-
- //按照要求定义一个trait
- trait Trait {
- //定义一个规范
- def getConnect()
- }
-
- //先将六个类的关系写出
- class A {}
-
- class B extends A {}
-
- class C extends A with Trait {
- override def getConnect(): Unit = {
- println("连接mysql数据库...")
- }
- }
-
- class D {}
-
- class E extends D {}
-
- class F extends D with Trait {
- override def getConnect(): Unit = {
- println("连接oracle数据库..")
- }
- }
8.4.3 特质trait的再说明
1) Scala提供了特质(trait),特质可以同时拥有抽象方法和具体方法,一个类可以实现/继承多个特质
- object boke_demo01 {
-
- def main(args: Array[String]): Unit = {
- //创建sheep
- val sheep = new Sheep
- sheep.sayHi()
- sheep.sayHello()
- }
- }
-
- //当一个trait有抽象方法和非抽象方法时
- //1. 一个trait在底层对应两个 Trait.class 接口
- //2. 还对应 Trait$class.class Trait$class抽象类
- trait Trait {
- //抽象方法
- def sayHi()
-
- //实现普通方法
- def sayHello(): Unit = {
- println("say Hello~~")
- }
- }
-
-
- //当trait有接口和抽象类是
- //1.class Sheep extends Trait 在底层 对应
- //2.class Sheep implements Trait
- //3.当在 Sheep 类中要使用 Trait的实现的方法,就通过 Trait$class
- class Sheep extends Trait {
- override def sayHi(): Unit = {
- println("小羊say hi~~")
- }
- }

2) 特质中没有实现的方法就是抽象方法。类通过extends继承特质,通过with关键字可以继承多个特质
3) 所有的Java接口都可以当做Scala特质使用

8.4.4带有特质的对象,动态混入
1) 除了可以在类声明时继承特质以外,还可以在构建对象时混入特质,扩展目标类的功能
2) 此种方法也可以应用于对抽象类功能进行扩展
3) 动态混入是Scala特有的方式(Java没有动态混入),可在不修改类声明/定义的情况下,扩展类的功能,非常的灵活,耦合性低
4) 动态混入可以在不影响原有的继承关系的基础上,给指定的类扩展功能
5) 同时要注意动态混入时,如果抽象类有抽象方法,如何混入
6) 案例演示
- object boke_demo01 {
-
- def main(args: Array[String]): Unit = {
- //在不修改类的定义基础,让它们可以使用trait方法
- val oracleDB = new OracleDB with Operate
- oracleDB.insert(100) //
-
- val mySQL = new MySQL with Operate
- mySQL.insert(200)
-
- //如果一个抽象类有抽象方法,如何动态混入特质
- val mySql_ = new MySQL_ with Operate {
- override def say(): Unit = {
- println("say")
- }
- }
- mySql_.insert(999)
- mySql_.say()
- }
- }
-
- trait Operate { //特质
- def insert(id: Int): Unit = { //方法(实现)
- println("插入数据 = " + id)
- }
- }
-
- class OracleDB { //空
- }
-
- abstract class MySQL { //空
- }
-
- abstract class MySQL_ { //空
- def say()
- }
-在Scala中创建对象的4种方式
1) new 对象
2) apply 创建
3) 匿名子类方式
4) 动态混入
8.4.5 叠加特质
-基本介绍
构建对象的同时如果混入多个特质,称之为叠加特质,那么特质声明顺序从左到右,方法执行顺序从右到左
-叠加特质应用案例
目的:分析叠加特质时,对象的构建顺序,和执行方法的顺序
案例演示:
- object boke_demo01 {
-
- def main(args: Array[String]): Unit = {
-
- //说明
- //1. 创建 MySQL实例时,动态的混入 DB 和 File
-
- //研究第一个问题,当我们创建一个动态混入对象时,其顺序是怎样的
- //总结一句话
- //Scala在叠加特质的时候,会首先从后面的特质开始执行(即从左到右)
- //1.Operate...
- //2.Data
- //3.DB
- //4.File
- val mysql = new MySQL with DB with File
- println(mysql)
-
- //研究第2个问题,当我们执行一个动态混入对象的方法,其执行顺序是怎样的
- //顺序是,(1)从右到左开始执行 , (2)当执行到super时,是指的左边的特质 (3) 如果左边没有特质了,则super就是父特质
- //1. 向文件"
- //2. 向数据库
- //3. 插入数据 100
- mysql.insert(100)
-
- println("===================================================")
- //练习题
- val mySQL = new MySQL with File with DB
- mySQL.insert(999)
- //构建顺序
- //1.Operate...
- //2.Data
- //3.File
- //4.DB
-
- //执行顺序
- //1. 向数据库
- //2. 向文件
- //3. 插入数据 = 999
- }
- }
-
- trait Operate { //特点
- println("Operate...")
-
- def insert(id: Int) //抽象方法
- }
-
- trait Data extends Operate { //特质,继承了Operate
- println("Data")
-
- override def insert(id: Int): Unit = { //实现/重写 Operate 的insert
- println("插入数据 = " + id)
- }
- }
-
- trait DB extends Data { //特质,继承 Data
- println("DB")
-
- override def insert(id: Int): Unit = { // 重写 Data 的insert
- println("向数据库")
- super.insert(id)
- }
- }
-
- trait File extends Data { //特质,继承 Data
- println("File")
-
- override def insert(id: Int): Unit = { // 重写 Data 的insert
- println("向文件")
- //super.insert(id) //调用了insert方法(难点),这里super在动态混入时,不一定是父类
- //如果我们希望直接调用Data的insert方法,可以指定,如下
- //说明:super[?] ?的类型,必须是当前的特质的直接父特质(超类)
- super[Data].insert(id)
- }
- }
-
- class MySQL {} //普通类
-叠加特质注意事项和细节
1) 特质声明顺序从左到右
2) Scala 在执行叠加对象的方法时,会首先从后面的特质(从右向左)开始执行
3) Scala 中特质中如果调用 super,并不是表示调用父特质的方法,而是向前面(左边)继续 查找特质,如果找不到,才会去父特质查找
4) 如果想要调用具体特质的方法,可以指定:super[特质].xxx(...).其中的泛型必须是该特质的直接超类类型
8.4.6 当作富接口使用的特质
富接口:即该特质中既有抽象方法,又有非抽象方法
- trait Operate {
- def insert(id: Int) //抽象
- def pageQuery(pageno: Int, pagesize: Int): Unit = { //实现
- println("分页查询")
- }
- }
8.4.7 特质中的具体字段
特质中可以定义具体字段,如果初始化了就是具体字段,如果不初始化就是抽象字段,混入该特质的类就具有了该字段,字段不是继承,而是直接加入类,成为自己的字段
- object boke_demo01 {
-
- def main(args: Array[String]): Unit = {
- val mySQL = new MySQL with DB {
- override var sal = 10
- }
- }
- }
-
- trait DB {
- var sal: Int //抽象字段
- var opertype: String = "insert"
-
- def insert(): Unit = {
- }
- }
-
- class MySQL {}
-反编译后的代码

8.4.8 特质中的抽象字段
特质中未被初始化的字段在具体的子类中必须被重写
8.4.9 特质构造顺序
-介绍
特质也是有构造器的,构造器中的内容由“字段的初始化”和一些其他语句构成
-第一种特质构造顺序(声明类的同时混入特质)
1) 调用当前类的超类构造器
2) 第一个特质的父特质构造器
3) 第一个特质构造器
4) 第二个特质构造器的父特质构造器,如果已经执行过就不再执行
5) 第二个特质构造器
6) ......重复4,5的步骤(如果有第3个,第4个特质)
7) 当前类构造器
-第二种特质构造顺序(在构建对象时,动态混入特质)
1) 调用当前类的超类构造器
2) 当前类构造器
3) 第一个特质构造器的父特质构造器
4) 第一个特质构造器
5) 第二个特质构造器的父特质构造器,如果已经执行过就不再执行
6) 第二个特质构造器
7) ......重复4,5的步骤(如果有第3个,第4个特质)
8) 当前类构造器
-两种方式对构造顺序的影响
1) 第一种方式实际是构建类对象,在混入特质时,该对象还没有创建
2) 第二种方式实际是构造匿名子类,可以理解成在混入特质时,对象已经创建了
-案例演示
- object boke_demo01 {
-
- def main(args: Array[String]): Unit = {
-
- //这时FF是这样 形式 class FF extends EE with CC with DD
- /*
- 调用当前类的超类构造器
- 第一个特质的父特质构造器
- 第一个特质构造器
- 第二个特质构造器的父特质构造器, 如果已经执行过,就不再执行
- 第二个特质构造器
- .......重复4,5的步骤(如果有第3个,第4个特质)
- 当前类构造器 [案例演示]
-
- */
- //1. E...
- //2. A...
- //3. B....
- //4. C....
- //5. D....
- //6. F....
- val ff1 = new FF()
-
- println(ff1)
-
- //这时我们是动态混入
- /*
- 先创建 new KK 对象,然后再混入其它特质
-
- 调用当前类的超类构造器
- 当前类构造器
- 第一个特质构造器的父特质构造器
- 第一个特质构造器.
- 第二个特质构造器的父特质构造器, 如果已经执行过,就不再执行
- 第二个特质构造器
- .......重复5,6的步骤(如果有第3个,第4个特质)
- 当前类构造器 [案例演示]
-
- */
- //1. E...
- //2. K....
- //3. A...
- //4. B
- //5. C
- //6. D
- println("=======================")
- val ff2 = new KK with CC with DD
- println(ff2)
-
- }
- }
-
- trait AA {
- println("A...")
- }
-
- trait BB extends AA {
- println("B....")
- }
-
- trait CC extends BB {
- println("C....")
- }
-
- trait DD extends BB {
- println("D....")
- }
-
- class EE { //普通类
- println("E...")
- }
-
- class FF extends EE with CC with DD { //先继承了EE类,然后再继承CC 和DD
- println("F....")
- }
-
- class KK extends EE { //KK直接继承了普通类EE
- println("K....")
- }
8.4.10 扩展类的特质
-特质可以继承类,以用来拓展该特质的一些功能
- trait LoggedException extends Exception {
- def log(): Unit = {
- println(getMessage()) // 方法来自于Exception类
- }
- }
-所有混入该特质的类,会自动成为那个特质所继承的超类的子类
- //1. LoggedException 继承了 Exception
- //2. LoggedException 特质就可以 Exception 功能
- trait LoggedException extends Exception {
- def log(): Unit = {
- println(getMessage()) // 方法来自于Exception类
- }
- }
-如果混入该特质的类,已经继承了另一个类(A类),则要求A类是特质超类的子类,否则就会出现了多继承现象,发生错误
- object boke_demo01 {
-
- def main(args: Array[String]): Unit = {
- println("h~~")
- }
- }
-
- //说明
- //1. LoggedException 继承了 Exception
- //2. LoggedException 特质就可以 Exception 功能
- trait LoggedException extends Exception {
- def log(): Unit = {
- println(getMessage()) // 方法来自于Exception类
- }
- }
-
- //因为 UnhappyException 继承了 LoggedException
- //而 LoggedException 继承了 Exception
- //UnhappyException 就成为 Exception子类
- class UnhappyException extends LoggedException {
- // 已经是Exception的子类了,所以可以重写方法
- override def getMessage = "错误消息!"
- }
-
- // 如果混入该特质的类,已经继承了另一个类(A类),则要求A类是特质超类的子类,
- // 否则就会出现了多继承现象,发生错误。
- class UnhappyException2 extends IndexOutOfBoundsException with LoggedException {
- // 已经是Exception的子类了,所以可以重写方法
- override def getMessage = "错误消息!"
- }
-
- class CCC {}
-
- //错误的原因是 CCC 不是 Exception子类
- //class UnhappyException3 extends CCC with LoggedException{
- // // 已经是Exception的子类了,所以可以重写方法
- // override def getMessage = "错误消息!"
- //}
8.4.11 自身类型
-说明
自身类型:主要是为了解决特质的循环依赖问题,同时可以确保特质在不扩展某个类的情况下,依然可以做到限制混入该特质的类的类型
-应用案例
举例说明自身类型特质,以及如何使用自身类型特质
- object boke_demo01 {
-
- def main(args: Array[String]): Unit = {
-
- }
- }
-
- //Logger就是自身类型特质,当这里做了自身类型后,那么
- // trait Logger extends Exception,要求混入该特质的类也是 Exception子类
- trait Logger {
- // 明确告诉编译器,我就是Exception,如果没有这句话,下面的getMessage不能调用
- this: Exception =>
- def log(): Unit = {
- // 既然我就是Exception, 那么就可以调用其中的方法
- println(getMessage)
- }
- }
-
- //class Console extends Logger {} //对吗? 错误
- //class Console extends Exception with Logger {}//对吗? 正确
8.5 嵌套类
8.5.1 嵌套类的使用1

8.5.2 Scala嵌套类的使用2
编写程序,在内部类中访问外部类的属性
-方式1
内部类如果想要访问外部类的属性,可以通过外部类对象访问
即访问形式:外部类名.this.属性名
案例演示
- //外部类
- //内部类访问外部类的属性的方法1 外部类名.this.属性
- class ScalaOuterClass {
- //定义两个属性
- var name = "Jack"
- private var sal = 199.6
-
- class ScalaInnerClass { //成员内部类,
-
- def info() = {
- // 访问方式:外部类名.this.属性名
- // 怎么理解 ScalaOuterClass.this 就相当于是 ScalaOuterClass 这个外部类的一个实例,
- // 然后通过 ScalaOuterClass.this 实例对象去访问 name 属性
- // 只是这种写法比较特别,学习java的同学可能更容易理解 ScalaOuterClass.class 的写法.
- println("name = " + ScalaOuterClass.this.name
- + " sal =" + ScalaOuterClass.this.sal)
- }
- }
-
- }
-方式2
内部类如果想要访问外部类的属性,也可以通过外部类别名访问(推荐)
即访问方式:外部类名别名.属性名
案例演示
- object boke_demo01 {
-
- def main(args: Array[String]): Unit = {
- //测试1. 创建了两个外部类的实例
- val outer1: ScalaOuterClass = new ScalaOuterClass();
- val outer2: ScalaOuterClass = new ScalaOuterClass();
-
- //在scala中,创建成员内部类的语法是
- //对象.内部类 的方式创建, 这里语法可以看出在scala中,默认情况下内部类实例和外部对象关联
- val inner1 = new outer1.ScalaInnerClass
- val inner2 = new outer2.ScalaInnerClass
-
- //测试一下使用inner1 去调用 info()
- inner1.info()
-
- //这里我们去调用test
- inner1.test(inner1)
- //在默认情况下,scala的内部类的实例和创建该内部类实例的外部对象关联.
- //
- inner1.test(inner2)
- inner2.test(inner2)
-
-
- //创建静态内部类实例
- val staticInner = new ScalaOuterClass.ScalaStaticInnerClass()
-
-
- }
- }
-
-
- //外部类
- //内部类访问外部类的属性的方法2 使用别名的方式
- //1. 将外部类属性,写在别名后面
- class ScalaOuterClass {
- myouter => //这里我们可以这里理解 外部类的别名 看做是外部类的一个实例
- class ScalaInnerClass { //成员内部类,
-
- def info() = {
- // 访问方式:外部类别名.属性名
- // 只是这种写法比较特别,学习java的同学可能更容易理解 ScalaOuterClass.class 的写法.
- println("name~ = " + myouter.name
- + " sal~ =" + myouter.sal)
- }
- }
-
- //定义两个属性
- var name = "Jack"
- private var sal = 999.9
- }
-
-
- object ScalaOuterClass { //伴生对象
- class ScalaStaticInnerClass { //静态内部类
- }
-
- }
8.5.3 类型投影
-案例演示
- //外部类
- //内部类访问外部类的属性的方法2 使用别名的方式
- //1. 将外部类属性,写在别名后面
- class ScalaOuterClass {
- myouter => //这里我们可以这里理解 外部类的别名 看做是外部类的一个实例
- class ScalaInnerClass { //成员内部类,
-
- def info() = {
- // 访问方式:外部类别名.属性名
- // 只是这种写法比较特别,学习java的同学可能更容易理解 ScalaOuterClass.class 的写法.
- println("name~ = " + myouter.name
- + " sal~ =" + myouter.sal)
- }
-
- //这里有一个方法,可以接受ScalaInnerClass实例
- //下面的 ScalaOuterClass#ScalaInnerClass 类型投影的作用就是屏蔽 外部对象对内部类对象的
- //影响
- def test(ic: ScalaOuterClass#ScalaInnerClass): Unit = {
- System.out.println("使用了类型投影" + ic)
- }
-
- }
-
- //定义两个属性
- var name = "Jack"
- private var sal = 999.9
- }
-解决方式-类型投影
类型投影是指:在方法声明上,如果使用 外部类#内部类 的方式,表示忽略内部类的对象关系,等同于Java中内部类的语法操作,我们将这种方法称之为 类型投影(即:忽略对象的创建方式,只考虑类型)