Java 下一代: Groovy、Scala 和 Clojure 中的共同点(一)2013-09-13 Neal Ford 探究这些下一代 JVM 语言如何处理操作符重载编程语言中的好理念可以延续并扩展到其他语言,就像美酒一样历久弥香。因此,不足奇怪的是,Java 下一代语言 — Groovy、Scala 和 Clojure — 具有很多共同的特性。在本期和下一期 Java 下一代 文章中,我将探讨每种语言语法中功 能清单的一致性。我从能够重载操作符这个特性说起 — 克服了Java 语言中长期存在的一个缺点。操作符重 载如果您改造过 Java BigDecimal 类,可能看到过类似于清单 1 的代码:清单 1. Java 代码中的 Lackluster BigDecimal 支持
BigDecimal op1 = new BigDecimal(1e12);BigDecimal op2 = new BigDecimal(2.2e9);// (op1 + (op2 * 2)) / (op1/(op1 + (op2 * 1.5e2))BigDecimal lhs = op1.add(op2.multiply(BigDecimal.valueOf(2)));BigDecimal rhs = op1.centeride(op1.add(op2.multiply(BigDecimal.valueOf(1.5e2))),RoundingMode.HALF_UP);BigDecimal result = lhs.centeride(rhs);System.out.println(String.format("%,.2f", result));
在 清单 1 中,我试图实现注释中的这个公式 。在 Java 编程中,因无法重载数学操作符,使得我只能求助于方法调用。静态导入可以解决问题,但是对于所选择的上下 文,显然需要适当的操作符重载。最初的 Java 工程师故意从语言上忽略操作符重载,不过这感觉增加了太大的复杂性。但 是经验表明,因缺乏这一特性而强加给开发人员的复杂性更甚于潜在的滥用机会。用稍微各不相同的方式,所有三 种 Java 下一代语言都实现了操作符重载。Scala 的操作符 Scala 通过放弃操作符与方法之间的区 别而允许操作符重载。操作符只不过是具有特殊名称的方法。例如,要重写乘法操作符,可以重写 * 方法。[* 是一个有效 的方法名称,这就是 Scala 使用下划线 (_) 符号而不是 Java 星号 (*) 符号来代表导入的原因之一。] 我 使用复数来说明重载。复数是一种数学表示,包括实部和虚部,例如通常写作 3 + 4i 这样的形式。复数在很多科学领域都 很常见,包括工程学、物理学、电磁学以及其他理论。清单 2 显示了复数的 Scala 实现:清单 2. Scala 复数
final class Complex(val real:Int, val imaginary:Int) {require (real != 0 || imaginary != 0) def +(operand:Complex) =new Complex(real + operand.real, imaginary + operand.imaginary) def +(operand:Int) =new Complex(real + operand, imaginary) def -(operand:Complex) =new Complex(real - operand.real, imaginary - operand.imaginary) def -(operand:Int) =new Complex(real - operand, imaginary) def *(operand:Complex) =new Complex(real * operand.real - imaginary * operand.imaginary,real * operand.imaginary + imaginary * operand.real) override def toString() =real + (if (imaginary < 0) "" else "+") + imaginary + "i" override def equals(that:Any) = that match {case other :Complex => (real == other.real) && (imaginary == other.imaginary)case _ => false} override def hashCode():Int =41 * ((41 + real) + imaginary)} Scala 通过折叠不必要的脚手架代码,大大降低了 Java 语言的啰嗦程度。例如,在 清单 2 中,类中 的构造函数参数和字段与类定义一起出现。在本例中,类的主体充当构造函数,所以对 require() 方法的调用在第一次实 例化操作过程中验证值的存在。因为 Scala 自动提供字段,所以类的其余部分包含方法定义。对于 +、- 和 * 操作符,我 都声明了接受 Complex 数作为参数的同名方法。复数的乘法不及加法和减法那么直观。清单 2 中已重载的 * 方法实现公 式:(x + yi)(u + vi) = (xu - yv) + (xv + yu)i