はじめに
現在の開発実務ではKotlin
を利用しているものの、たびたびJava
の知識が必要に感じることがある。
(定義ジャンプしたときとか、Kotlin
が便利すぎて色々省略されていて、「なんでこうなってんの??」って思うときとか、、そう言うとき)
なのでJava
についても理解を深めたい。
ジェネリクスとは
- クラス宣言時:そのクラス内で利用する要素の「仮の型名(Eなど)」を使っておく
- クラス利用時:「仮の型名(Eなど)」を
String
などの「実際の型」に置き換えて利用する
と言う仕組み
ジェネリクスのメリット
型を限定しないため汎用的なクラスを定義することができるとともに、クラスを利用する際は利用者自身で型安全を確保することができる。
ジェネリクスの注意点
- ジェネリクスの型に
int
などの基本データ型は利用不可(例:Generics<int>
コンパイルエラーになる) - ジェネリクスを用いたクラスの配列を作ることはできない。
-
Throwable
の子孫クラス(例外クラス)では、ジェネリクスを用いることができない
実際にコードを動かして確認してみる
ジェネリクスを使った例
// このクラスでは E の型は利用する際に決めることする
public class Generics<E> {
private E obj;
public Generics(E obj) {
this.obj = obj;
}
public E getObj() {
return obj;
}
}
class Main {
public static void main(String[] args) {
// 利用する際に E を String として利用することに決める
Generics<String> generics = new Generics<>("2021");
String obj = generics.getObj();
System.out.println(obj); // 出力結果: 2021
}
}
ジェネリクスを使わずにObject型
で同じことをしてみる
public class NoGenerics {
// ジェネリクスを利用せずに Object型 で利用する
private Object obj;
public NoGenerics(Object obj) {
this.obj = obj;
}
public Object getObj() {
return obj;
}
}
class Main {
public static void main(String[] args) {
// String型の引数obj としてインスタンス生成する
NoGenerics noGenerics = new NoGenerics("2021");
// キャストして取得する。キャストしないと String として利用できない。
String obj = (String) noGenerics.getObj();
System.out.println(obj); // 出力結果: 2021
}
}
ジェネリクスを利用する場合に比べて、これの何が問題か? というのを考えてみると。
- キャストが面倒くさい
- 誤ったキャストによるエラーが発生する危険性がある(
ClassCastException
)
2のエラー危険性があることが非常によろしくない。開発時にコンパイルエラーで危険性を教えてくれない。
ClassCastException になってしまう例
// ジェネリクスを利用しないクラス
public class NoGenerics {
// Object型 だからなんでも入れられちゃう
private Object obj;
public NoGenerics(Object obj) {
this.obj = obj;
}
public Object getObj() {
return obj;
}
}
class Main {
public static void main(String[] args) {
// String型の引数obj としてインスタンス生成する
NoGenerics noGenerics = new NoGenerics("2021");
// もしこの引数を Integer型 だと勘違いして利用しようとすると当然エラーが発生する
Integer obj = (Integer) noGenerics.getObj(); // ここで ClassCastException !!!
System.out.println(obj);
}
}
この例だと簡単な例なのでString
をInteger
に勘違いして利用しようとすることはイメージしづらいかもないものの、実際の開発で色々な処理が絡んでくると実際にあり得ることである。
実型引数に継承関係に基づく制限をかける
以下のようにすることで継承関係に基づいて制限をかけることができる
Generics<? extends E> // E もしくは、 Eの子孫クラス
Generics<? super E> // E もしくは、 Eの先祖クラス
最後に
ジェネリクスなんて今更かもしれませんが、改めて勉強になりました。