以一个有理数类为例演示Scala面向对象编程的各个方面
设想中的功能:
val 一半 = new 有理数(1, 2) // 结果为1/2
val 三分之二 = new 有理数(2, 3)
(一半 / 7) + (1 - 三分之二) // 结果为有理数17/42
class 有理数(分子: Int, 分母: Int)
不可变对象有好处, 也有短处. 这里创建不可变对象.
任何类内不为变量或者方法声明的部分都会被置于首要构建器.
class 有理数(分子: Int, 分母: Int) {
println("已创建" + 分子 + "/" + 分母)
}
调用new 有理数(1, 2)
会打印已创建1/2
class 有理数(分子: Int, 分母: Int) {
override def toString = 分子 + "/" + 分母
}
运行:
scala> new 有理数(1, 2)
res1: 有理数 = 1/2
class 有理数(分子: Int, 分母: Int) {
require(分母 != 0)
override def toString = 分子 + "/" + 分母
}
class 有理数(分子: Int, 分母: Int) {
require(分母 != 0)
val 分子值: Int = 分子
val 分母值: Int = 分母
override def toString = 分子值 + "/" + 分母值
def 加(数: 有理数): 有理数 =
new 有理数(
分子值 * 数.分母值 + 数.分子值 * 分母值,
分母值 * 数.分母值
)
}
运行:
scala> val 一半 = new 有理数(1, 2)
一半: 有理数 = 1/2
scala> val 三分之二 = new 有理数(2, 3)
三分之二: 有理数 = 2/3
scala> 一半 加 三分之二
res3: 有理数 = 7/6
也可取对象内变量值:
scala> 一半.分子值
res4: Int = 1
scala> 一半.分母值
res5: Int = 2
def 小于(数: 有理数) =
this.分子值 * 数.分母 < 数.分子 * this.分母
def 最大(数: 有理数) =
if (this.小于(数)) 数 else this
所有辅助构建器的第一句必须调用另一个构建器, 因此必须是this(...)
格式
def this(数: Int) = this(数, 1)
在有理数类中, 新添如下:
class 有理数(分子: Int, 分母: Int) {
require(分母 != 0)
private val 公约数 = 最大公约数(分子.abs, 分母.abs)
val 分子值: Int = 分子 / 公约数
val 分母值: Int = 分母 / 公约数
override def toString = 分子值 + "/" + 分母值
def 加(数: 有理数): 有理数 =
new 有理数(
分子值 * 数.分母值 + 数.分子值 * 分母值,
分母值 * 数.分母值
)
private def 最大公约数(甲: Int, 乙: Int): Int =
if (乙 == 0) 甲 else 最大公约数(乙, 甲 % 乙)
}
运行:
scala> new 有理数(66, 42)
res7: 有理数 = 11/7
定义了+和*:
class 有理数(分子: Int, 分母: Int) {
require(分母 != 0)
private val 公约数 = 最大公约数(分子.abs, 分母.abs)
val 分子值: Int = 分子 / 公约数
val 分母值: Int = 分母 / 公约数
def this(数: Int) = this(数, 1)
def + (数: 有理数): 有理数 =
new 有理数(
分子值 * 数.分母值 + 数.分子值 * 分母值,
分母值 * 数.分母值
)
def * (数: 有理数): 有理数 =
new 有理数(分子值 * 数.分子值, 分母值 * 数.分母值)
override def toString = 分子值 + "/" + 分母值
private def 最大公约数(甲: Int, 乙: Int): Int =
if (乙 == 0) 甲 else 最大公约数(乙, 甲 % 乙)
}
运行. 运算符优先级见5.9:
val x = new 有理数(1, 2)
val y = new 有理数(2, 3)
x + y
x + x * y
(x + x) * y
x + (x * y)
之前看到了两种标识符: 数字字母混合, 运算符
与Java不同的是, 常量在Java中是大写加下划线分隔, 如X_OFFSET
; Scala只要求驼峰命名而且开头大写, 如XOffset
运算符标识符也有不同, x<-y在Java中被识别为x < - y
, 但Scala中将<-
解析为单个标识符
混合标识符, 如unary_+
定义一元+运算符, myvar_=
定义赋值运算符 (第18章更多)
字面量标识符如x
, yield
, <clinit>
. 一个用法是访问Java的Thread
类, 由于yield
是Scala保留字, 不能写Thread.yield()
, 但可以用反引号: Thread.`yield`()
支持有理数+整数, 顺便加上減和除. 源码在有理数.scala(代码将命名作了调整, 以使得重复使用的命名尽量简约)
class 有理数(分子: Int, 分母: Int) {
require(分母 != 0)
private val 公约数 = 最大公约数(分子.abs, 分母.abs)
val 分子值: Int = 分子 / 公约数
val 分母值: Int = 分母 / 公约数
def this(数: Int) = this(数, 1)
def + (数: 有理数): 有理数 =
new 有理数(
分子值 * 数.分母值 + 数.分子值 * 分母值,
分母值 * 数.分母值
)
def + (数: Int): 有理数 =
new 有理数(分子值 + 数 * 分母值, 分母值)
def - (数: 有理数): 有理数 =
new 有理数(
分子值 * 数.分母值 - 数.分子值 * 分母值,
分母值 * 数.分母值
)
def - (数: Int): 有理数 =
new 有理数(分子值 - 数 * 分母值, 分母值)
def * (数: 有理数): 有理数 =
new 有理数(分子值 * 数.分子值, 分母值 * 数.分母值)
def * (数: Int): 有理数 =
new 有理数(分子值 * 数, 分母值)
def / (数: 有理数): 有理数 =
new 有理数(分子值 * 数.分母值, 分母值 * 数.分子值)
def / (数: Int): 有理数 =
new 有理数(分子值, 分母值 * 数)
override def toString = 分子值 + "/" + 分母值
private def 最大公约数(甲: Int, 乙: Int): Int =
if (乙 == 0) 甲 else 最大公约数(乙, 甲 % 乙)
}
运行:
scala> val x = new 有理数(2, 3)
x: 有理数 = 2/3
scala> x * x
res4: 有理数 = 4/9
scala> x * 2
res5: 有理数 = 4/3
现在仍不支持2*x
:
scala> 2 * x
<console>:13: error: overloaded method value * with alternatives:
(x: Double)Double <and>
(x: Float)Float <and>
(x: Long)Long <and>
(x: Int)Int <and>
(x: Char)Int <and>
(x: Short)Int <and>
(x: Byte)Int
cannot be applied to (有理数)
2 * x
运行. 注意如果在类内声明, 就只在类内有效, 而在交互环境内就无效, 因此必须在交互环境声明:
implicit def 整数到有理数(数: Int) = new 有理数(数)
之后再运行成功. 更多隐式转换见21章
scala> val r = new 有理数(2, 3)
r: 有理数 = 2/3
scala> 2 * r
res0: 有理数 = 4/3
运算符方法和隐式转换过头会导致代码可读性下降, 比如当运算符过于复杂或者晦涩, 而隐式转换的问题是它可能不写在源代码中. 因此需谨慎使用.
(第六章完)