ついにベールを脱いだJavaFX

第1回JavaFXの概要と基本

2008年12月4日(米国時間⁠⁠、とうとうJavaFXがリリースされました。2007年のJavaOneで発表されて以来1年半、やっとリリースまでこぎつけました。そこで、このたびリリースされたJavaFX 1.0をベースに、短期集中連載としてJavaFXを解説していきます。

JavaFXとは何だ

JavaFXはSun Microsystems(以下Sunと表記します)が提供するリッチクライアント向けの新しいプラットフォームです。Javaという名前が入っていますが、Javaとは別個のプラットフォームです。とはいうものの、技術的にはJavaがベースになっています。

JavaFXの開発はオープンソースで行われています。java.netのOpenJFXプロジェクトを中心に、OpenJFX-CompilerプロジェクトSceneGraphプロジェクトなどで精力的に開発が行われています。ただし、現状ではすべてのソースが公開されているわけではないようです。

JavaFXは、2007年にサンフランシスコで行われたJavaOneで発表されました。当初は、RIA(Rich Internet Application)向けという面が強調されていましたが、RIAに限らずさまざまなユーザインターフェースを構築することができます。たとえば、JavaのGUIのAPIであるSwingを用いて作成されたアプリケーションの一部だけをJavaFXで構築する、ということも可能です。

前述したようにJavaFXはJavaの技術をベースに構築されているため、Javaとの相性がよく、Javaの多様なAPIをシームレスに使用することもできます。

JavaFXがカバーする領域

JavaFXはデスクトップPCで動作するアプリケーションだけでなく、携帯電話をはじめとしたさまざまな領域をカバーしています。JavaFXはJava VM上で動作します。Java SEのJava VMだけでなく、Java MEのCLDC(Connected Limited Device Configuration)やCDC(Connected Device Configuration)でも動作させることが可能です。このため、携帯電話やスマートフォン、セットトップボックスなどでもJavaFXを動作させることができます。また、Blu-rayプレイヤで採用されているCDCをベースとしたBD-Jも、JavaFXがカバーする領域になります。CLDCを採用しないGoogle Androidでも、2008年のJavaOneにおいてJavaFXを動作させるデモが行われました。

このようにさまざまな領域をカバーしているJavaFXですが、それぞれの領域において、アプリケーションの作成方法が異なるというのでは、開発効率も上がりません。そこで、開発されたのがユーザインターフェースの構築に特化されたスクリプト言語であるJavaFX Scriptです。JavaFX Scriptを用いることで、かつてJavaが謳っていた⁠Write Once, Run Anywhere⁠をもう一度実現することができるのです。

JavaFXの開発史

もともとJavaFXは、SeeBeyond社のChristopher Oliver氏の個人的なプロジェクトとして開始されました。その当時はJavaFXではなく、Form Follows Function(F3)という名前でした。

2005年9月にSunがSeeBeyond社を買収したことで、F3はSunの技術資産になりました。その後、Oliver氏は2006年11月にブログを開設し、そこでF3を公開しました。その当時のF3はJava SEのSwingおよびJava 2Dに依存しており、デスクトップでしか動作できませんでした。しかし、携帯電話を中心にした組込み用途でもF3を動作させることは重要です。そこで採られた戦略が、SavaJe社の買収でした。これはJavaOne 2007の直前、2007年4月のことになります。

通常、携帯電話ではCLDCを動作させますが、SavaJe社では携帯電話向けのCDCを開発していました。CDCではJSR 209 Advanced Graphics and User Interface Optional Package for the J2ME PlatformによりSwingとJava 2Dを動作させることが可能です。そこで、CDCとJSR 209をベースに組込み用途向けのJavaFXが構築されました。

そして、JavaOne 2007でF3はJavaFXと名前が変更され、大々的に発表されたのでした。

JavaOne 2007ではもともとF3のセッションが組まれており、それほど注目を受けているわけではありませんでした。ところが、初日のジェネラルセッションでJavaFXが発表され、F3のセッションもJavaFXのセッションに変更になりました。そして、JavaFXのセッションは大入り満員。セッションに入れなかった人も多数おり、最終日に同じ内容をもう一度行うアンコールセッションも組まれたほどでした。

JavaOneで発表された直後に、OpenJFXプロジェクトが開始されJavaFXのSDKが公開されました。

この時に公開されていたJavaFXはインタープリタで動作しました。つまり、起動時にスクリプトが解釈され、Javaのクラスに変換、コンパイルを行ってから動作するという流れになっていました。このため、どうしても起動時間が長くなってしまうという欠点があったのです。

そこで、毎起動時に行っていたコンパイルを事前に行う、コンパイラ方式が採用されました。このコンパイラを開発するためにOpenJFX-Compilerプロジェクトが2007年7月に開設され、同時にGUIコンポーネントもSceneGraphプロジェクトで開発されることになったのです。その後、デスクトップ向けのJavaFX DesktopのプレビューSDKが公開されたのが今年の7月。もちろん、コンパイラ方式で動作するものです。

そして12月4日、JavaFX Desktop 1.0を含む JavaFX SDK 1.0 がリリースされました。また、JavaFX SDKには組込み用途向けのJavaFX Mobileのベータ版も含まれています。

なお、2007年に公開されていたインタープリタ版と今回のコンパイラ版では、文法やAPIなど多くの部分が変更されています。また、Preview SDKと正式版でも一部APIが変更になっているので注意が必要です。これらの変更点については、本連載の最後に付録にまとめますので、参考になさってください。

他のRIA技術との比較

JavaFXがよく比較されるのは、RIA向けの技術であるAdobe AIRやMicrosoftのSilverlightです。それぞれ微妙にターゲットが異なっているので、AIRやSilverlightという比較ではなく、Sun、Adobe、Microsoftという観点から比較してみましょう。

実行系

JavaFXはデスクトップアプリケーションとしてはJREを使用して動作します。また、ブラウザを介して配備するためのツールとしてJava Web Startを使用することができます。一方、ブラウザ上で動作させるアプリケーションはAppletとして動作させることができます。もちろん、Java Web Startを使うこともできます。

特にJava SE 6 update 10で導入された新しいJava Plug-inにはさまざまな特徴があり、JavaFXを動作させるのに適しています。この新しいJava Plug-inに関しては、Java Expert #03「Applet Reloaded? 再び進化するAppletを使おう」をご参照ください。

携帯電話などの組込み用途では、前述したようにCLDCやCDC用のJavaFX Mobileが公開される予定になっています。

Adobeでは、デスクトップアプリケーションのプラットフォームとしてAIRを使用します。ブラウザ上で動作させる場合は、主にFlash Playerが使用されます。組込み用途の場合、Flash Liteが使用されます。また、Google Android向けには、LiteではないFlash Playerが開発されているようです。

MicrosoftではデスクトップアプリケーションとしてはWPF(Windows Presentation Foundation)が使用されます。そして、ブラウザで動作させるためにWPFのサブセットとして開発されたSilverlightが使用されます。組込み用途にはSilverlight for Mobileが用いられます。

構築言語

JavaFXが他の技術と最も異なるのは構築言語の扱いです。AdobeやMicrosoftでは、ユーザインターフェースの構造に関する部分はXMLを使用し、ロジックの部分に手続き型言語が使用されます。Adobeの場合、構造はMXML、ロジックは主にActionScriptが使用されます。Microsoftでは、構造をXAML、ロジックにC#などが使用されます。

なぜ、このように2つの形式でアプリケーションを構築するのでしょうか。

ユーザインターフェースを構築する場合、構造的なデータとイベント処理などを行うロジックという2つの側面があります。たとえば、図1のようなアプリケーションを考えてみましょう。

図1 簡単GUIアプリケーション
図1 簡単GUIアプリケーション

このアプリケーションは図2のような構造を持っています。

図2 アプリケーションの構造
図2 アプリケーションの構造

FrameにはPanelが貼ってあり、PanelにはTextFieldとButtonが貼ってあります。そして、それぞれのコンポーネントはサイズやレイアウトなどのアトリビュートを持っています。このようなツリーで表される情報を表すのにXMLは適しています。一方、Buttonを押された時のアクションを記述するには、手続き型言語が優れています。このため、AdobeもMicrosoftも構造をXML、ロジックを手続き型言語で表しているのです。

では、JavaFXではどうでしょう。JavaFXでは前述したJavaFX Scriptだけを用いてアプリケーションを構築します。しかし、構造を表すのが苦手なのではと思われるかもしれません。そこで、取り入れられたのが構造を表すのに適している宣言的文法です。宣言的文法を採用することによって、XMLなどを使用せずにアプリケーションを構築することが可能になっているのです。宣言的文法に関しては詳しい説明は後述します。

開発環境

RIAを開発する場合、開発環境は非常に重要になります。ロジックが主体のアプリケーションと異なり、グラフィカルな要素が多分に含まれているためです。グラフィカルな要素の開発には、ドローツール的な機能と、ドローした要素をロジックに結びつける機能の2つの側面があります。

特にドローツール的な機能は、その使いやすさによって作業効率が大きく変わってしまいます。

AdobeはAIRやFlashなどのアプリケーションを作成するのに、Flash ProfessionalやFlex Builderなどの開発環境を使用することができます。もともと、Flash Professionalはドローツールとして提供されてきており、グラフィカルな開発はお手のものです。また、Flashのタイムラインが苦手という開発者には、FlashをベースにしたFlex Builderが提供されています。RIAアプリケーションを作成する場合、Flash Professionalよりも、Flex Builderが広く使われているようです。

一方のMicrosoftは、Visual StudioとExpression Studioを使用します。主にグラフィック部分をExpression、ロジックをVisual Stdioで開発します。双方とも同じプロジェクトをオープンすることができるので、デザイナと開発者の分業にも適しています。

JavaFXの開発にはNetBeansが使用されます。現状のところドローツール的な機能を持つグラフィカルな開発環境は提供されていません。ここが現時点でのJavaFXの一番の弱みであると筆者は考えています。そこで、グラフィック部分の作成を補助するため、Adobe IllustratorやAdobe Photoshopのプラグインを含む、JavaFX Production Suiteが提供されています。Production Suiteは以前Project Nileという名前でしたが、正式版リリースと共に名称が変更されたようです。

Production Suiteを使用することで、IllustratorやPhotoshopで作成したグラフィックをJavaFXに取り込むことが可能になります。しかし、FlashやExpressionなどの開発環境からすると、やはり見劣りがしてしまうのは確かです。

JavaFX Scriptの特徴

それでは、JavaFXアプリケーションを構築のためのスクリプト言語であるJavaFX Scriptについて見ていくことにしましょう。JavaFX Scriptには以下のような特徴があります。

  • オブジェクト指向
  • 宣言的文法
  • 静的型付け
  • バインド
  • 置換トリガ
  • 時間間隔のサポート

オブジェクト指向は当たり前のように使われているので、ここでは説明を省略します。その他の特徴について以下に説明します。

宣言的文法

前述したように、宣言的文法は構造的データを効率よく表すために使用されます。また、宣言的文法はオブジェクトの生成、初期化で使用されます。JavaFX Scriptはオブジェクトの生成を、以下のようにクラス名に続けた波括弧で行います。

リスト1
Stage { }

ここではStageクラスのオブジェクトを生成しています。

Javaではクラスはフィールドとメソッドを持ちますが、JavaFX Scriptではアトリビュートと関数を持ちます。名前は違いますが、行うことは同じです。アトリビュートの初期化はオブジェクトを生成する時に行うことができます。アトリビュートの初期化は、並括弧の中にアトリビュート名とその値をコロンでつなげて行います。

リスト2
Stage {
    title: "Hello"
    width:  160
    height: 80
}

この例ではStageオブジェクトのtitleアトリビュートに"Hello"、widthアトリビュートに160、heightアトリビュートに80を代入しています。このような表記が宣言的文法なのです。この表記を見ると、HTMLで使用されるCSSや、JavaScriptのオブジェクトリテラルを連想させますね。

ちなみに、Javaのコンストラクタと違い、JavaFX Scriptではアトリビュートの初期化に順序はありません。この例であれば、titleアトリビュートをwidthアトリビュートの後に初期化しても大丈夫です。また、すべてのアトリビュートを初期化する必要はありません。Javaとは異なり関数型が存在するため、関数型のアトリビュートに関数を代入することもできます。

リスト3
Stage {
    title: "Hello"
    width:  160
    height: 80
        
    onClose: function() {
        System.exit(0);
    }
}

この例ではonCloseアトリビュートにSystem.exit(0);をコールする関数を代入しています。もちろん、オブジェクトの初期化を入れ子にすることも可能です。

リスト4
Stage {
    title: "Hello"
    width: 160
    height: 80
 
    onClose: function() {
        System.exit(0);
    }
 
    scene: Scene {
        content: SwingLabel {
            font: Font {
                size: 24
            }
            text: "Hello, World!"
        }
    }
}

この例ではStageオブジェクトのsceneアトリビュートにSceneオブジェクトを生成して代入しています。また、SceneオブジェクトのcontentアトリビュートにはSwingLabelオブジェクトを生成して代入し、さらにSwingLabelオブジェクトのfontアトリビュートにFontオブジェクトを生成して代入しています。この例からわかるように、ツリー構造をそのままスクリプトとして表すことができます。

同じ処理をJavaで行ってみましょう。

リスト5
public class Hello {
    public Hello() {
        JFrame frame = new JFrame("Hello");
 
        frame.setSize(160, 80);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 
        JLabel label = new JLabel("Hello, World!");
        Font font = new Font(Font.SANS_SERIF, Font.PLAIN, 24);
        label.setFont(font);
 
        frame.add(label);
 
        frame.setVisible(true);
    }
 
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                new Hello();
            }
        });
    }
}

