`
wbj0110
  • 浏览: 1549784 次
  • 性别: Icon_minigender_1
  • 来自: 上海
文章分类
社区版块
存档分类
最新评论

scala --继承

阅读更多

继承类

使用extends关键字来继承一个类。如果将类声明为final的,则这个类不能被继承。如果将类的方法和字段声明为final,则它们不能被重写。

重写方法

在Scala中重写一个非抽象方法必须使用override修饰符。调用超类的方法就如Java一样,使用super关键字。

类型检查和转换

使用isInstanceOf方法来检测某个对象是否为某个特定的类;如果检测通过,则可以使用asInstanceOf方法来将对象转换为子类。

如果p是null,p.isInstanceOf[Employee]将会返回false;如果p不是Employee,则会抛出一个异常。

如果说需要检查p是一个Employee而且不是其子类,则如下使用:

还有个更好的选择,使用模式匹配:

 

protected字段和方法

用protected修饰符声明的字段和方法,可以被子类访问,但不能从其他位置访问。要注意,protected成员在当前类所属的包来说,也是不可见的,这是与Java不同的。

Scala提供了与private[this]类似的protected[this]。

超类的构造

在学习构造器时可以知道,辅助构造器最终都会调用主构造器。在Scala中,只有主构造器才能主动调用超类的构造器。

主构造器是与类的定义混合在一起的,调用超类构造器也是与类的定义交织在一起的。

 

重写字段

因为Scala的字段是由私有字段和setter/getter共同组成的,所以在重写的时候,对于不同类型的声明会有不同的限制。

声明的类型有val、var和def。限制:

现学现卖用HTML画的表格   用val 用def 用var
重写val
  • 子类有一个私有字段
  • getter方法重写超类的getter方法
错误 错误
重写def
  • 子类有一个私有字段
  • getter方法重写超类的getter方法
和Java一样 重写getter/setter,只重写getter报错
重写var 错误 错误 仅当超类的var是抽象的才可以

从这里看来,使用var是个很不好的选择。不过我暂时还不知道有什么样更好的方案。

 

匿名子类

 

从技术上讲,这会创建出一个结构类型的对象。结构类型要到后面才能细说。类型标记为 Person{def greeting: String}。可以将这个类型作为参数类型的定义:

 

抽象类

用abstract关键字来标记不能被实例化的类。如果某个类存在抽象方法,该类就必须是abstract的。

但抽象方法不需要使用abstract关键字,只要将方法体留为空即可。

重写超类的抽象方法时,不需要使用override关键字。

 

抽象字段

没有初始值的字段即是抽象字段。根据是val声明还是var声明,会生成相应的抽象的setter/getter。但是不生成字段。

查看编译后的字节码,可以得知,JVM类只生成了setter/getter,但没有生成字段。

 

构造顺序和提前定义(L3)

这个主题好高的技能等级…不过看下来也挺好理解。这个等级说明的是要求等级,而不是难度等级。

现有如下的类:

在构造时,发生的过程如下:

  1. Ant构造器在构造自己之前,调用超类构造器;
  2. Creature的构造器将range字段设为10;
  3. Creature的构造器初始化env数组,调用range字段的getter;
  4. range的getter被Ant类重写了,返回的Ant类中的range,但是Ant类还未初始化,所以返回了0;
  5. env被设置成长度为0的数组
  6. Ant构造器继续执行,将range字段设为2。

在Java中也会出现碰见相似的问题,被调用的方法被子类所重写,有可能结果不是预期的。在构造器中,不应该依赖val的值。(只能重写超类抽象的var声明字段,所以没有这个问题;如果是def,也一样会出现这种问题。)

这个问题的根本原因来自于Java语言的设计决定——允许在超类的构造方法中调用子类的方法。而在C++中,构造前后会更改指向虚函数的指针,所以不会出现这类问题。

这个问题有几种解决方法:

  • 将val声明为final,安全但不灵活;
  • 在超类中将val声明为lazy,安全但不高效;
  • 使用提前定义语法。

提前定义语法真的如书上所说——难看到家了,估计没人会喜欢。将需要提前定义的成员放在extends关键字后的一个语法块中,还需要使用with关键字:

提前定义的等号右侧只能引用之前已经有的提前定义,不能出现类中其他的成员(因为都还没初始化呢)。

Tip:

使用-Xcheckinit编译器标志来调试构造顺序问题。这个标志会在有未初始化的字段被访问时抛出异常。

Scala继承层级

这个还是搭配图来看比较好,这里贴的是《Programming in Scala》一书的图。

与Java中基本类型对应的类,以及Unit类,都继承自AnyVal。所有其他的类都继承自AnyRef,AnyRef相当于Java中的Object类。

AnyVal和AnyRef都继承自Any类,Any类是整个层级的根节点。Any类定义了isInstanceOf、asInstanceOf方法,以及用于相等性判断和哈希码的方法。

AnyVal没有添加任何方法,只是所有值类型的一个标记。AnyRef追加了来自Object类的监视方法wait和notify/notifyAll,同时提供了一个带函数参数的方法synchronized,等同于Java中的synchronized块。

所有Scala类都实现ScalaObject这个特质,这个特质没有定义任何方法。(ScalaObject在2.10.0版本后被移除了。)

在层级的另一端,Null是所有AnyRef类型的子类型;Nothing是所有类型的子类型。

Null类的唯一实例是null值。可以将null赋值给引用类型,但不能赋值给值类型。Nothing类型没有实例,但对泛型结构时常有用。比如,空列表Nil的类型是List[Nothing],是List[T]的子类型。

Noting类型与其他语言中出现的void是没有关系的。Scala中void由Unit类型表示,Unit类型只有一个值,( )。虽然Unit不是其他类型的超类,但是编译器允许任何值被体换成( )。

 

对象相等性(L1)

AnyRef的eq方法会检查两个引用是否指向同一个对象。而AnyRef的equals方法默认是调用eq方法的,也就是说,equals默认实现是检查两个引用是否是同一个对象。如果需要其他的相等性判断,就需要重写equals方法。

当定义equals时,也需要同时重写hashCode方法。计算哈希码时,应该只使用用来做相等性判断的字段来进行。

在应用程序当中,通常不直接调用eq或equals方法,使用==操作符就好了。对于引用类型来说,==操作符会在做完必要的null检查之后调用equals方法。


本章习题参考。只能作为小小的参考。

4.

 

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics