第06章 函数式对象
1 Rational类的规则定义
有理数用 n/d表示
2 构建Rational
//n,d为类参数
class Rational(n: Int, d: Int)
- 如果一个类没有定义体,并不需要给出空的花括号
- 圆括号中的 n , d 称作类参数,scala 编译器将创建一个主构造方法,主构造方法也会接收这两个参数
- 不可变对象的设计取舍,不可变对象可以安全的用作哈希表里的键。不可变对象的主要劣势是它们有时候会需要拷贝一个大的对象图,而实际上也许一个局部的更新就能满足要求。
- java中类有构造方法,构造方法可以接受参数,而在 scala 中,类可以直接接受参数
class Rational(n: Int, d: Int) {
//方法会编译进主构造器中
println("created " + n + "/" + d)
}
- scala编译器会将你在类定义体中给出的非字段和方法定义的代码编译进类的主构造方法中
3 重新实现toString方法
class Rational(n: Int, d: Int) {
println("created " + n + "/" + d)
override def toString(): String = n + "/" + d
}
- 在方法定义之前的override修饰符表示重写覆盖方法
4 检查前置条件
class Rational(n: Int, d: Int) {
require(d != 0)
override def toString(): Unit = println(n + "/" + d)
}
- require 方法接受一个boolean类型的参数,参数值为true将正常返回,否则抛出 IllegalArgumentException异常
- require 方法定义在Predef 这个独立对象中
5 添加字段
class Rational(n: Int, d: Int) {
require(d != 0)
override def toString(): String = n + "/" + d
val number = n
val denom = d
def add (that: Rational ) :Rational = {
//n和d非字段,下面代码编译无法通过
//new Rational(n * that.d + that.n * d, d * that.d)
new Rational(number * that.denom + that.number * denom, denom * that.denom)
}
}
- 参数 n , d 虽然在add方法作用域中,但只能访问执行add调用对象上的n和d的值,当你在add实现中用到n或d时,编译器会提供这些类参数对应的值,但它不允许使用that.n或that.d,因为that并非指向你执行add调用的那个对象。尽管 n 和 d 在类的定义中被使用,由于它们只出现在构造方法中,scala编译器并不会为它们生成字段
6 自引用
关键字this指向当前执行方法的调用对象
7 辅助构造方法
class Rational(n: Int, d: Int) {
require(d != 0)
override def toString(): String = n + "/" + d
val number = n
val denom = d
def add(that: Rational): Rational = {
//编译无法通过
//new Rational(n * that.d + that.n * d, d * that.d)
new Rational(number * that.denom + that.number * denom, denom * that.denom)
}
def this(n: Int) = {
this(n, 1)
}
}
- scala辅助构造方法以 def this(…) 开始,每个辅助构造方法都必须先调用同一个类的另一个构造方法。换句话说,scala 每个辅助构造方法的第一条语句必须是 " this(…) "形式。scala 的每个构造方法最终都会调用到该类的主构造方法,这样一来,主构造方法就是类的单一入口
- scalc的构造方法比 java 跟严格,scala 只有主构造方法可以调用超类的构造方法,scala 这样做用来换取根据精简的代码和 java 相比跟简单的构造方法
8 私有字段和方法
9 定义操作符
class Rational(n: Int, d: Int) {
require(d != 0)
//私有字段
private val g = gcd(n.abs, d.abs)
val number = n / g
val denom = d / g
override def toString(): String = number + "/" + denom
def this(n: Int) = {
this(n, 1)
}
//私有字段和方法
private def gcd(a: Int, b: Int): Int = {
if (b == 0) a else gcd(b, a % b)
}
def add(that: Rational): Rational = {
//编译无法通过
//new Rational(n * that.d + that.n * d, d * that.d)
new Rational(number * that.denom + that.number * denom, denom * that.denom)
}
//定义操作符
def +(that: Rational): Rational = {
add(that)
}
}
10 scala中的标识符
- 字母数字组合标识符
已字母或下划线开头,可包含更多的字母,数字,下划线,字符 “ $ ” 也算字母
scala 遵循了 java 使用驼峰命名法
虽然下划线是合法标识符,它们在scala中不常用,下划线在scala中还有许多其他非标识符的用法
在标识符的末尾使用下划线的一个后果是,如果你声明了一个变量 " val name_:Int = 1",你会得到一个变异错误。编译器会认为你要声明的变量名是 “ name_: ”。要让编译器通过,可以在" name_ "加个空格
java中常量的命名习惯是全大写,scala的习惯只要求首字母大写
- 操作标识符
操作符指的是那些可以被打印出来的ASCII字符,如 + * :::,scala 编译器会在内部将操作符用内嵌 $ 的方法转成合法的 Java 标识符,比如,:-> 这个操作符会在内部表示为 c o l o n colon colonminus$greater。如果你打算从 Java 中访问这些标识符,就需要使用这种内部形式
- 混合标识符
由一个字母数字组合操作符、一个下划线和一个符号操作符组成,如unary_+
- 字面标识符
用反引号括起来的任意字符串
11 方法重载
scala 解析重载方法的过程跟 java 很像,被选中的是哪个最匹配入参静态类型的重载版本,有时候没有一个唯一的最佳匹配版本,编译会给出提示(模糊引用)
12 隐式转换
implicit def intToRational(x: Int): Rational = new Rational(x)
在方法前加上 implicit ,在需要是自动转换,为了让隐式转换能工作,它需要在作用域内
13 注意事项
用操作符作为方法名称创建方法,以及定义隐式转换有助于设计出调用代码精简并易于理解的类库。scala 为你提供了强大的能力来设计这样的类库。不过请记住,能力越大责任越大。在设计类时,目标应该不仅让使用方代码尽量精简,而是要可读并且可被理解
14 结束
在scala中定义和使用不可变对象是很自然的一种编程方式