前回、JavaFXでHello, World!アプリケーションを作成してみましたが、いかがだったでしょうか。宣言的文法によるGUIの構築の雰囲気だけでも感じていただけたと思います。
とはいうものの、Hello, World!アプリケーションはあまりにも単純です。もう少し複雑なアプリケーションを作るためには、JavaFX Scriptの文法を知らなくてはなりません。そこで、今回は基本的な文法について解説します。そして、次回は今回紹介した文法を駆使してアプリケーションを作ることに挑戦してみましょう。
データ型
JavaFX Scriptの基本となるデータ型は以下の通りです。
- 文字列 String
- 数値 Integer, Number
- 論理値 Boolean
- 時間間隔 Duration
- Void
- 関数 function
ここでは、関数型以外の型について説明します。関数型は、後述する関数の節で説明します。
文字列
JavaFX Scriptにおいて文字列を表す場合、シングルクォーテーション、もしくはダブルクォーテーションで文字列を囲みます。シングルクォーテーションとダブルクォーテーションの違いは特にありません。
文字列の型はStringで、基本的なデータ型として扱われます。
ここでは変数の定義に型名を指定していますが、式の右辺が文字列であるため、型名を省略して記述することもできます。このスクリプトを実行すると、以下のようになります。
もちろん、シングルクォーテーションとダブルクォーテーションを混ぜて使うことはできません。しかし、たとえばシングルクォーテーションで囲んでいる場合、ダブルクォーテーションを文字列中に使うことは可能です。
文字列を囲っている文字と同じ文字を文字列中に使うには¥でエスケープします。
実行結果は次のようになります。
なお、インタープリタ版では複数の行で表される文字列を使用することができましたが、現在はサポートされていません。
文字列中に { } が存在すると、括弧内に埋めこまれた式が評価され、展開されます。
この例のように式の埋めこみを使用すると、数値を文字列に変換することも可能です。では、このスクリプトを実行してみましょう。
なお、文字列中に { } を使用したい場合、¥でエスケープします。
これを実行すると次のようになります。
なお、インタープリタ版では開括弧はエスケープが必要でしたが、閉括弧はエスケープが不要でした。1.0では、開括弧、閉括弧の両方ともエスケープが必要なので、ご注意ください。
文字列を連結するにはconcat関数を使用します。Javaのように演算子の + を使用することはできないので、ちょっと不便ですね。
concat関数は文字列を返すので、この例のようにconcat関数を並べて記述することができます。実行してみると、次のようになります。
また、式の埋めこみを使用して文字列を連結することも可能です。
実行結果を以下に示します。
数値
JavaFX Scriptでは、数値を2つの型で表します。整数型のIntegerと、浮動小数点数型のNumberです。Integerは、Javaのint、つまり32ビットの符号つき整数に対応します。同様にNumberはJavaのdouble、つまり符号部1ビット、指数部11ビット、仮数部52ビットで表される浮動小数点数に対応します。
インタープリタ版では、Integerがlongやjava.math.BigIntegerクラスで表される整数までサポートしていたのですが、現在はintで使用できる範囲に狭められてしまいました。
なお、IntegerからNumberへの代入は可能ですが、逆のNumberからIntegerへの代入はコンパイル時に精度が落ちている可能性があるという警告が発生します。これはJavaと同様です。NumberからIntegerに変換するにはintValue関数を使用します。
これを実行してみましょう。
結果からわかるように、小数点以下は切り捨てられます。
intValue関数は直接数値に対して記述することもできます。
下の式で、2はInteger、5.5はNumberです。このようにIntegerとNumberの数値を演算すると、Numberに変換されます。これもJavaと同様です。
このようにintValue関数でNumberからIntegerに変換することができるのですが、ちょっと違和感があります。こういう場合、キャストを使用することもできます。JavaFX ScriptのキャストはJavaとは異なり、式の後に as 型名 という記述をします。
実行結果を次に示します。
論理値
論理値はBooleanで表されます。取りうる値はtrueとfalseです。
使い方などを含めて、BooleanはJavaのbooleanと同じと考えることができます。ただし、否定演算子はJavaとは異なります。
このコードのように、否定をする場合はnotを使用します。実行すると、trueの否定のfalseが出力されます。
時間間隔
前回紹介したように、JavaFX Scriptでは時間間隔を表すDurationを使用することができます。
数値の後ろにスペースを加えずにms、s、m、hのいずれかを付加すると、Durationとして扱われます。それぞれ、ミリ秒、秒、分、時間を表します。日やナノ秒を直接表記することはできないので、他の時間単位を使用して表します。
この例のように、整数だけでなく小数点数でもDurationとして扱うことができます。
異なる時間単位を使用していても、比較を行うことは可能です。
このスクリプトを実行してみると以下のようになりました。
この結果からすると、内部ではすべてミリ秒で表しているようです。
時間間隔については、アニメーションの解説で再び取りあげる予定です。
Voidとnull
Voidは、Javaのvoidと同等です。Javaのvoidと同様に、関数が戻り値を返さない場合に使用します。後述する関数の節でVoidを使用した例を紹介します。
nullはデータ型ではなく値なのですが、ここでまとめて説明してしまいましょう。nullはJavaのnullと同様の使い方だけでなく、次のような使い方ができます。
詳しくは後述しますが、[]は配列に相当するシーケンスを表しています。つまり、変数xはInteger型のシーケンスで、要素はありません。textは空の文字列です。
これらをnullと比較してみたらどうなるでしょう。実際にやってみました。
つまり、要素が空のシーケンスや空の文字列はいずれもnullと同等と見なされるということです。
変数と定数
今まで、変数は何度も使用してきましたが、正しい定義は説明していませんでした。変数は var キーワードを使用して定義します。
変数の定義文で変数の初期化を行う場合、変数の型は明らかなので省略することが可能です。
この場合、式の右辺がTextオブジェクトを表しているので、自動的に変数messageの型はTextクラスとなります。
式の右辺のオブジェクトと異なるクラスを型としたい場合は明記する必要があります。
ここでは、変数messageの型を、javafx.scene.text.Textクラスのスーパークラスであるjavafx.scene.Nodeクラスに指定しています。
次は定数です。定数はdefキーワードで定義します。
定数なので、必ず定義する時に初期化も一緒に行う必要があります。もちろん、defで定義した定数を後から変更することはできません。
関数
JavaFX Scriptではメソッドを関数として表します。関数の定義はfunctionキーワードを使用して行います。なお、インタープリタ版ではfunctionだけでなく、operationも使用されていましたが、functionに統一されました。
ここでは2つの引数を取る場合を示しましたが、もちろん引数がない場合や、もっと多数の引数でもかまいません。ただし、可変引数は使用できません。
戻り値はJavaと同じくreturn文で記述します。return文がない場合、関数の最後の行が示しているオブジェクトを戻り値とします。
add関数はreturnが表記されていますが、subはreturn文がありません。しかし、関数の最後の行、ここでは1行しかないのでx - yが戻り値となります。
実行すると、次のようになります。
戻り値がない場合、戻り値の型はVoidとして定義します。
また、戻り値の型の定義がないメソッドはreturn文、もしくは関数の最後の行が示すオブジェクトの型になります。したがって、次の例のhelloメソッドの戻り値の型はStringとなります。
したがって、このスクリプトを実行すると次のように出力されます。
名前のない無名関数を定義することも可能です。
JavaFX Scriptでは変数やアトリビュートを関数型とすることができます。関数型の変数/アトリビュートに関数を代入する時に無名関数が使用されます。無名関数は関数名を表記する部分にfunctionと記述します。
変数の定義に関数型を明示する場合は、引数と戻り値を明記します。変数に無名関数を代入する場合、定義した引数および戻り値と合わせるようにします。関数型の変数が示している関数をコールする場合、通常の関数をコールするのと変わりません。ちなみに、インタープリタ版では(add)(a, b)のように変数名を括弧でくくる必要があったのですが、1.0では括弧が不必要となりました。
この関数型の変数はイベント処理などのハンドラなどに多用されます。Javaではリスナをインプリメントした無名クラスを作成してメソッドを実装しますが、JavaFX Scriptでは直接関数を代入できるため簡潔に表すことができます。
オブジェクトの生成
前回、Hello, World!サンプルの中でいくつかオブジェクトを作成しました。ここで、もう1度オブジェクトの生成について整理しておきましょう。
オブジェクトを生成する前に、使用するクラスのimport文を記述する必要があります。これはJavaと同様です。オブジェクトを生成するには、次のように記述します。
ただし、Javaのクラスのオブジェクトを生成するには new を使用します。
JavaFX Scriptはコンストラクタという概念はありません。その代わり、並括弧の中でアトリビュートの初期化を行います。たとえば、Colorクラスにはred, green, blue, opacityの4つのアトリビュートがあります。すべて値は0から1までのNumberです。したがって、Colorオブジェクトを生成するには次のように記述します。
アトリビュートの初期化は アトリビュート名: 値 のように記述します。文末はセミコロン、カンマ, 何もなしのいずれもOKです。また、アトリビュートの初期化する順序は決まっていません。したがって、先ほどのColorオブジェクトは次のようにも記述できます。
複数のアトリビュートの初期化を1行に書くことも可能です。1行に書くとしても、文末はセミコロン、カンマ、無しのどれでもかまいません。
アトリビュートの初期化のために、何らかの処理が必要なこともあるかもしれません。そのような場合、次のように記述することができます。
並括弧の中の最後の行にアトリビュートに代入したいオブジェクトを記述します。returnは書きませんが、関数の戻り値のようなものと考えることができます。たとえば、次のような使い方をします。
このように記述することで、Fooオブジェクトのsumアトリビュートには0からn(=100)までの和が代入されます。
ここでは、オブジェクトの生成について紹介しましたが、クラスを作ることについてはもう少しJavaFX Scriptに慣れてからにしましょう。
シーケンス
シーケンスというのは、Javaの配列に相当する概念です。しかし、配列にはない機能も持っています。
シーケンスの定義
シーケンスは[ ]で囲い、要素をカンマ区切りで記述します。
もちろん、定義と初期化を一緒に行うのであれば、型名は省略できます。
JavaFXでは多次元のシーケンスを扱うことができません。多重配列のようにシーケンスの中にシーケンスを表記すると、展開されて1次元のシーケンスとなります。たとえば、次のコードはどのように評価されるでしょうか。
では、実行してみましょう。
どちらも展開されてしまうので、同一のシーケンスと見なすことができます。
ここで、seq5.toString()のようにtoString関数を使用したのは、toString関数を使用しないと要素が並べて表示されてしまうためです。たとえば、次のコードを考えてみます。
これを実行すると、以下のようになります。
これではちょっとわかりにくいので、toString関数を使用したわけです。
では、本題に戻りましょう。シーケンスとして定義した変数もしくはアトリビュートに、シーケンスでない要素を代入すると、指定した要素だけを持つシーケンスとして解釈されます。
ここでは、変数seqに10を代入しています。これはどのように解釈されるでしょう。実行結果を次に示します。
このように10だけを要素に持つシーケンスと解釈されました。
この性質は、すでに前回の記事の中で使用しています。前回使用したhello.Mainクラスを以下に示します。
赤字で示したSceneクラスのcontentアトリビュートは、実際にはTextクラスのスーパークラスであるNodeクラスのシーケンスとして定義されています。しかし、ここでは単一のTextオブジェクトのみを代入しています。それが、ここで示した文法のおかげで、シーケンスを示す [ ] を記述しなくても正しく動作していたのです。
シーケンスのその他の定義法
数値を要素に取るシーケンスでは次のような定義をすることもできます。
- 始値と終値で指定した範囲
- 始値と終値で指定した範囲 ただし終値は含まない
- 始値と終値およびステップを指定した範囲
それぞれの次のように表記します。
このスクリプトを実行すると次のようになりました。
ステップを負の値にする場合、始値が終値より大きくなるようにしてください。
また、for文を使用してシーケンスの定義を行うこともできます。詳しくは次々回に説明しますが、JavaFX Scriptのfor文は、Java SEの拡張for文と同じような使い方をします。このfor文を以下のように記述すると、シーケンスを作成することができるのです。
このコードは、基となるシーケンスnumsをまず用意しておきます。そして、for文が解釈されると、numsの個々の要素を2倍したシーケンスが作成されます。上のコードを実行すると次のようになります。
シーケンスの部分からシーケンスを作成する
シーケンスの部分を取りだして、新しいシーケンスを作ることも可能です。これを行うには次のように表記します。
論理式がtrueになる要素が抽出されて、新しいシーケンスになります。いくつか例を示しましょう。
はじめの0から9までを要素にとるシーケンスを作成しておきます。a) の例は各要素を2で割って、余りがない要素(つまり偶数です)だけを抽出したシーケンスが作成されます。b) は要素の中から、5より小さい要素を抽出します。c) の例は同様に6以上の要素を抽出します。最後のd) の例はnodesというNodeクラスの派生クラスのオブジェクトによるシーケンスをまず用意します。実際にはそれぞれのクラスのimport文が必要ですが、ここでは省略せていただきました。
これらのNodeオブジェクトの内、Circleオブジェクトだけを抽出します。オブジェクトがあるクラスのインスタンスであるかどうかをチェックするには、Javaと同じくinstanceofを使用します。では、それぞれ実行するとどうなるでしょうか?
Circleオブジェクトを出力すると、すべてのアトリビュートを出力してしまうため、ここでは省略させていただきました。
また、インデックスによる抽出も可能です。インデックスの指定は、数値のシーケンス作成で使用したものと似ています。しかし、数値のシーケンス作成で指定したものは実際の要素になりますが、ここで使用するのはインデックスです。
a..bは、a番目の要素からb番目の要素を抽出するということです。a..<bだと、a番目の要素からb番目の要素の前までという意味になります。終わりのインデックスを指定していない場合は、最後までという意味になります。<が入ると、最後の要素は含まないという意味になります。
では、実際に実行してみましょう。
要素へのアクセス
シーケンスの各要素へのアクセスは、配列と同じように括弧内にインデックスを指定して行います。インデックスは0から始まります。
実行結果を次に示します。
なお、シーケンスの長さはsizeof演算子を使用して求めます。sizeof演算子はシーケンス名の前に記述します。
実行すると予想通り4が出力されます。
要素の挿入、更新、削除
配列とは異なり、シーケンスでは任意の位置に要素を追加することができます。同様に任意の位置の要素を更新することや削除することも可能です。
まず、要素の挿入を説明しましょう。要素を挿入するにはinsertを使用します。
これらについて、実際の例を示しました。
はじめの挿入はartistsの最後に挿入なので、["Young", "Lennon", "Dylan", "Simon", "Mitchell"]になるはずです。
そして、次はartists[1]の前に挿入です。artists[1]は"Lennon"ですから、["Young", "King", "Lennon", "Dylan", "Simon", "Mitchell"]になります。
最後の挿入はartists[3]の後に挿入します。artists[3]は"Dylan"なので、最終的にartistsは["Young", "King", "Lennon", "Dylan", "Taylor", "Simon", "Mitchell"]になるはずです。
では、実際に試してみましょう。
予想通りのシーケンスになりました。
要素の更新は、Javaの配列と同じです。
この例では、3番目の要素をSimonからGarfunkelに更新しています。
次は削除です。削除にはdeleteを使用します。削除する要素の指定には、要素を直接指定する方法と、インデックスで指定する方法があります。
要素を指定する場合は、削除する要素の後にfromを付加してシーケンス名を記述します。インデックスを指定する場合は、削除するインデックスを明記します。これも実際に試してみましょう。
はじめに"Lennon"を削除するので、シーケンスは["Young", "Dylan", "Simon"]になるはずです。そして、2番目の要素を削除するので、"Simon"が削除されます。実行結果は次のようになりました。
シーケンスの比較
シーケンスが等しいかどうかは、シーケンスの長さが同じ、かつ要素が同じということになります。
seq1とseq6が等しいということはわかりますが、他はどうでしょう。
seq7やseq8のようにseq1と定義が異なるシーケンスでも、要素が同一であれば等しいと見なされることがわかります。
シーケンスに対する演算
シーケンスに適用できる演算子には、reverse演算子とindexof演算子があります。reverse演算子はシーケンスの要素の順序を反対にする演算を行います 。
0から5までの整数のシーケンスをreverseで変換すると、次のようになりました。
reverseを行っても、基となるシーケンスは変化しないので注意してください。つまり、この例ではnumsは0から5の順序のままです。
indexof演算子は、for文と共に用い、ループ中のインデックスを示します 。
このスクリプトを実行した結果を次に示します。
さて、今回は基本的な文法を紹介しました。これ以外にも制御文などがありますが、今回は触れませんでした。それらについては次々回に解説する予定です。次回は今回紹介した文法を使用して、Hello, World!からの脱却を狙いましょう。