
あなたの知らない、4つのマニアックなJava文法
株式会社ガリレオ
小山博史
2010/7/27
■ エンクロージングインスタンスへのアクセス
前回作成したsample16.App4クラスをコピーして、sample17.App4クラスを作成しましょう。Eclipse上で、前回の要領でsample16.App4クラスをコピーして、「sample17」パッケージへ貼り付けるだけです。
sample17.App4クラスへ、残り時間を表示するRemainViewクラスを追加してみましょう。RemainViewクラスはsample17.App4.Observerインターフェイスを実装して、sample17.App4クラスのメンバ・クラスとして追加します。以下は、具体的なコードです。
sample17.App4.RemainView class RemainView implements Observer {
@Override
public void update() {
System.out.println("残り時間:" + count + "[sec]");
}
}
変数countが突然出てきていますが、これはRemainViewクラスにはありませんし、Observerインターフェイスにもありませんが、どこで宣言されているのでしょうか?
答えは、sample17.App4クラス内にあります。staticのネストしたクラスから生成されるインスタンスと違って、内部クラスのインスタンスは、エンクロージングインスタンスと関連付くため、sample17.App4クラス内に宣言されているcountフィールドにアクセスできます。
RemainViewクラスはsample17.App4クラスのメンバですから、このフィールドへアクセスできるのです。このsample17.App4のcountフィールドに残り時間が保持されているので、それを表示しています。
次に、sample17.App4クラスの内部クラスであるsample17.App4.RemainViewクラスのインスタンス生成について、sample17.App4以外のクラスから行ってみましょう。ここでも、内部クラスのインスタンスとエンクロージングインスタンスが関連付けされていることに関係したコーディングが出てきます。
もともとネストしたクラスは、エンクロージングインスタンスと深い関係があるため、エンクロージングインスタンス内でインスタンスを生成するのが原則です。しかし場合によっては、ほかのクラスでインスタンスを生成したいこともあるでしょうから、紹介しておきます。起動クラスとして、sample17.App4Runクラスを次のように作成してください。
sample17/App4Run.javapackage 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();
}
}
「app4.new RemainView()」に注目してください。内部クラスのインスタンスは、エンクロージングインスタンスと関連付けされているため、エンクロージングインスタンスを使って生成する必要があります。「new App4.RemainView()」とはできません。
型名については、staticのネストしたクラスと同様で「App4.RemainView」となる点も覚えておきましょう。App4.RemainView型のインスタンスを生成したら、app4.viewフィールドへaddObserverメソッドで追加しています。これで、残り時間も表示されるようになりました。
なお、mainメソッドに「throws Exception」を付けているので、忘れずに付けてください。
以下は、App4Runクラスの実行結果です。残り時間が正しく表示されています。
App4Run.javaの実行結果経過時間:1[sec]
開始時刻:1273397056473
カウント:12
終了時刻:0
残り時間:11[sec]
(略)
経過時間:12[sec]
開始時刻:1273397056473
カウント:12
終了時刻:0
残り時間:0[sec]
経過時間:12[sec]
開始時刻:1273397056473
カウント:12
終了時刻:1273397068502
残り時間:0[sec]
■ 内部クラスのextends
RemainViewクラスをextendsするクラスも宣言できます。ここでは、sample17.App4をextendsするApp5クラスを用意します。そこに、残り時間を表示する際に使う単位を「[sec]」から「秒」へ変更したRemainViewExクラスを内部クラスとして宣言します。このクラスをRemainViewクラスからextendsして作成してみましょう。
以下は、具体的なコードです。
sample17/App5.javapackage 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();
}
}
RemainViewクラスはApp4から実装を継承しているので、App5では宣言することなく利用できます。これをextendsしてRemainViewExクラスを宣言しています。sample17.App5クラスの実行結果は、残り時間の表示がちょっと変わっただけなので、省略します。
今回は、sample17.App4をextendsしたクラスを用意しましたが、もちろん同一クラス内にある内部クラスの宣言と、その内部クラスをextendsしたクラスの宣言を両方とも含めることもできます。
「隠蔽」とは
ここで、「隠蔽(hide)」について話をしておきましょう。以下のsample17.App4.ProgressViewクラスを見てください。
sample17.App4.ProgressView class ProgressView implements Observer {
int count = 0;
// 略
}
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.フィールド名」と明示的に指定することにより、アクセスできます。例えば、sample17.App4.ResultViewクラスを見てみましょう。自動生成したアクセサメソッドの中では、メソッドの仮パラメータ名にフィールド名と同じ「startTime」という変数名が使われています。仮パラメータの方は「startTime」とし、フィールドの方は「this.startTime」と記述することで、代入を実現しています。
sample17.App4.ResultView class ResultView implements Observer {
long startTime;
// 略
public void setStartTime(long startTime) {
this.startTime = startTime;
}
// 略
}
ここで、「this」というのはインスタンス自身のことだったのを思い出してください。
例えば、sample17.App4.ResultViewクラスのsetStartTimeメソッドを実際に呼び出す場合「resultView.setStartTime(0);」のようにしますが、このとき、setStartTimeメソッドはresultViewが参照しているインスタンスのsetStartTimeメソッドが呼び出すことになります。setStartTimeメソッド内ではthisを使うことにより、resultViewが参照しているインスタンスを参照できるのです。
■ 「隠蔽」をコードで体感してみよう
それでは、「隠蔽」について確認するために、sample17.App6クラスを作成してみましょう。次のように、sample17.App4クラスをextendsします。
sample17/App6.javapackage 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クラスには、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」の固定となっていることを確認してください。
App6.javaの実行結果経過時間:1[sec]
開始時刻:1273401363690
カウント:12
終了時刻:0
残り時間:11秒
App6 :-1秒
Inner :-2秒
(略)
経過時間:12[sec]
開始時刻:1273401363690
カウント:12
終了時刻:1273401376070
残り時間:0秒
App6 :-1秒
Inner :-2秒次ページでは、無名内部クラスとローカル内部クラスの概要との使い方を解説します。
| Index | ||||||||
|
||||||||
【改訂版】Eclipseではじめるプログラミング バックナンバー 連載インデックスへ»
- 第1回 Eclipse 3.4で超簡単Javaプログラミング基礎入門
- 第2回 Javaで一から理解するプログラムの変数と演算子
- 第3回 プログラミングの醍醐味! Javaで“条件式”を理解する
- 第4回 プログラミングの真骨頂! Javaで“反復処理”を覚える
- 第5回 データ集合を扱うのに便利なJavaの配列と拡張for文
- 第6回 複雑なデータを表現できるクラスやフィールドって?
- 第7回 クラスの振る舞いを表すJavaの“メソッド”とは?
- 第8回 Javaの参照型を文字列操作で理解して文法を総復習
- 第9回 プログラムを「変更」しやすくする“インターフェイス”
- 第10回 Javaの実案件に必須のパッケージとインポートを知る
- 第11回 「static」でクラス共有の変数・メソッドを使いこなせ!
- 第12回 継承やオーバーライドで簡単にクラスを“拡張”しよう
- 第13回 “コンストラクタ”と初期化、本当に理解できてる?
- 第14回 再利用性の高いクラス作成に重要な“アクセス制御”
- 第15回 Javaは「抽象クラス」で実装を上手に再利用できる
- 第16回 “ネスト”した型で始める軽量Javaプログラミング!?
- 第17回 あなたの知らない、4つのマニアックなJava文法
- 第18回 強く型付けされているJavaの理解に必修の“型変換”
- 第19回 キュー構造をJavaで実装してジェネリック型を理解する
- 第20回 拡張for文の真の実力を知り、反復処理を使いこなせ
- 第21回 7ステップで理解するJavaでの列挙型/enum使用法
- 第22回 いまさら聞けない「Javadoc」と「アノテーション」入門
- 第23回 プログラマの宿命! 例外とエラー処理を理解する
- 第24回 Javaの例外処理で知らないと損する7つのテクニック
| Java Solution全記事一覧 |
TechTargetジャパン
- Scalaのパッケージ、アクセス修飾子、オブジェクト継承 (2012/5/22)
インポート、パッケージオブジェクト、抽象クラス/抽象メソッド、オーバーライド、final、シールドクラスなども - 基幹系システムでCloud SQLは使えるか試してみた (2012/5/17)
サンプルとしてMRPシステムを作成して動かし、「再帰呼び出し」などのパフォーマンスを測定して検証してみます - アジャイル管理ツール9選+Pivotal Tracker入門 (2012/5/14)
群雄割拠のアジャイルプロジェクト管理ツールを9つ紹介し、特に注目を集めているPivotal Trackerの基本的な使い方を解説します - サーバサイドJSやJavaでWebアプリが作れるXPages (2012/5/11)
Notes/Dominoの資産をサーバサイドJavaScriptやJavaで操作し、HTMLやJavaScript、CSSをUIにできる技術を紹介
|
|
キャリアアップ
スポンサーからのお知らせ
- - PR -
イベントカレンダー
- - PR -
