akihiro kamijo: Core Language Archives

June 06, 2008

AS3 の論理積と論理和

最近質問を受けたので見てみたら Flash のヘルプドキュメントが古いのに気づいたので今日はこの話題です。(LiveDocs の AS3 リファレンス は最新です)

さて、まず下の例を考えてみます。

var foo:Boolean = true;
var bar:Object = {};
 
trace(foo && bar);  // 論理積 : 出力は [object Object]
trace(foo || bar);  // 論理和 : 出力は true
 

論理積と論理和の結果は、演算子の左側の式 (この場合は foo) の値で決定されます。ごく単純化すると、ルールは

論理積 (&&) : 左側の式が false なら左側の式の値、true なら右側の式の値
論理和 (||) : 左側の式が true なら左側の式の値、false なら右側の式の値
 

です。つまり、実際に論理演算が行われているわけではないのですね。演算結果の値も Boolean 型になるとは限りません。

さて、AS3 のプログラムでは Boolean もオブジェクトなので、左側の式を評価した後その値の型が何であっても同じルールを適用することができます (されます)。とにかく左辺の式を評価したら、結果を Boolean 型に変換して値を参照します。

それぞれの型を Boolean 型に変換した場合の値は以下のようになります。

Undefiend 	false
Null 		false
Boolean 	変換前のオブジェクトと同じ
Number 		0 または NaN は false それ以外の値は true
String 		空文字列は false  それ以外の値は true
Object 		true
 

ここで左辺が Boolean 型にならない例をひとつ。

var foo:String = "Hello";
var bar:Object = {};
trace(foo || bar); // 出力は Hello
 

”Hello” は空文字列でないため Boolean 型に変換した際の値は true になります。この例で使われている演算子は論理和 (||) なので、左側の式 (foo) の値が出力されるわけです。

論理積と論理和を使った条件文

上で記述したルールから想像されるとおり、論理積や論理和の演算において、演算子左側の式は必ず評価されますが、右側の式は左側の値次第で評価されたりされなかったりします。たとえば、論理積において、右側の式が評価されるのは左側の式の値が true の場合のみです。

この振る舞いを、条件文の代わりに使うことも可能です。たとえば以下の 2 つは同じ意味を持ちます。

1. if 文で記述
if (foo) {
  doSomething();
}
 
2. 論理積で記述
foo && doSomething();
 

この例とは逆に、ある条件が偽の場合のみ実行したい場合には、論理和 (||) を使うことができます。

論理積や論理和が if 文と交換可能であることは以下の例のほうが分かりやすいでしょうか。

1. if 文のみで記述
if (foo) {
  if (bar) {
    sayHello();
  }
}
2. if 文と論理積で記述
if (foo && bar) {
  sayHello();
}
3. 論理積のみで記述
foo && bar && sayHello();
 

else 文には論理和で対応できます。複雑な条件になってくるとこんがらがりやすいので積極的にお勧めはしませんが。

&&= 演算子と ||= 演算子

特に、オブジェクトを Boolean 型に変換したときの値により代入を制御したい場合には &&= と ||= 演算子が用意されています。&&= は左側のオブジェクトの値が真の場合のみ、||= は値が偽の場合のみ右側の式の評価と左側のオブジェクトへの代入を行います。

下の例では 「foo の値が空文字列以外の文字列だったらタグに変換する」 という条件を 2 種類の方法で記述しています。

1. if 文で記述
if (foo) {
  foo =  "<" + foo + "/>";
}
 
2. &&= 演算子で記述
foo &&= "<" + foo + "/>";

Posted by ackie at 06:46 PM | Comments (0)

July 12, 2007

== 演算子と暗黙の型変換

前回の続きです。なぜ "" == foo が true と評価されるのか、まず、暗黙の型変換が行われる際のルールを一つ確認します。

暗黙の型変換の基準

暗黙の型変換が行われる際の基準となる型は、変数に関連付けられた型注釈ではなく、値であるオブジェクト自体の型です。ですので変数の型に注目していても暗黙の型変換の結果は分かりません。

例えば、今回問題になっている例 foo:Object = 0 では、値である 0 の型が暗黙の型変換の基準になります。foo の型ではありません。そのため if 文の条件は文字列とと数値の比較として扱われます。

var foo:Object = 0;
if ("" == foo) { // 実際には "" == 0 が評価される
  trace("foo is null String");
}
 

そうすると、今回のケースでは "" と 0 が等価であるかどうかが評価されていることになります。

== 実行時の暗黙の型変換のルール

さて、文字列と数値を == で評価する際には、「文字列を数値に変換してから比較する」というルールが適用されます。そのため、まず空文字列から数値への変換が行われます。

AS3 では数値表現として扱うことのできない文字列を数値に変換した場合の結果は 0 になります。空文字列も意味の有る数値表現ではありませんから数値に型変換されたときの値は 0 です。すると "" == 0 は 0 == 0 となります。これにより if 文は最終的に true になるわけです。

別の例を見てみましょう。今度は空の配列と空文字列を比較するケースです。

var foo:Object = [];
if ("" == foo) {
  trace("foo は空文字列です");
}
 

こちらのケースも ”foo は空文字列です” が表示されます。

ここで適用されたルールは、「文字列と配列を == で評価する場合は配列を文字列に変換してから行う」というものです。空の配列を String 型に変換すると空文字列になるため、上の if 文の条件は "" == "" となり、結果として true になったと考えることができます。

ここまでのまとめ

ここで一旦まとめてみましょう。まず、== 演算子では比較対照のデータ型が異なる場合、暗黙の型変換が行われます。そして、変換のルールは型の組み合わせに応じていくつかのルールの中から選ばれます。

ECMAScript の定義を参照すると == 演算子を実行する前に、適用される暗黙の型変換ルールを決定する 23 ステップ相当の条件チェックが必要であることが分かります。従って == 演算子の結果を正しく予測するには、この 23 ステップを適用した場合に起こりうる全てのケースについて考慮する必要があります。さらに、それぞれのケースについて、「空文字列は 0 に変換される」とか「空の配列は空文字列に変換される」といったルールを全て把握していなければなりません。

これはあまり現実的とはいえませんよね。ということで、ここまでのまとめとしては、

  • == 演算子の結果は信用できない

ということになります。

少し AS3 をかばっておくと、これは ECMAScript に準拠したために起きている状況で、AS3 の設計者がみんなを混乱させてやろうとか思った訳では (決して) ありません。

ともあれ、== を使わずにプログラムを書くのも難しいので、プログラムを記述する側で何らかの対応をすることが必要になります。次はその対応方法を考えてみたいと思います。

Posted by ackie at 06:48 PM | Comments (0)

July 09, 2007

AS3 の変数の型に関連する話

今回は AS3 の型システムの特徴についての話です。strict モードの人には関係ない話が続きましたが、ここからはコンパイラの環境設定に依存しない話題です(たぶん)。

まず、下のコードについて考えてみます。 オブジェクト型の変数 foo に数値の 0 を代入して空文字列と比較しています。

var foo:Object = 0;
if ("" == foo) {
  trace("foo は空文字列です");
}
 

上の If 文は空文字を検出するためのものに見えますが、実際にはこれを実行すると ”foo は空文字列です” が表示されます。

この結果は予想通りだったでしょうか?想定外だった人はこの先を。

上記の結果は、AS3 が弱い型の性質を持っているために起こります。AS3 では ’+’ 等のオペレータに対して複数のデータ型が混在すると処理の前に暗黙の型変換が行われます。これによって例えば trace("count:" + 0) のように文字列と数値を”足して”もそれらしい結果を得ることができるわけです。

暗黙の型変換は上の trace() の例のようにしばしば便利な機能として使われますが、一方で予期せぬ結果になるケースもある面倒なものだったりもします。例えば、この記事の最初に挙げた例も暗黙の型変換の結果によるものですが、実際にどのような変換が行われて true と評価されたのかは直感的には理解し難いものです。

ということで、これから上のサンプル内の if 文で実際に起きていたこと、このような結果を避ける方法について話を移そうと思いますが、その前に 2 つほど基礎となる項目の確認です。

int がオブジェクトであること

とりあえず、下のサンプルをみてください。

var foo:int = 0;
var bar:String = foo.toString();
 

2 行目のように int 型の変数のメソッドが呼びだせるということは、AS3 の int は Java でいうと Integer に近いものだということです。ASDoc を見てみると、実際に int が Object のサブクラスであることが確認できます。同様に uint や Number も Object のサブクラスです (Number はクラス名っぽいですね)。

このため冒頭の例でも Object 型の変数に数値を代入することができたわけです。

ちなみに下の 2 行はプログラムの意味的には同じになります。もちろん動的な属性を持たない int 型のオブジェクトのコンストラクタをわざわざ呼ぶ必要はありませんが。

var i:int = 0;
var i:int = new int(0);
 

「型が無い」という型

数値もオブジェクトとして扱われるということで全ての値は Object 型に属するように思えますが、実際は例外があります。Object は undefined を値として持つことができません。undefined が属性値として必要な場合は * を型として指定します。

AS2 では Object 型オブジェクトの初期値は undefind でしたが AS3 では null になっています。* 型の初期値は undefined です。

ところで * は型指定が無いという型(ちょっと矛盾?)です。というわけで、* は型注釈を省略したときに使われる型でもあります。

// 下の2行は同じ意味
var foo;
var foo:*;
 

変数の型を明示的にしない場合 Object 型ではないことに注意しましょう。* 型のオブジェクトは属性やメソッドを持たないため、コンパイル時の型チェックが行われません。

実行時まで型チェックを遅らせられることは動的言語の利点ですが、厳密な型チェックを行う strict モードでも * を使うとこの恩恵にあずかることができます。

ちょっと長くなったので、続きは次回に。

Posted by ackie at 05:46 PM | Comments (0)

July 06, 2007

AS3 での変数宣言とパフォーマンス

