この記事は、Javaに精通した開発者の方に、ActionScript 3.0(以下AS3)がどのような言語なのか、どこがJavaと異なっているのか(あるいは同じなのか)を一覧できるようまとめたものです。
主にAS3の静的な側面をまとめた、『文法編』(前回の記事)および『クラス宣言編』(Adobe Developer Connection『クラス宣言編』にて公開中)と、動的な側面をまとめた、『属性操作編』(この記事)および『振舞い編』(Adobe Developer Connection『振舞い編』にて公開中)の4編に分けて、Java開発者が引っかかりやすいと思われる点を中心に記述しました。
厳密な言語解説よりは、まずAS3の概要が分かること、を目的に書かれています。さらに詳しい言語仕様についてはActionScript 3.0の学習をご覧ください。
属性操作編の内容
属性操作編では、クラス宣言編で紹介した属性の使い方について、より深く掘り下げます。AS3の属性はJavaと異なり動的な要素を持っています。そのため、Javaでは一般的ではない使い方をすることもしばしばです。この記事では具体的な例を使いながら、AS3の属性の特徴と振舞いを紹介します。
まず、クラス宣言編でも紹介したアクセッサーの特徴をもう一度確認します。
アクセッサー
Javaでは、アクセッサーの使用はフィールドへの直接アクセスとは明確に区別されます。一方、AS3では、ほとんどの場合アクセッサーの使用を意識することはありません。そのため、AS3の方がクラスの仕様変更をより柔軟に行うことができます。
例えばJavaの場合、以下のように祝日の日数をpublic
な定数として、クラスFoo
に定義した場合を考えてみます。
public class Foo { // 国民の祝日は年に16日 public static int nationalHolidays = 16; }
このとき、クラスFoo
を利用する側では以下のようなコードを記述することになります。
// フィールドにアクセス int bar = Foo.nationalHolidays;
ところが、実は祝日の数が毎年変わることに後から気づいて(ちょっと間抜けな例ですが)、定数の代わりにゲッターを使って日数を取得させるよう変更したとします。この場合、クラスの使い方が変わってしまうため、上のコードはコンパイルが通らなくなります。
public class Foo { public static int getNationalHolidays() { // 現在の日付により、その年の休日数を返すよう変更 } }
そのため、Foo
を利用する側のコードは修正することになります。
// ゲッターを呼び出す int bar = Foo.getNationalHolidays();
このような変更作業を行わなければならないのは、あまり望ましいとは言えません。Javaではアクセッサーの実装がベストプラクティスとして推奨されているという理由もありますが、後からコードを修正することによるコストに対する問題意識から、最初の段階からアクセッサーを実装するのが一般的です。
一方、AS3では、途中からアクセッサーを実装するよう変更しても、それまでのコードが利用できます。例えば、上の例をAS3に置き換えた場合、クラスFoo
の実装をゲッターに変更すると、下のようになります。
public class Foo { public static function get nationalHolidays():int { // 現在の年に応じて休日数を返すよう変更 } }
しかし、ゲッターを呼び出す側の記述は、属性に直接アクセスするのと変わりません。
// ゲッターを呼び出す var bar:int = Foo.nationalHolidays;
このように、AS3はアクセッサーのへ変更を後から行いやすい仕様になっています。
AS3でも、アクセッサーの実装が好ましいことに変わりはありません。しかし、変更による影響が少ないため、まずあまり手間をかけずにプロトタイプを作ってしまいたい、その後でコードを洗練させたい、という開発が行いやすくなります。この点は、クライアントアプリ用に使われる言語としては、有利な特徴といえるでしょう。
属性の上書き
AS3で、アクセッサーの使用と属性の直接操作が区別されないという特徴は、他にもJavaにはない使い方を可能にします。属性のオーバーライドです。
Javaでは、子クラスで親クラスと同じ名前のフィールドを宣言すると、フィールドが再定義され、親クラスのフィールドは隠蔽されます。
class FooBase { String bar = "FooBase"; } class Foo extends FooBase { String bar = "Foo"; } FooBase foo1 = new Foo(); Foo foo2 = new Foo(); // 親クラスの変数として参照 System.out.println(foo1.bar); // FooBaseが出力される // 子クラスの変数として参照 System.out.println(foo2.bar); // Fooが出力される
AS3も基本的には同様で、var
またはconst
キーワードで宣言された属性をオーバーライドすることはできません。Javaと同じく別の属性定義として扱われます。
一方、get
やset
を使って宣言された属性は、アクセッサーをオーバーライドして、属性のオーバーライドと同等の機能を実現できます。下の例では、子クラスでセッターのオーバーライドをして、値が変更されたらイベントを発生させる機能を追加しています。
class FooBase { private var _bar:int; public function set bar(value:int):void { _bar = value; } public function get bar():int { return _bar; } } class Foo extends FooBase implements IEventDispatcher { override public function set bar(value:int):void { // 親クラスの set を呼び出し super.bar = value; // 変更をイベントとして伝える dispatchEvent(new Event(Event.CHANGE)); } ... }
これでbar
という属性を以下のように使うことができます。
Foo foo = new Foo(); foo.bar = 1; // ここでイベントが発生する trace(foo.bar); // 1
アクセッサーのオーバーライドについてはオンラインヘルプのgetterとsetterのオーバーライドもご覧ください。
属性名を使ったアクセス
Javaでは、フィールドへのアクセスには、「ドット(.
)+フィールド名」を使います。
public class Foo { public int bar; } Foo foo = new Foo(); foo.bar = 1;
既に見てきたように、AS3の属性へのアクセスも、同様のシンタックスを使います。しかし、AS3にはもう1つ、属性名の文字列を使って、属性にアクセスするという方法も用意されています。
public class Foo { public var bar:int; } var foo:Foo = new Foo(); // 以下は同じ意味 foo.bar = 1; foo["bar"] = 1;
このサンプルのように、オブジェクト名に続いて[]
内に属性名を記述します。属性名には変数を使うこともできます。
var foo:Foo = new Foo(); var name:String = "bar"; foo[name] = 1;
リフレクション(属性)
Javaも、リフレクションの機能を使うと文字列を使ってフィールドにアクセスすることができます。
Class c = foo.getClass(); String name = "bar"; Field f = c.getField(name); f.set(foo, 1);
このように、リフレクションを利用すると実行時にアクセスするフィールドを選択できるため、より柔軟性の高いコードを記述できます。しかし、通常の記述に比べてコードが複雑で読みにくくなってしまうことなどにより、リフレクションの使用はあまり一般的ではありません。
一方、上で触れたように、AS3では同様の記述が1行で済みます。Javaのようにはコードの保守性を損なわないため、実行時にアクセスするフィールドを選択する、という記述が使いやすくなっています。
foo[name] = 1;
存在しない属性へのアクセス
Javaでフィールドに直接アクセスする場合、存在しないフィールド名を指定すると、コンパイルエラーになります。
public class Foo {} Foo foo = new Foo(); foo.bar = 1; // Foo にはフィールドが無いため、コンパイルエラーになる
これは、AS3でも同じです。
public class Foo {} var foo:Foo = new Foo(); foo.bar = 1; // Foo には属性が無いため、コンパイルエラーになる
しかし、属性名を使って、属性にアクセスする場合、コンパイル時にフィールドの有無のチェックが行われません。存在しない属性名が指定された場合は、ランタイムエラーになります。
var foo:Foo = new Foo(); foo["bar"] = 1; // Foo にはフィールドが無いため、ランタイムエラーになる
Map
Object
Javaでは、コレクションフレームワークにMap
が含まれます。Map
は指定された型のオブジェクトをキーとして値を管理する機能を提供します。例えば、下はString
をキーとするHashMap
を使って、String
型の値を管理する例です。
HashMap<String,String> map = new HashMap<String,String>(); String bar = "bar"; map.put("foo", bar) // "foo"に対応する値を取得する Object value = map.get("foo");
AS3では、Object
がMap
の機能を持っています。キーとして使えるオブジェクトはString
に限定されます。一方、値は任意の型になります。
var obj:Object = new Object(); var bar:String = "bar"; obj["foo"] = bar; // "foo"に対応する値を取得する var value:Object = obj["foo"];
ところで、上のサンプル内の記述方法は、クラスの属性にアクセスする場合と同じ[
"属性名"]
です。通常のクラスでは、属性名で指定した属性が存在しなければ、ランタイムエラーになります。しかし、Object
の場合は、任意の名前の指定が可能です。このことから、Object
は、実行時に属性を追加することができるクラス、と考えることもできます。
Object
から、存在しないキーを指定して値を参照すると、undefined
が返されます。通常のクラスであればランタイムエラーが発生しているところです。
var obj:Object = new Object(); trace(obj["foo"]); // undefined が出力される
さらに、Object
の場合は下のような記述をしても、コンパイルエラーは発生しません。
var obj:Object = new Object(); trace(obj.foo); // undefined が出力される
ところで、AS3のObject
のインスタンスを初期化するのに、下のような書き方もできます。コロン(:
)の前が要素名、後が要素の値です。要素間はカンマ(,
)で区切ります。見た目にもMap
の初期化っぽく見えます。
var obj:Object = { foo:10, bar:"Hello" };
Dictionary
AS3で任意のオブジェクトをキーとしてMap
を使いたい場合、Dictionary
クラスを使います。
var map:Dictionary = new Dictionary(); var foo:Object = new Object(); map[foo] = "foo"; var value:Object = map[foo];
Dictionary
のキーは、値ではなくインスタンス自体です。同じ値をもつ同じ型のインスタンスでも、違うキーとして認識されます。下の例は、2つのObject
のインスタンスを使って、Dictionary
から値を取得しています。
var map:Dictionary = new Dictionary(); var foo:Object = new Object(); var bar:Object = new Object(); map[foo] = "foo"; trace(map[foo]); // fooが出力される trace(map[bar]); // undefinedが出力される
Dictionary
の詳細はASDocのDictionaryをご覧ください。
Array
AS3のArray
は数値をキーとしたMap
のような動作をします。これは、JavaのArray
と大きく異なる点です。
例えば、Javaでは配列の大きさがインスタンス生成時に決まります。
Array arr = new Array(1); arr[0] = new Object(); arr[1] = new Object(); // ランタイムエラーが発生
AS3では、任意のキーを指定できます。
var arr:Array = []; arr[0] = new Object(); arr[10] = new Object(); // 10番目の要素が追加される
ダイナミッククラス
AS3のObject
には任意の属性を追加することができます。また、存在しない属性名を指定してもコンパイルエラーにはなりません。AS3では、Object
以外にも、クラス宣言にdynamic
キーワードが付いているクラスは、同じ使い方をすることができます。
// Objectの場合 var foo:Object = new Object(); foo.x = 5; // dynamicキーワード付きでクラスを宣言 public dynamic class Bar {} var bar:Bar = new Bar(); // 下は正常に実行される bar.x = 5;
上のサンプルで、Bar
クラスには何も属性が定義されていません。しかし、x
という属性に値を設定するコードが、正常にコンパイルされ、実行されます。これは、x
という属性が動的に作成されたからです。もしくは、Bar
が"x
"をキーとして持つMap
として機能すると理解することもできます。
このように、dynamic
付きで宣言されたクラスをダイナミッククラスと呼びます。ダイナミッククラスは、属性やメソッドを実行時に追加したり変更したりできる、変更可能なオブジェクトです。
ダイナミッククラスのインスタンスに実行時に追加する属性は、実行時のみの存在です。これらの属性の型チェックはコンパイル時には行われません。また、この方法で追加する属性に型注釈を付けることはできません。
public dynamic class Bar {} var bar:Bar = new Bar(); bar.x = 10; bar.y = "foo"; trace(bar.x, bar.y); // 10 fooが出力される
dynamic
キーワード付きで宣言されたクラスでは、プライベートな属性にアクセスできません。ドット演算子(.
)を使用してアクセスすると、コンパイルエラーが発生します。属性名による参照では、コンパイルエラーにはなりませんが(ランタイムエラーにもなりません)、属性が参照不可のため、undefined
が値として返されます。下の例では、ダイナミッククラスであるBar
クラスのprivate
属性を参照しようとしています。
public dynamic class Foo { private var bar:String = "プライベートな属性"; } var foo:Foo = new Foo(); trace(foo["bar"]); // undefinedが出力される trace(foo.bar); // コンパイルエラーになる
ダイナミッククラスの詳細はオンラインヘルプのダイナミッククラスをご覧ください。
この記事の続き『JavaとActionScript 3 の違い:振舞い編』は、Adobe Developer Connectionで公開中です。