Javaではまずオブジェクトを生成し、必要に応じてプロパティをセッターメソッドなどでセットしていくという形式になります。つまり、手続き的に構造を作り上げていくわけです。どちらが優れているという比較は意味をなしませんが、GUIのようにツリーで表される構造的な情報を表すには、宣言的文法の方が簡潔に記述できるのです。

静的型付け

多くのスクリプト言語が動的な型付けを行うのに対し、JavaFX Scriptでは静的な型付けを採用しています。AdobeのActionScriptもバージョンを重ねるごとに静的な型付けの側面が強化されているので、ユーザインターフェースを構築するには静的型付けの方が適しているのかもしれません。

JavaFX Scriptでは変数をvarキーワードで定義します。

リスト6
var number: Integer;

このように、変数名の後にコロンを書き、その次に型名を書きます。しかし、型が明らかな場合、型の定義を省略することができます。

リスト7
var number = 10;

10はIntegerであるため、numberがIntegerであることは明らかです。このような場合、型定義を省略できます。

バインド

MVC(Model - View - Controller)構造を採用したGUIアプリケーションでは、モデル部が持つ情報をビューが表示します。

通常、モデルとビューの情報を同期させるにはデザインパターンのオブザーバパターンやイベントなどが使用されます。つまり、モデルの情報が更新したら、それをイベントとしてビューに通知します。Javaの場合、モデルとしてJava Beansを使用したとしましょう。Beanが保持している情報が更新した場合、PropertyChangeEventを発生させ、ビューに値の更新を通知します。

