函数式对象
本文的重点在于定义函数式对象,也就是说,不具备任何可变状态的对象的类。不可变对象提供了若干强于可变对象的优点和一个潜在的缺点:
- 优点:
- 首先,不可变对象常常比可变对象更易理清头绪,因为它们的内部状态不因时间的变化而变化。
- 其次,当你把可变对象传递给其他方法或函数时,可能在执行代码的过程中改变对象的内部状态,如果你在代码执行完毕后,你仍然需要对象原先的内部状态,你需要在传递对象之前建立一个副本。
- 第三,一旦不可变对象完成构造之后,就不会有线程因为并发访问而破坏对象的内部状态,因为根本没有线程可以改变对象的内部状态。
- 缺点:
- 不可变对象唯一的缺点就是有时需要赋值很大的对象表而可变对象的更新可以在原地址上发生,在这样的情况下可能产生性能瓶颈。
类Rational的规格说明书
有理数是一种可以表达为比率n/d的数字,这里的n和d是数字,其中d不能为0。n被称作分子,d被称作分母。有理数的例子有:1/2,2/3,11/13,15/7,2/1。与浮点数相比较,有理数的优势是小数部分可以得到完全表达,没有舍入或估算
创建有理数类Rational
类Rational没有主体,就不一定要指定一对空的花括号。在类名Rational之后的括号里的n和d,被称作类参数。Scala编译器会收集这两个类参数并构造出带同样的两个参数的主构造器:
class Rational(n: Int, d: Int)
代码1-1,Scala编译器会把类内部的任何既不是字段也不是方法定义的代码编译至主构造器中,Scala会把require和println调用放进Rational的主构造器中,require方法带一个布尔型参数,如果传入的值为真,require将正常返回。反之将抛出IllegalArgumentException异常阻止对象被构造
代码1-1
Scala> class Rational(n: Int, d: Int) { require(d != 0) println("Created " + n + "/" + d) } defined class Rational Scala> new Rational(1, 2) Created 1/2 res5: Rational = Rational@6cbcf243 Scala> new Rational(3, 2) Created 3/2 res6: Rational = Rational@3fc9dfc5 Scala> new Rational(5, 0) java.lang.IllegalArgumentException: requirement failed
代码1-2重新实现toString方法
默认情况下,类Rational继承自java.lang.Object,类Object实现了toString方法打印对象的内存地址,方法前定义的override修饰符说明这是对原方法定义的重载
代码1-2
Scala> class Rational(n: Int, d: Int) { require(d != 0) override def toString = n + "/" + d } defined class Rational Scala> new Rational(1, 2) res8: Rational = 1/2 Scala> val x = new Rational(1, 2) x: Rational = 1/2 Scala> println(x) 1/2 Scala> val y = new Rational(3, 2) y: Rational = 3/2 Scala> println(y) 3/2
字段和自引用
代码1-3中,我们创建了两个字段,分别是number和denom,并用类参数n和d初始化,在add方法中,调用that的是字段,而不是类参数,类参数只能在本对象中引用,一旦想在对象之外引用对象的类参数,编译器将报错。同时,关键字this代表指向本对象的引用,number和denom两个字段也可以用this.number和this.denom来表示
代码1-3
Scala> class Rational(n: Int, d: Int) { require(d != 0) val number = n val denom = d override def toString = number + "/" + denom def add(that: Rational): Rational = new Rational(number * that.denom + that.number * denom, denom * that.denom) def lessThan(that: Rational) = this.number * that.denom < that.number * this.denom def max(that: Rational) = if (lessThan(that)) that else this } defined class Rational Scala> val x = new Rational(1, 2).add(new Rational(1, 3)) x: Rational = 5/6
辅助构造器
有时候一个类里需要多个构造器,Scala里主构造器之外的构造器被称为辅助构造器,比方说分母为1的有理数只写分子会更为简洁,则可以用def this(n: Int) = this(n, 1)来声明一个辅助构造器,只传入分子,如代码1-4第4行
代码1-4
Scala> class Rational(n: Int, d: Int) { require(d != 0) val number = n val denom = d def this(n: Int) = this(n, 1) override def toString = number + "/" + denom def add(that: Rational): Rational = new Rational(number * that.denom + that.number * denom, denom * that.denom) def lessThan(that: Rational) = this.number * that.denom < that.number * this.denom def max(that: Rational) = if (lessThan(that)) that else this } defined class Rational Scala> val x = new Rational(5, 1) x: Rational = 5/1
定义方法和操作符
Scala也可以和java一样定义私有(private)方法,也支持方法重载,如代码1-5,代码第31行方法gcd方法即为私有方法,目的在求出分子分母的最大公约数,使2/6可以简化为1/3,同时,Scala支持定义操作符,如代码的36行和39行,可以看到变量x和变量y的值是一样的。
代码1-5
Scala> class Rational(n: Int, d: Int) { require(d != 0) private val g = gcd(n, d) val number = n / g val denom = d / g def this(n: Int) = this(n, 1) override def toString = number + "/" + denom def add(that: Rational): Rational = new Rational(number * that.denom + that.number * denom, denom * that.denom) def add(that: Int): Rational = add(new Rational(that)) def subtract(that: Rational): Rational = new Rational(number * that.denom - that.number * denom, denom * that.denom) def subtract(that: Int): Rational = subtract(new Rational(that)) def multiply(that: Rational): Rational = new Rational(number * that.number, denom * that.denom) def multiply(that: Int): Rational = new Rational(number * that, denom) def divide(that: Rational): Rational = new Rational(number * that.denom, denom * that.number) def divide(that: Int): Rational = new Rational(number, denom * that) def +(that: Int) = add(that) def +(that: Rational): Rational = add(that) def -(that: Int) = subtract(that) def -(that: Rational): Rational = subtract(that) def *(that: Rational): Rational = multiply(that) def *(that: Int) = multiply(that) def /(that: Rational): Rational = divide(that) def /(that: Int) = divide(that) def lessThan(that: Rational) = this.number * that.denom < that.number * this.denom def max(that: Rational) = if (lessThan(that)) that else this private def gcd(a: Int, b: Int): Int = if (b == 0) a else gcd(b, a % b) } defined class Rational Scala> val x = new Rational(2, 6) x: Rational = 1/3 Scala> val y = new Rational(2, 3) y: Rational = 2/3 Scala> val z = x.multiply(y) z: Rational = 2/9 Scala> val k = x * y k: Rational = 2/9 Scala> val l = x - y l: Rational = 1/-3
只是,我们可以写成new Rational(2, 3) + 1,却不能写成1 + new Rational(2, 3),因为1是Int类型,Int类中并没有定义带有Rational参数的加法,不过,Scala有另外的方法来解决这个问题,可以创建在需要的时候自动把整数转换为有理数的隐式转换,如代码1-6
implicit def intToRation(x: Int) = new Rational(x)
这行代码定义了Int到Rational的转化方法,方法前面的implicit修饰符告诉编译器在一些情况下自动调用,有一点需要注意的是,隐式转换需要定义在作用范围之内,如果把隐式方法定义在类Rational之内,它就不在解释器范围
代码1-6
Scala> implicit def intToRation(x: Int) = new Rational(x) warning: there were 1 feature warning(s); re-run with -feature for details intToRation: (x: Int)Rational Scala> 2 * new Rational(3, 5) res0: Rational = 6/5