首页 > 代码库 > Scala之继承

Scala之继承

1. 继承

Scala语言通过 extends 关键字来继承类.
那么继承一个类有什么好处呢? 子类除了拥有继承自超类的方法和字段(即为val(常量), var(变量)所定义的), 还可以添加自己需要的新方法和新字段, 而且不但可以重写(override)超类的方法, 还可以重写超类的字段.

final 关键字

在Scala中, 不仅可以将类声明为final, 而且可以将字段和方法声明为final

当类被声明为final时, 类不可以被继承; 当方法和字段被声明为final时, 对应的方法和字段不可以被子类重写, 看看下面这个例子就一目了然

class Person {  final val key = 0
  val e = 1}class Kid extends Person {  // 报错: Value ‘key‘ can not override final member
  // override val key = 1  
  override val e = 2 }object test extends App {  val k = new Kid;  println(k.key)  println(k.e)
}/*output02*/

2. 重写方法

在Scala中重写一个非抽象方法必须使用override修饰符, 如:

 override def toString: String = getClass.getName + "[name=" + name + "]"

override修饰符可以在多个常见的情况下给出有用的错误提示, 包括:

  1. 当你拼错了要重写的方法名和字段名

  2. 当你不小心在新方法中使用了错误的参数类型

  3. 当你在超类中引入了新方法, 而这个新方法和子类的方法抵触

Scala语言使用super关键字调用超类的方法, super.toString 相当于Person.toString

class Person {  final val key = 0
  val name = "person"
  val age = 1

  override def toString: String = getClass.getName + "[name=" + name + "]"}class Kid extends Person {  //override val key = 1
  override val name = "kid"
  override val age = 2

  override def toString: String = super.toString + "[age=" + age + "]"}object test extends App {  val k = new Kid;  println(k.key)  println(k.age)  println(k)
}/*output02chap08.Kid[name=kid][age=2]*/

3. 类型检查和转换

Scala语言中可以使用 isInstanceOf[T] 方法, 测试某个对象实际类型是否属于某个给定类T或者类T的子类; 测试成功之后可以用 asInstanceOf[T] 方法将对象引用转化为的(子类)类T引用(一般来说对象的引用类型是T的父类, 而实际类型是T或者T的子类)

  if (kid.isInstanceOf[Kid]) {    val s = kid.asInstanceOf[Kid]
  }

如果kid是null, 则 kid.isInstanceOf[Kid] 返回false, kid.asInstanceOf[Kid] 返回null, 如果kid不是一个Kid, kid.asInstanceOf[Kid]将抛出异常

如果要测试kid指向Kid类又不是其子类, 使用如下方法:

if (kid.getClass == classOf[Kid]) {    val s = kid.asInstanceOf[Kid]
  }

4. 超类构造

类有一个主构造器和任意数量的辅助构造器, 而每个辅助构造器都必须以对先前定义的辅助构造器或者主构造器的调用开始, 这样做的结果就是:

辅助构造器永远都不可能直接调用超类的构造器; 子类的辅助构造器最终都会调用主构造器; 只有主构造器而已调用超类的构造器.

主构造器是和类的定义交织在一起, 调用超类的构造器同样也交织在一起

class Kid(gender: String, val height: Double) extends Person(gender)

Kid类有2个参数, 一个被"传递"到超类

scala语言的(主)构造器中, 你不能调用super(paras)

5. 重写字段

Scala的字段(Fields)由一个私有字段和取值器/改值器方法构成

你可以用一个同名的val字段重写一个val或者不带参数的def, 子类有一个私有字段和一个共有的getter方法, 而这个getter方法重写了超类的getter方法.

class Smiler(val happy: String) {  override def toString: String = getClass.getName + "[happy: " + happy + "]"}class Laughter(veryhappy: String) extends Smiler(veryhappy) {  override val happy: String = "Laughter"
  override val toString: String = super.toString}

更常见的例子是 val 重写抽象的 def,就像这样:

abstract class Smiler(val happy: String) {  def degree: Int}class Laughter(lhappy: String, override val degree: Int) extends Smiler(lhappy) {
}

注意如下限制:

  • def 只能重写另一个def

  • val 只能重写另一个val或者不带参数的def

  • var 只能重写另一个抽象的var

6. 匿名子类

你可以通过包含带有定义或重写的代码块的方式创建一个匿名子类,比如

val alien = new Person("good") {    def greeting = "hi, good"
    }

7. 抽象类 与 抽象字段

  • 不需要对抽象方法和抽象字段用abstract关键字

  • 子类中重写超类的抽象方法和抽象字段时, 不需要override关键字

  • 只要类中存在抽象方法, 该类必须声明为 abstract

7.1 抽象类

Scala中使用 abstract关键字来标记不能实例化的类, 通常是因为它的某个或者几个方法没有完整定义. 例如

abstract class Smiler(val happy: String) {  def degree: Int}
  • 在Scala中, 不需要对抽象方法用abstract关键字, 只是省去其方法体

  • 只要类中存在抽象方法, 该类必须声明为 abstract

  • 子类重写超类的抽象方法时, 不需要override关键字

class Laughter(lhappy: String) extends Smiler(lhappy) {  def degree = lhappy.hashCode }

7.2 抽象字段

除了抽象方法外, 类还可以有抽象字段; 抽象字段就是一个没有初始值的字段. 具体的子类必须提供具体的字段; 和方法一样, 子类中重写超类的抽象字段时, 不需要override关键字

abstract class Abstract {  val id: Int // 没有初始化, 这是一个带有getter方法的抽象字段
  var name: String // 没有初始化, 这是一个带有getter和setting方法的抽象字段}class AbstractField(val id: Int) extends  Abstract {  var name = getClass.getName  // override 可选}

可以随时用匿名类型来定制抽象字段

val laught = new Abstract {  val id = 10
  var name = "laught"
  }

8. Scala继承层级

技术分享

  1. 与Java基本类型相对应的类以及Unit类型(相当于Java的void)都扩展自AnyVal

  2. 所有其他类都是AnyRef的子类, Any是整个继承层级的根节点, AnyVal和AnyRef扩展自Any类

  3. Any类定义了isInstanceOf asInstanceOf方法, 以及用于相等性判断和哈希码方法, AnyVal并没有追加方法, 只是所有值类型的一个标记

  4. Null类型唯一的实例就是null, 你可以将null赋值给任何引用, 但不能赋值给值类型的变量, 举例来说不能讲Int赋值为null

  5. Nothing类型没有实例, 它对于泛型结构很有用, 比如说空列表Nil是List[Nothing], 它是List[T]的子类型, T可以是任何类型

Scala中的Nothing类型和Java中void不是一个概念;
Scala中void由Unit类型表示, 该类型只有一个值, 那就是();
<<快学Scala>>中所说虽然Unit不是任何类型的超类, 但编译器允许任何值来替换成(), 不过现在测试scala2.12.2, Scala已经修改这个语法, 现在不管传入什么值, printUnit的输出结果都是()

def printAny(x: Any) {print(x)}def printUnit(x: Unit) {print(x)}printAny("happy")printUnit("happy")/*output:happy()/*

9. 对象相等性

Scala中调用 ==, 如果比较的是引用类型, Scala会先做null检查, 然后调用equals 方法


Scala之继承