ITお絵かき修行

3歩歩いても忘れないために

Upgrade Java SE 7 to Java SE 8 OCP Programmer(1Z0-810)対策 第一章 ラムダ式(1)

Upgrade Java SE 7 to Java SE 8 OCP Programmer(1Z0-810)を受けるための勉強メモ。
下記サイトが詳しそうな雰囲気だったので訳しながら追ってゆく。

■注意
全文が意訳です。正確な情報は下記リンクより一次情報を参照してください。

■お世話になる試験対策っぽいページ
java.boot.by

githubにも別の対策サイトがあったので紹介
rahulsh1.github.io


■試験
Upgrade Java SE 7 to Java SE 8 OCP Programmer | Oracle Certification Exam

1.1. ネストしたクラス、静的クラス、ローカル・クラス、無名クラスなど、Javaの内部クラスを使用する

Javaでは4種類の内部クラスを実装することができる。下記に名称と実装例を示す。

staticな内部クラスコンパイルが通るよう修正済

public class Outer {
	static class Inner {
		void doIt() {
			System.out.print("Static nested class reference is: " + this);
		}
	}

	public static void main(String[] args) {
		Outer.Inner n = new Outer.Inner();
		n.doIt();
	}
}


●標準出力

Static nested class reference is: chap1.stat.Outer$Inner@2a139a55

内部クラスと似ているが、静的クラスとして宣言している。
エンクロージングクラスのインスタンスに依存しない。エンクロージングクラスのインスタンスのstaticメソッドとフィールドへのみアクセスすることができる。
「利用シーン」
・エンクロージングクラスのインスタンスと分離したい場合
・エンクロージングクラスのインスタンスより生存期間が長い場合


内部クラス(インナークラス)
内部クラスは明示的なインスタンスとして生成する。(メンバ変数として外部のクラス内で宣言される。)
内部クラスはエンクロージングクラスと内部クラスの全てのメソッド、フィールド、エンクロージングインスタンスのthisによる参照にアクセスすることができる。

public class Outer {

    private int secretVar = 1;

    public void makeInner() {
        Inner in = new Inner();
        in.seeOuter();
    }

    class Inner {
        public void seeOuter() {
            System.out.println("Outer 'secretVar' is " + secretVar);
            System.out.println("Inner class reference is " + this);
            System.out.print("Outer class reference is " + Outer.this);
        }
    }

    public static void main(String... args) {
        Inner i = new Outer().new Inner();
        i.seeOuter();
    }
}


●標準出力

Outer 'secretVar' is 1
Inner class reference is chap1.inner.Outer$Inner@2a139a55
Outer class reference is chap1.inner.Outer@15db9742


内部クラスはエンクロージングクラスとは別のスコープで定義される。
無名クラスであることがある。(次のセクションを参照)
外部・内部クラスはお互いのメソッドを直接参照することが可能(※private宣言されてる場合でも)
OuterクラスのメソッドはInnerクラスのインスタンスを生成することでInnerクラスを使用する。
内部クラスのインスタンスはエンクロージングクラスのインスタンスとは分離されている。
内部クラスのインスタンスはエンクロージングクラスのインスタンスよりインスタンス化する必要がある。
内部クラスはエンクロージングインスタンスの生成タイミングに縛られる。(※変更不可)


ローカル内部クラス(ローカルインナークラス)
ローカル内部クラスはブロック内で定義される。ブロックは0以上の要素で構成され互いに関係する。

インスタンスメソッドにおけるローカル内部クラス」

public class Outer {

    private String x = "outer";

    public void doStuff() {

        class MyInner {
            public void seeOuter() {
                System.out.println("x is " + x);
            }
        }

        MyInner i = new MyInner();
        i.seeOuter();
    }

    public static void main(String[] args) {
        Outer o = new Outer();
        o.doStuff();
    }
}

●標準出力

x is outer


「staticメソッドにおけるローカル内部クラス」

public class Outer {

    private static String x = "static outer";

    public static void doStuff() {

        class MyInner {
            public void seeOuter() {
                System.out.println("x is " + x);
            }
        }

        MyInner i = new MyInner();
        i.seeOuter();
    }

    public static void main(String[] args) {
        Outer.doStuff();
    }
}

●標準出力

x is static outer

内部クラスはエンクロージングクラスに対するアクセスを持つ。
加えて、内部クラスはエンクロージングクラス内の変数に対するアクセスを持つ。
JavaSE7以前のバージョンでは、内部クラスはエンクロージングクラスの「定数」に対するアクセスを持っている。
内部クラスがエンクロージングクラスの変数、パラメータへアクセスする時は他の変数やパラメータで保持する。

