Platform: All
Version: Flash MX 2004
3.1 クラスの作成と使用について
すでに解説したとおり(「2.2 クラスファイルの作成」参照)、クラスは2つの部分から構成されます。「宣言」(declaration)と「本体」(body)です。クラス宣言は最低限、classステートメントと、それに続けてクラス名となる識別子、そして始まりと閉じの中括弧({})で構成されます。中括弧内は、すべてクラス本体になります。その例は、つぎのとおりです。
class className {
// クラス本体
}
クラスは、ActionScript(AS)ファイル内でのみ定義できます。たとえば、クラスをFLAファイルのフレームアクションで定義することはできません。
クラス名は、識別子でなければなりません。つまり、(訳者註: 使用する文字はすべて半角で)最初のひと文字は英字かアンダースコア(_)、ドル記号($)で始まり、続く文字列は半角英数字かアンダースコア、ドル記号で構成される必要があります。またクラス名は、それを記述したASファイルの名前と、大文字小文字の別も含めて、正確に一致しなければなりません。つぎの例のようにShapeというクラスを作成したら、クラス定義を記述したASファイルにはShape.asという名前をつける必要があるのです。
// Shape.asファイル
class Shape {
// Shapeクラス本体
}
作成したすべてのASクラスファイルは、所定のクラスパス(classpath)フォルダのいずれかに保存しなければなりません。クラスパスフォルダは、スクリプトをコンパイルするときにFlashがクラス定義を検索する対象となります。そのひとつは、クラスを参照するFLAファイルが保存されているフォルダです(詳しくは、「6. クラスパスの考え方」参照)。
多くのクラスを作成する場合、「パッケージ」を用いてクラスファイルを管理します。パッケージはひとつ以上のクラスファイルを格納したディレクトリで、指定されたクラスパスディレクトリに納められます。クラス名は、それが宣言されたファイルを完全に指定する(fully
qualified)必要があります。つまり、クラス名には、それが保存されているディレクトリが反映されなければなりません。
たとえば、myClasses.education.currirulum.RequiredClassという名前のクラスは、パッケージmyClasses/education/currirulumに保存します。RequiredClass.asファイルの中で、クラスはつぎのように宣言されます。
class myClasses.education.curriculum.RequiredClass {
}
このため、パッケージの構造は、クラスを作成する前に検討しておくことをお勧めします。それを怠って、クラスファイルを作成後に移動することになった場合、クラス宣言のステートメントを新たな場所に合わせて修正しなければならなくなります。
3.2 コンストラクタ関数
クラスの「コンストラクタ」(constructor)は特別な関数で、new演算子を使ってクラスのインスタンスを作成すると、自動的に呼出されます。コンストラクタ関数は、それを記述したクラスと同じ名前にします。たとえば、前に作成したPersonクラスには、以下のコンストラクタ関数がありました。
// Personクラスコンストラクタ関数
function Person(myName:String, myAge:Number) {
this.name = myName;
this.age = myAge;
}
コンストラクタ関数が明示的に宣言されていないとき、つまりクラスと名前の一致する関数を作成していない場合は、コンパイラが自動的に空のコンストラクタ関数を生成します。
クラスは、コンストラクタ関数をひとつだけもつことができます。コンストラクタ関数の多重定義(overload)は、ActionScript
2.0では許されていません。
コンストラクタ関数には、戻り値の型指定はできません。
[注] thisキーワードは、ActionScript 2.0のクラス定義では必要とされません。コンパイラが参照を解析して、バイトコードにthisを加えるからです。しかし、thisを用いることで、コードが読みやすくなります。詳しくは、Flash MX 2004オンラインヘルプで[ActionScriptユーザーガイド] > [ActionScriptの基礎] > [ベストプラクティスの使用] > [ActionScriptのコーディング規則] > [スコープの使用]の「thisキーワードの使用」をご参照ください。
3.3 プロパティとメソッドの作成
クラスのメンバは、プロパティ(変数宣言)とメソッド(関数宣言)とで構成されます。すべてのプロパティとメソッドは、クラス本体(中括弧{}内)に宣言する必要があります。そうしないと、コンパイル時にエラーが発生します。
クラス内でなおかつ関数の外に宣言された変数は、クラスのプロパティになります。たとえば、つぎの例に示すように、前述「クラスの使用 − シンプルな例」で採上げたPersonクラスには、2つのプロパティageとnameがあり、それぞれNumberとStringに型指定されていました。
class Person {
var age:Number;
var name:String;
}
同様にクラス内に宣言された関数は、クラスのメソッドになります。Personクラスの例では、getInfo()というメソッドをひとつ作成しました[*1]。
class Person {
var age:Number;
var name:String;
function getInfo():Void {
// getInfo()メソッドの定義
}
}
クラス定義におけるthisキーワードの使用については、前述3.3の[注]をご参照ください。
[訳者註*1] 7.2アップデート以前は、メソッド名がshowInfo()とされていました。
|
3.4 メンバアクセスの制御
デフォルトでは、クラスのプロパティまたはメソッドは他のクラスすべてからアクセスが可能です。クラスのすべてのメンバは、デフォルトではpublicです。しかし、クラスのデータやメソッドが、他のクラスからアクセスできないようにしたい場合もあります。それらのメンバは、privateにする必要があります。privateのメンバは、それらを宣言あるいは定義したクラスからのみアクセスすることができます。
メンバをprivateにするか逆にpublicにするかは、publicまたはprivateというメンバ属性を使って行います。たとえば、以下のコードはprivate変数(プロパティ)とprivateメソッド(関数)を宣言します[*2]。つぎのクラス(LoginClass)は、userNameという名前のprivateプロパティとgetUserName()という名前のprivateメソッドを定義しています。
class LoginClass {
private var userName:String;
private function getUserName():String {
return this.userName;
}
// コンストラクタ
function LoginClass(user:String) {
this.userName = user;
}
}
[訳者註*2] 通常は変数と関数に、オブジェクトのプロパティとメソッドを対比します。したがって、「private変数(プロパティ)とprivate関数(メソッド)を宣言します」とすべきでしょう。
|
privateメンバ(プロパティとメソッド)は、それらのメンバを定義したクラスとそのクラスを継承したサブクラスからのみアクセスすることができます[*3]。元のクラスのインスタンスあるいはそのクラスを継承したサブクラスのインスタンスから、private宣言されたプロパティやメソッドにはアクセスできません。つまり、privateメンバは、クラス定義内でのみアクセス可能で、インスタンスレベルからはできないのです。
[訳者註*3] サブクラスからアクセスできる点で、Javaのprivate修飾子とは異なります。Javaの修飾子でいうと、protectedに近いでしょう。
|
たとえば、LoginClassのサブクラスを作成して、NewLoginClassとします。このサブクラスは、LoginClassに定義されたprivateのプロパティ(userName)とメソッド(getUserName())にアクセスすることができます。
class NewLoginClass extends
LoginClass {
// userNameとgetUserName()にアクセス可能
}
しかし、LoginClassやNewLoginClassのインスタンスは、これらのprivateメンバにはアクセスできません[*4]。たとえば、以下のコードをFLAファイルのフレームアクションに加えると、コンパイルエラーになります。getUserName()はprivateなので、アクセスできないと表示されます。
var loginObject:LoginClass
= new LoginClass("Maxwell");
var user:String = loginObject.getUserName();
[訳者註*4] たとえば、NewLoginClassを、つぎのように定義してみましょう(スクリプト001)。getUser()メソッドは、スーパークラスから継承したgetUserName()メソッドを呼出しています。
スクリプト001■クラスNewLoginClassを定義
// ファイル: NewLoginClass
class NewLoginClass extends LoginClass {
function NewLoginClass(user:String) {
super(user);
}
function getUser():String {
// 継承したスーパークラスのgetUserName()メソッドを呼出す
return this.getUserName();
}
}
|
すると、NewLoginClassのインスタンスからのgetUserName()メソッドの呼出しは、スーパークラスのprivateメンバなのでコンパイルエラーを発生します。しかし、getUser()メソッドはNewLoginClassのメソッドで、その中からスーパークラスのprivateメソッドはアクセスできるので、呼出しが可能になります(スクリプト002)。
スクリプト002■クラスNewLoginClassのテスト
// テスト
// フレームアクション
var obj1:NewLoginClass
= new NewLoginClass("Fumio");
trace(obj1.getUserName()); // コンパイルエラー
trace(obj1.getUser()); // 呼出し可能
|
|
メンバのアクセス制御は、コンパイル時のみの機能です。ランタイムでは、Flash
Playerはprivateメンバかpublicメンバかの区別をしません。
クラス定義におけるthisキーワードの使用については、前述3.3の[注]をご参照ください。
3.5 プロパティのインライン初期化
プロパティは、インラインで初期化できます。つまり、それらを宣言するときに、つぎのようにデフォルト値を設定することが可能です。
class Person {
var age:Number = 50;
var name:String = "Jogn Doe";
}
プロパティをインラインで初期化するとき、代入の右辺の式は「コンパイル時定数」(compile-time
constant)でなければなりません。つまりその式は、ランタイムに設定あるいは定義される値を参照することはできません。コンパイル時定数とは、ストリングリテラルや数値、ブール(論理)値、null、undefinedなどです。また、ArrayやBoolean、Number、Object、Stringといった定義済みクラスのコンストラクタ関数も含まれます。
たとえば、以下のクラス定義は、いくつかのプロパティをインライン初期化しています。
class CompileTimeTest {
var foo:String = "my foo"; // OK
var bar:Number = 5; // OK
var bool:Boolean = true; // OK
var name:String = new String("Jane"); // OK
var who:String = foo; // OK。fooは定数です
var whee:String = myFunc(); // エラー! コンパイル時定数の式ではありません
var lala:Number = whee; // エラー! コンパイル時定数の式ではありません
var star:Number = bar + 25; // OK。barも25も定数です
function myFunc():String {
return "Hello world";
}
}
この原則は、インスタンス変数(クラスの各インスタンスにコピーされる変数)[*5]にのみ適用され、クラス変数(クラス自身に帰属する変数)には当てはまりません。これらの変数について、詳しくは「4.
インスタンスとクラスのメンバ」をご参照ください。
[訳者註*5] クラス内でコンストラクタ関数の外で設定されたプロパティは、クラスのprototypeに定義されます。したがって厳密には、値が各インスタンスにコピーされる訳ではありません。インスタンスごとにプロパティを設定したい場合には、その値をコンストラクタ関数内で設定する必要があります。
たとえば、クラスInstancePropertyTestを定義して、Number型とArray型のプロパティをひとつずつインライン初期化してみます(スクリプト003)。
スクリプト003■プロパティのインライン初期化
// ファイル: InstancePropertyTest.as
class InstancePropertyTest {
var myNumber:Number = 0;
var myList:Array = new Array();
function InstancePropertyTest(nNum0:Number, nNum1:Number) {
myNumber = nNum0;
myList.push(nNum1);
}
function showProperties():String {
return "myNumber: "+myNumber+", myList: "+myList;
}
}
// テスト
// フレームアクション
var obj1:InstancePropertyTest = new InstancePropertyTest(1, 10);
trace(obj1.showProperties()); // 出力: myNumber: 1, myList: 10
var obj2:InstancePropertyTest = new InstancePropertyTest(2, 20);
trace(obj1.showProperties()); // 出力: myNumber: 1, myList: 10,20
trace(obj2.showProperties()); // 出力: myNumber: 2, myList: 10,20
delete obj1.myNumber;
trace(obj1.showProperties()); // 出力: myNumber: 0, myList: 10,20
delete obj1.__proto__.myNumber;
trace(obj1.showProperties()); // 出力: myNumber: undefined, myList: 10,20
|
インライン初期化された配列myListは、クラスのprototypeに設定されたため、インスタンスを作成するときに値がmyListの参照に対して追加されています。したがって、プロパティmyListは、すべてのインスタンスに共通の値となります。
それに対してmyNumberは、コンストラクタ関数内で値を代入しているため、インスタンスのプロパティとして設定されます。しかし、prototypeの設定値は存続しているので、インスタンスのプロパティmyNumberを削除すると、スコープチェーンからprototypeの設定値が参照されます。つまり、インライン初期化した値は、デフォルト値として機能しています。__proto__プロパティを使ってprototypeに設定したプロパティmyNumberを直接削除すると、デフォルト値が未定義値undefinedになります。
したがって、インスタンスのプロパティとしてオブジェクトを設定したい場合には、インライン初期化せず、コンストラクタ関数内で初期化を行います(スクリプト004)。
スクリプト004■オブジェクトのプロパティをコンストラクタで初期化
// ファイル: InstancePropertyTest2.as
class InstancePropertyTest2 {
var myList:Array;
function InstancePropertyTest2(nNum0:Number) {
myList = new Array();
myList.push(nNum0);
}
function showProperty():String {
return "myList: "+myList;
}
}
// テスト
// フレームアクション
var obj1:InstancePropertyTest2 = new InstancePropertyTest2(10);
var obj2:InstancePropertyTest2 = new InstancePropertyTest2(20);
trace(obj1.showProperty()); // 出力: myList: 10
trace(obj2.showProperty()); // 出力: myList: 20
|
|
3.6 サブクラスの作成
オブジェクト指向プログラミングでは、サブクラスはプロパティやメソッドを、スーパークラスと呼ばれる他のクラスから継承します。こうした関係を2つのクラス間で構築するには、classステートメントにextends節を使用します。スーパークラスを指定するには、つぎのようなシンタックスを使用します。
class SubClass extends SuperClass
{}
SubClassに指定したクラスは、スーパークラスに定義されたすべてのプロパティとメソッドを継承します。たとえば、Mammalクラスを作成して、すべてのほ乳類(mammal)に共通のプロパティとメソッドを定義したとします。MammalクラスのバリエーションとしてMarsupial(有袋動物)といったクラスを作成するには、Mammalクラスを拡張(extends)します。つまり、Mammalクラスのサブクラスを作成するのです。
class Marsupial extends Mammal
{}
サブクラスは、すべてのプロパティとメソッドをスーパークラスから継承します。それらのプロパティやメソッドには、privateキーワードを使用してプライベート宣言されたものも含まれます(private変数について、詳しくは次節「3.4
メンバアクセスの制御」をご参照ください)。
独自に作成したクラスも、拡張可能です。また、XMLやSound、MovieClipクラスといったActionScriptの定義済みクラスも、同じように継承できます(TextFieldクラスまたは、MathやKey、Mouseなどの静的クラスは継承できません)。定義済みのActionScriptクラスを拡張すると、そのサブクラスはすべてのメソッドやプロパティをその定義済みクラスから継承します。
たとえば、以下のコードはクラスJukeBoxを定義して、定義済みのSoundクラスを拡張しています。このクラスには、songListという配列とplaySong()というメソッドが定義されています[*6][*7]。playSong()メソッドは、loadSoundメソッドを呼出して、音楽を再生します。loadSoundメソッドは、Soundクラスから継承したものです。
class JukeBox extends Sound
{
var songList:Array = new Array("beathoven.mp3", "bach.mp3", "mozart.mp3");
function playSong(songID:Number):Void {
this.loadSound(songList[songID], true);
}
}
[訳者註*6] 原文では、以下のようにメソッドplaySong()の戻り値が型指定されていません。クラス定義のサンプルとしては、型指定をすべきでしょう(Macromedia LiveDocs Flash MX 2004では修正済み)。また、メソッド中のSound.loadSoundメソッドの第2引数が、指定されていません。第2引数の省略はドキュメント上認められていませんが、その場合イベントサウンド(引数値false)として扱われるようです。したがって、ロード終了後にSound.start()メソッドを呼出さないかぎり、サウンドは再生さません(なお、「Flash
MX 2004ヘルプ正誤表 」参照)。
■オンラインヘルプ原文
class JukeBox extends Sound {
var songList:Array = new Array("beathoven.mp3", "bach.mp3", "mozart.mp3");
function playSong(songID:Number):Void {
this.loadSound(songList[songID]);
}
}[訳者註*7] プロパティsongListは、インライン初期化されています。したがって、すべてのインスタンスが、同一の配列を参照することになります。そのため、複数のインスタンスを作成する意味がなく、クラス定義のサンプルとしては適切と思えません。
最低限の動作がテストできるサンプルとしては、以下のようなクラス定義が考えられるでしょう(スクリプト005)。なお、super()を使用するときは、コンストラクタ関数内で最初に呼出す必要があります。
スクリプト005■Soundクラスを継承したサブクラスの定義
// ファイル: JukeBox.as
class
JukeBox extends Sound {
var songList:Array;
function JukeBox(target_mc:MovieClip) { // ターゲットを引数に受取る
// ターゲットを引数に渡してスーパークラスを初期化
super(target_mc);
// 配列はコンストラクタ関数内で初期化
songList = new Array();
}
function playSong(songID:Number):Boolean {
var mySound = songList[songID];
if (mySound) {
this.loadSound(songList[songID], true);
return true; // songListに値が存在すればtrueを返す
}
return false; // songListに値が存在しないとfalseを返す
}
// songListに値を追加
function append(soundName:String):Number {
return songList.push(soundName); // songListの長さを返す
}
// songListから値を削除
function deleteOne(soundName:String):Boolean {
for (var i = 0; i<songList.length; ++i) {
if (songList[i] == soundName) {
songList.splice(i, 1);
return true; // songListに値が存在すればtrueを返す
}
}
return false; // songListに値が存在しないとfalseを返す
}
}
// テスト
// フレームアクション
var obj1:JukeBox = new JukeBox();
var obj2:JukeBox = new JukeBox(this.my_mc);
trace(obj1.append("beethoven.mp3")); // 出力: 1
trace(obj1.append("bach.mp3")); // 出力: 2
trace(obj2.append("mozart.mp3")); // 出力: 1
trace(obj1.playSong(1)); // 出力: true
trace(obj2.playSong(0)); // 出力: true
this.onMouseDown = function() {
obj1.stop(); // 継承したSound.stop()メソッドを呼出し
trace(obj1.deleteOne("bach.mp3")); // 出力: true
trace(obj1.playSong(1)); // 出力: false
};
|
|
サブクラスのコンストラクタ関数でsuper()の呼出しを行っていない場合、コンパイラは自動的に直接のスーパークラスのコンストラクタに対する呼出しを生成します。この呼出しは、コンストラクタ関数の最初のステートメントとして、引数なしに実行されます。そのスーパークラスにコンストラクタがなければ、コンパイラは空のコンストラクタ関数を作成し、サブクラスからその呼出しを生成します。しかし、スーパークラス(訳者註: のコンストラクタ)が定義の中で引数を取る場合には、サブクラスにコンストラクタを作成し、スーパークラスを必要な引数つきで呼出す必要があります。
多重継承(multiple inheritance)あるいは複数のクラスを継承することはできません。しかし、複数のクラスを結果として継承することは、そのそれぞれにextendsステートメントを使用することで可能になります。
// 使用不可
class C extends A, B {}
// 使用可能
class B extends A {}
class C extends B {}
また、インタフェースを使えば、限定されたかたちで多重継承を実現することもできます。詳しくは、「1.4 インタフェース」および「5. インタフェースの作成と使用」をご参照ください。
出典
Flash MX 2004 Using ActionScript: Creating and using classesより邦訳。
_____