面向对象的编程(高级特性)
1. 静态属性和静态方法
1.1 基本介绍
scala
语言是完全面向对象的语言,所以并没有静态的操作(即在Scala
中没有静态的概念)。但是为了能够和Java
语言交互(因为Java
中有静态概念),就产生了一种特殊的对象来模拟类对象,我们称之为类的伴生对象。这个类的所有静态内容都可以放置在它的伴生对象中声明和调用。
1.1class Person
称为伴生类,将非静态的内容写到该类中。
1.2object Person
称为伴生对象,将静态的内容写入到该对象中。
1.3class Person
编译后,底层生成Person.class
。
1.4object Person
编译后,底层生成Person$.class
。
1.5 对于伴生对象的内容,可以通过Person.属性
或Person.方法
。
1.2 伴生对象的小结
Scala
中伴生对象采用object
关键字声明,伴生对象中声明的全是 "静态"内容,可以通过伴生对象名称直接调用。- 伴生对象对应的类称之为伴生类,伴生对象的名称应该和伴生类名一致。
- 伴生对象中的属性和方法都可以通过伴生对象名(类名)直接调用访问。
- 从语法角度来讲,所谓的伴生对象其实就是类的静态方法和成员的集合。
- 从技术角度来讲,
scala
还是没有生成静态的内容,只不过是将伴生对象生成了一个新的类,实现属性和方法的调用。 - 从底层原理看,伴生对象实现静态特性是依赖于
public static final MODULE$
实现的。 - 伴生对象的声明应该和伴生类的声明在同一个源码文件中(如果不在同一个文件中会运行错误!),但是如果没有伴生类,也就没有所谓的伴生对象了,所以放在哪里就无所谓了。
- 如果
class A
独立存在,那么A
就是一个类, 如果object A
独立存在,那么A
就是一个"静态"性质的对象[即类对象], 在object A
中声明的属性和方法可以通过A.属性
和A.方法
来实现调用。
1.3 伴生对象的 apply 方法
-
在伴生对象中定义
apply
方法,可以实现: 类名(参数) 方式来创建对象实例,从而无需使用new
关键字。 -
基本用法
object testApply { def main(args: Array[String]) { val a = Apply(参数) } } class Apply(参数列表) { // 伴生类 ... } object Apply { // 伴生对象 ... def apply(参数列表): Apply = new Apply(参数) // apply 方法 }
2. 接口
2.1 介绍
- 从面向对象来看,接口并不属于面向对象的范畴,
Scala
是纯面向对象的语言,在Scala
中,没有接口。 Scala
语言中,采用特质trait
(特征)来代替接口的概念,也就是说,多个类具有相同的特质时,就可以将这个特质独立出来,采用关键字trait
声明。理解trait
等价于(interface + abstract class
)。
2.2 trait 的声明
-
基本格式
trait 特质名 { 特质体 }
-
在
scala
中,java
中的接口可以当做特质使用。
2.3 Scala 中 trait 的使用
-
一个类具有某种特质(特征),就意味着这个类满足了这个特质(特征)的所有要素,所以在使用时,也采用了
extends
关键字,如果有多个特质或存在父类,那么需要采用with
关键字连接。1.1 没有父类
class 类名 extends 特质1 with 特质2 with 特质3 ...
1.2 有父类
class 类名 extends 父类 with 特质1 with 特质2 with 特质3 ...
3. 特质
Scala
引入trait
,第一可以替代Java
的接口,第二也是对单继承机制的一种补充。
3.1 特质再说明
- 特质可以同时拥有抽象方法和具体方法,一个类可以实现/继承多个特质。
- 特质中没有实现的方法就是抽象方法。类通过
extends
继承特质,通过with
可以继承多个特质。 - 所有的
Java
接口都可以当做Scala
特质使用。
3.2 带有特质的对象,动态混入
-
除了可以在类声明时继承特质以外,还可以在构建对象时混入特质,扩展目标类的功能。
-
此种方式也可以应用于对抽象类功能进行扩展。
-
动态混入是
Scala
特有的方式,可在不修改类声明/定义的情况下,扩展类的功能,非常的灵活,耦合性低。 -
动态混入可以在不影响原有的继承关系的基础上,给指定的类扩展功能。
-
同时要注意动态混入时,如果抽象类有抽象方法,如何混入。
object textMixIn { def main(args: Array[String]): Unit = { val cls = new Cls with Trt // 混入特质 val cls = new Abs with Abs { // 混入特质同时构建抽象类的实现类 ... } } } class Cls { ... } abstract class Abs { ... } trait Trt { ... }
3.3 叠加特质
3.3.1 基本介绍
- 构建对象的同时如果混入了多个特质,称之为叠加特质。那么特质声明从左到右,方法执行顺序从右到左。
3.3.2 叠加特质注意事项和细节
- 特质声明顺序从左到右。
Scala
在执行叠加对象的方法时,会首先从后面的特质(从右到左)开始执行。Scala
中特质中如果调用super
,并不是表示调用父特质的方法,而是向前面(左边)继续查找特质,如果找不到,才会去父特质查找。- 如果想要调用具体特质的方法,可以指定:
super[特质].xxx(…)
,其中的泛型必须是该特质的直接超类类型。
3.4 当做富接口使用的特质
- 富接口:即该特质中既有抽象方法,又有非抽象方法。
3.5 特质中的具体字段
- 特质中可以定义具体字段,如果初始化了就是具体字段,如果不初始化就是抽象字段。混入该特质的类就具有了该字段,字段不是继承,而是直接加入类,成为自己的字段。
3.6 特质中的抽象字段
- 特质中未被初始化的字段在具体的子类中必须被重写。
3.7 特质构造顺序
3.7.1 声明类的同时混入特质
- 调用当前类的超类构造器。
- 第一个特质的父特质构造器。
- 第一个特质构造器。
- 第二个特质构造器的父特质构造器,如果已经执行过了,就不再执行。
- 第二个特质构造器。
- …重复4、5的步骤。
- 当前类构造器。
3.7.2 在构建对象时,动态混入特质
- 调用当前类的超类构造器。
- 当前类构造器。
- 第一个特质的父特质构造器。
- 第一个特质构造器。
- 第二个特质构造器的父特质构造器,如果已经执行过了,就不再执行。
- 第二个特质构造器。
- …重复4、5的步骤。
3.7.3 分析两种方式对构造顺序的影响
- 第一种方式实际是构建类对象,在混入特质时,该对象还没有创建。
- 第二种方式实际是构造匿名子类,可以理解成在混入特质时,对象已经创建了。
3.8 扩展类的特质
- 特质可以继承类,以用来拓展该特质的一些功能。
- 所有混入该特质的类,会自动成为那个特质所继承的超类的子类。
- 如果混入该特质的类,已经继承了另一个类
(A类)
,则要求(A类)
是特质超类的子类,否则就会出现多继承现象,发生错误。
3.9 自身类型
-
自身类型:主要是为了解决特质的循环依赖问题,同时可以确保特质在不扩展某个类的情况下,依然可以做到限制混入该特质的类的类型。
//trait Logger extends Exception,要求混入该特质的类也是 Exception 子类 trait Logger { // 明确告诉编译器,我就是 Exception,如果没有这句话,下面的 getMessage 不能调用 this: Exception => def log(): Unit ={ // 既然我就是 Exception, 那么就可以调用其中的方法 println(getMessage) } }
4. 嵌套类
4.1 嵌套类的使用
-
定义
scala
的成员内部类和静态内部类,并创建相应的对象实例。class ScalaOuterClass { class ScalaInnerClass{} // 成员内部类 } object ScalaOuterClass { class ScalaStaticInnerClass{} // 静态内部类 } // 创建外部类 val outer1: ScalaOuterClass = new ScalaOuterClass() val outer2: ScalaOuterClass = new ScalaOuterClass() // 创建内部类 val inner1 = new outer1.ScalaInnerClass() val inner2 = new outer2.ScalaInnerClass() // 创建静态内部类 val staticInner = new ScalaOuterClass.ScalaStaticInnerClass()
-
在内部类中访问外部类的属性:
2.1 通过外部类对象访问,即
外部类名.this.属性名
。class ScalaOuterClass { var name = "zgl" var age = 23 class ScalaInnerClass{} // 成员内部类 def info() { print("name: " + ScalaOuterClass.this.name + " age: " + ScalaOuterClass.this.age) } }
2.2 也可以通过外部类别名访问,即
外部类别名.属性名
.class ScalaOuterClass { myouter => // 外部类别名 var name = "zgl" var age = 23 class ScalaInnerClass{} // 成员内部类 def info() { print("name: " + myouter.name + " age: " + myouter.age) } }
4.2 类型投影
- 指,在方法声明上,如果使用
外部类#内部类
的方式,表示忽略内部类的对象关系,我们将这种方式称之为类型投影(即:忽略对象的创建方式,只考虑类型)。