連載
» 2010年07月27日 00時00分 公開

あなたの知らない、4つのマニアックなJava文法【改訂版】Eclipseではじめるプログラミング(17)(2/3 ページ)

[小山博史,株式会社ガリレオ]

エンクロージングインスタンスへのアクセス

 前回作成したsample16.App4クラスをコピーして、sample17.App4クラスを作成しましょう。Eclipse上で、前回の要領でsample16.App4クラスをコピーして、「sample17」パッケージへ貼り付けるだけです。

 sample17.App4クラスへ、残り時間を表示するRemainViewクラスを追加してみましょう。RemainViewクラスはsample17.App4.Observerインターフェイスを実装して、sample17.App4クラスのメンバ・クラスとして追加します。以下は、具体的なコードです。

    class RemainView implements Observer {
        @Override
        public void update() {
            System.out.println("残り時間:" + count + "[sec]");
        }
    }
sample17.App4.RemainView

 変数countが突然出てきていますが、これはRemainViewクラスにはありませんし、Observerインターフェイスにもありませんが、どこで宣言されているのでしょうか?

 答えは、sample17.App4クラス内にあります。staticのネストしたクラスから生成されるインスタンスと違って、内部クラスのインスタンスは、エンクロージングインスタンスと関連付くため、sample17.App4クラス内に宣言されているcountフィールドにアクセスできます。

 RemainViewクラスはsample17.App4クラスのメンバですから、このフィールドへアクセスできるのです。このsample17.App4のcountフィールドに残り時間が保持されているので、それを表示しています。

 次に、sample17.App4クラスの内部クラスであるsample17.App4.RemainViewクラスのインスタンス生成について、sample17.App4以外のクラスから行ってみましょう。ここでも、内部クラスのインスタンスとエンクロージングインスタンスが関連付けされていることに関係したコーディングが出てきます。

 もともとネストしたクラスは、エンクロージングインスタンスと深い関係があるため、エンクロージングインスタンス内でインスタンスを生成するのが原則です。しかし場合によっては、ほかのクラスでインスタンスを生成したいこともあるでしょうから、紹介しておきます。起動クラスとして、sample17.App4Runクラスを次のように作成してください。

package sample17;
 
public class App4Run {
    public static void main(String[] args)
    throws Exception {
        App4 app4 = new App4();
        App4.RemainView remainView = app4.new RemainView();
 
        app4.view.addObserver(remainView);
        app4.execute();
    }
}
sample17/App4Run.java

 「app4.new RemainView()」に注目してください。内部クラスのインスタンスは、エンクロージングインスタンスと関連付けされているため、エンクロージングインスタンスを使って生成する必要があります。「new App4.RemainView()」とはできません。

 型名については、staticのネストしたクラスと同様で「App4.RemainView」となる点も覚えておきましょう。App4.RemainView型のインスタンスを生成したら、app4.viewフィールドへaddObserverメソッドで追加しています。これで、残り時間も表示されるようになりました。

 なお、mainメソッドに「throws Exception」を付けているので、忘れずに付けてください。

 以下は、App4Runクラスの実行結果です。残り時間が正しく表示されています。

経過時間:1[sec]
開始時刻:1273397056473
カウント:12
終了時刻:0
残り時間:11[sec]
 
(略)
 
経過時間:12[sec]
開始時刻:1273397056473
カウント:12
終了時刻:0
残り時間:0[sec]
 
 
経過時間:12[sec]
開始時刻:1273397056473
カウント:12
終了時刻:1273397068502
残り時間:0[sec]
App4Run.javaの実行結果

内部クラスのextends

 RemainViewクラスをextendsするクラスも宣言できます。ここでは、sample17.App4をextendsするApp5クラスを用意します。そこに、残り時間を表示する際に使う単位を「[sec]」から「秒」へ変更したRemainViewExクラスを内部クラスとして宣言します。このクラスをRemainViewクラスからextendsして作成してみましょう。

 以下は、具体的なコードです。

package sample17;
 
public class App5 extends App4 {
    App5() {
        super();
        view.addObserver(new RemainViewEx());
    }
    class RemainViewEx extends RemainView {
        @Override
        public void update() {
            System.out.println("残り時間:" + count + "秒");
        }
    }
    public static void main(String[] args)
    throws Exception {
        App5 app5 = new App5();
        app5.execute();
    }
}
sample17/App5.java

 RemainViewクラスはApp4から実装を継承しているので、App5では宣言することなく利用できます。これをextendsしてRemainViewExクラスを宣言しています。sample17.App5クラスの実行結果は、残り時間の表示がちょっと変わっただけなので、省略します。

 今回は、sample17.App4をextendsしたクラスを用意しましたが、もちろん同一クラス内にある内部クラスの宣言と、その内部クラスをextendsしたクラスの宣言を両方とも含めることもできます。

