ポイントというか心にとまったことのアウトプットです。
※個人的な重要度のバイアスがかかっているので、「べつにいいかー」と思ってたりすることはメモしてません。
※手を動かして理解したことなど、間違いを含む可能性があります。
※手元の環境は断りがなければ基本はjava 7 をIntelliJ IDEA Ultimate on OSX で動かしてます。
「ジェネリック型」
型名<型名> で新しい型を作るイメージ(あくまでイメージ)
- 同様のことをしたければ、型名<型名>の組み合わせごとにクラスを作るか、型名
- 後者は要素を下位クラスへのダウンキャストする必要があるため、実行時例外が発生する可能性あり
型パラメータ E(Element), T(Type), K(Key), V(Value)
型パラメータ、型変数、型引数、パラメータ化された型
- 型パラメータは定義
- 型変数は、ジェネリック定義内での変数
- 型引数は、具体的に渡す型
- パラメータ化された型は、
List<String>
など
型変数は、インスタンスフィールド・メソッド系で使える(クラスフィールド・メソッド系では使えない)
内部的に
List<Integer>
、List<String>
は、List<Object>
を使い回しているだけ- 内部的にはObject型のため、ジェネリック内部で、new E() はできない(EはObject型のため)
境界のある型パラメータ
Hoge<E extends CharSequence>
など- 境界型
Hoge<E extends Interface1 & Class1 & Interface2>
という風に複数指定も可能
ジェネリックメソッド(コンストラクタ)
- ジェネリックメソッドとジェネリック型は独立している。
- ↑で型パラメータ名が同じだとしても、別の型変数として扱われる
public class Gene1<T> {
private T elem = null; //ジェネリック型定義の型変数が利用可能
public void set(T arg){ //ジェネリック型定義の型変数が利用可能
elem = arg;
}
public T get(){ //ジェネリック型定義の型変数が利用可能
return elem;
}
}
public class Gene2<T> {
private T elem = null;
public <T> void set(T arg){
// elem = arg; //コンパイルエラー
}
public <T> T get() {
// return elem; //コンパイルエラー
return null;
}
}
Gene1<String> g1 = new Gene1<>();
g1.set("test");
System.out.println(g1.get()); //test
Gene1とGene2の例から、ジェネリック型の型変数は、クラス全体で利用可能、ジェネリックメソッドの型変数はメソッド内でのみ利用可能で、ジェネリック型の型パラメータ名と同じ型パラメータをとることもできるが、名前の重複で判断できないときはコンパイルエラーになる
ダイアモンド演算子(java7から)
パラメータ化された型が複雑になる場合(入れ子など)、新しい型をつくったほうがいいかも
ジェネリックメソッドの型は
<型引数>
で指定しなくても、推論される(指定することもできる)型引数のワイルドカード
?
List<CharSequence> l = new ArrayList<String>(); //これはコンパイルエラー
List<? extends CharSequence> l = new ArrayList<String>(); //これはOK
- ↑のListに対して、StringやStringBuilderで入出力はできない
- できるのはCharSequenceでの要素の取り出しのみ
- ↑の問題を解決するために
List<? super CharSequence>
を使うと、StringやStringBuilderでの入力が可能になる
List<? super CharSequence> lst = new ArrayList<>();
lst.add("String");
lst.add(new StringBuilder("SB"));
// CharSequence s = lst.get(0); //なぜコンパイルできない?
↑のget()ができないのがわからない。。。