一方、JavaFX Scriptではモデルの情報とビューで表示される情報が自動的に同期されます。これをバインドといいます。Java SE 7で採用される予定のBeans Bindingと同等の機能を言語レベルで使用することができるわけです。バインドを表すにはbindキーワードを使用します。

リスト8
var x = 10;
var y = bind x;
 
println("Y: {y}");
 
x = 20;
 
println("Y: {y}");

bindを使用できるのは変数もしくはアトリビュートの初期化時だけです。初期化時に、式の右辺にbindを書き、続いて同期させたい変数もしくはアトリビュートを書きます。上記の例では変数xを変数yにバインドしています。ここで使用しているprintlnは、標準出力に出力を行うための関数です。printlnはJavaのSystem.out.printlnメソッドに相当します。

文字列の中の並括弧は、並括弧の中に埋めこまれた変数もしくは式が展開されることを意味しています。これを実行すると次のようになります。

図3
Y: 10
Y: 20

xの値ははじめは10だったのが、途中で20に更新しています。すると、xにバインドされたyも自動的に20になります。

単にバインドするだけでなく、式にすることも可能です。

リスト9
var x = 10;
var y = bind x * 2;
 
println("Y: {y}");
 
x = 20;
 
println("Y: {y}");

この例では、yはxを2倍にした値に自動的に更新されます。実行してみると次のようになります。

図4
Y: 20
Y: 40

また、複数の変数もしくはアトリビュートとバインドすることもできます。

リスト10
var x = 10;
var y = 5;
var z = bind x + y;
 
println("Z: {z}");
 
x = 20;
 
println("Z: {z}");
 
y = 10;
 
println("Z: {z}");

ここでは変数zを、xとyの和にバインドしています。この場合、xとyのどちらが更新されても、zが更新されます。

図5
Z: 15
Z: 25
Z: 30

関数の引数をバインドすることもできます。関数をバインドする場合、関数定義の前にboundキーワードを付加します。そして、変数の初期化時にbindを付加して関数をコールします。以下の例は引数のaとbを加えた数を返すメソッドです。

リスト11
bound function add(a: Integer, b: Integer): Integer {
    return a + b;
}
 
var x = 10;
var y = 5;
var z = bind add(x, y);
 
println("Z: {z}");
 
x = 20;
 
println("Z: {z}");
 
y = 10;
 
println("Z: {z}");

これを実行するとどうなるでしょう。

図6
Z: 15
Z: 25
Z: 30

関数を再度コールしているわけではありませんが、関数の引数に使用したxとyを更新すると、自動的にメソッドが評価され、zの値が更新します。

今までのバインドはすべて一方向でした。つまり、bindされた変数を更新することはできません。しかし、双方向のバインドを行いたい場合もあります。このような場合は双方向バインドを使用します。双方向バインドはbindと共にwith inverseキーワードで表します。

リスト12
var x = 10;
var y = bind x with inverse;
 
println("X: {x} Y: {y}");
 
x = 20;
 
println("X: {x} Y: {y}");
 
y = 30;
 
println("X: {x} Y: {y}");

なお、双方向バインドでは変数を用いた式などを記述することはできません。常に対応する2つの変数は同じものとして扱います。では、これを実行してみましょう。

図7
X: 10 Y: 10
X: 20 Y: 20
X: 30 Y: 30

xを変更しても、yを変更しても、両変数とも常に同じ値になっていることがわかります。

置換トリガ

置換トリガは変数もしくはアトリビュートが変更された時に、決められた処理を行うために使用します。この置換トリガもJava BeansでPropertyChangeEventが発生した場合のイベント処理 と同じように考えることができます。

置換トリガは変数もしくはアトリビュートの定義時に設定することができ、定義の後にon replaceを付加して処理を記述します。

リスト13
var value: String on replace oldValue {
     println("\nValue has changed!");
     println("Old Value: {oldValue}");
     println("New Value: {value}");
};
 
value = "JavaFX";
value = "JavaFX Script";

oldValueには変更する前の値が代入されます。oldValueは省略可能です。では、このコードを実行してみましょう。

図8
Value has changed!
Old Value: 
New Value: 
 
Value has changed!
Old Value: 
New Value: JavaFX
 
Value has changed!
Old Value: JavaFX
New Value: JavaFX Script

上記の例では変数valueを定義しただけで初期化を行っていません。そのため、値がnullとして置換トリガが呼び出されます。もちろん、定義と共に初期化を行うこともできます。

リスト14
var value: String = "Java" on replace oldValue {
     println("\nValue has changed!");
     println("Old Value: {oldValue}");
     println("New Value: {value}");
};
 
value = "JavaFX";
value = "JavaFX Script";

この場合、on replace の前に変数に値を代入します。これを実行すると次のようになります。

図9
Value has changed!
Old Value: 
New Value: Java
 
Value has changed!
Old Value: Java
New Value: JavaFX
 
Value has changed!
Old Value: JavaFX
New Value: JavaFX Script

時間間隔のサポート

JavaFXではアニメーションが多用されるため、時間を扱うことが重要になります。そこで、言語レベルで時間間隔を表すことができます。時間間隔はDuration型で表されます。

リスト15
var duration1: Duration = 10ms; // 10ミリ秒
var duration2: Duration = 1s; // 1秒
var duration3: Duration = 1m; // 1分
 
println("{duration1} * 100 == {duration2}? {duration1* 100 == duration2}");

数字の後にmsが続けばミリ秒、sが続けば秒というように解釈されます。

duration1は10ミリ秒なので、100倍すれば1秒になるはずです。では、最後の式を実行するとどうなるでしょう?

図10
10ms * 100 == 1000ms? true

当然のことながら、trueになりました。また、duration2が1000msと表記されていることから、内部的にはミリ秒で保持されていることがわかります。

次回では、このような特徴を持つJavaFX Scriptを用いて、実際にアプリケーションを作ってみましょう。

おすすめ記事

記事・ニュース一覧