Java 11 Gold 取得に向けた学習記録
Enclosingクラス
クラスの内部でさらにクラスを定義した場合、外側のクラスはEnclosingクラスと呼ばれ、内側のクラスはネストしたクラスと呼ばれる(enclose=囲う)。
いくつかの分類がある。
定義場所 | public |
protected |
private |
abstract |
final |
stataic |
|
---|---|---|---|---|---|---|---|
インナークラス | クラス内 | ◯ | ◯ | ◯ | ◯ | ◯ | ◯ |
ローカルクラス | メソッド内 | × | × | × | ◯ | ◯ | × |
匿名クラス | メソッド内 | × | × | × | × | × | × |
インナークラス
クラス内で定義されたクラスをインナークラスと言う。
class OuterClass {
class InnerClass {
}
}
インナークラスはEnclosingクラスと同様のアクセス修飾子(public
, protected
, private
, abstract
, final
)を使用する事ができる。
またインターフェースを実装(implements
)することもできる。
インナークラスはEnclosingクラスのインスタンスに属しているため、Enclosingクラスのインスタンスを介してアクセスを行う。
OuterClass outer = new MyClass();
OuterClass.InnerClass inner = outer.new InnerClass();
以下のように1行にまとめて記述することもできる。
OuterClass.InnerClass inner = new OuterClass().new InnerClass();
またimport
を行えば、このような記述も可能。
import my.package.OuterClass.InnerClass;
InnerClass inner = new OuterClass().new InnerClass();
特徴
インナークラスはEnclosingクラスで定義されたフィールドやメソッドと同様に、Enclosingクラスのインスタンス変数に直接アクセスする事ができる(ここが個人的に最初全然理解できていなかったところで、インナークラスを理解する上での肝だと思った)。
class OuterClass {
String outerField = "hello";
class InnerClass {
String innerFiled = outerField + " world";
}
}
この特徴からわかるように、インナークラスはEnclosingクラスのインスタンスに依存している。
インナークラスはEnclosingクラスのインスタンスに依存している
static
インナークラス
インナークラスをstatic
修飾したものを特にstaticインナークラスと呼ぶ。
class OuterClass {
static class InnerClass {
}
}
static
インナークラスは、インスタンス化にEnclosingクラスのインスタンスを必要としない。
個人的に混同していたのが、static
インナークラスはあくまでも「クラス」なので、インスタンス化(new
)が不要なわけではない。
static
インナークラスはstatic
だからと言ってnew
しなくても良いわけではない
OuterClass.InnerClass inner = new OuterClass.InnerClass();
以下のような記述も可能。
import my.package.OuterClass.InnerClass;
InnerClass inner = new InnerClass();
static
インナークラスはEnclosingクラスのインスタンスではなく、Enclosingクラス自体に属するため、Enclosingクラスのインスタンスに依存しない。
そのため、Enclosingクラスの非static
フィールドや、非static
メソッドにはアクセスする事ができない。
class OuterClass {
String outerField = "hello";
static class InnerClass {
// コンパイルエラー
String innerFiled = outerField + " world";
}
}
static
インナークラスはEnclosingクラスのインスタンスに依存しない。
Enclosingクラス自体に依存している。
インナークラスでstatic
フィールドは定義できない
非static
インナークラス内でstatic
なフィールドを定義しようとした場合、コンパイルエラーが発生する。
class OuterClass {
class InnerClass {
// コンパイルエラー
static int staticField = 0;
// 定数は定義可能
static final int CONSTANT_FIELD = 10;
}
}
static
フィールドはクラス自体に依存しているが、非static
インナークラスはEnclosingクラスのインスタンスに依存している。
ここでのstaticField
は、InnerClass
のインスタンスではなくクラス自体に依存する。一方で、InnerClass
はOuterClass
のインスタンスに依存している。そのため、依存関係が一致していない。
そのため、インナークラスをstatic
修飾しなければならない。
class OuterClass {
static class InnerClass {
static int staticField = 10;
}
}
ローカルクラス
メソッド内で定義されるクラスをローカルクラスと言う。
(メソッド内で定義される変数をローカル変数と言う)
class OuterClass {
void outerMethod() {
class LocalClass {
}
}
}
ローカルクラスは自身が定義されたスコープ内でのみ、インスタンス化を行う事ができる。
class OuterClass {
void outerMethod() {
class LocalClass {
}
LocalClass local = new LocalClass();
}
}
Factoryパターンを使用する際に、クラス定義をメソッド内に隠蔽させる使い方などがある。
ローカルクラスのインスタンスは、メソッドの実行後もメモリ上に存在し続ける可能性がある。
interface MyInterface {
void myMethod();
}
class OuterClass {
MyInterface outerMethod() {
class LocalClass implements MyInterface {
@Override
public void myMethod() {
}
}
return new LocalClass();
}
}
MyInterface local = new OuterClass().outerMethod();
// outerMethod()実行後も、LocalClassのインスタンスはまだ生きている
local.myMethod();
一方で、ローカル変数のスコープはメソッドの終了とともに消滅する(メモリ上から削除される)。
そのためローカルクラス内でEnclosingクラスのローカル変数を使用した場合、実質的にfinal
(effectively final)な定数扱いになる(ラムダ式と同様)。
class OuterClass {
void outerMethod() {
String localVal = "hello";
class LocalClass {
void innerMethod(){
// コンパイルエラー
localVal = "world";
}
}
}
}
JavaScriptではクロージャーと呼ばれる外側のスコープ(レキシカルスコープ)を保持し続ける関数を定義する事ができる。
function outer() {
// レキシカルスコープ
let outerVar = "hello";
function inner() {
outerVar = "world";
console.log(outerVar);
}
return inner;
}
const innerFn = outer();
innerFn();
// >> "world"
匿名クラス
名前のないクラスのインスタンスを生成するための特殊な構文。
普通、Javaのクラスは名前とともに宣言され、宣言された名前とnew
キーワードを使ってインスタンス化が行われる。
MyClass obj = new MyClass();
匿名クラスは宣言と同時にインスタンスが生成される。名前を持たないクラスから生成されるため、クラス名を用いて宣言する必要があるコンストラクタを定義することはできない。
匿名クラスにはコンストラクタを定義できない
また、匿名クラスはインターフェースに使用した場合にはインターフェースを「実装(implements
)」したクラスになり、通常のクラスに使用した場合にはそのクラスを「継承(extends
)」したサブクラスになる。
インターフェースに対して使用する匿名クラス
public interface MyInterface {
void method();
}
public class Main {
public static void main(String[] args) {
MyInterface obj = new MyInterface() {
// ここが MyInterface を「実装」するクラス
@Override
public void method() {
System.out.println("ここでインターフェースを実装する");
}
};
}
}
スレッドの生成時にも利用される。
// java.lang.Thread、java.lang.Runnable を使用
// 匿名クラスを使ってRunnableインターフェースを実装する
var thread = new Thread(new Runnable(){
@Override
public void run() {
System.out.println("非同期的に処理を実行します。");
}
});
thread.start();
// >> 非同期的に処理を実行します。
クラスに対して使用する匿名クラス
public class SuperClass {
public void method() {
System.out.println("hello world");
}
}
public class Main {
public static void main(String[] args) {
SuperClass obj = new SuperClass() {
// ここが SuperClass を「継承」する匿名クラス
@Override
public void method() {
System.out.println("ここでスーパークラスを継承する");
}
};
}
}
匿名クラスは、クラス名やコンストラクタは定義する事ができないため、初期化を行いたい場合インスタンス初期化子を使用する。
インスタンス初期化子についてはこちら。
public class SuperClass {
String field;
public void method() {
System.out.println(field);
}
}
public class Main {
public static void main(String[] args) {
SuperClass obj = new SuperClass() {
// インスタンス初期化子
{
field = "hello world";
}
@Override
public void method() {
System.out.println(field);
}
};
obj.method();
// >> hello world
}
}
List
の初期化では匿名クラスの初期化子が利用されている。
List<String> list = new ArrayList<String>(){
// ここが匿名クラス部分
{
// ここが匿名クラスのインスタンス初期化子部分
add("Apple");
add("Orange");
add("Melon");
}
};