前回に引き続き変数宣言の有無による違いを少し追求してみたいと思います。今回はパフォーマンスの違いについてです。よく知られている話だとは思いますがいちおう。

変数宣言の有無とパフォーマンス

まずは、以下のコードを考えます。Date のオブジェクトを生成して getTime() メソッドを 10 万回呼び出しています。変数宣言は行われていません。

date = new Date()
for (i=0; i<100000; i++)
  date.getTime()
 

手持ちの環境で上のコードを実行してみます。すると、for ループの箇所の実行に約 470ms かかりました。

これを下のように変えてみます。2 行目で var を使った変数宣言を追加しています。それから for ループ内では、2 行目で新しく追加した変数経由でメソッドを呼び出しています。

date = new Date()
var dateVar = date  // 追加
for (i=0; i<100000; i++)
  dateVar.getTime()
 

こちらはループの実行に約 340ms かかりました。処理時間が 130ms 程短縮されています。

for ループ内で参照しているオブジェクトは、どちらのケースも同じく Date のインスタンスです。両者の違いは、参照に使用している変数の宣言に var が付いているかどうかだけです。このことから var 無しの変数からオブジェクトを参照する行為には、ずいぶん余計な時間がかかっていることが分かります。

関数内で var を付けて宣言した変数をローカル変数といいます。上の結果が示すようにこローカル変数へのアクセスは高速です。また、ローカル変数の有効範囲は宣言された関数内のみですので、参照の消し忘れによるメモリリークの心配もありません。

一方、var 無しで変数を使用した場合は、前回見たように、クラス内で有効な一種のグローバル変数になります。上の結果のとおり、グローバル変数へのアクセスはローカル変数より時間がかかります。

型注釈の有無とパフォーマンス

次は、変数宣言に型注釈をつけてみます。2 行目の宣言で明示的に Date 型を指定しています。

date = new Date()
var dateTyped:Date = date  // 型を指定
for (i=0; i<100000; i++)
  dateTyped.getTime()
 

実行してみるとループの箇所に約 300ms かかりました。先程の 340ms と比べ更に一割ほど短縮されています。

これは、AS3 の実行環境が型情報をパフォーマンス向上に役立てる機能を持っているからです。また、型注釈をつけるとメモリの使用量を減らす効果もあります。このような実行環境による型情報のサポートは AS3 の大きな特徴です。(注:この箇所は dynamic なクラスのオブジェクトに動的に追加されたメソッドやプロパティには当てはまりません)

変数宣言の追加とここまでのまとめ

さて、よく見ると、ループ内には i という変数も使われています。これもローカル変数として宣言してみます。

date = new Date()
var dateTyped:Date = date
for (var i=0; i<100000; i++)  // i をローカル変数に
  dateTyped.getTime()
 

この結果、約 20ms になりました。i は 2 回使われている分効果が大きいようです。

さらに i に型をつけてみます。

date = new Date()
var dateTyped:Date = date
for (var i:int=0; i<100000; i++)  // i を int 型に
  dateTyped.getTime()
 

これで約 13ms になりました。最初の数値である 470ms と比べて 3% 以下になっています。

var とか型とか書くのを習慣にしたほうがよさそう、という結論になりそうですが、10 万回繰り返してやっとこの程度の差という見方もできます。ここでの例のように後から変数宣言を追加すればよい話ですし、パフォーマンスの観点からだけなら、あまり神経質にならなくてもよいかもしれません。特に、型注釈の有り無しによる差はほとんど無いケースも多かったりします。

ただ、前回説明したように、グローバル変数 (var を使った宣言無しの変数) の使用はプログラムの構造を分かりにくくする可能性があります。パフォーマンスの件と併せると、特に理由の無い限り避けた方が賢明そうですね。

そうすると、ここまでのまとめとしては、

  • var は基本的に付ける
  • 型注釈はあったほうがまし

ということになります。

が、型注釈が無いことでプログラムの実行結果が非常に分かりにくくなる場合があったりします。次はこの点について掘り下げてみようと思います。

Posted by ackie at 06:25 PM | Comments (0)

July 05, 2007

AS3 での変数宣言 (var) の省略について

ちょっと前の記事 (AS3 で動的言語のススメ) に書いたように strict オプションを指定しないことで AS3 の記述の自由度は格段に上がります。とはいえ、”With great power comes great responsibility” の言葉どおり、選択肢が増えた分だけ、コードを記述する側の責任が大きくなることは意識しておく必要があります。

手抜きの書き方を勧めたことに少し責任を感じて、今回は var による変数宣言の有無による変数の振舞いの違いに少し触れてみたいと思います。

var 無し変数の初期化

Flash CS3 でも Flex でも AS3 コンパイラの厳密な型チェックのオプションをオフにすれば var 宣言無しで変数が使用できるようになります。例えば下のコードは問題なくコンパイルできます。

public class Foo
{
  public function setMyGlobal(val:int):void
  {
    i = val;
  }
  public function printMyGlobal():void
  {
    trace(i);
  }
}
 

以下のコードを試してみると 10 が出力されます。ちゃんと動作していることが確認できます。

var foo:Foo = new Foo();
foo.setMyGlobal(10);
foo.printMyGlobal(); // 10 が出力される
 

ところが、setMyGlobal() を呼び出さずに printMyGlobal() を呼び出すと、実行時に i が定義されていないというエラーメッセージが出力されます。

var foo:Foo = new Foo();
foo.printMyGlobal(); // ランタイムエラー発生
 

このことから setMyGlobal() 内で i の初期化が行われていたことが分かります。

public function setMyGlobal(val:int):void
{
  i = val;  // この行が最初に実行された時点で i が初期化される
}
 

値を代入するコードの実行が、最初の回に限り変数の初期化も兼ねています。つまり、コードの実行される順番に結果が依存するわけです。

var を使った宣言を行わない場合は初期化のタイミングに注意するようにしましょう。明示的に var を使って宣言した変数であればインスタンス生成時に変数の初期化が行われます。

var 無し変数のスコープ

さて、再び最初に定義したクラス Foo を使って次のコードを実行してみましょう。

var foo1:Foo = new Foo();
var foo2:Foo = new Foo();
 
foo1.setMyGlobal(10);
foo2.printMyGlobal(); // 10 が出力される
 

そうすると 10 が出力されると思います。foo1 で設定した値が foo2 から参照できていることになります。つまり var を使わずに使用された変数 i はクラスのインスタンス間で共有されているわけです。

var を使わないで変数を初期化するコードを記述するのは、スタティックイニシャライザを記述するようなものだと考えてもよいでしょう。もし、Foo クラスの定義が i をインスタンス変数として定義し忘れたものだったとしたら、ずいぶんと異なる振舞いを記述していることになります。

Flash CS3 の場合なら、ムービークリップのタイムラインに var を付けずに使用している変数があれば、その値は全てのインスタンスで共有されることになるわけです。

コンパイラで厳密な型チェックを行うように指定すれば var 宣言されていない変数はコンパイル時に発見して通知してくれます。AS3 コンパイラの設定がデフォルトで strict モードになっているのはなるほどというところでしょうか。Flash 8 以前の fla を AS3 に設定しなおした場合は strict オプションは選択されていませんのでご注意を。

Posted by ackie at 05:54 PM | Comments (2)

June 16, 2006

継承-その2

継承の話題の続きです。

インスタンスメソッドのオーバーライド

継承したクラスのインスタンスメソッドをオーバーライドするには override キーワードを使います。オーバーライドするメソッドは以下の条件を満たす必要があります。

  • 同じ名前である
  • 同じアクセス修飾子である
  • 同じ数の引数を持つ
  • 個々の引数の型が同じである
  • 戻り値の型が同じである

final キーワード付で宣言されたメソッドはオーバーライドできません。

また、private 属性のクラスと同じ名前のメソッドを定義する場合は override を付けません。親クラスの private なメソッドは「見えない」からです。

public class MyBase
{
  final public function finalFunc():void {
  }
  public function publicFunc():void {
  }
  private function privateFunc():void {
  }
}

この場合、MyBase クラスの中でオーバーライドできるメソッドは一つだけです。

public class MySub extends MyBase
{
  // final キーワードが付いているためオーバーライドできない
  override public function finalFunc():void {
  }
  // オーバーライド可能
  override public function publicFunc():void {
  }
  // override を付けずに宣言することは可能
  override private function privateFunc():void {
  }
}

super

関数をオーバーロードする際、継承元クラスのメソッドを参照するには super を使います。super.methodName() のような形で使います。

コンストラクタでは super() で親クラスのコンストラクタを参照します。

public class MySub extends MyBase
{
  // コンストラクタ定義
   public function MySub() {
    super();
  }
  // 関数をオーバーロード
  override public function publicFunc():void {
    super.publicFunc();
  }
}

プロパティのオーバーライド

AS3 では属性をオーバーライドをすることができません。

ですが、get や set を使ったプロパティは関数でアクセスするためオーバーライドが可能です。例えば、以下のような定義のクラスがあるとします。

public class MyBase
{
  private var _myProp:String = "myprop";
 
  public function get myProp():String {
    return _myProp;
  }
  public function set myProp(val:String):void {
    _myProp = val;
  }
}

これを継承したクラスでは以下のような記述をすることができます。

public class MySub extends MyBase
{
  override public function get myProp():String {
    return super.myProp;
  }
  override public function set myProp(val:String):void {
    super.myProp = val;
  }
}

Posted by ackie at 07:36 PM | Comments (0)

June 15, 2006

継承

おくればせながら、継承に関する話題を少しまとめます。

extends

サブクラスを定義するには extends キーワードを使います。

// 親クラスの定義
public class MyBase 
{
  public function hello():String {
    return "hello";
  }
}
// サブクラスの定義
public class MySub extends MyBase
{
}

サブクラスのオブジェクトは親クラスのオブジェクトと同様に使うことができます。

// MyBase 型の変数に代入
var base:MyBase = new MyBase();
var sub:MyBase = new MySub();
// MyBase 型の引数を持つ関数にわたす
needBase(sub);
needBase(base);
 
public function needBase(obj:MyBase):void {
  trace(obj.hello());
}

逆に親クラスはサブクラスの代わりにはなりません、が、下のコードは -strict オプションを付けなければ実行できます。

var sub:MyBase = new MySub();
needSub(sub);
 
public function needSub(obj:MySub):void {
  trace(obj.hello());
}

アクセス制限

プロパティの宣言に private を指定すると、他のクラスからは参照できなくなります。また、public を指定した場合は、どのクラスからも参照できます。

protected を指定したプロパティは、継承したクラスからは参照ができます。デフォルトである internal の場合は、同じパッケージ内のクラスであれば参照できます。

少し注意が必要なのは、protected の場合、他のパッケージのクラスであってもサブクラスであれば参照可能なことです。逆に、protected のプロパティは同じパッケージ内のクラスでもサブクラスでなければ参照できません。

また、internal のプロパティは同じパッケージ内のサブクラスのみにしか継承されません。

ちょっと例を見てみましょう。まず、foo というパッケージに FooBase クラスを定義します。クラス内には protected と internal の2種類の関数を定義します。

package foo
{
  public class FooBase
  {
    protected function protectedFunc():void {
    }
    internal function internalFunc():void {
    }
  }
}

次に、bar というパッケージに上記の FooBase のサブクラスを定義します。関数をオーバーライドするため override キーワードを使っています。

package bar
{
  import foo.FooBase;
  
  public class BarSub extends FooBase
  {
    // 親クラスの定義をオーバーライド
    override protected function protectedFunc():void {
    }
    // こっちはオーバーライドできないはず
    override internal function internalFunc():void {
    }
  }
}

これをコンパイルしてみると、protectedFunc() は通りますが、internalFunc() の方はそんな関数は見つからないといわれてしまいます。

また、サブクラス内のコードで、直接親クラスの protected プロパティを参照することはできません。

public class BarSub extends FooBase
{
  public function callProtected():void {
    var foo:FooBase = new FooBase();
    // protected のメソッドを呼び出してみる
    foo.protectedFunc();
  }
}

上記の例の場合、callProtected() が呼ばれるとエラーが発生します。

Posted by ackie at 06:10 PM | Comments (0)

June 01, 2006

NaN と Infinity

NaN と Infinity

Number 型は浮動小数点数以外に3つの値を持ちます。

最初の2つは Infinity と -Infinity です。Infinity は正の無限大の意味で Number.POSITIVE_INFINITY として定義されています。Number.MAX_VALUE の範囲を超えた値はこれになります。同様に-Infinity は負の無限大の意味で Number.NEGATIVE_INFINITY として定義されています。こっちは -Number.MAX_VALUE よりも小さい値ということになります。

3つ目の値は NaN で、Number ではない値という値です。Number.NaN でも使えます。

これら3つの値は、それぞれ以下のような場合に使われます。

trace(1 / 0); // Infinity が出力される
trace(-1 / 0); // -Infinity が出力される
trace(0 / 0); // NaN が出力される
trace(Math.sqrt(-1)); // NaN が出力される

最後の例では負の数の平方根を求めようとしているため結果が NaN になっています。(解が実数でないため)

ところで、NaN は条件文で比較すると結果が必ず false になります。

trace(1/0 == Infinity); // true が出力される
trace(0/0 == NaN); // false が出力される
trace(NaN == NaN); // false が出力される

そのために isNaN() というグローバル関数が用意されています。NaN かどうかを調べる際にはこれを使います。

trace(isNaN(0/0)); // true が出力される

同様に Infinity や -Infinity を調べるための isFinite() 関数もあります。

trace(isFinite(1/1)); // true が出力される
trace(isFinite(0/1)); // false が出力される
trace(Infinity == Infinity); // true が出力される
trace(Infinity == -Infinity); // false が出力される

Infinity と -Infinity はちゃんと区別されます。

Posted by ackie at 05:50 PM | Comments (0)

May 31, 2006

Number

Number (livedocs@lab) は整数や浮動小数点数を表すことのできる型です。Number のオブジェクトは IEEE-754 に定義される 64-bit の倍精度のフォーマットで約 4.94e-324 から 1.80e+308 の値を正負それぞれの領域で表すことができます。このため int や uint ではカバーできない数値も扱うことができます。

正確な最大値/最小値は以下のとおり定義されています。

Number.MAX_VALUE // 正の数の最大値
Number.MIN_VALUE // 正の数の最小値

Number オブジェクトは toString() メソッドを使って文字列に変換できます。Number クラスでも引数に基数を指定することができます。が、指定した場合は小数部が無視されます。

var myNum:Number = 9.5;
trace(myNum.toString()); // 9.5 が出力される
trace(myNum.toString(2)); // 1001 が出力される
trace(myNum.toString(16)); // 9 が出力される

toFixed() メソッド

toFixed() メソッドは Number オブジェクトの持つ値を小数点数で表現した文字列として返します。引数には小数点以下の有効な桁数を指定します。指定できる範囲は 0 から 20 です。

var num1:Number = 7.31843;
trace(num1.toFixed(2)); // 7.32 が出力される
var num2:Number = 4;
trace(num2.toFixed(2)); // 4.00 が出力される

指定された桁数分の小数部が無い値には必要な数だけ 0 が追加されます。

toExponential() メソッド

toExponential() メソッドは Number オブジェクトの持つ値を指数形式で表した文字列として返します。toFixed() と同じように引数には小数点以下の有効な桁数を指定します。指定できる範囲はこっちも 0 から 20 です。

var num1:Number = 731843;
trace(num1.toExponential(2)); // 7.31e+5 が出力される
var num2:Number = 4;
trace(num2.toExponential(2)); // 4.00 が出力される
var num1:Number = 0.731843;
trace(num1.toExponential(2)); // 7.31e-1 が出力される

0 から 10 の間の数値には e+0 とかがついたりはしないようです。

toPrecision() メソッド

toPrecision() メソッドは Number オブジェクトの持つ値を指定した有効桁数の文字列に変換します。戻り値は小数の場合も指数形式の場合もあります。引数の範囲は 1 から 21 です。

var num1:Number = 73.1843;
trace(num1.toPrecision(3)); // 73.1 が出力される
var num2:Number = 4000;
trace(num2.toPrecision(3)); // 4.00e+3 が出力される

Number() 関数

Number() (livedocs@lab)はプリミティブ型の値を浮動小数点数に変換するグローバル関数です。

trace(Number("")); // 0 が出力される
trace(Number(" 2.4")); // 2.4 が出力される
trace(Number("2.a")); // NaN が出力される

int() や uint() と違い数字で無い文字を含む文字列は NaN になります。

Posted by ackie at 06:23 PM | Comments (0)

May 30, 2006

int と uint

int 型と uint 型

int (livedocs@lab) と uint (livedocs@lab) はそれぞれ 32 ビットの符号付/符号無し整数を表す型です。

表現できる範囲は int が -2,147,483,648 から 2,147,483,647、uint が 0 から 4,294,967,295 です。この範囲で足りない場合には Number を使うことになります。int/uint を使用したほうが Number よりも演算速度は速いため int/uint で用が足りる場合はこちらを選ぶのがよさそうです。

さて、int/uint それぞれの最大値と最小値は以下のように定義されています。

int.MAX_VALUE // int の最大値
int.MIN_VALUE // int の最小値
uint.MAX_VALUE // uint の最大値
uint.MIN_VALUE // uint の最小値

この記述の仕方からも推測できるように int/uint ともに小文字で始まっていますがクラスです。そのため以下のような記述も可能です。

var myInt:int = 1234;
trace(myInt.toString()); // 1234 が出力される
// さすがに以下の記述はむり
// trace(1234.toString());  コンパイルエラー

toString() メソッドには引数に文字列変換する際の基数を指定することもできます。基数の範囲は 2 から 32 です。

var myUint:uint = 9;
trace(myUint.toString(2)); // 1001 が出力される
trace(myUint.toString(8)); // 11 が出力される

コンストラクタを使用てオブジェクトを生成したり、オブジェクトからプリミティブ値(ちょっと定義が変ですが)を取り出したりすることもできます。

var num:int = new int(2);
trace(num.valueOf()); // 2 が出力される

RGBA 値(RGB とアルファ値)を指定する場合には uint を使う必要があります。AS2 に慣れた方はご注意を。

int() 関数 / uint() 関数

int() (livedocs@lab) と uint() (livedocs@lab) はプリミティブ型(Boolean、null、Number、String、undefined)の値を符号付または符号無しの整数に変換するグローバル関数です。

trace(int(1.2)); // 1 が出力される
trace(int(true)); // 1 が出力される
trace(int(false)); // 0 が出力される
trace(int("")); // 0 が出力される
trace(int(" 2")); // 2 が出力される
trace(int("2a")); // 0 が出力される

数字以外の文字を含む文字列は 0 になります。

parseInt() 関数

上記の int() を使っても文字列を int に変換できましたが、parseInt() 関数 (livedocs@lab) を使うと変換時の基数を指定することができます。基数は省略可能です。

trace(parseInt("100")); // 100 が出力される
trace(parseInt("100", 16)); // 256 が出力される
trace(parseInt("100X", 16)); // 256 が出力される

parseInt() は引数の文字列が数字で始まっていれば、変換できるところまで変換した値を返します。

0x で始まる数値は16進数として扱われます。AS2 と違い 0 で始まる数値は8進数として扱われません。

trace(parseInt("0x100")); // 256 が出力される
trace(parseInt("0100")); // 100 が出力される

parseFloat() 関数

浮動小数点数が必要な場合は parseFloat() 関数 (livedocs@lab) を使用します。

trace(parseFloat("100.1")); // 100.1 が出力される

