论坛首页 Java企业应用论坛

Java.next:第一部分——共同点

浏览 2832 次
该帖已经被评为良好帖
作者 正文
   发表时间:2008-09-19  
OO
  原文地址Java.next: Common Ground
  翻  译Eastsun
  本文是Java.next系列的第一部分。在这一部分,我将探讨作为Java.next的语言所具有的共同特征。
  我选择了四种语言作为“Java.next”的代表:Clojure,Groovy,JRuby,以及Scala。乍看起来,这几种语言有着很大的不同。Clojure是Lisp方言;Groovy是作为“类Java”的选择;JRuby即具有Ruby语言的优雅,同时也有着Rails所带来的优势;与其他都不一样的是Scala,它有着静态语言所具有的特点。
  正如你所料想的一样,有很多关于这些语言中谁才是最好的辩论。之所以有着这么多的辩论,很大程度上是因为这些语言有着很多共同点。它们有着一个共同的演变背景:Java语言。Java语言所具有的优点以及缺陷影响着这些语言的设计方向。
  在这篇文章中,我着重从下面两个方面来阐述这些语言的共同点:

  ☆ 过去的10年中,我们在基于虚拟机、面向对象的语言编程中得到了很多关于如何开发易读的、可维护的应用。Java.next吸取了这些成果,使得这些语言更注重于问题的本质而不是形式。
  ☆ “本质 VS 形式”的设计理念使得编程方式发生了很大的改变,这种观念的变化比以前从C/C++到Java的转变更大。


  我将Java.next所具有的共同优点概括为以下八点:
  ● 一切皆对象
  ● 简洁的属性定义方式
  ● 易用的集合类
  ● 函数式编程
  ● 运算符重载
  ● 可维护的异常处理
  ● 给已有类增加新方法
  ● 创建新的语言结构



一切皆对象
  在Java中,我们每时每刻都要面对对象类型与基本类型的不同之处。这种不同导致三个实际问题:
  1.API必须写两份:一个针对对象类型;一个针对基本类型。更糟糕的情形是需要重写多份:一个针对对象类型,然后对每一个基本类型各写一份。
  2.默认的数值类型有着范围限制,一旦越界,程序会以诡异的形式中断。
  3.对于那些高精度类型(译者注:指BigInteger等类型),你不能使用直观的数学操作符(+,-,etc.)来操作它们。

  在Java.next中,一切皆是对象。你可以在所有的类型上使用相同的语法调用方法。
; clojure
(. 1 floatValue)
1.0

// groovy
1.floatValue()
===> 1.0

# ruby
1.to_f
=> 1.0

// scala
1.floatValue
res1: Float = 1.0


简洁的属性定义方式
  在Java中建立一个属性,你必须定义一个域,一个Getter,一个Setter,以及(通常)一个相应的构造函数,每一个定义都需要适当的访问修饰词。在Java.next中,你可以毕其功于一役。
; clojure
(defstruct person :first-name :last-name)

// groovy
class Person {
    def firstName
    def lastName
}

# ruby
Person = Struct.new(:first_name, :last_name)

// scala
case class Person(firstName: String, lastName: String) 

  如果你需要复写(或删除)一个Getter,Setter或是构造函数,你也可以做到,而无需重写其它部分。
  这还不是全部。所有这些语言信奉TMTOWTDI (There's More Than One Way To Do It),因此这儿不止一种方式可以实现上面同样的要求。


易用的集合类
  Java.next针对大部分重要的集合类提供了便利的语法:array与map。此外,你可以通把将函数作为参数将一系列操作连贯起来,而避免使用Java中那种显式的迭代或循环方式。譬如:找出100以内的奇完全平方数:
; clojure
(filter (fn [x] (= 1 (rem x 2))) (map (fn [x] (* x x)) (range 10)))
(1 9 25 49 81)

// groovy
(1..10).collect{ it*it }.findAll { it%2 == 1}
===> [1, 9, 25, 49, 81]

# ruby
(1..10).collect{ |x| x*x }.select{ |x| x%2 == 1}
=> [1, 9, 25, 49, 81]

// scala
(1 to 10).map(x => x*x).filter(x => x%2 == 1)
res20: Seq.Projection[Int] = RangeMF(1, 9, 25, 49, 81)

对哈希表(字典)有着同样便利的操作。

函数式编程
  上面集合类的便利使用只是函数式编程的一个特殊情形。Java.next中函数作为一等公民,支持将函数作为参数,将函数作为返回值,以及支持闭包。举一个简单的例子:创建一个函数adder,使得能计算与一个运行时指定的值之和:
; clojure
(defn adder [x] (fn [y] (+ x y)))

// groovy
adder = { add -> { val -> val + add } } 

# ruby
def adder(add)
  lambda { |x| x + add }
end

// scala
def sum(a: Int)(b: Int) = a + b


运算符重载
  在Java中,你不能重载运算符。因此你只能像这样操作BigDecimal:
// Java math
balance.add(balance.multiply(interest));

  Java.next允许你重载运算符。这样你能够创建新的类型并使得它像内建类型一样工作。譬如你能够新建一个ComplexNumber或RationalNumber,使其支持+,-,*,/运算符:
; Clojure
(+ balance (* balance interest))

// Groovy
balance + (balance * interest)

# JRuby
balance + (balance * interest)

// Scala 
balance + (balance * interest)


可维护的异常处理
  检查型异常(Checked Exception)是一个失败的试验。检查型异常的处理代码使得Java代码变得臃肿并掩盖了问题的关键。更糟糕的是,检查型异常使得代码的维护变得困难。
  Java.next不要求你声明检查型异常,也不要求你显式的处理检查型异常。这表明Java平台的其他语言完全可以避免Java语言中丑陋的检查型异常。

给已有类增加新方法
  在Java中,你不能给已有类添加方法。这使得面向对象模型变得很荒谬,开发者需要创建一些工具类,而这是与OO相违背的。
// Java (from the Jakarta Commons)
public class StringUtils { 
  public static boolean isBlank(String str) { 
    int strLen; 
    if (str == null || (strLen = str.length()) == 0) { 
      return true; 
    }  
    for (int i = 0; i < strLen; i++) { 
    if ((Character.isWhitespace(str.charAt(i)) == false)) { 
      return false; 
    } 
  }
}

  在Java.next中,你能够给已有类增加方法:
; Clojure
(defmulti blank? class)
(defmethod blank? String [s] (every? #{\space} s))
(defmethod blank? nil [_] true)

// Groovy
String.metaClass.isBlank = {
  length() == 0 || every { Character.isWhitespace(it.charAt(0)) }
}

# Ruby (from Rails)
class String 
  def blank? 
    empty? || strip.empty? 
  end 
end 

// Scala
implicit def strWrapper(s :String) = new {
    def isBlank = s.forall{ _.isWhitespace }
}


创建新的语言结构
  Java包括Java语言以及API库,这两部分是截然不同的:你能够创建新的API库,但你不能够添加新的语言特性。
  而在Java.next中,API库与语言特性没有明显的界线。你能够创建新的语言结构使得它像核心语言特性一样工作。例如,Clojure提供了一个and函数:
; clojure
(and 1 2) => 2

  你需要解决的不一定都是这样二元化的问题。你可能需要一个most函数,当它参数中大部分真时返回true。Clojure中没有这个,但你可以自己动手写一个:
; clojure
(most 1 2) => true
(most 1 2 nil) => true
(most 1 nil nil) => false

  这里的关键之处不是“我的语言是否需要一个most条件?”,而是不同的领域有不同的需求。在Java.next中,语言与库的界限被最小化,你可以添加适当的语言特性以适应你的领域需求,而不是相反。
  再举一个例子,考虑Ruby的attribute语法:
# Ruby
class Account
  attr_accessor :name
  dsl_attribute :type
end

  attr_accessor是Ruby固有的语法. dsl_attribute是我写的一个库方法,它允许你在做赋值操作的时候省略"=",像下面这样:
# normal attributes
account.name = "foo"

# equals-free attributes
account.type checking


结论
  这些Java.next语言有着相当多的共同点。尽管我使用一些孤立的例子来说明这些特点,但只有将它们一起使用时才能体现这些语言的真正威力。综合Java.next的所有特征,会导致一个完全不同的编码方式:

  ★ 在编码时你不再需要为了代码的可测试与适应性而采取保守方式:使用类工厂,设计模式以及依赖注入。作为代替,你可以构建一个最小解决方案,并随时改进它。
  ★ 在Java.next中,你可以开发更适合你问题的内部领域特定语言(DSLs)。


  以我的经验,这种编码方式能将代码量减少一个数量级,同时提高代码的可读性。
  很多人在寻找“next big language”。下一个“big language”已经在这里,但它不是一个单独的语言,而是一组概念的综合体,就如Java.next中表现出来的那样。
  过渡到Java.next配的上"big"这个称号吗?绝对可以。根据我的经验,一旦你作出转变,每一步都有着巨大的进步,包括学习曲线以及生产力。
  在这个系列的后续部分,我将讨论这些语言的不同之处。
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics