Scala By Example: 类和对象

    技术2022-05-20  33

    累……哦,不对,是类

    1: // If class A extends class B, we say that type A 'conforms' to type B. If a 2: // class does not mention a supperclass in its definition, the root type 3: // scala.AnyRef (an alias for java.lang.Object) is implicitly assumed. 4: class Rational(n: Int, d: Int) { 5: private def gcd(x: Int, y: Int): Int = { 6: if(x == 0) y 7: else if(x < 0) gcd(-x, y) 8: else if(y < 0) -gcd(x, -y) 9: else gcd(y % x, x) 10: } 11: private val g = gcd(n, d) 12:  13: val numer: Int = n / g 14: val denom: Int = d / g 15: def +(that: Rational) = 16: new Rational(numer * that.denom + that.numer * denom, 17: denom * that.denom) 18: def -(that: Rational) = 19: new Rational(numer * that.denom - that.numer * denom, 20: denom * that.denom) 21: def *(that: Rational) = 22: new Rational(number * that.numer, denom * that.denom) 23: def /(that: Rational) = 24: new Rational(number * that.denom, denom * that.numer) 25:  26: // Unlike in Java, methods in Scala do not necessarily take a parameter 27: // list, but can be invoked by simply mentioning its name 28: def square = new Rational(numer * numer, denom * denom) 29:  30: // Note that, unlike in Java, redefining definitions need to be preceded by 31: // an 'override' modifier. 32: override def toString = "" + numer + "/" + denom 33: }

    一个表示有理数的类(开始怀念小学时的数学组了 T_T)。在单根体系中,所有的类都继承自同一超类,在 Java 中是 Object,在 Scala 中是 AnyRef,当然,你可以在两者间画个等号。与 Java 不同的是,首先在 Scala 里,方法不一定需要参数列表(见 square)。实际上,无参数方法(parameterless method)的调用和值(value field)形式上是完全相同的。两者的不同点在于,对于 value field 而言,其等号右边的表达式会在创建对象的时候进行求值并且从此保持不变(仅右操作数的值不变!不是说 value field);无参数方法则在每次调用的时候都会进行求值。这样做的好处是,当某天你需要把一个 value field 重构成计算值(computed value)时,直接改就可以,调用这个值的代码无需做任何修改。(参考在 Groovy 中的基于 JavaBean 命名方式的另一种解决方案,当你写一个 getX() 方法时,你可以通过 x 的方式像调用一个值一样调用方法,即使 x 本身存在于类定义内)。另一个和 Java 的差异则是覆写超类成员的方法。在 Java 中,你有可能在试图覆写超类时由于拼写错误而创造一个新的发法,对此编译器将无能为力(小小提示:打开你的 @Override 注释)。而在 Scala 中,由于必须加上 override 修饰符,事情就变得简单了。

    在代码 15 ~ 24 行,可以看到代表了四则运算的四个函数,事实上,这并非“运算符重载”,而只是普通的方法而已。

    最后我们在代码中也看到了熟悉的 private 修饰符。在 Scala 中,访问控制修饰符的应用远比 Java 来的细致(抑或复杂),但眼下,让我们略过这些,简单飘过。(按我的经验,在多数日常任务中,Scala 或 Groovy 的基本权限控制就很不错了,但写类库给别人用时确实要注意!还有就是,当你面对 Groovy 强大的元编程力量时,你究竟能不能控制住还是个问题)

    补充:在铅笔书的第四章提到了非常重要的一点:只有主构造函数才能向基类构造函数中传递参数

    抽象累……类

    1: trait IntSet { 2: def incl(x: Int): IntSet 3: def contains(x: Int): Boolean 4: } 5:  6: object EmptySet extends IntSet { 7: def contains(x: Int) = false 8: def incl(x: Int): IntSet = new NonEmptySet(x, this, this) 9: } 10:  11: class NonEmptySet(elem: Int, left: IntSet, right: IntSet) extends IntSet { 12: def contains(x: Int) = 13: if(x < elem) left contains x 14: else if(x > elem) right contains x 15: else true 16: def incl(x: Int): IntSet = 17: if(x < elem) new NonEmptySet(elem, left incl x, right) 18: else if(x > elem) new NonEmptySet(elem, left, right incl x) 19: else this 20: }

    在 Scala 中抽象类和 Java 是一样的。在以上二叉树代码中,我用 trait 关键字代替了 abstract class,你可以把它直接替换回来。但是注意,trait 并不等同于抽象类,在以后的示例中我们会看到 trait 其实更像拥有实现代码的接口,依赖 trait 的 mix-in 机制,可以解决很多多重继承的问题。

    在第二行,我们看到了 object 关键字。EmptySet 是一个(单例)对象,Scala 没有 static,也不需要仅仅为了访问一个(静态)量而创建一个类。在第 8 行,更清晰的写法是用 EmptySet 来代替 this (见仁见智?)。当然,单例在编译后就是一个使用静态方法的 Java 类,同理,仅仅在第一次调用其值时,jvm 才会实际生成这个对象(lazy evaluation)。

    习题 1: 向二叉树添加 union 和 intersection 函数

    习题 2: 添加 excl(x: Int) 函数来排除某个元素(顺便加上 isEmpty 函数)

    基础型别(依稀记得侯捷老师好像是这么翻译 primitive type 的)和类

    在 Scala 的语法层面,万事皆类,基础型别亦然(它们其实是 Scala 的 Predef 模块中类的别名,如 type int = scala.Int)。但是在具体实现层面,编译器会在适当的场合使用基础型别。可以这么说,基础型别仅仅是“优化措施”而已(相对的,在 Java 中则是重要的语法元素)。没有基础型别,Scala 依然可以完成所有任务!以下来看一个没有基础型别的自然数实现:

    1: abstract class Nat { 2: def isZero: Boolean 3: def predecessor: Nat 4: def successor: Nat 5: def + (that: Nat): Nat 6: def - (that: Nat): Nat 7: } 8:  9: object Zero extends Nat { 10: def isZero = true 11: // how to throw out an Error exception 12: def predecessor: Nat = error("negative number") 13: def successor: Nat = new Succ(Zero) 14: def + (that: Nat) = that 15: def - (that: Nat): Nat = 16: if(that.isZero) Zero 17: else error("negative number") 18: } 19:  20: // a lot of recursive calls 21: class Succ(x: Nat) extends Nat { 22: def isZero = false 23: def predecessor = x 24: def successor: Nat = new Succ(this) 25: def + (that: Nat): Nat = x + that.successor 26: def - (that: Nat): Nat = 27: if(that.isZero) this 28: else x - that.predecessor 29: } 30:  31: val one = new Succ(Zero) 32: val two = new Succ(one) 33: val three = new Succ(two) 34:  35: println((three - two - one).isZero) 36:  37: // will throw Error exception! 38: // one - two

    注意到一旦开始使用 Scala 你会多么频繁的使用递归!当然,注意 Scala 是如何抛出一个 Error 异常的。

    习题 3:不依靠 Scala 的数字类或基础型别实现 Integer 类,包含 Nat 的所有方法,并加上 isPositive 函数和 negate 函数。

    PS. 这一章看完学到很多数学方面的单词……

    PS II. 又冷又累又饿:又降温了,怎么穿都不热(少穿两件也不觉得更冷);又思考了,怎么睡都不醒(少眯两个钟头也不会更困);又发育了,怎么吃都不饱(少吃两斤大米也没见得更瘦)

    Technorati 标签: Scala

    最新回复(0)