Java 11 Gold 取得に向けた学習記録
型推論
型推論はコンパイル時に代入演算子の右辺の値を元に行われ、ローカル変数、メソッドの戻り値や引数に対して利用することができる。
ただしvar
による型推論は、ローカル変数にのみ利用することができる。
また、配列の初期化時にも型推論が利用される事がある。
int[] array = {1, 2, 3};
ジェネリクスにおける型推論
ジェネリクスでも型推論を利用することができる。
型を指定しない記述方法はダイアモンド演算子<>
と呼ばれる。
// 型推論あり
List<String> list = new ArrayList<>();
// 型推論なし
List<String> list = new ArrayList<String>();
またジェネリクス自体に型引数を与えなかった場合、Object
型を指定したものとして扱われる。
// 型引数を与えない
ArrayList list = new ArrayList<>();
// 型引数を与える
ArrayList<Object> list = new ArrayList<>();
型推論はメソッドの戻り値や引数にも利用できる。
// 型推論あり
List<String> getList() {
return new ArrayList<>();
}
// 型推論なし
List<String> getList() {
return new ArrayList<String>();
}
void setList(List<String> list) {
}
// 型推論あり
setList(new ArrayList<>());
// 型推論なし
setList(new ArrayList<String>());
ワイルドカード
ジェネリクスの型引数を柔軟に指定するための機能。
ワイルドカードを使うことで、特定の型に依存しない汎用的なコードを記述することができる。
class Animal { }
class Dog extends Animal { }
class Cat extends Animal { }
List<?> list = new ArrayList<>();
List<? extends Animal> animals = new List<Dog>();
List<? extends Animal> animals = new List<Cat>();
List<? super Dog> dogs = new List<Animal>();
List<? super Cat> cats = new List<Animal>();
非境界ワイルドカード
非境界ワイルドカードを使用することで、コンパイル時に型が未定だと判断され、実行時にあらゆる型を使用することができるようになる。
一方で、引数に使用した非境界ワイルドカードは、実際には null
しか許容されなくなる。また、戻り値に使用した非境界ワイルドカードは、Object
型になるという特徴がある。
class Dog {}
class Cat {}
class Zoo<T> {
T animal;
void set(T t) {
this.animal = animal;
}
T get() {
return animal;
}
}
Dog dog = new Dog();
Cat cat = new Cat();
// あらゆる型を許容する
Zoo<?> dogZoo = new Zoo<>(dog);
Zoo<?> catZoo = new Zoo<>(cat);
// 引数は null しか許容されない
dogZoo.set(null);
catZoo.set(null);
// コンパイルエラー
dogZoo.set(new Dog());
catZoo.set(new Cat());
// 戻り値は Object 型になる
Object obj1 = dogZoo.get();
Object obj2 = catZoo.get();
// コンパイルエラー
Dog dog2 = dogZoo.get();
Cat cat2 = catZoo.get();
不変性・共変性・反変性
ジェネリクスや型引数において、型の代入互換性に関する性質として共変性、反変性と呼ばれるものがある。
混同しやすいものにポリモーフィズムの要素の一つである「スーパークラス型の変数にはサブクラス型を代入することができる」という性質があるが、これはこのあと整理するジェネリクスが持つ共変性や反変性とは別である。
class Animal { }
class Dog extends Animal { }
class Cat extends Animal { }
Animal animal = new Animal();
// Animal ◀️ Dog
Animal animal = new Dog();
// Animal ◀️ Cat
Animal animal = new Cat();
// Dog ◀️ Cat (コンパイルエラー)
Dog dog = new Cat();
// Cat ◀️ Dog (コンパイルエラー)
Cat cat = new Dog();
List<Animal> animals = new ArrayList<>();
// Animal ◀️ Animal
animals.add(new Animal());
// Animal ◀️ Dog
animals.add(new Dog());
// Animal ◀️ Cat
animals.add(new Cat());
ジェネリクスには一見クラスの型と同じように扱えるように思えるが、クラスで言うところのスーパージェネリクス、サブジェネリクスといった概念は存在しない。
不変性
Invariance
代入されるジェネリクスが、宣言時に指定された型以外を許容しない性質。ジェネリクスは原則として、不変性の性質を持つ。
class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
class Zoo<T> {}
ジェネリクスは完全に一致する型のみを許容する。
Zoo<Animal> animalZoo;
// Zoo<Animal> ◀️ Zoo<Dog> (コンパイルエラー:不変性により許容されない)
animalZoo = new Zoo<Dog>();
// Zoo<Animal> ◀️ Zoo<Cat> (コンパイルエラー:不変性により許容されない)
animalZoo = new Zoo<Cat>();
個人的にとても混同してしまいがちなのが、ポリモーフィズムの性質。別の性質であることは改めて整理しながら理解したいところ。
Animal animal;
// Animal ◀️ Dog
animal = new Dog();
// Animal ◀️ Cat
animal = new Cat();
List
でも確かめてみる。
// List<Animal> ◀️ List<Dog> (コンパイルエラー。やっぱりジェネリクスは不変性)
List<Animal> animals = new List<Dog>();
ジェネリクスは原則、不変性である
extends
ジェネリクスの宣言にextends
キーワードを使用することで、実行時に許容する型引数を特定のスーパークラスのサブクラスに制限することができる。この制限のことを上限境界(upper bound)と言う。
class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
class Fox {}
class Monkey {}
// Animal もしくは Animal を継承したクラス型しか許容しない
class Zoo<T extends Animal> {}
Zoo
クラスのジェネリクスに与える実行時の型引数は、Animal
クラスを継承したクラスのみに制限されている。
// OK
Zoo<Animal> animalZoo;
// OK
Zoo<Dog> dogZoo;
// OK
Zoo<Cat> catZoo;
// コンパイルエラー(Fox が Animal を継承していないため、許容されない)
Zoo<Fox> foxZoo;
// コンパイルエラー(Monkey が Animal を継承していないため、許容されない)
Zoo<Monkey> monkeyZoo;
共変性
Covariance
型引数がその型のサブタイプに対して互換性を持つ性質。
ジェネリクスは基本的に不変性だが、extends
キーワードを使用することで共変性を実現することができる
extends
は型引数に制限を課すものだが、「不変性」という観点から考えてみると、本来は完全一致する型しか許容しない性質を、サブクラスも許容できるよう柔軟性を与えていると考えることもできる。
先程のextends
でのコードを共変性という観点でもう一度見てみる。
class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
// Animal を継承したサブクラス型も許容できるよう柔軟性が加わる
class Zoo<T extends Animal> {}
Zoo
クラスに対する実行時の型引数は、Animal
クラスに加えて、Animal
クラスのサブクラスも許容されるようになる。
// Animal を継承する Dog も許容される
Zoo<Dog> dogZoo;
// Animal を継承する Cat も許容される
Zoo<Cat> catZoo;
extends
を使用することで、ジェネリクスは共変性を持つことができる
上限境界ワイルドカード
extends
とワイルドカードを組み合わせた表現 <? extends T>
を上限境界ワイルドカードと言う。
class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
class Zoo<T> {}
Zoo<Animal> animalZoo;
Zoo<? extends Animal> extendsAnimalZoo;
Zoo<Dog> dogZoo = new Zoo<>();
Zoo<Cat> catZoo = new Zoo<>();
// コンパイルエラー(不変性)
animalZoo = dogZoo;
// コンパイルエラー(不変性)
animalZoo = catZoo;
// 共変性
extendsAnimalZoo = dogZoo;
// 共変性
extendsAnimalZoo = catZoo;
List
でも確かめてみる。
List<Animal> animals;
List<? extends Animal> extendsAnimals;
List<Dog> dogs = new ArrayList<>();
List<Cat> cats = new ArrayList<>();
// コンパイルエラー(不変性)
animals = dogs;
// コンパイルエラー(不変性)
animals = cats;
// 共変性
extendsAnimals = dogs;
// 共変性
extendsAnimals = cats;
上限境界ワイルドカードは主に読み取り専用の操作に使用される。
class Animal {}
class Dog extends Animal {}
class Zoo<T> {
T animal;
void set(T animal) {
this.animal = animal;
}
T get() {
return animal;
}
}
// 共変性
Zoo<? extends Animal> dogZoo = new Zoo<Dog>();
// 読み取りはできる(Dog ▶️ Animal のアップキャストは暗黙的)
Animal animal = dogZoo.get();
// 書き込みはできない(コンパイルエラー)
dogZoo.set(new Dog());
// 書き込みはできない(コンパイルエラー)
dogZoo.set(new Animal());
上限境界ワイルドカードが読み取り専用なのは、サブクラス型のオブジェクトをスーパークラス型に代入した際に起こるキャスト(アップキャスト)が暗黙的に行われるためである。
List
でも確かめてみる。
// 共変性
List<? extends Animal> animals = new ArrayList<Dog>();
// 読み取りはできる
Animal animal = animals.get(0);
// 書き込みはできない(コンパイルエラー)
animals.add(new Dog());
// 書き込みはできない(コンパイルエラー)
animals.add(new Animal());
共変戻り値
サブクラスでオーバーライドされたメソッドの戻り値型が、親クラスで定義されたメソッドの戻り値型のサブタイプにすることができるという概念のこと。
class Animal {}
class Dog extends Animal {}
class AnimalZoo {
Animal animal;
AnimalZoo(Animal animal){
this.animal = animal;
}
Animal get() {
return animal;
}
}
class DogZoo extends AnimalZoo {
Dog dog;
DogZoo(Dog dog){
super(dog);
this.dog = dog;
}
// Animal のサブクラス Dog を返せる
@Override
Dog get() {
return dog;
}
}
配列
配列も共変性を持つ。
class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
Animal[] animals = {};
Dog[] dogs = {};
Cat[] cats = {};
// 共変性(Animal[] ◀️ Dog[])
animals = dogs;
// 共変性(Animal[] ◀️ Cat[])
animals = cats;
// コンパイルエラー(Dog[] ◀️ Cat[])
dogs = cats;
配列は共変性である
super
/ 下限境界ワイルドカード
ジェネリクスの宣言にsuper
キーワードを使用することで、実行時に許容する型引数を特定のサブクラスのスーパークラスに制限することができる。この制限のことを下限境界(lower bound)と言う。
super
はextends
キーワードと異なりクラスの宣言に使用することはできず、必ずワイルドカードと併せて使用する(下限境界ワイルドカード)。
<? super T>
で表現する。
class Animal { }
class Dog extends Animal { }
class Cat extends Animal { }
class Zoo<T> {}
// class Zoo<T super Dog> {} や、
// class Zoo<T super Cat> {} という使い方はできない
Zoo<Animal> animalZoo = new Zoo<>();
// Zoo<? super Dog> ◀️ Zoo<Animal>
Zoo<? super Dog> dogZoo = animalZoo;
// Zoo<? super Cat> ◀️ Zoo<Animal>
Zoo<? super Cat> catZoo = animalZoo;
// コンパイルエラー(不変性)
Zoo<Dog> dogZoo = animalZoo;
// コンパイルエラー(不変性)
Zoo<Cat> catZoo = animalZoo;
List
でも確かめてみる。
List<Animal> animals = new ArrayList<>();
// OK
List<? super Dog> dogs = animals;
// OK
List<? super Cat> cats = animals;
// コンパイルエラー(不変性)
List<Dog> dogs = animals;
// コンパイルエラー(不変性)
List<Cat> cats = animals;
反変性
Contravariance
型引数がその型のスーパータイプに対して互換性を持つ性質。スーパータイプにはインターフェースも含む。
extends
キーワードによって共変性が実現されたように、super
キーワードを使用することで反変性を実現することができる。
先程のsuper
でのコードを反変性という観点でもう一度見てみる。
class Animal { }
class Dog extends Animal { }
class Cat extends Animal { }
class Zoo<T> {}
Zoo<Animal> animalZoo = new Zoo<>();
// Dog のスーパークラス型も許容できるよう柔軟性が加わる
Zoo<? super Dog> dogZoo = animalZoo;
// Cat のスーパークラス型も許容できるよう柔軟性が加わる
Zoo<? super Cat> catZoo = animalZoo;
下限境界ワイルドカードは主に書き込み専用の操作に使用される。
class Animal {}
class Dog extends Animal {}
class Zoo<T> {
T animal;
void set(T animal) {
this.animal = animal;
}
T get() {
return animal;
}
}
// 反変性
Zoo<? super Dog> animalZoo = new Zoo<Animal>();
// 読み取りができない(コンパイルエラー。Animal ▶️ Dog のダウンキャストは暗黙的に行われない。)
Dog dog = animalZoo.get();
// 読み取りができない(コンパイルエラー)
Animal animal = animalZoo.get();
// 型安全が保証されないためObject形で受け取る必要がある
Object obj = animalZoo.get();
// 書き込みはできる
animalZoo.set(new Dog());
反変性において読み取りができないのは、読み取り時にスーパークラスのオブジェクトが取得される可能性があり、仮にスーパークラスのオブジェクトが取得された際のダウンキャストが暗黙的に行われないためである。このような状況を「型安全ではない」と言う。
List
でも確かめてみる。
class Animal { }
class Dog extends Animal { }
class Cat extends Animal { }
// 反変性
List<? super Dog> dogs = new ArrayList<Animal>();
// 読み取りができない(コンパイルエラー)
Animal animal = dogs.get(1);
// 読み取りができない(コンパイルエラー)
Animal dog = dogs.get(1);
// 読み取りができない(コンパイルエラー)
Animal cat = dogs.get(1);
// Object形なら読み取り可
Object obj = dogs.get(1);
// 書き込みはできる
dogs.add(new Dog());