// Java SE 7 syntax
...
public void validatePhoneNumber(String phoneNumber) {

    final int numberLength = 10;

    class PhoneNumber {
        PhoneNumber(String phoneNumber) {
            if (phoneNumber.length() > numberLength) { ... }
        }
    }
}
...

※WARNING※
Java8からは、内部クラスがエンクロージングクラスの定数または"事実上の定数"へアクセスできる。
初期化後に一度も変更されていない変数、パラメータを"事実上の定数"とする。

// Java SE 8 - successfully compiles and runs;
// Java SE 7 - compilation failure
...
public void validatePhoneNumber(String phoneNumber) {

    int numberLength = 10; // no mandatory 'final' anymore, as long as variable not modified

    class PhoneNumber {
        PhoneNumber(String phoneNumber) {
            if (phoneNumber.length() > numberLength) { ... }
        }
    }
}
...

※WARNING※
下記の例はエンクロージングクラスの変数(numberLength)の代入文が内部クラス(PhoneNumberクラス)にある場合。
変数numberLengthは定数ではないため、Javaコンパイラは「Local variables referenced from an inner class must be final or effectively final」というエラーメッセージを生成する。

// Java SE 8 - compilation failure
...
public void validatePhoneNumber(String phoneNumber) {

    int numberLength = 10;

    class PhoneNumber {

        numberLength = 12;

        PhoneNumber(String phoneNumber) {
            if (phoneNumber.length() > numberLength) { ... }
        }
    }
}
...


匿名インナークラス
GUIアプリにおけるウィジェットに対するActionリスナーを追加するときによく利用する。

public class Outer {

    public void init() {
        JButton button = new JButton();
        JLabel comp = new JLabel();

        button.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                comp.setText("Button has been clicked");
            }
        });
    }
}

内部クラスは匿名クラスであり、エンクロージングクラスのインスタンスの一部となる。
内部クラスの一部の箇所でインスタンス化して使用したいときによく使用される。

SomeType x = new SomeType() {     // unnamed (i.e. anonymous) inner class
    // body of class goes here
};

・SomeTypeは存在するクラスまたはインタフェースでなければならない。
・SomeTypeがクラスの場合:提供されたクラスの拡張クラス
・SomeTypeがインタフェースの場合:提供されたインタフェースの実装クラス


シャドーイング
変数やパラメータ等の箇所にて宣言した型が独特なスコープ(内部クラスやメソッドの宣言部)にて、エンクロージングクラスのスコープ内の別の宣言と同じ名前があると、
エンクロージングクラスのスコープの宣言を隠蔽する。
覆い隠された宣言部へ対して、宣言の名前単独で参照することはできない。

public class Shadow {

    public int x = 0;

    class FirstLevel {

        public int x = 1;

        void methodInFirstLevel(int x) {
            System.out.println("x = " + x);
            System.out.println("this.x = " + this.x);
            System.out.println("Shadow.this.x = " + Shadow.this.x);
        }
    }

    public static void main(String... args) {
        Shadow s = new Shadow();
        Shadow.FirstLevel fl = s.new FirstLevel();
        fl.methodInFirstLevel(2);
    }
}

●標準出力

x = 2
this.x = 1
Shadow.this.x = 0

この例では3つのxという変数が定義されている。Shadowクラスのメンバ変数、内部クラスのFirstLevelのメンバ変数、methodInFirstLevelメソッドのパラメータである。
内部クラスのFirstLevelのメンバ変数xは、methodInFirstLevelメソッドのパラメータの変数を隠蔽する。
その結果、methodInFirstLevelメソッドのパラメータの変数を使用したい時は、メソッドのパラメータを参照するようにする。
内部クラスのFirstLevelのメンバ変数を参照するには、エンクロージングクラスのインスタンスのスコープを表すthisキーワードを使用する。

System.out.println("this.x = " + this.x);


参照するメンバ変数は、所属するクラスよりもより大きなスコープに属している。
例えば、下記の代入文はmethodInFirstLevelメソッドからShadowクラスのメンバ変数へアクセスする。

System.out.println("Shadow.this.x = " + Shadow.this.x);


■参考
四種類の内部クラス - にょきにょきブログ
【改訂版】Eclipseではじめるプログラミング(17):あなたの知らない、4つのマニアックなJava文法 (1/3) - @IT
裏Javaメモ(Hishidama's Java Memo)