ついにベールを脱いだJavaFX

第5回演算子と構文

今回は、制御構文など第3回で触れられなかった文法について説明していきます。

演算子

JavaFX Scriptで使用可能な演算子の一覧を表1に示しました。基本的にJavaとそれほど変わることはありません。また、演算の評価順序もJavaのそれに準拠しています。なお、インタープリタ版では不等号を表す演算子は<>でしたが、1.0では!=に変更されています。

しかし、論理演算子のandやorなど、Javaとは表記が異なる演算子もあります。表記が異なるだけで、使い方はJavaと変わりません。表記も使い方も異なるのが、キャストとシーケンスに対する演算子です。これらはいずれも、第3回ですでに説明していますので、ここでは省略させていただきます。

また、アニメーションで使用するトゥイーン演算子はJavaにはありません。このトゥイーン演算子に関してはアニメーションを解説する回で詳しく触れる予定です。

Javaには存在するのに、JavaFX Scriptでは存在しない演算子には、%=やシフト演算、ビット論理演算などがあります。また、条件演算子の?:もJavaFX Scriptには存在しません。条件演算子に関しては、後述するif文で代用することができます。

表1 JavaFX Scriptでの演算子一覧
演算子の種類 演算子 説明 相当するJavaの演算子 オペランドの型 結果の型
代入演算子 = 代入 =    
複合代入演算子 += 加算して代入 += Integer += Integer Integer
Integer += Number Integer
Number += Integer Number
Number += Number Number
Duration += Duration Duration
-= 減算して代入 -= Integer -= Integer Integer
Integer -= Number Integer
Number -= Integer Number
Number -= Number Number
Duration -= Duration Duration
*= 乗算して代入 *= Integer *= Integer Integer
Integer *= Number Integer (計算はNumberを使用)
Number *= Integer Number
Number *= Number Number
Duration *= Integer Duration
Duration *= Number Duration
/= 除算して代入 /= Integer /= Integer Integer
Integer /= Number Integer (計算はNumberを使用)
Number /= Integer Number
Number /= Number Number
Duration /= Integer Duration
Duration /= Number Duration
トゥイーン演算子 => トゥイーン なし Duration => Duration Duration
二項論理演算子 and 条件論理積 && Boolean and Boolean Boolean
or 条件論理和 || Boolean or Boolean Boolean
単項論理演算子 not 否定 ! not Boolean Boolean
型演算子 instanceof 型の比較 instanceof Object instanceof 型 Boolean
as キャスト (型) Object as 型 (型で指定した)Object
関係演算子 == 等しい == Object == Object Boolean
!= 等しくない != Object != Object Boolean
< 左辺の値が右辺の値より小さい < Integer < Integer Boolean
Integer < Number
Number < Integer
Number < Number
Duration < Duration
<= 左辺の値が右辺の値以下 <= Integer <= Integer Boolean
Integer <= Number
Number <= Integer
Number <= Number
Duration <= Duration
> 左辺の値が右辺の値より大きい > Integer > Integer Boolean
Integer > Number
Number > Integer
Number > Number
Duration > Duration
>= 左辺の値が右辺の値以上 >= Integer >= Integer Boolean
Integer >= Number
Number >= Integer
Number >= Number
Duration >= Duration
二項算術演算子 + 加算 + Integer + Integer Integer
Integer + Number Number
Number + Integer Number
Number + Number Number
Duration + Duration Duration
- 減算 - Integer - Integer Integer
Integer - Number Number
Number - Integer Number
Number - Number Number
Duration - Duration Duration
* 乗算 * Integer * Integer Integer
Integer * Number Number
Number * Integer Number
Number * Number Number
Integer * Duration Duration
Number * Duration Duration
Duration * Integer Duration
Duration * Number Duration
/ 除算 / Integer / Integer Integer
Integer / Number Number
Number / Integer Number
Number / Number Number
Duration / Integer Duration
Duration / Number Duration
Duration / Duration Number
mod 除算の余り % Integer mod Integer Integer
前置単項演算子 - 符号の反転 - - Integer Integer
- Number Number
- Duration Duration
sizeof シーケンスのサイズ length sizeof シーケンス Integer
reverse シーケンスの反転 なし reverse シーケンス シーケンス
++ インクリメント ++ ++Integer Integer
++Number Number
-- デクリメント -- --Integer Integer
--Number Number
indexof シーケンスのインデックス なし   Index
後置演算子 ++ インクリメント ++ Integer++ Integer
Number++ Number
-- デクリメント -- Integer-- Integer
Number-- Number

構文

ブロック構文

ブロック構文は波括弧でくくられた複数の式からなります。複数の式の最後の式がブロック構文の値となります。

第3回のオブジェクトの生成で、アトリビュートの初期化に次のような構文を使用しました。

リスト1
var n = 100;

Foo {
    sum: {
        var tmpSum = 0;
        for (x in [0..n]) {
            tmpSum += x;
        }
        tmpSum;
    }
}

sumアトリビュートに値を代入するため、波括弧でくくられた複数の式を使用しています。これがブロック構文です。sumにはブロック構文の最後の式、つまりtmpSumの値が代入されます。

このブロック構文はアトリビュートの初期化に限らず、変数に代入することや関数の引数などにも使用することが可能です。たとえば、リスト2のスクリプトは正月までの日数の計算処理をブロック構文で実現しています(import文は省略してあります⁠⁠。

リスト2 正月までの日数を表示するスクリプト
var remainingDays = {
    var today = Calendar.getInstance();
    var newyearsEve = Calendar.getInstance();
    newyearsEve.set(Calendar.MONTH, Calendar.DECEMBER);
    newyearsEve.set(Calendar.DATE, 31);
  
    var days
        = newyearsEve.get(Calendar.DAY_OF_YEAR)
        - today.get(Calendar.DAY_OF_YEAR) + 1;

    days;
};

Stage {
    title: "もういくつ寝ると"    
    scene: Scene {
        width: 210
        height: 40
        content: [
            Text {
                font: Font {
                    size: 18
                }
                x: 10
                y: 30
                content: "お正月まであと         日"
            },
            Text {
                font: Font {
                    size: 36
                }
                x: 140
                y: 32
                content: "{remainingDays}"
            }
        ]
    }
}

赤字の部分がブロック構文で表した部分です。ここでは、Javaのjava.util.Calendarクラスを使って日にちを表しています。スクリプトを実行した日から大晦日までの日数を求め、それに1を足すことで正月までの日数を求めています。

このスクリプトの実行結果を図1に示します。実行した日は12/10だったため、正月までは22日と表示されます。

図1 ブロック構文の例
図1 ブロック構文の例

if 文

if文は条件により処理を切り替えるために使用します。

表記はJavaとほぼ同じですが、処理はJavaと少し異なります。というのも、JavaFX Scriptでは、条件によって切り替えるのは括弧でくくられた複数の文ではなく、ブロック構文であるためです。つまり、JavaFX Scriptではリスト3のような表記を行うこともできます。

リスト3
var value = ...... 
 
var color = if (value mod 2 == 0) {
    Color.RED;
} else {
    Color.BLUE;
}

valueが偶数であれば変数colorにはColor.REDが代入され、奇数であればColor.BLUEが代入されます。

また、Javaの条件演算子?:のように、1行に記述することも可能です。リスト3と同内容のスクリプトを1行で記述したものが、リスト4のスクリプトです。

リスト4
var value = ...... 

var color = if (value mod 2 == 0) Color.RED else Color.BLUE;

もちろん、Javaのように処理だけを記述することもできます。

リスト5
var value = ...... 
 
if (value mod 2 == 0) {
    println("{value}は偶数");
} else {
    println("{value}は奇数");
}

また、else ifを使用することで、複数の条件式を表記することも可能です。

リスト6
if (value mod 3 == 0) {
    println("{value}は 3 で割り切れる");
} else if (value mod 3 == 1) {
    println("{value}は 3 で割ると 1 余る");
} else if (value mod 3 == 2) {
    println("{value}は 3 で割ると 2 余る");
}

なお、JavaFX Scriptにはswitch文はないので、if...else if...で代用します。

ここで、if文の使用例として、下駄を投げて天気予報をしてみましょう。

リスト7
// 乱数
def random = new java.util.Random();
 
// 乱数で天気を決める
var weather: Boolean = random.nextBoolean();
 
var stop1: Color;
var stop2: Color;
 
// 天気によりグラデーションの色を変化させる
if (weather) {
    stop1 = Color.ORANGE;
    stop2 = Color.LEMONCHIFFON;
} else {
    stop1 = Color.CORNFLOWERBLUE;
    stop2 = Color.LIGHTSTEELBLUE;
}
 
Stage {
    title: "下駄の天気予報"
    scene: Scene {
        width: 200
        height: 40
 
        // 天気により背景色を変化させる
        fill: LinearGradient {
            startX: 0.0, startY: 0.0,
            endX: 0.0, endY: 1.0
            proportional: true
            stops: [
                Stop { offset: 0.0 color: stop1 }
                Stop { offset: 1.0 color: stop2 }
            ]
        }
 
        content: [
            Text {
                font: Font {
                    size: 24
                }
                x: 50
                y: 30
                // 天気により文字列を変化させる
                content: if (weather) "明日は晴" else "明日は雨"
            }
        ]
    }
}

この例では2つのif文があります。青字の方が単に処理を条件によって切り替えているのに対し、赤字の方は条件演算子のように使用しています。青字の方はグラデーションで使用する色を条件によって変化させています。一方の赤字の方は、条件によってTextオブジェクトのcontentアトリビュートに代入する文字列を変化させています。

スクリプトは乱数によって、背景のグラデーションと表示させる文字列を変化させます。もちろん、乱数で天気を予想しているだけなので、当たるかどうかは天のみ知るといったところです。

では、実際に実行してみましょう。

図2 下駄の天気予報の実行結果
図2 下駄の天気予報の実行結果 晴 図2 下駄の天気予報の実行結果 雨

for文

JavaFX Scriptのfor文は、Javaの拡張for文のような使い方をします。

リスト8
var values = [0..10];
 
for (value: Integer in values) {
    println("{value}");
}

Javaで拡張for文が使用できるのは、java.util.ArrayListクラスのようにjava.lang.Iterableインタフェースを実装したクラスと配列ですが、JavaFX Scriptではシーケンスのみです。

また、シーケンスの個々の要素を代入する変数と、シーケンスの間にはinを記述します。なお、valueの型名は明らかなので、省略できます。

リスト8のスクリプトを実行すると、0から10までを出力します図3⁠。

図3
0
1
2
3
4
5
6
7
8
9
10

break文やcontinue文も、Javaと同様に使用することができます。

リスト9
for (x in [0..10]) {
    if (x == 5) {
        continue;
    } else if (x == 8) {
        break;
    }
 
    println("x: {x}");
}

xが5の時、continue文によりprintln関数をコールすることなく、次の繰り返しに制御を移動させます。xが8の時には、break文によりfor文を脱出します。スクリプトを実行すると、5は出力されません。また、8以上も出力されないことがわかります図4⁠。

図4
x: 0
x: 1
x: 2
x: 3
x: 4
x: 6
x: 7

このようにbreak文とcontinue文を使用することができますが、Javaのようにラベルつきのbreakやcontinueは使用できません。

if文と同じく、for文も並括弧でくくられた複数の文を繰り返し実行するのではなく、ブロック構文を繰り返し実行することになります。そして、その結果はシーケンスとなります。

リスト10
var odds: Integer[] = for (value: Integer in [0..10]) {
    value * 2;
}
 
println(odds);

リスト10のスクリプトはシーケンス[0..10]の各要素を2倍したシーケンスを作成します。実行した結果を図5に示します。

図5
[ 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20 ]

ループのインデックスを使用したい場合は、第3回で説明したindexof演算子を使用します。

リスト11
var days = ["Monday", "Tuesday", "Wednesday", "Thursday",
             "Friday", "Saturday", "Sunday"];

for (day in days) {
    println("{indexof day}: {day}");
}

ではリスト11を実行してみましょう。

図6
0: Monday
1: Tuesday
2: Wednesday
3: Thursday
4: Friday
5: Saturday
6: Sunday

for文の括弧の中には複数のシーケンスを書くことができます。この記述法は、for文を二重に記述したものと等価です。

リスト12
for (x in [0..2], y in [0..1]) {
    println("x: {x} y: {y}");
}

リスト12のスクリプトはリスト13と同じ結果になります。

リスト13
for (x in [0..2]) {
    for (y in [0..1]) {
        println("x: {x} y: {y}");
    }
}

実行結果を図7に示します。

図7
x: 0 y: 0
x: 0 y: 1
x: 1 y: 0
x: 1 y: 1
x: 2 y: 0
x: 2 y: 1

break文やcontinue文はどうなるでしょうか。リスト14のスクリプトでは、yが1の時にbreakでfor文を脱出しています。

リスト14
for (x in [0..2], y in [0..1]) {
    if (y == 1) {
        break;
    }
    println("x: {x} y: {y}");
}

しかし、実行すると予想に反した動作をします図8⁠。

図8
x: 0 y: 0
x: 1 y: 0
x: 2 y: 0

これはバグとして登録されているので、近いうちに修正されるはずです。

JavaFXのバグはJavaFX-JIRAで管理されており、サインアップすると誰でもバグの登録をすることができます。ちなみに、上記のバグはJFXC-2504として管理されています(参照するのもサインアップが必要です⁠⁠。

最後にfor文の簡単なサンプルを示しましょう。複数の円を描画するサンプルです。

リスト15 複数の円の描画
var circles = for (decFlag in [false, true], num in [1..10]) {
    Circle {
        var radius = if (decFlag) 50 - num * 5 else num * 5

        centerX: if (decFlag) num * 10 + 100 else num * 10
        centerY: radius + 10
        radius: radius
        fill: Color.rgb(0, 192, 255, 0.25)
        stroke: Color.BLUE
    };
}

Stage {
    title: "Circles"
    scene: Scene {
        width: 200
        height: 120
        content: [ circles ]
    }
}

変数circlesにはfor文によりCircleクラスのシーケンスが代入されます。個々のCircleオブジェクトは変数decFlagと変数numによって、位置と半径が決まります。半径は一度大きくなってから、再び小さくなるようにしてみました。実行結果を図9に示します。

図9 for文を利用した複数円の描画
図9 for文を利用した複数円の描画

while文

while文はJavaと記述方法が同じです。ブロック構文の値は取らないので、for文のようにシーケンスに代入することはできません。

リスト16
var x = 0;

while (x 

実行結果を図10に示します。

図10
x: 0
x: 1
x: 2
x: 3
x: 4
x: 5
x: 6
x: 7
x: 8
x: 9

また、break文、continue文も使用することができます。

例外

JavaFX Scriptでも例外を扱うことができます。使用法もJavaの例外と同じです。しかし、チェックされる例外(checked exception)であっても、try ... catchは必須ではありません。たとえば、リスト17のスクリプトではjava.io.FileNotFoundException例外やjava.io.IOException例外がスローされる可能性がありますが、catchを書く必要はありません。

リスト17
var reader = new BufferedReader(new FileReader("sample.txt"));
var line = reader.readLine();

reader.close();

とはいうものの、頑健なスクリプトを記述するのであれば、必要に応じてtry ... catchを書くべきであると筆者は考えています。

なお、インタープリタ版では任意のオブジェクトをスローできましたが、1.0ではJavaと同じくjava.lang.Throwableクラスだけになりました。

さて、今回は演算子と構文について説明を行いました。制御構文は通常ロジックで使用しますが、ブロック構文を使用することで、宣言的文法と合わせて使うことが可能になります。ぜひご活用ください。

次回は、JavaFX ScriptでSwingのコンポーネントを使う方法を紹介します。

おすすめ記事

記事・ニュース一覧