ネストした型はエンクロージング型にアクセスできる場合にだけアクセスできる。また、アクセスの観点などから1レベルだけのネストが推奨される。
■staticのネストしたクラス
public class BankAccount { private long number; private long balance; public static class Permissions { public boolean canDeposit, canWithdraw, canClose; } }
■ネストしたインターフェース
ネストしたインターフェースは常にstaticであるが慣習的に省略される。
■内部クラス
staticでないクラスメンバーはそのクラスのインスタンスと関連付けされている。内部クラスのオブジェクトは常にエンクロージングインスタンスと関連付けされている。
public class BankAccount { private long number; private long balance; private Action lastAction; public class Action { private String action; private long amount; Action(String action, long amount) { this.action = action; this.amount = amount; } public String toString() { return number + ": " + action + " " + amount; } } public void deposit(long amount) { balance += amount; lastAction = new Action("deposit", amount); } public void withdraw(long amount) { balance -= amount; lastAction = new Action("withdraw", amount); } }
通常、内部クラスのオブジェクトはdepositやwithdrawの中で行われたようにエンクロージングクラスのインスタンスメソッドの内部で生成される。
lastAction = new Action("deposit", amount);
上述のコードを厳密に書くと以下のようになる。
lastAction = this.new Action("deposit", amount);
制限
staticフィールドを持つことができないのでstatic finalにするかインスタンスフィールドにする必要がある。
アクセス
以下のようにエンクロージングクラスは明示的な参照を通して、内部クラスのprivateメンバにアクセスできる。
public void deposit(long amount) { balance += amount; lastAction = new Action("deposit", amount); String str = lastAction.action; }
以下のように内部クラスからエンクロージングクラスにアクセスすることができる。
public String toString() { return number + ": " + action + " " + amount; }
また以下のように限定的なthisを用いて記述することもできる。
public String toString() { return BankAccount.this.number + ": " + action + " " + amount; }
但し、staticメソッドなどから限定的なthisを用いることはできない。
■拡張
以下のようなクラスを考える。
class Outer { class Inner {} }
以下のようにして拡張することができる。
class ExOuter extends Outer { class ExInner extends Inner {} }
内部サブクラスのエンクロージングクラスがOuterのサブクラスでない場合、内部サブクラス自身が内部クラスでない場合、superを通してInnerのコンストラクタを呼び出す際にOuterオブジェクトへの明示的な参照を提供しなくてはならない。
class Unrelated extends Outer.Inner { Unrelated(Outer ref) { ref.super(); } }
■隠蔽
以下のように同名のメソッドを内部クラスで定義すると、引数に関係なくエンクロージングクラスのメソッドが隠蔽される。
class Outer { void print() {} void print(int val) {} class Inner { void print() {}// エンクロージングクラスの全てのprintメソッドを隠蔽する void show() { print(1);// コンパイルエラー } } }
■ローカル内部クラス
以下のようにメソッド本体(コンストラクタや初期化ブロックなど)のコードブロックの中で内部クラスを定義することができる。
public void onClick(View v) { class BankAccount {} BankAccount bankAccount = new BankAccount(); }
コードブロックの外からはアクセスできず完全にローカルである。また、staticとする事はできない。
■無名内部クラス
以下のようにして無名内部クラスを定義することができる。無名内部クラスはコンストラクタを持つことができない。
private static Iterator<Object> walk(final Object[] objs) { return new Iterator<Object>() { private int position = 0; @Override public boolean hasNext() { return (position < objs.length); } @Override public Object next() { if (position >= objs.length) { throw new NoSuchElementException(); } return objs[position++]; } @Override public void remove() { throw new UnsupportedOperationException(); } }; }
明示的なコンストラクタを必要とする場合はローカル内部クラスを使用するべきである。また、6行を超えるような無名内部クラスは可読性の観点から避けるべきである。
■ネストした型の継承
以下のようなコードを考える。
abstract class Device { abstract class Port { } }
class Printer extends Device { class SerialPort extends Port { } Port serial = new SerialPort(); }
class HighSpeedPrinter extends Printer { class SerialPort extends Printer.SerialPort { } // 継承されたメンバserialは親クラスのSerialPortのインスタンスである }
一方で以下のようにファクトリーメソッドを用いてオブジェクトを生成する場合を考える。
class Printer extends Device { class SerialPort extends Port { } Port serial = createSerialPort(); protected Port createSerialPort() { return new SerialPort(); } }
class HighSpeedPrinter extends Printer { class EnhancedSerialPort extends SerialPort { } // 継承されたメンバserialは初期化の際にカレントクラスでOverrideされたcreateSerialPortが呼ばれる protected Port createSerialPort() { return new EnhancedSerialPort(); } }
■インターフェース内でのネスト
以下のようにしてインターフェース内でネストしたクラスを定義することができる。
interface Changeable { class Record { public Object changer; public String changeDesc; } Record getLastChange(); }
暗黙的にpublicかつstaticになる。
インターフェース内での変更可能な変数
以下のようにすることでSharedDataを利用しているコードは、data参照を通して共通の状態を共有できる。
interface SharedData { class Data {// 暗黙的にstatic private int x = 0; public int getX() {return x;} public void setX(int x){this.x = x;} } Data data = new Data(); }