こちらは基数を指定したり 0x で始まる文字列を使うことはできません。

Posted by ackie at 08:20 PM | Comments (0)

May 15, 2006

dynamic クラス

Dreamweaver 8.0.2 アップデータが公開されました。IE のアクティブコンテンツ関連の変更に対応します。ダウンロードはこちらです。(Dreamweaver 8.0.2 アップデータ

さて、遅ればせながら、前の記事のフォローということで。

ダイナミック(dynamic)クラス

AS3 のクラスはコンパイル時に属性が決定されるのが基本ですが、ダイナミッククラスとして宣言すると実行時に属性や関数を定義することができます。

ダイナミッククラスにするにはクラス定義に dynamic 属性を付けます。

public dynamic class FooDynamic {
  private var privProp:String = "private property";
  public var pubProp:String = "public property";
}

上記の FooDynamic のインスタンスを作成すると属性や関数定義を追加することができます。例えば、以下の例では属性と関数を追加しています。

var foo:FooDynamic = new FooDynamic();
foo.dynamicProp = "foo";
foo.dynamicFunc = function():String { return "foo"; };

ダイナミッククラスは動的に属性が追加できるため、コンパイル時に存在しない属性への参照が見つかってもエラーにはなりません。また、実行時でも、存在しない属性への参照は undefined の値となりリファレンスエラーにはなりません。

とはいえ、実行時に存在しない関数を呼び出そうとするとさすがにリファレンスエラーになります。

var foo:FooDynamic = new FooDynamic();
trace(foo.nonProp); // undefined が出力される
foo.nonFunc(); // リファレンスエラーになる

ところで、動的に追加された関数からは private な属性や関数にアクセスすることができません。public な属性や関数へのアクセスは可能ですが、明示的に this またはオブジェクトを指定することが必要です。

var foo:FooDynamic = new FooDynamic();
foo.traceProperties = function():void {
  trace(this.privProp); // undefined が出力される(存在しない属性として扱われる)
  trace(this.pubProp); // public property が出力される
}
foo.traceProperties();

上の例は FooDynamic クラスに traceProperties() メソッドを追加して private/public それぞれの属性を参照しています。

ちなみに、追加する際に private 宣言されている属性や関数と同じ名前を使ってもエラーにはなりません。別の定義として扱われるためです。

Posted by ackie at 09:22 PM | Comments (0)

May 12, 2006

* (アスタリスク)と void と Null

AS3 のオブジェクトは基本的には Object 型(またはそのサブクラス)で表されますが、例外となる型が3つあります。今回はその3つの紹介です。

* (アスタリスク)

* は「型指定なし」という型情報を指定するときに使います。つまり型の指定を省略したときと同じになります。

public var foo:*; // 型指定なし
public var bar;  // こちらも * と同じ扱い

だとすると * なんて付けない方が楽そうですが、何も無いと、こんどは付け忘れたのか本当に型指定を省略したかったのかが分からなくなってしまいます。ですので、きっちりとコードを書くのが趣味の人は * を、型なんてそもそも気にする気の無い人は何も無しを選択するのがよいでしょう。

「型指定なし」で宣言した変数には、コンパイル時に型チェックが起きません(当たり前ですが)。そのため、とりあえずエラーが出るまでは使っちゃおう、というずぼらな使い方ができます。動的言語ならではの使い方ですね。

ただし、実行時には何らかの値をもつわけですから、いずれにせよ実行時には型チェックが行われます。

また、型指定なしにすると Object 型では使用できない値、すなわち undefined を使うことができます。

void

void が持つ値は一つだけで undefined です。void は関数の戻り値を指定する際にしか使用できません。

public function foo():void {
  // return なし
}

戻り値がそもそも存在しないので値が undefined というわけです。

Null

Null が持つ値も一つだけで null です。Null はクラスと関連付けられていないため、変数等の型指定には使用できません。

null は Object 型への参照が値を持たないことを示します。初期化されていない Object 型の変数への参照は null になります。

null と比較すると undefined は値だけでなく型情報もないことを示す値であることが分かると思います。

例えば dynamic クラスで、存在しない属性を参照したときの値も undefined です(この場合も型情報のない点が共通になっています)。このように AS3 のような動的に属性の追加できる言語では null と undefined を区別できることが役立つケースもあるわけです。

Object と Null と void を合わせると AS3 の全ての値を持つことができます。

Posted by ackie at 06:42 PM | Comments (0)

February 08, 2006

this とクロージャ

今回は AS3 のthis と Java の this はよく似ているという話です。

ActionScript のようなプロトタイプベースの言語では、this の扱いが少々分かりにくくなります。 たとえば以下のような例を考えて見ましょう。

public class ClassA {
  public var myName:String = "classA";
  public function foo() {
    trace(this.myName);
  }
}
 
public class ClassB {
  public var myName:String = "classB";
  public var bar:Function;
}
 
var a:ClassA = new ClassA();
var b:ClassB = new ClassB();
b.bar = a.foo;
b.bar(); // this は a と b どっちを指す?

まず ClassA の定義を見ると foo という関数内で this が使用されています。この this はコンパイル時のスコープを基準にすれば ClassA のインスタンスを指すはずです。

一方、 サンプルの下4行では、同じ関数を ClassB のインスタンスが参照する関数として呼び出しています。従って、実行時を基準にすると this は b すなわち ClassB のインスタンスを指すようにも思われます。

呼び出し方によって this の指すものが変わってしまったらプログラムの動作を理解するのが難しくなってしまいますし、せっかくのコンパイラによる型チェックも無駄になってしまいます。しかしプロトタイプベースの言語では上のコードのような状況が可能です。

そこで、AS3 では this が常に所属するインスタンスを指すための仕組みを提供しています。

b から呼び出された場合でも関数 foo 内の this が a を指せるためには、関数 foo の定義情報とそれに加えて関数定義を評価するための環境(例えば ClassA 内の他のプロパティやメソッド等の情報)が必要になります。これをクロージャといいます。

AS3 では自クラス内で定義されているメソッドへの参照を他のインスタンスに渡す際に、自動的にクロージャが作成されます。このため、上記サンプルコード内の最終行の出力は classA になります。AS2 では classB が出力されていたところです。

メソッドと Function 型プロパティと無名関数

ところで関数本体の定義方法によって上で説明したようなクロージャが生成されないことがあります。

public class ClassC {
  public var myName:String = "classC";
  // メソッドの形式で定義
  public function mFunc() {
    trace(this.myName);
  }
  // Function 型プロパティとして定義
  public var pFunc:Function = function() {
    trace(this.myName);
  }
}

上記サンプル内では2種類の異なる方法で関数 mFunc と pFunc が定義されています。それぞれの関数を他のインスタンスから呼び出すと結果は以下のように異なります。

var b:ClassB = new ClassB();
var c:ClassC = new ClassC();
b.bar = c.mFunc;
b.bar();  // classC が出力される
b.bar = c.pFunc;
b.bar();  // classB が出力される

pFunc の this は b を指していますね。このことから、たとえクラス定義内での記述でも Function 型のプロパティにアサインされている無名関数内の this に対するクロージャは作成されないことが分かります。

さらに、pFunc に対してはコンパイル時の this に対する評価も行われません。なので、インスタンスのメソッドとして関数を定義する際は Function 型のプロパティとして定義するのを第一の選択にするのは考えたほうがよさそうです。

Function 型のプロパティを使用する必要があるのは、実行時に関数オブジェクトを代入する場合です。メソッドの形式で定義した場合、このような目的に使うことはできません。

var c:ClassC = new ClassC();
var b:ClassB = new ClassB();
c.mFunc = b.bar;  // コンパイルエラー
c.pFunc = b.bar;  // こちらは OK

無名関数とクロージャ

さて、AS3 ではメソッドと無名関数の扱いが明確に区別されているらしいことが分かりました。メソッドは静的なもの、無名関数は動的なものといったところでしょうか。無名関数は、特定のインスタンス(すなわち固定された this) とは関係ない使い方をするものと割り切ってよさそうです(AS2 ではこっちが基本でしたが)。実際、this を使用しなければクロージャが使用できます。

public class ClassD {
  public var myName:String = "classD";
  public var pFunc:Function = function() {
    trace(myName);
  }
}
 
var d:ClassD = new ClassD();
var b:ClassB = new ClassB();
b.bar = d.pFunc;
b.bar();  // classD が出力される

上記のように ClassD 内の pFunc にアサインされている無名関数から this を使わずに直接 myName を参照すると pFunc の定義が ClassD 内の値とカプセル化されます。この機能は、特定の状態のスナップショットが欲しい場合にはとても便利です。

Posted by ackie at 06:33 PM | Comments (4) | TrackBack

February 06, 2006

prototype 修飾子

Flex2 のベータ1からインスタンス変数の宣言に prototype 修飾子を使用することができるようになりました。例えば、以下の例では pProp という変数に prototype 修飾子が定義されています。

public class ProtoSample {
  public prototype var pProp:int = 10;
}

prototype 属性の変数は通常のインスタンス変数と同様に使用することができます。

var p1:ProtoSample = new ProtoSample();
trace(p1.pProp);  // 10 が出力される
p1.pProp = 20;
trace(p1.pProp);  // 20 が出力される

一方、クラスオブジェクトの持つ prototype オブジェクトから prototype 属性の変数の値を変更すると、そのクラスのインスタンスが複数存在する場合にはそれらにまとめて変更が反映されます。

var p1:ProtoSample = new ProtoSample();
var p2:ProtoSample = new ProtoSample();
trace(p1.pProp);  // 10 が出力される
trace(p2.pProp);  // 10 が出力される
// クラスの prototype オブジェクトから値を変更
ProtoSample.prototype.pProp = 20;
trace(p1.pProp);  // 20 が出力される
trace(p2.pProp);  // 20 が出力される

ただし、一旦直接値を変更したインスタンスに対しては変更が反映されません。

var p1:ProtoSample = new ProtoSample();
var p2:ProtoSample = new ProtoSample();
// p2 の pProp の値を直接変更
p2.pProp = 30;
// prototype オブジェクトから値を変更
ProtoSample.prototype.pProp = 20;
trace(p1.pProp);  // 20 が出力される
trace(p2.pProp);  // 30 が出力される

prototype 修飾子と関数

prototype 修飾子を使用できるのはインスタンス変数のみです。従ってメソッドの宣言に対して prototype 修飾子を使用することはできませんが、(代わりに?) Function 型のインスタンス変数には使うことができます。下の例では、doIt という Function 型のプロパティに prototype 属性を定義しています。

public class ProtoSample {
  public prototype var doIt:Function = 
    function() { trace("foo"); }
}

このように関数を prototype 属性付きで宣言しておくと、インスタンスの振る舞いをまとめて変えることができるようになります。

var p1:ProtoSample = new ProtoSample();
var p2:ProtoSample = new ProtoSample();
p1.doIt();  // foo が出力される
p2.doIt();  // foo が出力される
// prototype オブジェクト経由で別の関数を設定
ProtoSample.prototype.doIt = bar;
p1.doIt();  // bar が出力される
p2.doIt();  // bar が出力される
 
// 代入される関数の定義
function bar() {
  trace("bar");
}

prototype オブジェクトの簡単な補足

prototype オブジェクトを使用すると動的にプロパティを追加定義することができます(注:既存定義の上書き変更はできません)。追加されたプロパティは、prototype 修飾子付きで定義されたプロパティと同じように扱えます。

var p1:ProtoSample = new ProtoSample();
var p2:ProtoSample = new ProtoSample();
ProtoSample.prototype.pProp2 = 100;
ProtoSample.prototype.doIt2 = bar;
trace(p1.pProp2);  // 100 が出力される
trace(p2.pProp2);  // 100 が出力される
p1.doIt2();        // bar が出力される
p2.doIt2();        // bar が出力される
 
function bar() {
  trace("bar");
}

上のコードをコンパイルするには、コンパイルオプションに strict を使用しない、またはプロパティを追加するクラスを dynamic クラスとして宣言する必要があります。いずれにせよコンパイル時の型チェックがあまくなるので、可能な場合はクラス定義時に prototype 修飾子付きのプロパティとして定義しておいたほうが分かりやすいと思います。

Posted by ackie at 06:33 PM | Comments (0)

February 02, 2006

Flex2 Beta1 での変更点

Cairngorm 2.0 アルファも公開されました。(こちらです

さて、Flex2 のベータ1では Player API や言語レベルでの変更が行われています。このため、アルファ版で作成したコードは一部修正の必要があるケースがあります。今回は、変更された箇所の中から今までの記事に関連する項目をお伝えします。

* (no type) の導入

新しく、どの型でもないことを示す * が使用できるようになりました。変数や関数の戻り値がどんな値でも持てることを指定する際に使います。* は型ではありませんが、他の型と同じように使えます。

private var foo:*;
public function bar(arg:*):* {
  ...
}

ところで、上記のサンプルは型指定を何もしない場合と同じ意味になります。アルファ版では型指定をしないと Object 型の指定と同様に扱われていました。

private var foo;
public function bar(arg) {
  ...
}

また、a[i] のような配列の要素も Object ではなく * として扱われます。

Object 型の変更

* の導入により Object 型はいくつかの変更を受けています。まず、未定義時の値が null になります。undefined の値を持つことができるのは * として宣言された変数のみです。

private var foo:Object;
trace(foo);  // null が出力される

また、Object型 は自動的に型変換されなくなり、他の型に代入する際は明示的に as を使うなどしてキャストをする必要があります。 一方、* 宣言された変数は自動的に他の型に変換されます。

private var foo:Object = new Object();
private var bar:String = foo;  // エラー
 
private var baz:* = new Object();
bar = baz;  // OK

その他の型に関連する変更

void はもともとクラスではないとのことから、Void が void (全部小文字)に変更されました。とはいえ、従来どおり戻り値のない関数の型指定として使用します。

public function foo(o:Object):void {
  ...
}

as は元の値そのままか null のどちらかを返します。値の変換は行わなくなりました。

アクセス修飾子

クラスを private 宣言することができなくなりました。また、ヘルパークラスが必要な場合は、パッケージブロック外に記述します。

package sample {
  import flash.events.*;
  public class SampleClass {
    ...
  }
}
 
class PrivateClass {
  ...
}

イベント関連

従来、イベントの種類は専用のクラスに定義されていましたが、イベントクラスに定義が移っています。例えば、MouseEventType.MOUSE_UP は MouseEvent.MOUSE_UP になります。

EventDispatcher クラスの addEventListener() メソッドの priority プロパティの指定が逆になりました。数値の大きいほうが先に実行される(優先度が高い)ようになります。

Posted by ackie at 06:38 PM | Comments (0)

January 25, 2006

as 演算子

as 演算子は、ある値が特定の型に属することを保障したいときに使います。例えば、以下のような使い方です。

var myArray:Array = ["foo"];
var myString:String = myArray[0] as String;

見てのとおり括弧を使用したキャストとよく似ています。

var myArray:Array = ["foo"];
var myString:String = String(myArray[0]);

両者の違いが現れるのは、対象の値が目的の型にキャストできない場合です。括弧を使用したキャストではキャストできないと実行時エラーになります。一方、as 演算子の場合はキャストできないと式の値が null になります。

この動作は目的の型のオブジェクトのみを扱う処理を書く時に便利です。例えば、

for (i = 0; i < myArray.length; i++) {
  var myObj:MyComponent = myArray[i] as MyComponent;
  if (myObj) {
    // myObj の処理を行う
  }
}

のように記述すると、配列の要素が MyComponent に属する値でない場合、myObj が null になるため if ブロック内の処理をスキップさせることができます。

Posted by ackie at 10:25 AM | Comments (0)

January 24, 2006

is 演算子

is は AS3 から新しく追加された演算子で、ある変数が特定の型に属しているか判定するときに使用します。is の左側の変数が右側の型のメンバーであれば式の値は true に、そうでなければ false になります。

var mySprite:Sprite = new Sprite();
trace (mySprite is Sprite);           // true が出力される

AS2 では instanceof 演算子が同じ目的で使用されていましたが、AS3 ではサポートされていません。代わりに is を使用するようにしましょう。instanceof 演算子を使用すると正しく判定されないケースが出てきます。例えば、下の例では Sprite のインスタンスに対して、3種類の型に対して判定を行っています。

var mySprite:Sprite = new Sprite();
trace (mySprite is Sprite);           // true が出力される
trace (mySprite is DisplayObject);    // true が出力される
trace (mySprite is IEventDispatcher); // true が出力される

Sprite は DisplayObject のサブクラスで、かつ IEventDispatcher を継承した EventDispatcher クラスのサブクラスです。従って全てのケースで true になるはずです。ところが、 is の代わりに instanceof 演算子を使用すると、下の2つは false になってしまいます。

var mySprite:Sprite = new Sprite();
trace (mySprite instanceof Sprite);           // true が出力される
trace (mySprite instanceof DisplayObject);    // false が出力される
trace (mySprite instanceof IEventDispatcher); // false が出力され

基本データ型と is

数値型や文字列の値の型判定は、型情報がないので実際の値を評価する必要があります。データ型について詳しくはまだ取り上げていませんが、とりあえず、以下に代表的と思われる例に対する値を表にしましたので参考にしてください。

値 

String

Number

int

uint

Boolean

Object

"foo"

true

false

false

false

false

true

"1"

true

false

false

false

false

true

null

false

false

false

false

false

false

undefined

false

false

false

false

false

false

true

false

false

false

false

true

true

false

false

false

false

false

true

true

0

false

true

true

true

false

true

1

false

true

true

true

false

true

-1

false

true

true

false

false

true

1.0

false

true

false

false

false

true

NaN

false

true

false

false

false

true

Posted by ackie at 03:25 PM | Comments (0)

January 13, 2006

Namespace とステートパターン

今回は名前空間を使ってステートパターンにトライしてみたいと思います。基本となるのは、名前空間とステートを一対一に対応させようという考え方です。

以前 Sprite とマウスイベントという記事で Sprite でボタンが作れることに簡単に触れていますが、今回はそれをステートパターンで実装してみます。

では、ボタンとなるクラスの定義を始めましょう。とりあえず、ボタンに必要な状態の定義から始めます。

StatePatternButton.as:
public class StatePatternButton extends Sprite{
  // ボタンの状態を名前空間として3種類定義する
  private static namespace out;   // マウスがボタンの外にある
  private static namespace over;  // マウスがボタンの上にある
  private static namespace down;  // マウスがボタンの上で押された
 
  // それぞれの状態に応じた振舞いを定義
  out function handle(e:Event):Void {
    // out の状態での振舞いを記述
  }
 
  over function handle(e:Event):Void {
    // over の状態での振舞いを記述
  }
 
  down function handle(e:Event):Void {
    // down の状態での振舞いを記述
  }
}

今回は各ステートにおける振る舞いを一つのメソッドとして実装できると仮定しています。この部分はステート毎の差異しだいで変数の宣言になったり共通のインターフェースを継承したクラスになったりすることと思います。

次にステート変更用の手段を定義します。

  // 現在のステートを保持する変数
  private var currentState:Namespace;
 
  // 新しいステートを設定する関数
  private function changeState(newState:Namespace):Void {
    currentState= newState;
  }

現在のステートを currentState という変数に保持することにします。ステートの変更には changeState 関数を使用します。

さらに、マウスイベントの種類と名前空間(ステート)の関連を定義します。

  // 連想配列の作成
  private var stateMap:Object = new Object();
 
  // イベントの種類とステートの関連付け
  private function initStateMap():Void {
  	stateMap[MouseEventType.MOUSE_OUT] = out;
  	stateMap[MouseEventType.MOUSE_OVER] = over;
  	stateMap[MouseEventType.MOUSE_UP] = over;
  	stateMap[MouseEventType.MOUSE_DOWN] = down;
  }
 
  // イベントタイプに応じたステートを返す
  private function getState(e:Event):Namespace {
    // ほんとは引数や戻り値が null だった場合の考慮も必要
    return stateMap[e.type] as Namespace;
  }

というわけでマウスイベントハンドラの定義です。呼び出される処理はイベントの種類ではなくステートに応じて呼び出されるため、イベントハンドラは一つだけです。イベントハンドラからは handleEvent という関数を呼び出します。この関数の内部でステートに応じた処理を呼び出します。

  // イベントハンドラ(種類に関係なく使用される)
  private function onMouseEvent(e:Event):Void {
    var ns:Namespace = getState(e);
    changeState(ns);
    handleEvent(e);
  }
 
  // 現ステート用の処理を呼び出す関数
  private function handleEvent(e:Event):Void {
    currentState::handle(e);
  }

あとは、コンストラクタ内で必要な初期化を行えばおしまいです。

  // 初期状態のステートを定義
  private static var initState:Namespace = out;
 
  public function StatePatternButton() {
    // ボタンとして動作するように設定
    buttonMode = true;
 
    // イベント種類とステートの対応表作成
    initStateMap();
 
    // 関連するマウスイベントにリスナ関数を登録
    addEventListener(MouseEventType.MOUSE_OUT, onMouseEvent);
    addEventListener(MouseEventType.MOUSE_OVER, onMouseEvent);
    addEventListener(MouseEventType.MOUSE_UP, onMouseEvent);
    addEventListener(MouseEventType.MOUSE_DOWN, onMouseEvent);
 
    // ステートの初期化
    changeState(initState);
    // 他に必要な初期化があれば
    // initSomethingElse();
  }
}

イベントの種類とハンドラ名を関連付ける代わりに、イベントタイプと名前空間(=ステート)を関連付けてみました。MOUSE_UP と MOUSE_OVER に同じ関数名を関連付けるよりはちょっと気持ちいい感じです。

以下のようなクラスを作って実際に動かしてみましょう。

package {
  import flash.display.Sprite;
 
  public class StatePattenTest extends Sprite {
    public function StateTest() {
      addChild(new StateButton());
    }
  }
}

というわけで名前空間の使用例を紹介してみました。名前空間はなじみがないとなんの役に立つのか想像しにくいものです。とりあえずでも名前空間の使い方について考えるきっかけになればと思います。

Posted by ackie at 05:08 PM | Comments (0)

January 12, 2006

Namespace でのアクセス制御

前回の記事で public 宣言された関数を名前空間を使って上書きできる云々を書きましたが、このことから推測できるように public も名前空間です。実際に public の別名を作ることもできます。

public static namespace myNS1 = public;

public に限らずアクセス修飾子は全て名前空間として扱われます。この辺りから、なぜ EcmaScript4 のプロポーザルでは protected の実装が必要ないと判断されたかなんとなく想像できそうですね。

オープンな名前空間には、明示的に宣言していなくても public, internal, protected, private は予め含まれています。そのために public や private 宣言された属性の参照に public:: 等をつける必要が無かったわけです。

インスタンス属性の修飾名による参照

ここで、ちょっと寄り道をして、インスタンスの属性を参照するときの名前空間の指定方法について説明します。

まず、以下のような名前空間&クラス定義があるとします。

myNS1.as:
public namespace myNS1
 
myNS2.as:
public namespace myNS2
 
SampleClass.as:
public class SampleClass {
  myNS1 var foo:int = 0;
  myNS2 var foo:int = 1;
}

この場合 SampleClass のインスタンスから foo を参照する際は名前空間を意識しなくてはなりません。属性参照時に明示的に名前空間を指定するには以下のように記述します。

var sc:SampleClass = new SampleClass();
sc.myNS1::foo = 100;

また、名前空間の指定に変数を使用することもできます。変数の型は Namespace になります。

var sc:SampleClass = new SampleClass();
var ns:Namespace = myNS1;
sc.ns::foo = 100;

名前空間によるアクセス制御の例

さて、準備もできたので、ここで名前空間を使ったアクセス制御の例を紹介します。C++ のフレンド関数もどきの機能を実現してみたいと思います。

public class WithSecret {
  private static namespace mySecretNS;
  // アクセスを許可するクラス名を設定
  private var myFriend:Object = MyFriend;
 
  public function checkFriend(target:Object):Namespace {
    if(target is myFriend) {
      return mySecretNS;
    } else {
      return null;
    }
  }
  // プライベートな名前空間に関数を定義
  myNS function secretFunction():Void { 
    trace("this is my secret");
  } 
}

クラス WithSecret には mySecretNS という名前空間に定義した関数 secretFunction があります。mySecretNS はプライベートな名前空間なので、他のクラスから直接指定することはできません。つまり secretFunction は事実上「見えない」関数になっています。

一方、特定のクラス(この例では MyFriend クラス)のインスタンスに対してのみ mySecretNS を返す関数も実装されています。受け取ったインスタンスは mySecretNS を指定できるため、以下のようにして秘密の関数を呼び出すことができます。

public class MyFriend {
  public function getSecret(obj:WithSecret) {
    var ns:Namespace = obj.checkFriend(this);
    if(ns != null) {
      obj.ns::secretFunction();
    }
  }
}

Posted by ackie at 06:55 PM | Comments (0)

January 11, 2006

オープンな名前空間

open namespaces (とりあえず本記事内では「オープンな名前空間」と訳すことにします)という名前空間のリストがあります。これは、修飾子の付かない属性の参照が行われたとき(例えば ns1::foo ではなく foo)、属性を検索するのに使用されるリストです。リスト内のどの名前空間にも該当する属性名が含まれていなければその属性は参照不可と判断されます。

ある名前空間をオープンな名前空間に宣言するには use namespace を使います。

public static namespace myNS1;
public static namespace myNS2;
 
myNS1 var foo:int = 0;
myNS2 var foo:String = "zero";
 
use namespace myNS1  // NS1 をオープンな名前空間に宣言
trace(foo)           // 0 が出力される

この例では2つの名前空間に foo という属性が定義されていますが、myNS1 がオープンな名前空間に宣言されてから foo が参照されたため myNS1::foo の方が参照されることになります。

オープンな名前空間に候補となる属性が2つ以上見つかった場合はエラーになります。例えば、上記の例で myNS2 も宣言してから foo を参照すると2つの候補が見つかるため参照の解決が不能な状態になってしまいます。このような場合は myNS2::foo のように修飾名を使用する必要があります。

オープンな名前空間は、クラス定義や関数定義などのブロック単位で有効です。

オープンな名前空間の使用例

オープンな名前空間に明示的に宣言された名前空間は public より優先されます。つまり、同名の属性が2つの名前空間にあっても、片方が public だと public 宣言された属性は無視され曖昧な参照にはなりません。これを利用して、例えばデフォルトの動作が public 関数で実装されている場合のバージョンアップを、元の関数を書き換える代わりに新しく関数を追加する形で行うことができます。

以下は具体的なサンプルです。(ところで、アルファ版ではバグのために以下のコードは動きません。public が同列に扱われてしまうためです。アルファ版で動かすには public の代わりに version1 などの名前空間を使うことになります。)

まず、以下のクラスが最初のバージョンとして作成されていたとします。

SampleClass.as:
public class SampleClass {
  // デフォルトの関数を定義
  public function myFunc() {}
}

次に、バージョンアップした関数を version2 の名前空間に同じ識別子で定義します。

version2.as:
// 新バージョン名を名前空間として定義
public namespace version2
 
SampleClass.as:
public class SampleClass {
  // デフォルトの関数を定義
  public function sampleFunc() {}
  // 新しい振舞いの関数を追加定義
  version2 function sampleFunc() {}
}

SampleClass を上記のように変更しても従来から SampleClass を使用していたプログラムには影響しません。一方、version2 の名前空間をオープンな名前空間に宣言すれば新しく追加された関数を使用することができます。

MyClass.as:
public class MyClass {
  // version2 をオープンな名前空間に宣言
  use namespace version2
 
  public function myFunc() {
    var sc:SampleClass = new SampleClass();
    // version2::sampleFunc が呼ばれる
    sc.sampleFunc();
  }
}

名前空間を使ったバージョン管理の例でした。

Posted by ackie at 04:23 PM | Comments (0)

January 10, 2006

Namespace

クラスに名前をつけることは、クラス内で定義されているプロパティをクラスの名前空間に結びつける行為と考えることができます。パッケージやインターフェースについても同様です。これらの名前空間はプログラムの構造と固く結びついていますが、namespace を使うとプログラムの構造から独立して名前空間を扱えるようになります。例えば、任意の単位でクラスの振舞いを変えたり、実行時に動的に名前空間をプロパティと結びつけて可視/不可視をコントロールすることができます。

namespace の宣言

namespace はパッケージ内とクラス内で定義することができます。定義方法は基本的に変数の定義と同様です。

SampleClass.as:
package sample {
  public class SampleClass {
    // クラス内に名前空間を定義
    public static namespace myNSinClass;
  }
}
 
myNSinPackage.as:
package sample {
  // パッケージ内に名前空間を定義
  public namespace myNSinPackage;
}

パッケージレベルで public な名前空間を宣言するときは単独のファイルに記述します。public なリソースの宣言は一ファイルに付き一つのルールが適用されるためです。(EcmaScript プロポーザルによれば、クラス内の名前空間の宣言は static にする必要があるはずですが、現バージョンではインスタンス変数としても宣言できてしまいます。これは将来のバージョンでは修正されるものと思います)

名前空間には別名を作成したり明示的に値を与えることもできます。

// 匿名の名前空間を定義
public namespace myNS1;
// myNS1 の別名を定義
public namespace myNS2 = myNS1;
// 明示的に値を指定して名前空間を定義
public namespace myNS3 = "http://www.sample.com/namespace";

修飾名の使用

属性を定義する際に属性が所属する名前空間を指定するには、以下のように記述します。

public static namespace myNS1;
public static namespace myNS2;
myNS1 var foo:int = 0;
myNS2 var foo:String = "zero";

この例では foo という名前の属性が2つ定義されていますが、それぞれ別の名前空間に属しているため異なる属性として区別されます。参照する際は以下の形式で修飾名を記述します。

trace(myNS1::foo); // 0 が出力される
trace(myNS2::foo); // zero が出力される

また、属性定義時に複数の名前空間を指定することもできます。この場合、属性の実体はあくまで一つです。属性の別名を定義したと考えると良いでしょう。

myNS1, myNS2 var foo:int = 0;
trace(myNS1::foo); // 0 が出力される
trace(myNS2::foo); // 0 が出力される

属性の宣言時、namespace を使って定義された名前空間の指定はクラスのトップレベルの属性のみに適用可能です。メソッド内のローカル変数定義等には使用できません。

public class SampleClass {
  public static namespace myNS1;
 
  myNS1 var foo:int = 0; // OK
  public function myFunction():Void {
    myNS1 var bar:int = 0; // NG
  }
}

Posted by ackie at 05:44 PM | Comments (0)

January 06, 2006

静的なプロパティ

クラスには静的なプロパティとインスタンスプロパティを定義することができます。インスタンスプロパティはその名のとおりインスタンス毎に存在するプロパティですが、静的プロパティはインスタンスと関連付けられることは無く、代わりにクラスに一つだけ存在するクラスオブジェクトと結び付けられます。

静的変数

変数を宣言する際 static をつけると静的変数の宣言になります。

public static var myStaticVar:int;

静的変数はそれを宣言しているクラスまたはそのサブクラスからは直接参照することができます。

public class BaseClass {
  public static var myStaticVar:int;
  // クラス内で宣言されている静的変数にアクセス
  public function myBaseFunc():Void {
    myStaticVar = 0;
  }
}
 
public class ChildClass extends BaseClass {
  // 親クラスの静的変数にアクセス
  public function myChildFunc():Void {
    myStaticVar = 1;
  }
}

しかし、静的変数は継承されません。従って外部のクラスからは宣言されているクラスオブジェクトを通じてのみアクセスすることができます。

trace(BaseClass.myStaticVar);  // OK
trace(ChildClass.myStaticVar); // NG

継承されないということは、サブクラスに同じ名前で異なる型の静的変数を定義するのも可能ということです(クラスを使う側はちょっと混乱しそうですが)。また、静的変数と同名のインスタンス変数を定義することもできます。ますます混乱しそうなので、この辺りの説明は省略することにします。

静的関数

関数宣言時に static をつけると静的関数の宣言になります。

public static function myStaticFunction():Void;

もう何回も書いているのでそろそろしつこくなってきた感もありますが、関数もオブジェクトですから上記のセクションでの説明がそのまま当てはまります。補足しておきたい点は、

  1. 静的関数内からは静的に宣言されたプロパティしか参照することができません。
  2. 静的関数は継承されないので、サブクラスで同名の関数を宣言する場合でも override はつけません。シグニチャが一致する必要もありません。(むしろ関数である必要もありません)

Posted by ackie at 06:38 PM | Comments (0)

January 05, 2006

interface

AS2 では、パフォーマンスの観点からインターフェースの使用は控えるべきと言われていました。が、AS3 からはようやくその恩恵に与ることができそうです。実際、Flex2 のフレームワークでもインターフェースが多用されています。

インターフェースの定義

インターフェースは公開するメソッドの集合としての型を定義するのに使います。例えば、こんな感じです。

public interface ISample {
  function sampleFunc(arg:String = "hey"):Void; 
}

この例では、「sampleFunc という名前で hey という初期値の String 型の引数を一つ持ち返り値の型が Void である関数」を持つ型を定義し、それに ISample という名前をつけています。クラス定義と異なり、メソッドの定義だけが記述されています。メソッド本体はインターフェースを直接継承するクラス毎にオーバーライドして実装します。

public class SampleClass implements ISample {
  public function sampleFunc(arg:String = "hey"):Void {
    // メソッド本体を記述
  }   
}

インターフェースの継承は implements キーワードで宣言します。 このときインターフェースに定義されている全てのメソッドをオーバーライドする必要があります。オーバーライドするメソッドに public を宣言して、それ以外はインターフェース内の定義とまったく同じにします。

なお、静的メンバはインターフェースには定義できません。

インターフェースとプロパティ

以前に get と set についての記事で、アクセッサを定義すると関数名をプロパティのように扱えることを書きました。get も set も関数ですからインターフェースに定義することができます。つまり、実質的にインターフェースにもプロパティを定義できることになります。

ちょっと例を見てみましょう。

// インターフェースに get/set を定義
public interface IGetSet {
  function get myProp():String;
  function set myProp(val:String):Void;
}
 
// 上記のインターフェースを実装
public class SampleClass implements IGetSet {
  private var privateVar:String;
 
  public function get myProp():String {
    return privateVar;
  }
  public function set myProp(val:String):Void {
    privateVar = val;
  }
}

IGetSet を正しく実装したクラスでは以下のような操作ができるようになります。

var sc:SampleClass = new SampleClass():
sc.myProp = "hello";
trace(sc.myProp);   // hello が出力される

Posted by ackie at 05:25 PM | Comments (0)

December 29, 2005

Function/関数/メソッド

今回は、関数に関連するタームの整理です。

  1. Function
    ActionScript では関数もプロパティの一種ですが、関数として認識されるオブジェクトの型の名前が Function です。Function 型のオブジェクトは引数を渡して処理を呼び出すことができます。
  2. 関数
    Function 型のオブジェクトの定義やそのインスタンス全般を指します。
  3. メソッド
    クラス定義の一部としてある関数定義や実行時にオブジェクトから呼び出し可能な関数オブジェクトを指します。別の見方をすると、クラス定義の外で定義される関数やオブジェクトから呼び出すことのできない状態の関数が有るということになります。

それでは、皆様良いお年を。

Posted by ackie at 01:14 PM | Comments (0)

December 28, 2005

コンストラクタ (と Singleton)

コンストラクタはクラス名と同じ名前を持つ関数で、インスタンスを初期化する際に呼び出されます。コンストラクタは初期化時の定型処理を記述するのに使われますが、特に定義されていなくても文法上の問題は有りません。

class SampleClass {
  // コンストラクタ定義
  public function SampleClass() {
    // 初期化処理の記述
  }
}

ActionScript では基本的に関数のオーバーロードができないのに倣って、コンストラクタも一つしか定義することができません。コンストラクタを定義する際は、とりあえず引数無しにするか、引数を持つ場合はデフォルト値を指定するというケースが多くなりそうです。

もう一つのコンストラクタの特徴は、返り値の型を指定しないことです。また、AS3 ではコンストラクタのアクセス修飾子として public のみが指定できます。

Singleton

コンストラクタのアクセス修飾子が public のみだと、いくつかのデザインパターンの使用が難しくなります。そこで、シングルトンパターンの実装例を紹介しておきます。(見て分かるようにあまりきれいといえるものではありません)

package pattern {
 
  public class Singleton {
    private static var instance:Singleton;
 
    // コンストラクタ定義
    // 引数が PrivateClass のインスタンス
    public function Singleton(x:PrivateClass) {
    }
 
    // Singleton クラスのインスタンスを返す関数
    public static function getInstance():Singleton {
      if(Singleton.instance == null) {
        Singleton.instance = new Singleton(new PrivateClass());
      }
      return Singleton.instance;
    }
  }
  /* 以下コメントアウト (2006/03/09 追加)
  // プライベートクラスの定義
  private class PrivateClass {
  }
  */
}
// プライベートクラスの定義 (2006/03/09 追加)
class PrivateClass {
}

上記の Singleton クラスはコンストラクタの実行に private クラスへのアクセスが要るため、他のクラスからは引数を正しく指定できません。とりあえず new Singleton() を呼んだとしても、引数の数が違うためコンパイルエラーが出ます。以下は使用例です。

package {
  import pattern.Singleton;
 
  public class SingletonTest {
    public function SingletonTest() {
      var singleton:Singleton = Singleton.getInstance();
    }
  }
}

Posted by ackie at 07:54 PM | Comments (6)

December 27, 2005

get と set

get と set は特別な関数を定義するために予約されている名前です。オブジェクトのカプセル化のため属性に直接アクセスする代わりに関数を定義したい、という人のための機能です。 他の言語では getXXX() とか setXXX() とかいう関数を定義したりすると思いますが、ActionScript では少し流儀が異なります。

下が、get 関数と set 関数それぞれを定義しているサンプルです。

class AccessFuncs {
  // private のプロパティを宣言
  private var privateProp:String;
  // get 関数を定義 
  public function get publicProp():String {
    return privateProp;
  }
  // set 関数を定義
  public function set publicProp(val:String):Void {
    privateProp = val;
  }
}

サンプルコード内で public な get および set の2つの関数を定義する際、get/set それぞれの後にスペースをはさんで、ある名前 (ここでは publicProp) を指定しています。 そして、get 関数では private なプロパティである privateProp の値を返し、set 関数では逆に渡された値を privateProp に代入しています。

さて、このような宣言をすると、以下のような使い方ができるようになります。

var myAccess:AccessFuncs = new AccessFuncs():
myAccess.publicProp = "hello";
var msg = myAccess.publicProp;
trace(msg);   // hello が出力される

上記のコード内で publicProp が属性のように扱われているのが分かると思います。実際には、2行目で "hello" を publicProp に代入する際には publicProp に対する set 関数が呼ばれ privateProp に値が格納されています。そして、3行目で publicProp を参照する際には同様に get 関数が呼ばれ privateProp の値が返されています。しかし、クラスの利用者は publicProp という属性があるかのようにコードを書くことができます。

これだと、とりあえず属性を定義しておいて後からアクセッサを定義すればいいや、というずぼらなこともできそうですが、(そんな人はいませんよね...)アクセッサとして使われる名前と実体である属性の名前は別にする必要があるので、結局クラス内部のコードの書き直しが発生しそうです。

get と set はどちらかだけでも構いません。その場合、参照専用または 更新専用のプロパティとして属性を公開することとなります。

Posted by ackie at 10:05 PM | Comments (0)

December 26, 2005

関数の引数

引数のデフォルト値

AS3 では関数宣言時に引数のデフォルト値を指定することができます。デフォルト値の無い引数と有る引数が存在する場合には、デフォルト値の無い引数を引数の列の最初にまとめます。デフォルト値として使用できる値は、コンパイル時に既知の定数のみです。

function sampFunc(x:int, y:int, z:int = 1) {
  // 呼び出し時に指定がなければ z の値は 1 になる
}
sampFunc(1, 0);

デフォルト値のある引数は呼び出し時に省略可能になります。通常は意識する必要のない引数に使用するとよいでしょう。

argument オブジェクト

デフォルト値の指定されていない引数を省略して関数を呼び出そうとするとコンパイラーがエラーを返します。また、宣言されているより多くの引数を指定して呼び出す場合もコンパイルエラーになります。ですが、どちらの場合も実行は許されます。(型の不一致は当然実行時エラーになりますが)

その際、引数にアクセスするためには arguments というオブジェクトを使用することができます。これは宣言無しで関数内で参照できるオブジェクトで、渡された引数の配列と呼び出し元のオブジェクト (arguments.callee) をプロパティとして持っています。ただし、引数名に arguments が使用されている関数内では arguments はその引数を指し、arguments オブジェクトは使用できなくなります。

arguments オブジェクトは以下のような再帰関数の実現に便利です。

var factirial:Function = function(x:int):int {
  if (x == 0) {
    return 1;
  } else {
    return (x * arguments.callee(x - 1));
  }
}

ところで、引数の数が不定の場合はコンパイルエラーを無視して関数を呼び出さなければならない、というのもちょっと気持ち悪いものです。そこで、AS3 には、arguments に代わる次のような機能が用意されています。

「...」 引数

AS3 では最後の引数として 「... + 任意の名前」 を指定すると、定義された数以上の引数を渡すことができるようになります。明示的に名前の指定されていない分の引数は、「任意の名前」で指定された名前を持つ配列として使用できます。下の例では、rest という配列に 2 と 3 が格納されます。

function sampFunc(x:int, ... rest) {
  if (rest.length > 0) {
    trace(rest[0];
  }
}
// 2 が出力される
sampFunc(1, 2, 3);

arguments オブジェクトと違って、この方法であればコンパイラーも文句を言いません。関数定義からも引数の数が不定であることが分かります。事前に引数の数が特定できない場合は 「...」 の使用をお勧めします。なお、「...」 が指定されていると arguments オブジェクトは使用できません。

Posted by ackie at 08:35 PM | Comments (0)

December 22, 2005

関数の定義

ActionScript のオブジェクトは Java や C++ と少し違う点があります。それは、オブジェクトがプロパティの集合であるという点です。プロパティには関数も含まれます。そのため実行時に関数を代入することも可能です。また、関数も他のオブジェクトと同様に、固有のプロパティをやメソッドを持つことができます。

関数の定義

AS3 でのクラス定義の例を下に示します。ここでは、関数は function ステートメントを使用して定義されています。

class SampleClass {
  var sampleVar:String;
  function sampleFunction():Object {
  }
}

さて、関数もプロパティなのですから、以下のような記述が可能です。

class SampleClass {
  var sampleVar:String;
  var sampleFunction:Function;
}

上の例では、Function 型のプロパティ sampleFunction を宣言しています。冒頭に書いたように、このような宣言をしておくと実行時に Function オブジェクト(正しくは、Function オブジェクトへのリファレンス)を sampleFunction プロパティに代入することができます。

そのため、function 式を使って、以下のような形で関数定義をすることも可能です。(注:この function キーワードはエクスプレッションで、関数定義をする function ステートメントとは別のものです。)

class SampleClass {
  var sampleVar:String;
  var sampleFunction:Function = function ():Object {
  }
}

一番最初に紹介した形式での関数定義の方が見やすいと思いますし、クラス定義にこの書き方はあまりお勧めしません。

関数のオーバーライド

クラスを拡張して別のクラスを定義するとき関数を上書きして定義する場合には override を付けます。override は Function 型のプロパティがメソッドのように扱われること、すなわち上書きされる関数とシグニチャが同一になることを保障します。

class ExtendedClass extends SampleClass {
  var sampleVar:String;
  override function sampleFunction():Object {
  }
}

継承については、また別途詳しく説明します。

Posted by ackie at 07:00 PM | Comments (0)

December 15, 2005

package

AS2 でも package の機能はありましたが、その役割は基本的にクラス名の重複を防ぐことでした。AS3 の package はアクセススコープの単位にもなります。

package とアクセス修飾子

AS3 では以下のようにして定義するクラスが含まれるパッケージ名を宣言します。以下の例では、sample パッケージ内に SampleClass クラスを定義しています。このときクラスの完全限定名は sample.SampleClass になります。

package sample {
  import othersample.OtherClass;
  class SampleClass {
    function sampleFunction() {}
  }
}

他のパッケージに定義されたクラスを使用するには、import を使用します。上の例のように明示的にクラス名を指定する代わりに * を使うこともできますが、一つ一つ指定した方が性能的には良い結果になるようです。

さて、AS3 では以下の4つのアクセス修飾子が使えます。AS2 では private が AS3 の protected に相当するものでした。注意しましょう。(ちなみに protected は ECMAScript 標準ではサポートされていません。本来、アクセススコープの管理は namespace を使うという考え方のようです。)

public     // どのクラスからでもアクセス可
internal   // 同一パッケージ内のクラスからのみアクセス可
protected  // 自身とサブクラスからのみアクセス可
private    // 外部からのアクセスなし

最初に示したサンプルコードの様に特にアクセス修飾子が指定されていない場合、AS3 では internal が適用されます。その他の修飾子を指定したい場合には、クラス名や関数名の前に public 等をつけます。

as ファイル

AS3 のクラスを定義したファイル名はクラス名に .as の拡張子をつけたものにします。また、パッケージを指定した場合は、パッケージ名と同じディレクトリ階層下にファイルを置きます。この記事の最初のサンプルは sample\SampleClass.as の内容というわけです。これは Java とおんなじですね。

一つのファイル内に複数のクラスを定義することもできます。ただし、public なリソースの定義はファイル内に一つだけ可能で、他のクラス定義は同一ファイル内のみでの参照になります。Java と少し違うのは、public 以外の定義に private をつけることです。また、Java のメンバクラスに相当する機能はありません。

package sample {
  import flash.events.*;
  public class SampleClass {
    public function SampleClass() {
      // コンストラクターを定義
      var innerClass:InnerClass = new InnerClass();
    }
  }
  private class InnerClass {
    // プライベートクラスの定義
  }
}

Posted by ackie at 07:28 PM | Comments (0)

November 28, 2005

コンパイラのモード設定

ActionScript3 は動的言語ですので、実行時につじつまが合えば型の指定は省いても構わない、という記述方法が可能です。とりあえず単純な例としては、

var forDebug; // 型指定なしの宣言
forDebug = 0; // 数値を代入 trace(forDebug);
forDebug = "1"; // 文字列を代入 trace(forDebug);

のような使い方です。(ちなみに、型宣言をしないと Object 型の変数になります) これはこれで便利な機能でめんどくさいときなどは結構重宝しているのですが、やはりきちんと型指定をしておきたいケースも多々あったりします。その際は、例えば、

var forDebug:String; // String 型を指定
forDebug = 0; // 数値を代入 trace(forDebug);
forDebug = "1"; // 文字列を代入 trace(forDebug);

のように、型指定の変数を宣言することも可能です。ただ、上のサンプルでは型宣言を追加したものの、コードの2行目で数値を代入しているため、そこで型の不一致が起き正しく実行できないはずです。ためしに実行してみると、”Type Coersion failed: cannot convert 0 to String” というランタイムエラーが表示されます。当たり前のようにも見えながら、このランタイムエラーは ActionScript3 からの待ちに待った新機能です。今までのようにどこかで静かに処理が失敗しているという状況からはようやく開放され、開発生産性の向上に大きく貢献してくれそうです。(ランタイムエラーが表示されるのはデバッグプレーヤのみで、一般のユーザにはこのようなメッセージは表示されません。)

しかし、どうせ実行時にエラーになると分かっている件については、コンパイル時に検出してくれたほうが簡単に対応できます。そのため、コンパイラは strict モードが選択できるようになっています。コンパイル時、引数に -strict を付けることでコンパイラは型チェックを行うようになります。

FlexBuilder2 では strict モードがデフォルトの設定になっています。これを変更するには、画面左のナビゲータパネル内で対象のプロジェクト名を右クリックし、表示されたメニューから Properties を選んでポップアップウインドウを表示します。ウインドウ内の左のメニューから ActionScript Compiler を選ぶと下の画面になります。

compiler options

このパネルで、Enable compile-time type checking (-strict) のチェックを外してOKを押すと、コンパイル時に型チェックが行われなくなります。

また Enable warnings (-coach) は AS2 からのマイグレーションに関連する警告(シンタックスの変更など)を表示させるためのオプションです。分かって使っている人にはちょっとうざいメッセージが出力されますが、とりあえずチェックをかけたいときにはなかなか便利です。

なお、これらの設定はプロジェクト単位で有効です。

Posted by ackie at 08:34 PM | Comments (0)