在咖啡里加糖 论Java世界的Ruby

发表于:2007-07-04来源:作者:点击数: 标签:
利用动态脚本编写你的Java应用程序以及重用你的Java类库 自从计算机诞生以来,软件开发就倾向于使用高级语言进行开发。从汇编,到C,到C++,再到JAVA,每一次升级就会面临来自各界同样的问题:太慢、而且有太多的 Bug 、开发者不想放弃对这些原有语言的使用
  利用动态脚本编写你的Java应用程序以及重用你的Java类库

  自从计算机诞生以来,软件开发就倾向于使用高级语言进行开发。从汇编,到C,到C++,再到JAVA,每一次升级就会面临来自各界同样的问题:太慢、而且有太多的Bug、开发者不想放弃对这些原有语言的使用。渐渐地,随着硬件的快速发展,新的研究和开发技术大大改进了编译器、解释器、和虚拟机,开发者不得不向高级语言转移,放弃他们使用的低级语言开发以提高生产力(将他们从低级语言的障碍中释放出来以提高他们的生产力)。

  Java现在在软件开发的很多领域里面占有主导地位,但是在这个发展过程中,动态脚本很有可能无情地取代它的地位。许多年以来,像Python、Perl、Rexx、Groovy、TCL和Ruby这样的语言能够在很多专业领域里面非常出色地工作,例如文件处理、自动测试、软件构建、代码重构、和Web图形页面设计——他们有着历史性的名字“脚本语言”。而且在最近的一些年里,在大多数由Java,C++和其他编译型计算机语言开发的大型工作里面,他们也取得了相应的进展。

  去年的时候,Ruby on Rails(RoR)Web框架使Ruby有了更进一步的发展。RoR结构利用简单的Ruby代码定义了一个典型的多层次Web应用程序——图形页面层、业务逻辑层和数据持久层,因此减小了冗余文件、样本文件代码、生成的源代码以及配置文件。RoR框架能够更加优化更加容易地使用Ruby语言;而且Ruby,这种完善的脚本语言,相对于RoR框架来说可以在更多的领域里面使用。
  
  作为一个长期的Java开发者,我很可能坚持在一段时间里一直用Java作开发。但是我仍然保持在我开发的基于Java的系统里面使用其他的语言,而且Ruby最近显示出来是特别好的一种候选语言。在JRuby解释器的帮助下,Ruby和Java一起工作得很好,包括配置、整合、和Java软件的重用。而且在简单学习Ruby的过程中也提高了我Java代码的质量。使用Ruby可以让我很容易地完成像功能程序和元程序一样的技术手法,这些技术手法我在Java里面都是很难实现的。学习这些Ruby里面的技术手法可以帮助我更好鉴别什么时候而且怎样在Java开发中使用它。

  这篇文章,我希望能够和你一起分享我在开发Java系统的时候使用Ruby的那种兴奋感。我比较一下Java和Ruby的优点和缺点,而且介绍一下JRuby解释器的支持者和反对者。而且我会向大家显示区分Ruby和Java使用的最佳实践以让它们各自得到最优化的使用。我会使用一些简单的代码来举例说明这个观点,并且介绍一个消息实例来展示在Java系统里面怎样结合使用Ruby,使其能够更好地使用动态元程序语言的弹性、表现方式以及功能。

  Ruby vs. Java

  这篇文章从一个Java开发者的角度解释了Ruby,主要是集中比较这两种语言。像Java一样,Ruby也是一种完全的面向对象的语言。但是这两种语言有很大的不同。Ruby是动态类型的而且是在源代码解释器里面运行的,这种语言能够像程序和功能范例一样支持元编程。我这里不会介绍Ruby的具体语法,接下来的文章里面会广泛地覆盖其他各个方面。

  动态类型

  Java有静态类型。你定义每个变量的类型,接下来在编译的过程中,如果你使用了类型错误的变量将会得到一个编译时错误。Ruby却相反,拥有动态类型:你不用定义函数和变量的类型,而且没有到运行的时候不会使用类型检测,如果你调用一个不存在的方法就会得到错误信息。尽管这样,Ruby不会关心一个对象类型,仅仅看它是否在一个方法里面调用了这个对象的方法。因为这个原因,这种动态方法可以得到这样一个duck类型:“如果一个事物走起来像一只鸭子(duck)而且像一只鸭子(duck)呷呷地叫,它就是一只鸭子。”

Listing1.Duck typing
class ADuck
    def quack()
        puts "quack A";
    end
end
class BDuck
    def quack()
        puts "quack B";
    end
end
# quack_it doesn't care about the type of the argument duck, as long
# as it has a method called quack. Classes A and B have no
# inheritance relationship.
def quack_it(duck)
    duck.quack
end
a = ADuck.new
b = BDuck.new
quack_it(a)
quack_it(b)


  Java也可以通过反射让你使用动态类型,但是这种笨拙冗长的工作会导致很多混乱的异常发生,像NoSuchMethodError和InvocationTargetException;在实践中,这些异常倾向于在Java反射的代码中突然出现,而且相对于Ruby而言出现频率更高。

  即使在没有使用反射的Java代码中,你会经常丢失掉静态类型的信息。比如,在Command设计模式里面使用execute()方法必须返回Object胜于在Java代码里面使用的特殊类型,结果会导致很多ClassCastException发生。同样的,当在编译时和运行时修改方法签名的时候,运行时错误就会发生。在实践开发中,不论是Java还是Ruby,这样的错误很少引起严重的程序Bug。一个健壮的单元测试——任何时候你都会用到的——通常都能够及时捕捉他们。

  Ruby的动态类型意思是你不用重复问你自己一个问题:在Java里面你是否经常在一行里面遇到这样冗长的代码:
XMLPersistence xmlPersistence
= (XMLPersistence)persistenceManager.getPersistence();

  Ruby消除了这种对于类型定义和转换的需要,上边的代码用一个典型的Ruby等价表达为;
xmlPersistence = persistence_manager.persistence.


  Ruby的动态类型意义上不是弱类型——Ruby经常需要你传递正确类型的对象。事实上,Java强制类型转换比Ruby要弱。例如,Java里面:”4”+2 等于”42”,这里会将整数转化为字符串,在Ruby里会抛出一个TypeError,告诉你这个“can't convert Fixnum into String.”(Fixnum类型是不可以转化为String的)。同样的,Java里,因为作类型校正牺牲了速度,而且过多地做了整型操作,产生像Integer.MAX_VALUE + 1的整型,和Integer.MIN_VALUE等价,可是Ruby类型校正整型只是在需要的时候。

  不论Ruby有什么优点,Java的静态类型可以让它在大规模的项目里面作为首选:Java工具能够在开发时候明白代码意思。IDE能够在类之间依赖跟踪,找到方法和类的用处,自动检标识符而且帮助你检测代码。同样的虽然Ruby工具在这些功能上存在限制,它缺乏类型信息所以不能够完成上边这些工作。

         利用动态脚本编写你的Java应用程序以及重用你的Java类库

  自从计算机诞生以来,软件开发就倾向于使用高级语言进行开发。从汇编,到C,到C++,再到JAVA,每一次升级就会面临来自各界同样的问题:太慢、而且有太多的Bug、开发者不想放弃对这些原有语言的使用。渐渐地,随着硬件的快速发展,新的研究和开发技术大大改进了编译器、解释器、和虚拟机,开发者不得不向高级语言转移,放弃他们使用的低级语言开发以提高生产力(将他们从低级语言的障碍中释放出来以提高他们的生产力)。

  Java现在在软件开发的很多领域里面占有主导地位,但是在这个发展过程中,动态脚本很有可能无情地取代它的地位。许多年以来,像Python、Perl、Rexx、Groovy、TCL和Ruby这样的语言能够在很多专业领域里面非常出色地工作,例如文件处理、自动测试、软件构建、代码重构、和Web图形页面设计——他们有着历史性的名字“脚本语言”。而且在最近的一些年里,在大多数由Java,C++和其他编译型计算机语言开发的大型工作里面,他们也取得了相应的进展。

  去年的时候,Ruby on Rails(RoR)Web框架使Ruby有了更进一步的发展。RoR结构利用简单的Ruby代码定义了一个典型的多层次Web应用程序——图形页面层、业务逻辑层和数据持久层,因此减小了冗余文件、样本文件代码、生成的源代码以及配置文件。RoR框架能够更加优化更加容易地使用Ruby语言;而且Ruby,这种完善的脚本语言,相对于RoR框架来说可以在更多的领域里面使用。
  
  作为一个长期的Java开发者,我很可能坚持在一段时间里一直用Java作开发。但是我仍然保持在我开发的基于Java的系统里面使用其他的语言,而且Ruby最近显示出来是特别好的一种候选语言。在JRuby解释器的帮助下,Ruby和Java一起工作得很好,包括配置、整合、和Java软件的重用。而且在简单学习Ruby的过程中也提高了我Java代码的质量。使用Ruby可以让我很容易地完成像功能程序和元程序一样的技术手法,这些技术手法我在Java里面都是很难实现的。学习这些Ruby里面的技术手法可以帮助我更好鉴别什么时候而且怎样在Java开发中使用它。

  这篇文章,我希望能够和你一起分享我在开发Java系统的时候使用Ruby的那种兴奋感。我比较一下Java和Ruby的优点和缺点,而且介绍一下JRuby解释器的支持者和反对者。而且我会向大家显示区分Ruby和Java使用的最佳实践以让它们各自得到最优化的使用。我会使用一些简单的代码来举例说明这个观点,并且介绍一个消息实例来展示在Java系统里面怎样结合使用Ruby,使其能够更好地使用动态元程序语言的弹性、表现方式以及功能。

  Ruby vs. Java

  这篇文章从一个Java开发者的角度解释了Ruby,主要是集中比较这两种语言。像Java一样,Ruby也是一种完全的面向对象的语言。但是这两种语言有很大的不同。Ruby是动态类型的而且是在源代码解释器里面运行的,这种语言能够像程序和功能范例一样支持元编程。我这里不会介绍Ruby的具体语法,接下来的文章里面会广泛地覆盖其他各个方面。

  动态类型

  Java有静态类型。你定义每个变量的类型,接下来在编译的过程中,如果你使用了类型错误的变量将会得到一个编译时错误。Ruby却相反,拥有动态类型:你不用定义函数和变量的类型,而且没有到运行的时候不会使用类型检测,如果你调用一个不存在的方法就会得到错误信息。尽管这样,Ruby不会关心一个对象类型,仅仅看它是否在一个方法里面调用了这个对象的方法。因为这个原因,这种动态方法可以得到这样一个duck类型:“如果一个事物走起来像一只鸭子(duck)而且像一只鸭子(duck)呷呷地叫,它就是一只鸭子。”

Listing1.Duck typing

class ADuck
    def quack()
        puts "quack A";
    end
end
class BDuck
    def quack()
        puts "quack B";
    end
end
# quack_it doesn't care about the type of the argument duck, as long
# as it has a method called quack. Classes A and B have no
# inheritance relationship.
def quack_it(duck)
    duck.quack
end
a = ADuck.new
b = BDuck.new
quack_it(a)
quack_it(b)



  Java也可以通过反射让你使用动态类型,但是这种笨拙冗长的工作会导致很多混乱的异常发生,像NoSuchMethodError和InvocationTargetException;在实践中,这些异常倾向于在Java反射的代码中突然出现,而且相对于Ruby而言出现频率更高。

  即使在没有使用反射的Java代码中,你会经常丢失掉静态类型的信息。比如,在Command设计模式里面使用execute()方法必须返回Object胜于在Java代码里面使用的特殊类型,结果会导致很多ClassCastException发生。同样的,当在编译时和运行时修改方法签名的时候,运行时错误就会发生。在实践开发中,不论是Java还是Ruby,这样的错误很少引起严重的程序Bug。一个健壮的单元测试——任何时候你都会用到的——通常都能够及时捕捉他们。

  Ruby的动态类型意思是你不用重复问你自己一个问题:在Java里面你是否经常在一行里面遇到这样冗长的代码:

XMLPersistence xmlPersistence
= (XMLPersistence)persistenceManager.getPersistence();


  Ruby消除了这种对于类型定义和转换的需要,上边的代码用一个典型的Ruby等价表达为;

xmlPersistence = persistence_manager.persistence.


  Ruby的动态类型意义上不是弱类型——Ruby经常需要你传递正确类型的对象。事实上,Java强制类型转换比Ruby要弱。例如,Java里面:”4”+2 等于”42”,这里会将整数转化为字符串,在Ruby里会抛出一个TypeError,告诉你这个“can't convert Fixnum into String.”(Fixnum类型是不可以转化为String的)。同样的,Java里,因为作类型校正牺牲了速度,而且过多地做了整型操作,产生像Integer.MAX_VALUE + 1的整型,和Integer.MIN_VALUE等价,可是Ruby类型校正整型只是在需要的时候。

  不论Ruby有什么优点,Java的静态类型可以让它在大规模的项目里面作为首选:Java工具能够在开发时候明白代码意思。IDE能够在类之间依赖跟踪,找到方法和类的用处,自动检标识符而且帮助你检测代码。同样的虽然Ruby工具在这些功能上存在限制,它缺乏类型信息所以不能够完成上边这些工作。

       JRuby:Java里的Ruby

  作为一个Java程序员,你不要想在产品中使用Ruby直到你能够让它和存在的Java应用程序和类库进行交互,而这些程序和类库之中能够支持Ruby的很多种类的基本功能。JRuby,JVM下的一个开源Ruby解释器,能够在Java里面使用Ruby类库。就像标准的Ruby解释器一样,除开使用Ruby调用本地方法(C代码)或者Java类库以外,Ruby代码都能够在JRuby里面正确执行。

  相比较于微软的.NET平台的公共语言运行时,JVM往往只能够支持一种语言。但是事实上,JVM平台不仅仅能够支持Java,而且可以支持Python、JavaScript、Groovy、Scheme,和其他各种语言,这意味着有必要的时候,Ruby代码能够和这些语言很好地进行交互。
  
       在2006年7月中旬,JRuby仅仅有一个预览版本(0.9)。但是它迅速发展起来:一个志愿者团队从2005年一月开始总共发布了五个版本。JRuby通过针对标准解释器的不断评估测试逐渐成熟起来,而且现在已经超过90%的测试都是在基本支持Ruby on Rails这个框架。

  为了尝试JRuby,保证Java SE 5 是安装好了的而且JAVA_HOME环境变量也是设置好了的。从JRuby的工程页面下载压缩包然后解压。设置JRUBY_HOME环境变量到JRuby安装的根目录。你可以在bin目录里面尝试着用jirb进行交互。大多数场合,你将使用JRuby解释器——创建一个文件将文件名作为一个参数传递到JRuby的bin目录下批处理脚本。

  除了执行先前的Ruby代码,你仍然可以使用JRuby来构造Java对象,调用Java方法,从一个Java类继承。一个Ruby类能够实现Java接口——有必要的话可以在Java里面静态调用Ruby方法。

  为了从Ruby访问Java需要初始化类库,需要以”java”命令开始。接下来用include_class方法指定需要使用的Java类,比如,include_class “javax.jms.Session”。你能够使用include_package导入整个Java包到Ruby模块里面。就像Java导入包的通配符语句一样,尽量避免include_package使用产生的名称冲突是明智的;在JRuby里,如果解释器为了需要的类搜索所有的包也是格外不明智的。尽可能严格地使用include_class。

  很多Java标准类的名称和Ruby类的名称相同。为了解决这样的冲突,传递一个代码块到include_class函数,为这个Java类返回一个新名称,而且JRuby将使用这个名称作为Java类的别名。(见Listing4)

  Listing 4. Include a Java class with clashing name

require "java"
# The next line exposes Java's String as JString
include_class("java.lang.String") { |pkg, name| "J" + name }
s = JString.new("f")

  或者,你可以创建一个包含java类定义的Ruby模块,但是需要在一个隔离的名称空间里面。例如:

  Listing 5. Java module importing multiple Java classes

require "java"
module JavaLang
    include_package "java.lang"
ends = JavaLang::String.new("a")

  JRuby的好处是什么?

  像Ruby一样的动态语言经常使用在专业领域就像整合其他系统一样;JRuby在Java里面扮演了这个角色。比如,JRuby能够从一个系统读出数据,将这个数据传递插入到另外一个系统里。当需求改变的时候,修改一段JRuby脚本相对于修改配置文件来说简单得多,所以避免了Java综合代码里面复杂的编译和发布周期。

  除开在Ruby里使用Java,你也可以从在Java里使用Ruby,让你的应用程序容易编写。利用JRuby的最小化构造语句方法,你可以创建更加容易使用的专业领域语言供用户工作。比如,一个赌博引擎的脚本系统能够引入Ruby类来描述字符,媒介和其他游戏实体。

  此为,使用Ruby的动态机制,用户能够改变脚本类的定义。这些Ruby对象允许直接使用方法管理它的状态和行为。另外一方面,一般使用用户配置好的键值通过Java映射传递,削弱了对象的功能完整性。

  Ruby脚本有点像加速过的配置文件。一般Java应用程序的配置都是使用xml文件或者属性文件,但是这些对于参数定义在开发时间上受到了一定限制。使用Ruby脚本能够使你的系统要么从一个文本要么从一个内置编辑器里面读取,用户能够自由自定义行为无论什么情况只要你想放置脚本。这样的方法,Ruby使用行为结合配置,提供了Java插件API的功能,而且不需要JavaIDE或者编译器,节省了构建和发布jar文件的步骤。

  例如,一个用户提供的脚本能够嵌入到一个应用程序事件管理里面用来对确认的可疑条件进行过滤,然后将发送一个通知给系统管理员以及在一个特定的安全发行数据库里面记入日志,或者启动脚本能够清除旧文件以及支配用户数据存储。同样的很多富客户端允许用户改变菜单和工具条的位置——使用Ruby嵌入,一个用户的新菜单可以触发任何用户想要的行为。

  为了方便编写Java应用程序,Bean Scripting Framework(BSF)在JVM和多种动态语言之间提供了一种标准接口,包括Ruby、Python、BeanShell、Groovy、和JavaScript;Java Specification Request(JSR)223,提供BSF的成功规范,将成为Java 6里面的标准部分。Java代码能够发送一个变量到JRuby的名称空间JRuby可以直接操作这些Java对象或者返回一个值到Java。使用BSF和JSR223,在Java和任何脚本语言之间的用于解释的语法是相同的。Listing 6显示了一个BSF使用Ruby的基本例子,这些在线例子的完整代码放置在bsf_example目录下。注意BSF不仅包括包外的JRuby支持;但是增加它的简单指令在JRuby文档里面是可使用的。

  Listing 6. Embed a Ruby interpreter

...
// JRuby must be registered in BSF.
// jruby.jar and bsf.jar must be on classpath.
BSFManager.registerScriptingEngine("ruby",
    "org.jruby.javasupport.bsf.JRubyEngine",  new String[]{"rb"});
BSFManager manager = new BSFManager();
// Make the variable myUrl available from Ruby.
manager.declareBean("myUrl", new URL("http://www/jruby.org"), URL.class);
// Note that the Method getDefaultPort is available from Ruby
// as getDefaultPort and also as defaultPort.
// The following line illustrates the combination of Ruby syntax
// and a Java method call.
String result = (String) manager.eval(
    "ruby", "(java)", 1, 1, "if $myUrl.defaultPort< 1024 then " +
    "'System port' else 'User port' end");

  在Java里面直接使用JRuby解释器也是可能的,但是像这样会连接你的Java代码到Ruby规范化的Java封装类,这些在BSF/JSR223里面是很严格的。
          局限性

  很重要的一点是你必须记住JRuby仍然在开发中,它还有很多局限,这些在1.0 release版本之前会固定下来。

  一个Ruby类是不能从一个Java抽象类继承的。不幸的是,这种限制使你不能简单创建一个具体的Java子类,使用虚方法实现抽象方法用于让Ruby类来继承。这是因为在Ruby/Java继承在JRuby的最近的版本中的第二个限制:Java代码不能多态地调用重写一个Java方法的Ruby方法。

  这些限制让Swing的使用显得很困难。比如,通过继承AbstractTableModel去利用添加到TableModel接口的功能是不可能的。你能够将继承转化为代理绕过这个限制:使用一个具体Java填充类继承于抽象类作为类型接口的一个代理,这个接口包括所有的Java方法。一个Ruby类实现这个代理接口。尽管这种做法近似于排除了JRuby的平常优势,但是它提供了基于这个局限的工作区而且在需要的地方提供了弹性:在子类功能里。

  Ruby的标准库提供了很多方面的功能。这些不使用本地代码的方式,在JRuby版本中是包含了的。JRuby团队逐渐地完善很多类库,虽然一些是在本地代码上的一个简单层次,就像GUI类Tk一样,将不会再完善。包含其中的Java标准库,提供了很多必要的功能。

  JRuby现在在WEBrick Web服务器上提供了Ruby on Rails的基本支持;接下来的一个milestone版本里面将会在任何Servlet容器里面实现RoR的支持。RoR支持将使程序员利用一系列存在的Java库与Web框架的简易性结合在一起,但是确认JRuby是一种可选的Ruby解释器。

  因为有BUG,JRuby 0.9需要Java SE 5,但是将会在下一个版本中支持JRE 1.4。

  例子

  例子jms_example.rb举例说明了JRuby增长长度的用法。它显示了如何将消息从一个类结构传到另外一个类,结合两种不同点的模拟软件定义了分布式游戏引擎。这些代码使用先进的功能和元程序技术将任何类型的XML消息作为Ruby对象进行传输。代码注释可以帮助理解程序逻辑。

  在Java里面使用配置到达这种水平是不可能的;至少,用户能够插入编译代码。但是在Ruby里,动态代码是像静态代码一样自然和容易使用的。

  Ruby和Java的未来

  Ruby可以教会Java程序员很多。RoR框架显示了开发Web应用程序是如此的简单;利用JRuby,RoR将能够重用存在的Java功能。JRuby将在Java应用程序中加入Jython,JavaScript,和其他各种动态脚本语言。想要掌握这些技巧的开发人员需要学习动态脚本,这些将会覆盖越来越多的应用程序开发领域。尽管不使用动态脚本,Java开发者也能够看到Ruby带来的利益如同功能程序和元程序的概念。

原文转自:http://www.ltesting.net