快学Scala第8章----继承

快学Scala第8章—–继承

本章要点

  • extends、final关键字和Java中相同
  • 重写方法时必须用override
  • 只有主构造器可以调用超类的主构造器
  • 你可以重写字段

扩展类

Scala扩展类的方式和Java一样—–使用extends关键字

class Employee extends Person {
    var salary = 0.0
    ...
}

可以将类声明为final,这样就不会被扩展。也可以具体到某个方法或者字段使用final,以确保它们不被重写。


重写方法

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

public class Person {
    ...
    override def toString = getClass.getName + "[name=" + name + "]"
}

override的作用是会给出有用的错误提示

  • 当你拼错了要重写的方法名
  • 当你不小心在新的方法中使用了错误的参数类型
  • 当你在超类中引入了新的方法,而这个新的方法与子类的方法相抵触

在Scala中调用超类的方法使用super关键字:

public class Employee extends Person {
    ...
    override def toString = super.toString + "[salary=" + salary +]"
}

类型检查和转换

如何测试一个对象是否属于某个给定的类或者它的子类: 使用isInstanceOf方法。如果测试成功,你就可以用asInstanceOf方法将引用转换为子类的引用。这往往用在参数传递时的多态。

if (p.isInstanceOf[Employee]) {
    val s = p.asInstanceOf[Employee]
    ...
}

如果p指向的是Employee类及其子类的对象,则 p.isInstanceOf[Employee]将返回true; 如果p是null,则返回false,且 p.asInstanceOf[Employee] 也会返回false; 如果p不是一个Employee,则p.asInstanceOf[Employee]将抛出异常。
如果需要判断p指向的是一个Employee对象但又不是其子类的话,可以用:

if(p.getClass == classOf[Employee])

不过,使用模式匹配通常是更好的选择:

p match {
    case s: Employee => ...   // 将s作为Employee处理
    case _ =>   // p 不是Employee
}

受保护字段和方法

Scala的protected修饰符和C++/Java的一样,被修饰的成员可以被子类访问,但是不能被其他位置访问到。与Java不同,protected的成员对于类所属的包而言是不可见的。
Scala还提供了protected[this]的变体,将访问权限定在当前的对象,类似的还有private[this]


超类的构造

由于类中的主构造器是其他所有辅助构造器的根源,所以辅助构造器永远都不可能直接调用超类的构造器。子类的辅助构造器最终都会调用主构造器,只有主构造器可以调用超类的构造器。

class Person(val name: String, val age: Int) {}

class Employee(name: String, age: Int, var salary: Double) extends Person(name, age){}

注意在Employee的主构造器的参数中,name和age是普通参数,它们将传递给超类的主构造器,如果加上了val则就变成了重写超类的字段,此时需要加上override。

Scala类还可以扩展Java类,它的主构造器必须调用Java超类的某一个构造方法:

class Square(x: Int, y: Int, width: Int) extends java.awt.Rectangle(x, y, width, width)

重写字段

你可以用一个同名的val字段重写一个val(或不带参数的def)

class Person(val name: String, val age: Int) {}

// name 字段被子类重写, age字段还是父类的
class Employee(override val name: String, age: Int, var salary: Double) extends Person(name, age){}

// 重写def
abstract class Person {
    def id: Int // 以某种方式计算出来的ID
    ...
}

class Student(override val id: Int) extends Person {}

注意:

  • def只能重写另一个def
  • val只能重写另一个val或者不带参数的def
  • var只能重写另一个抽象的var
重写用val用def用var
重写val子类有一个私有字段(与超类的字段名字相同)错误错误
重写def子类有一个私有字段和Java一样var 可以重写getter/setter对
重写var错误错误仅当超类的var是抽象的才可以

匿名子类

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

val alien = new Person("Fred") {
    def greeting = "Greetings, Earthling! My name is Fred"
}

这将会创造出一个构造类型的对象,该类型标记为Person{def greeting: String}。你可以用这个类型作为参数类型的定义:

def meet(p: Person{def greeting: String}) {
    println(p.name + "says: " + p.greeting)
}

抽象类

使用abstract定义抽象类:

abstract class Person(val name: String) {
    def id: Int  // 没有方法体----这是一个抽象的方法
}

class Employee(name: String) extends Person(name) {
    def id = name.hashCode
}

在Scala中不需要对抽象方法使用abstract,只需要省去其方法体,但是该类必须声明为abstract。子类中重写超类的抽象方法时,不需要使用override,这与重写非抽象的方法不同。


抽象字段

抽象字段就是一个没有初始值的字段:

abstract class Person {
    val id: Int  // 没有初始化,这是一个带有抽象的getter方法的抽象字段
    var name: String // 也是抽象字段,带有抽象的getter和setter方法
}

// 子类有具体的id属性和name属性, 不需要override关键字修饰
class Employee(val id: Int) extends Person {
    var name = ""
}

// 使用匿名类型
val fred = new Person {
    val id = 1729
    val name = "Fred"
}

构造顺序与提前定义

先来看一个问题:

class Creature {
    val range: Int = 10
    val env: Array[Int] = new Array[Int](range)
}

class Ant extends Creature {
    override val range = 2
}

当创建Ant对象时,env数组的长度是多少呢,10 or 2,或者还是其他的值。首先要说明的一点是Java语言的一个设计决定—–允许在超类的构造方法中调用子类的方法(作为一个写C++的,感觉这个好变态,老子还没创建完,儿子就有东西可以被使用了)。Ant类对象的构建过程:
1. Ant的构造器在做它自己的构造之前,首先调用Creature的构造器;
2. Creature的构造器将它的range字段设为10;
3. Creature的构造器为了初始化env数组,调用range的getter方法:range();
4. 但是该方法被子类Ant重写了,输出Ant类的range字段值。但是Ant类的range字段值还没有初始化,即还没有执行 val range = 2 初始化语句,此时range的值是0(这是对象被分配空间时所有的整型字段的初始值)
5. 因此range()方法返回的是0
6. env被设为长度为0的数组, Creature构造完毕
7. Ant构造器继续执行,将其range字段设为2

这里的教训是你在构造器内不应该依赖val的值。
解决方式:
- 将val声明为final,这样很安全但并不灵活
- 在超类中将val声明为lazy,这样很安全但是并不高效
- 在子类中使用提前定义语法
所谓提前定义语法就是可以在超类的构造器指向之前初始化子类的val字段

class Ant extends {
    override val range = 2
} with Creature

Scala继承层级

一图胜千言(有点搓,推荐一个好的文章Scala的类层级
这里写图片描述
这里需要说明的是Null类型的唯一实例是null。你可以将null赋值给任何引用,但是不能赋值给值类型的变量。 Nothing类型没有实例。它对泛型结构比较有用,在前面的异常的学习时,throw表达式的类型就是Nothing。 Nothing类型不与C++或Java的void是两个不同的概念。Scala中有Unit表示void类型,该类型只有一个值,那就是()。任何类型都可以被替换成Unit。


对象相等性

在Scala中,AnyRef的eq方法检查两个引用是否指向同一个对象。AnyRef的equals方法调用eq。对于你自定义的类,想要实现相同比较,需要重写equals。

class Item(val description: String, val price: Double) {
    final override def hashCode = 13 * description.hashCode * 17 * price.hashCode

    final override def equals(other: Any) = {
        val that = other.asInstanceOf[Item]
        if (thar == null) false
        else description == that.description && price == that.price
    }
}

在这里将方法定义为final,是因为通常而言在子类中正确的扩展相等性判断非常困难。问题出在对称性上,如a.equals(b) 和b.equals(a),尽管b属于a的子类。
注意:在定义equals方法的参数类型必须是Any,以下代码是错误的:

final def equals(other: Item) = {...} // error, 这是一个不相关的方法,并不会重写AnyRef的equals方法

当你定义equals时,记得同时也定义hashCode。
在应用程序中,通常不会直接调用eq或者equals方法,只要==操作符就好了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值