Scala学习(八)继承

1.扩展类(继承)

Scala扩展类的方式和Java同样,使用extends关键字:java

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

基本上,Scala中的继承和Java没有什么区别,惟一的区别就是:数组

和Java同样,你能够将类声明为final,这样它就不能被继承。你还能够将单个方法或字段声明为final,以确保他们不能被重写。注意这里和Java不一样,在Java中final字段是不可变的,在Scala中时val。安全

2.重写方法

在Scala中重写一个非抽象的方法必须使用override修饰符。例如:并发

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

在Scala中调用父类的方法和Java彻底同样,使用super关键字:ide

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

3.类型检查和转换

要测试某个对象是否属于某个给定的类,能够用isInstanceOf方法。若是测试成功,你能够用asInstanceOf方法将引用转换为子类引用:函数

if(p.isInstanceOf[Employee]){
    val s = p.asInstanceOf[Employee] //s的类型为Employee
}

------------------------------------------------学习

若是你想要测试p指向的是一个Employee对象但又不是其子类的话,能够用:测试

//这种判断是精确判断,==号两边必须彻底同样。isInstanceOf方法是模糊判断只要两边存在继承关系就能够返回trueflex

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

说明:getClass方法会返回引用对象的类型及其子类类型Class[_ <:A]。classOf方法获得的是指定对象类型Class[A]。this

说明:这里总结下Scala中的类型界定:

  1. 协变 [+T], covariant (or “flexible”) in its type parameter T,相似Java中的(? extends T), 便可以用T和T的子类来替换T,里氏替换原则。
  2. 不变 不支持T的子类或者父类,只知支持T自己。
  3. 逆变 [-T], contravariant, 相似(? supers T) 只能用T的父类来替换T。是逆里氏替换原则。
  4. 上界: 只容许T的超类U来替换T。 [U >: T]
  5. 下界: 只容许T的子类U来替代T。 [U <: T]

-----------------------------------------------

Scala和Java中类型检查和转换

Scala Java
obj.isInstanceOf[Cl] obj instanceof Cl
obj.asInstanceOf[Cl] (Cl) obj
classOf[Cl] Cl.class

4.受保护的字段和方法

和java或C++同样,你能够将字段或方法声明为protected。这样的成员能够被任何子类访问,但不能从其余位置看到。

与Java不一样,protected的成员对于类所属的包而言,是不可见的。

Scala还提供了一个protected[this]的变体,将访问权限定在当前的对象,相似private[this]。

5.超类的构造

以前的学习中咱们提到过,Scala类有一个主构造器和任意数量的辅助构造器,而每一个辅助构造器都必须对先前定义的辅助构造器或主构造器的调用开始。

这样作的结果是,复制构造器永远都不可能直接调用超类的构造器。

子类的复制构造器最终都会调用主构造器。只有主构造器能够调用超累的构造器。

主构造器是和类定义交织在一块儿的。调用超类构造器的方式也一样交织在一块儿。例如:

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

}

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

}

注:在父类中定义了的参数,在子类中没必要从新定义,只要名称相同便可,参考上面的name和age。

将类和构造器交织在一块儿能够给咱们带来更精简的代码。把主构造器的参数当作是类参数可能更容易理解。本例中Employee类有三个参数:name、age和salary其中两个被“传递”到了超类。

------------------------------------------------

Scala能够扩展Java 类。这种状况写,他的主构造器必须调用java 超类的某一个构造方法:

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

6.重写字段

Scala 中重写字段由以下限制(同时参考下表):

  • def只能重写def。
  • val只能重写另外一个val或不带参数的def。
  • var只能重写另外一个抽象的var
  用val 用def 用var
重写val
  • 子类有一个私有字段(与超类的字段名称相同(这没问题))
  • getter方法重写超类的getter方法
错误 错误
重写def
  • 子类有一个私有字段
  • getter方法重写超类的方法
和Java同样 var能够重写getter/setter对。只重写getter会报错
重写var 错误 错误 仅当超类的var是抽象的才能够

例如:

abstract class Person{
    def id: Int 
}

class Student(override val id: Int) extends Person

7.匿名子类

和Java同样,你能够经过包含带有定义或重写的代码块的方式建立一个匿名的子类,好比:

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)
}

8.抽象类

和Java同样,你能够用abstract关键字类标记不能被实例化的类,一般由于某个或某几个方法没有被完整定义。例如:

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

在子类中重写超类的抽象方法时,你不须要使用override关键字。

class Employ(name: String) extends Person(name){
    def id = name.hashCode //不须要override关键字
}

9.抽象字段

除了抽象方法外,类还能够拥有抽象字段。抽象字段就是一个没有初始值的字段。例如:

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

该类为id和name字段定义了抽象的getter方法,为name字段定义了抽象的setter方法。生成的Java类并不带字段。

具体的子类必须提供具体的字段,例如:

class Employee(val id:Int) extends Person{ //子类有具体的id属性
    var name = " " //和具体的name属性
}

和方法同样,在子类中重写超类中的抽象字段时,不须要override关键字。

你能够锁是用匿名类型来定制抽象字段:

val fred = new Person{
    val id - 1729
    var name = "Fred" 
}

10.构造顺序和提早定义

当你在子类中重写val而且在超类的构造器中使用该值的话,其行为并不那么显而易见。有这样一个示例:动物能够感知其周围的环境。简单起见,咱们假定动物生活在一维的世界里,而感知数据以整数表示。动物在默认状况下能够看到前方10个单位:

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

不过蚂蚁是近视的:

class Ant extends Creature {
    override val range=2
}

面临问题

咱们如今面临一个问题:range值在超类的构造器中用到了,而超类的构造器先于子类的构造器运行。确切地说,事情发生的过程是这样的:

  1. Ant的构造器在作它本身的构造以前,调用Creature的构造器
  2. Creature的构造器将它的range字段设为10
  3. Creature的构造器为了初始化env数组,调用range()取值器
  4.  该方法被重写以输出(还未初始化的)Ant类的range字段值
  5. range方法返回0。这是对象被分配空间时全部整型字段的初始值
  6. env被设为长度为0的数组
  7. Ant构造器继续执行,将其range字段设为2

虽然range字段看上去多是10或者2,但env被设成了长度为0的数组。这里的教训是你在构造器内不该该依赖val的值。

解决方案

在Java中,当你在超类的构造方法中调用方法时,会遇到类似的问题。被调用的方法可能被子类重写,所以它可能并不会按照你的预期行事。事实上,这就是咱们问题的核心所在range表达式调用了getter方法。有几种解决方式:

  1. 将val声明为final。这样很安全但并不灵活
  2. 在超类中将val声明为lazy。这样很安全但并不高效
  3. 在子类中使用提早定义语法

提早定义语句

所谓的"提早定义"语法,让你能够在超类的构造器执行以前初始化子类的val字段。这个语法简直难看到家了,估计没人会喜欢。你须要将val字段放在位于extends关

键字以后的一个块中,就像这样:

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

注意:超类的类名前的with关键字,这个关键字一般用于指定用到的特质。提早定义的等号右侧只能引用以前已有的提早定义,而不能使用类中的其余字段或方法。

11.Scala继承层级

在Scala中,与Java基本类型相对应的类,以及Unit类型,都扩展自AnyVal。

全部其余类都是AnyRef的子类,AnyRef至关于Java中的Object

而AnyVal与AnyRef都扩展自Any,Any类是整个继承层级的根节点。

Any类定义了isInstanceOf、asInstanceOf方法,以及用于相等性的判断和哈希码的方法。

AnyVal并无追加任何方法。他只是全部值类型的标记。

AnyRef类追加了来自Object类的监事方法wait和notify/notifyAll。同时提供了一个带函数参数的方法synchronized。这个方法等同于Java中的synchronized块。例如:

account.synchronized{ account.balance += amount}

说明:和Java同样,我建议你远离wait、notify和synchronized——除非你有充分的理由使用这些关键字而不是更高层次的并发结构。

全部的Scala类都实现ScalaObject这个标记接口,这个借口没有定义任何方法。

Scala继承层级的另外一端是Nothing和Null类型。

Null类型的惟一实例时null值,你能够将null值赋给任何引用,但不能赋值给类型变量。

Nothing类型没有实例,它对于泛型结构时很是有用。举例来讲,空列表Nil的类型是List[Nothing],它是List[T]的子类型,T能够试任何类。

注:Nothing类型和Java中的void彻底是两个概念。在Scala中void用Unit表示,该类型只有一个值,那就是()。注意Unit并非任何类型的超类型。可是依然容许任何值被替换成()。例如:

def printAny(x: Any){ println(x) }

def printUnit(x: Unit){ println(x) }

printAny("Hello") //将打印Hello

printUnit("Hello") //将“Hello”替换成(),而后调用printUnit(()),打印出()

12.对象相等性

在Scala中,AnyRef的eq方法检查了两个引用是否指向同一个对象。AnyRef的equals方法调用eq。当你实现类的时候应该考虑重写equals方法,以提供一个天然地与你的实际状况相称的相等性判断。

如下是相应的equals方法定义:

final override def equals(other: Any) = {
    ....
}

注意: 请确保定义的equals方法参数类型为Any。这样并不会重写AnyRef的equals方法。

当你定义equals时,记得同时定义hashCode:

final override def hashCode = {  ...  }

在应用程序当中,你一般并不直接调用eq或equals,只要用==操做符就好。对于引用类型而言,他会在作完必要的null检查后调用equals方法。

相关文章
相关标签/搜索