继承类
使用extends关键字来继承一个类。如果将类声明为final的,则这个类不能被继承。如果将类的方法和字段声明为final,则它们不能被重写。
重写方法
在Scala中重写一个非抽象方法必须使用override修饰符。调用超类的方法就如Java一样,使用super关键字。
类型检查和转换
使用isInstanceOf方法来检测某个对象是否为某个特定的类;如果检测通过,则可以使用asInstanceOf方法来将对象转换为子类。
1
2
3
4
|
if
(
p
.
isInstanceOf
[
Employee
]
)
{
val
s
=
p
.
asInstanceOf
[
Employee
]
.
.
.
}
|
如果p是null,p.isInstanceOf[Employee]将会返回false;如果p不是Employee,则会抛出一个异常。
如果说需要检查p是一个Employee而且不是其子类,则如下使用:
1
|
if
(
p
.
getClass
==
classOf
[
Employee
]
)
// classOf方法在Predef中
|
还有个更好的选择,使用模式匹配:
1
2
3
4
|
p
match
{
case
s
:
Employee
=
>
.
.
.
// 是Employee
case
_
=
>
.
.
.
}
|
protected字段和方法
用protected修饰符声明的字段和方法,可以被子类访问,但不能从其他位置访问。要注意,protected成员在当前类所属的包来说,也是不可见的,这是与Java不同的。
Scala提供了与private[this]类似的protected[this]。
超类的构造
在学习构造器时可以知道,辅助构造器最终都会调用主构造器。在Scala中,只有主构造器才能主动调用超类的构造器。
主构造器是与类的定义混合在一起的,调用超类构造器也是与类的定义交织在一起的。
1
2
3
4
5
|
class
Employee
(
name
:
String
,
age
:
Int
,
val
salary
:
Double
)
extends
Person
(
name
,
age
)
// 如果是扩展Java类,则如下
class
Square
(
x
:
Int
,
y
:
Int
,
width
:
Int
)
extends
java
.
awt
.
Rectangle
(
x
,
y
,
width
,
width
)
|
重写字段
因为Scala的字段是由私有字段和setter/getter共同组成的,所以在重写的时候,对于不同类型的声明会有不同的限制。
声明的类型有val、var和def。限制:
用val | 用def | 用var | |
---|---|---|---|
重写val |
| 错误 | 错误 |
重写def |
| 和Java一样 | 重写getter/setter,只重写getter报错 |
重写var | 错误 | 错误 | 仅当超类的var是抽象的才可以 |
从这里看来,使用var是个很不好的选择。不过我暂时还不知道有什么样更好的方案。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
class
Person
(
val
name
:
String
)
{
override
def
toString
=
getClass
.
getName
+
"[name="
+
name
+
"]"
}
class
SecretAgent
(
codeName
:
String
)
extends
Person
(
codeName
)
{
override
val
name
=
"secret"
override
val
toString
=
"secret"
}
abstract
class
Person
{
def
id
:
Int
.
.
.
}
class
Student
(
override
val
id
:
Int
)
extends
Person
|
匿名子类
1
2
3
|
val
alien
=
new
Person
(
"Fred"
)
{
def
greeting
=
"Greetings, Earthing! My name is Fred."
}
|
从技术上讲,这会创建出一个结构类型的对象。结构类型要到后面才能细说。类型标记为 Person{def greeting: String}。可以将这个类型作为参数类型的定义:
1
2
3
|
def
meet
(
p
:
Person
{
def
greeting
:
String
}
)
{
println
(
p
.
name
+
"says: "
+
p
.
greeting
)
}
|
抽象类
用abstract关键字来标记不能被实例化的类。如果某个类存在抽象方法,该类就必须是abstract的。
但抽象方法不需要使用abstract关键字,只要将方法体留为空即可。
1
2
3
|
abstarct
class
Person
(
val
name
:
String
)
{
def
id
:
Int
}
|
重写超类的抽象方法时,不需要使用override关键字。
1
2
3
|
class
Employee
(
name
:
String
)
extends
Person
(
name
)
{
def
id
=
name
.
hashCode
}
|
抽象字段
没有初始值的字段即是抽象字段。根据是val声明还是var声明,会生成相应的抽象的setter/getter。但是不生成字段。
1
2
3
4
|
abstract
class
Person
{
val
id
:
Int
var
name
:
String
}
|
查看编译后的字节码,可以得知,JVM类只生成了setter/getter,但没有生成字段。
1
2
3
4
5
6
7
|
Compiled
from
"Person.scala"
public
abstract
class
Person
implements
scala
.
ScalaObject
{
public
abstract
int
id
(
)
;
public
abstract
java
.
lang
.
String
name
(
)
;
public
abstract
void
name_
$
eq
(
java
.
lang
.
String
)
;
public
Person
(
)
;
}
|
构造顺序和提前定义(L3)
这个主题好高的技能等级…不过看下来也挺好理解。这个等级说明的是要求等级,而不是难度等级。
现有如下的类:
1
2
3
4
5
6
7
8
|
class
Creature
{
val
range
:
Int
=
10
val
env
:
Array
[
Int
]
=
new
Array
[
Int
]
(
range
)
}
class
Ant
extends
Creature
{
override
val
range
=
2
}
|
在构造时,发生的过程如下:
- Ant构造器在构造自己之前,调用超类构造器;
- Creature的构造器将range字段设为10;
- Creature的构造器初始化env数组,调用range字段的getter;
- range的getter被Ant类重写了,返回的Ant类中的range,但是Ant类还未初始化,所以返回了0;
- env被设置成长度为0的数组
- Ant构造器继续执行,将range字段设为2。
在Java中也会出现碰见相似的问题,被调用的方法被子类所重写,有可能结果不是预期的。在构造器中,不应该依赖val的值。(只能重写超类抽象的var声明字段,所以没有这个问题;如果是def,也一样会出现这种问题。)
这个问题的根本原因来自于Java语言的设计决定——允许在超类的构造方法中调用子类的方法。而在C++中,构造前后会更改指向虚函数的指针,所以不会出现这类问题。
这个问题有几种解决方法:
- 将val声明为final,安全但不灵活;
- 在超类中将val声明为lazy,安全但不高效;
- 使用提前定义语法。
提前定义语法真的如书上所说——难看到家了,估计没人会喜欢。将需要提前定义的成员放在extends关键字后的一个语法块中,还需要使用with关键字:
1
2
3
|
class
Ant
extends
{
override
val
range
=
2
}
with
Creature
|
提前定义的等号右侧只能引用之前已经有的提前定义,不能出现类中其他的成员(因为都还没初始化呢)。
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不是其他类型的超类,但是编译器允许任何值被体换成( )。
1
2
|
def
printUnit
(
x
:
Unit
)
{
println
(
x
)
}
printUnit
(
"Hello"
)
// 将"Hello"替换成(),将会打印出()
|
对象相等性(L1)
AnyRef的eq方法会检查两个引用是否指向同一个对象。而AnyRef的equals方法默认是调用eq方法的,也就是说,equals默认实现是检查两个引用是否是同一个对象。如果需要其他的相等性判断,就需要重写equals方法。
当定义equals时,也需要同时重写hashCode方法。计算哈希码时,应该只使用用来做相等性判断的字段来进行。
在应用程序当中,通常不直接调用eq或equals方法,使用==操作符就好了。对于引用类型来说,==操作符会在做完必要的null检查之后调用equals方法。
本章习题参考。只能作为小小的参考。
4.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
abstract
class
Item
(
)
{
def
price
:
Double
def
description
:
String
}
class
SimpleItem
(
val
price
:
Double
,
val
description
:
String
)
extends
Item
{
}
import
scala
.
collection
.
mutable
.
ArrayBuffer
class
Bundle
(
var
items
:
ArrayBuffer
[
Item
]
)
extends
Item
{
def
this
(
)
{
this
(
new
ArrayBuffer
[
Item
]
)
}
override
def
price
:
Double
=
{
if
(
items
==
null
||
items
.
size
==
0
)
return
0D
var
total
=
0D
items
.
foreach
(
total
+=
_
.
price
)
total
}
override
def
description
=
{
if
(
items
==
null
||
items
.
size
==
0
)
"Nothing in bundle"
else
items
.
map
(
_
.
description
)
.
mkString
(
"Items in Bundle: ["
,
", "
,
"]"
)
}
def
addItem
(
item
:
Item
)
{
items
+=
item
}
}
val
item1
=
new
SimpleItem
(
2.0
,
"paper"
)
val
item2
=
new
SimpleItem
(
38
,
"How to read a book"
)
val
items
=
new
Bundle
items
addItem
item1
items
addItem
item2
println
(
"Bundle price: "
+
items
.
price
)
println
(
items
.
description
)
|