本文是Java.next系列的第二部分。在這一部分,我們來看Java.next語言如何與Java進行互操作。
在所有這些Java.next語言中,與Java互操作都是很簡單的一件事。這得歸功於Java虛擬機規范,它使得JVM上的其它語言能夠很容易的反射以及調用Java代碼。
一個Swing的例子
作為與Java互操作的第一個例子,考慮通過調用Swing API創建一個應用程序,使其包含:
● 一個窗口
● 一個按鈕
● 點擊按鈕時彈出一個模式對話框
作為參照,這裡先給出使用原始Java代碼給出的實現:
// Java
import javax.swing.JFrame;
import javax.swing.JButton;
import javax.swing.JOptionPane;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
public class Swing {
public static void main(String[] args) {
JFrame frame = new JFrame("Hello Swing");
JButton button = new JButton("Click Me");
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
JOptionPane.showMessageDialog(null,
String.format("<html>Hello from <b>Java</b><br/>" +
"Button %s pressed", event.getActionCommand()));
}
});
frame.getContentPane().add(button);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
}
}
接下來,我將使用Java.next中的語言來實現這個Swing應用。對於這些代碼,有兩點需要注意的地方:
■ 在這個例子中,我根據這些語言與Java語法差異從小到大的順序呈現代碼。這樣做使得我們能從熟悉東西自然過渡到陌生的事物。
■ 下面這些實現並不是這些語言的最佳實踐。它們被刻意簡化,使得我們能將注意力集中在與Java的互操作上。在接下來的文章中,我將更多的展現這些語言的習慣用法。
Groovy的實現
Groovy是Java.next語言中與Java最相似的,下面是Groovy的實現代碼:
// Groovy
import javax.swing.JFrame
import javax.swing.JButton
import javax.swing.JOptionPane
import java.awt.event.ActionListener
frame = new JFrame("Hello Swing")
button = new JButton("Click Me")
button.addActionListener({
JOptionPane.showMessageDialog(null, """<html>Hello from <b>Groovy</b>
Button ${it.actionCommand} pressed""")
} as ActionListener)
frame.contentPane.add button
frame.defaultCloseOperation = JFrame.EXIT_ON_CLOSE
frame.pack()
frame.visible = true
與Java的實現對比一下可以發現,它們幾乎一樣。只不過省略了一些冗余的代碼結構。Groovy版本使得我們能夠忽略:
◇ 分號
◇ 類型聲明
◇ 大部分括號
◇ 屬性的Getter與Setter
Groovy版本的最大優勢體現在事件監聽器上,它展現了:
◇ 多行字符串(使用"""界定)
◇ 使用${}往字符串裡插入it.actionCommand
◇ 不需要匿名內部類,簡單的傳遞一個匿名函數
在SwingBuilder項目中可以看到如何用更符合Groovy習慣用法去使用Swing。
我們可以得出一個顯而易見的結論:在Groovy中與Java互操作相當簡單。
Scala的實現
接下來,讓我們看看Scala的版本:
// Scala (almost right, see below)
import javax.swing._
import java.awt.event.{ActionEvent, ActionListener}
object HelloWorld extends JFrame("Hello Swing") {
def showButtonMessage(msg: String) =
JOptionPane.showMessageDialog(null, String.format("""<html>Hello from <b>Scala</b>. Button %s pressed""", Array(msg)));
def main(args: Array[String]) {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)
val button = new JButton("Click Me")
button.addActionListener((e:ActionEvent) => showButtonMessage(e.getActionCommand.toString))
getContentPane add button
pack
setVisible(true)
}
}
Scala版本與Groovy相對Java具有許多相同的優點:
◇ 更少的類型聲明
◇ 更少的分號
◇ 更少的括號
我們還可以看到Scala獨有的一些性質:
◇ 在Scala中import通配符是_而不是熟悉的*。*號具有其它意義
◇ Scala能夠單行引入同一個package中的多個class
◇ 因為我們只需要一個實例,所以我們用object聲明而不是class
◇ 該object繼承了JFrame,Scala允許我們使用內嵌的方式調用JFrame的構造函數,而不必再單獨聲明一個構造函數
與Groovy一樣,最大的不同是事件監聽器。Scala同樣允許我們簡單的傳入一個匿名函數,而不需要使用匿名內部類。
button.addActionListener((e:ActionEvent) =>
showButtonMessage(e.getActionCommand.toString))
看起來很強大,只不過這兒我做了一個小小的弊。Scala是強類型語言,它不會自動將一個函數類型轉換為一個ActionListener。因此上面的代碼還不能被編譯通過。幸運的是,Scala的隱式轉換功能讓我們擁有這個能力:強類型加上一個便利的類型系統。所有我們要做的是告訴Scala這個轉換是合法的:
// Yes, we can
implicit def actionPerformedWrapper(func: (ActionEvent) => Unit) =
new ActionListener { def actionPerformed(e:ActionEvent) = func(e) }
將上面的代碼放到適當的位置,我們就能夠在需要ActionListener的地方傳入一個函數。
已經有多個將Swing包裝為Scala的項目。使用這些庫,你能夠書寫更加簡潔的代碼。作為示例,你可以參看ScalaGUI。
JRuby的實現
現在來看看JRuby的情形:
include Java
import javax.swing.JFrame
import javax.swing.JButton
import javax.swing.JOptionPane
import java.awt.event.ActionListener
button = JButton.new "Click Me"
button.add_action_listener do |evt|
JOptionPane.showMessageDialog(nil, <<-END)
<html>Hello from <b>JRuby</b>.
Button '#{evt.getActionCommand()}' clicked.
END
end
frame = JFrame.new "Hello Swing"
frame.content_pane.add button
frame.default_close_operation = JFrame::EXIT_ON_CLOSE
frame.pack
frame.visible = true
如果與之前Groovy的代碼進行對比,你會發現它們幾乎具有相同的特點:
◇ 更少的類型聲明
◇ 更少的分號
◇ 更少的括號
◇ 簡潔的屬性訪問(沒有getter與setter)
◇ 多行字符串(使用END界定)
◇ 使用${}往字符串裡插入it.actionCommand
相比而言,JRuby的ActionListener實現比Groovy稍微簡單一點。JRuby能夠自動根據block生成ActionListener:
button.add_action_listener { |evt|
# do stuff
}
在JRuby的例子中,我依照Ruby的命名方式來使用Java方法名:
# Ruby
frame.content_pane
Java程序員習慣Camel命名方式。為了便利,JRuby同時支持這兩種命名方式:
# Groovy, Scala, or JRuby
frame.contentPane
由於Ruby語言的靈活性,鼓勵試驗不同的語法與Java交互,可以參看JRUBY-903以了解相關歷史。如果想了解更符合JRuby習慣用法的Swing使用方式,可以看Profligacy項目。
結論:在JRuby中,與Java互操作很簡單。
Clojure的實現
這兒是Clojure的版本:
; Clojure
; Clojure
(import '(javax.swing JFrame JButton JOptionPane))
(import '(java.awt.event ActionListener))
(let [frame (JFrame. "Hello Swing")
button (JButton. "Click Me")]
(.addActionListener button
(proxy [ActionListener] []
(actionPerformed [evt]
(JOptionPane/showMessageDialog nil,
(str "<html>Hello from <b>Clojure</b>. Button "
(.getActionCommand evt) " clicked.")))))
(.. frame getContentPane (add button))
(doto frame
(setDefaultCloseOperation JFrame/EXIT_ON_CLOSE)
pack
(setVisible true)))
Clojure是一種Lisp方言,因此其語法與其它幾種有著本質上的不同。這一點就得花上幾個小時來討論,但我們現在的焦點是與Java的互操作,所以我將這一點留給這系列以後文章來討論。現在,讓我們把注意力放到與Java互操作上來。
導入Java類是件很容易的事。import之後跟著一串參數,第一項是package,其余的是要導入到當前名字空間的class。注意,這樣允許我們在一行中導入多個class。
(import '(javax.swing JFrame JButton JOptionPane))
創建一個Java實例也很簡單,使用(class. &args)的形式:
(JFrame. "Hello Swing")
有多種途徑來調用Java類中的方法。你可以使用(.methodName obj &args)的方式來調用單個方法。對於靜態方法,使用(class/method &args)的方式:
(JOptionPane/showMessageDialog nil "A message")
在Java中,可以通過x.y().z()的方式使用鏈式調用。在Clojure中你可以使用(.. x (y) (z))的方式:
(.. frame (getContentPane) (add button))
最後的三個方法調用都是在frame對象上。使用Clojure的doto,你能夠在一個對象上執行多次操作並避免每次都要重寫這個對象:
(doto frame
(setDefaultCloseOperation JFrame/EXIT_ON_CLOSE)
pack
(setVisible true)))
與其它幾個例子一樣,事件監聽器是最有趣的部分。在Clojure中,proxy能夠動態創建Java實例,並允許你實現所需的接口與方法。
(proxy [ActionListener] []
(actionPerformed [evt] {do stuff here...}))
同JRuby一樣,相對Groovy這個解決方案更具有普適性,同時需要使用更多的語法。同樣,你能夠構建自己的語法結構。
結論:在Clojure中與Java互操作很簡單。
結論
在上面的例子中,我演示了Java.next可以方便的與Java互操作。每一個例子都使用了比Java更少的代碼來操作Swing庫。更重要的是,Java.next的版本抓住了問題的本質並簡化了形式。
與Java的無縫銜接並不是衡量Java.next語言的首要標准,因為它們都做得很好。這裡並沒有體現出這些語言在復雜情形下的表現,但我認為它們與Java互操作的問題已經得到了根本的解決。
Java.next系列的前兩篇文章中,我采用了接近Java語言的風格來展示Java.next語言的特性。有了這些基礎,是時候去使用Java.next各自的習慣用法了。在本系列文章接下來的部分,我們將會看到Java.next語言如何支持領域特定語言。