「隠蔽」とは

 ここで、「隠蔽(hide)」について話をしておきましょう。以下のsample17.App4.ProgressViewクラスを見てください。

    class ProgressView implements Observer {
        int count = 0;
// 略
    }
sample17.App4.ProgressView

 sample17.App4.RemainViewクラスではcountフィールドがありませんでしたが、こちらにはcountフィールドがあります。sample17.App4クラスのcountフィールドとの関係は、どうなるのでしょうか?

 実は、このようにsample17.App4.ProgressViewクラス内に、エンクロージング型(ここではsample17.App4クラス)にあるフィールドと同じフィールド名(ここではcount)を宣言した場合、エンクロージング型のフィールドは「隠蔽」されて、sample17.App4.ProgressViewクラスからは見えなくなっています

優先順位は?

 sample17.App4.RemainViewクラスのcountフィールドとsample17.App4のcountフィールドのように、同じ変数名が出てきた場合、どちらを優先するかが決まっています。基本的には、コードを囲むブロックのうち、より近いブロックで宣言されている変数が優先されます

 例えば、メソッドの仮パラメータ名にクラスのフィールド名と同じ変数名を付けた場合、メソッドの仮パラメータの方が優先されます。

thisで明示的に指定できる

 メソッドの仮パラメータ名にクラスのフィールド名と同じ変数名を付けている場合、そのメソッド内でクラスで宣言されているフィールドへアクセスするには、どうすればいいのでしょうか?

 this.フィールド名」と明示的に指定することにより、アクセスできますthis.フィールド名」と明示的に指定することにより、アクセスできます。例えば、sample17.App4.ResultViewクラスを見てみましょう。自動生成したアクセサメソッドの中では、メソッドの仮パラメータ名にフィールド名と同じ「startTime」という変数名が使われています。仮パラメータの方は「startTime」とし、フィールドの方は「this.startTime」と記述することで、代入を実現しています。

    class ResultView implements Observer {
        long startTime;
// 略
        public void setStartTime(long startTime) {
          this.startTime = startTime;
        }
// 略
    }
sample17.App4.ResultView

 ここで、「this」というのはインスタンス自身のことだったのを思い出してください。

 例えば、sample17.App4.ResultViewクラスのsetStartTimeメソッドを実際に呼び出す場合「resultView.setStartTime(0);」のようにしますが、このとき、setStartTimeメソッドはresultViewが参照しているインスタンスのsetStartTimeメソッドが呼び出すことになります。setStartTimeメソッド内ではthisを使うことにより、resultViewが参照しているインスタンスを参照できるのです。

「隠蔽」をコードで体感してみよう

 それでは、「隠蔽」について確認するために、sample17.App6クラスを作成してみましょう。次のように、sample17.App4クラスをextendsします。

package sample17;
  
public class App6 extends App4 {
    int count = -1;
    App6() {
        super();
        view.addObserver(new RemainViewEx());
    }
    class RemainViewEx extends RemainView {
        int count = -2;
        @Override
        public void update() {
            System.out.println("残り時間:" + App6.super.count + "秒");
            System.out.println("App6    :" + App6.this.count + "秒");
            System.out.println("Inner   :" + count + "秒");
        }
    }
    public static void main(String[] args)
    throws Exception {
        App6 app6 = new App6();
        app6.execute();
    }
}
sample17/App6.java

 sample17.App6クラスには、countフィールドを宣言します。これにより、sample17.App4クラスのcountフィールドが「隠蔽」されて、sample17.App6クラス内でcountと書いた場合には、sample17.App6クラスのcountを指すことになります。

 また、内部クラスとしてsample17.App4.RemainViewクラスをextendsするsample17.App6.RemainViewExクラスを宣言します。ここでも、countフィールドを宣言します。これにより、sample17.App6クラスのcountフィールドが「隠蔽」され、sample17.App6.RemainViewExクラス内でcountを書いた場合、sample17.App6.RemainViewExクラスのcountを指すことになります。

 ここで、sample17.App6.RemainViewExクラスのupdateメソッド内に注目をしてください。「App6.super.count」と書くことで、sample17.App6クラスのスーパークラスであるsample17.App4のcountフィールドを参照しています。「App6.this.count」と書くことで、sample17.App6クラスのcountフィールドを参照しています。単に「count」と書いた場合は、sample17.App6.RemainViewExクラスのcountとなることは説明済みです。

 これを実行すると、次のように説明した通りの結果となります。App6とInnerの値がそれぞれ「-1」と「-2」の固定となっていることを確認してください。

経過時間:1[sec]
開始時刻:1273401363690
カウント:12
終了時刻:0
残り時間:11秒
App6    :-1秒
Inner   :-2秒
 
(略)
 
経過時間:12[sec]
開始時刻:1273401363690
カウント:12
終了時刻:1273401376070
残り時間:0秒
App6    :-1秒
Inner   :-2秒
App6.javaの実行結果

 次ページでは、無名内部クラスとローカル内部クラスの概要との使い方を解説します。

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。