Javaを使ってきて、自分の教育資料用のまとめ。
JavaはJDK1.5からジェネリックプログラミングができるようになった。日本語では総称型。C++ではテンプレートと同じような機能。それについてのまとめメモ。
ジェネリックのメリット
- ジェネリックを導入される前のコード。
import java.util.ArrayList; import java.util.List; public class Generic { public static void main(String[] args) { List list = new ArrayList(); list.add("one"); String str = (String) list.get(0); // キャストしないとエラーになる } }
JDK1.4以前だとキャストばっかりになってしまう。キャストエラーは実行時にしか検出できない。
- ジェネリックが導入された後のコード
import java.util.ArrayList; import java.util.List; public class Generic { public static void main(String[] args) { List<String> list = new ArrayList<String>(); list.add("one"); String str = list.get(0); } }
変数の宣言時に型指定をしておくことで、コンパイル時にチェックがかかるので、使用する時に実行時例外やキャストのことを気にしなくてよくなる。
クラスのジェネリック
class Sample<T>{ private T t; void setT(T t){ this.t = t; } T getT(){ return this.t; } public Sample(T t) { this.t = t; } } public class Generic { public static void main(String[] args) { Sample<String> s1 = new Sample<>("s1"); System.out.println("s1:" + s1.getT().getClass()); // -- A s1:class java.lang.String Sample<Integer> s2 = new Sample<>(2); System.out.println("s2:" + s2.getT().getClass()); // -- B s2:class java.lang.Integer } }
クラス宣言時に"
メソッドのジェネリック
import java.util.List; class Sample{ <T> T setT(T t){ T returnT; returnT = t; return returnT; } <T> void listT(List<T> t){ // -- A for (T t2 : t) { System.out.println(t2); } } } public class Generic { public static void main(String[] args) { Sample s = new Sample(); System.out.println(s.setT("string").getClass()); //class java.lang.String System.out.println(s.setT(1).getClass()); //class java.lang.Integer System.out.println(s.setT(true).getClass()); //class java.lang.Boolean System.out.println(s.<Boolean>setT(true).getClass()); // 明示的に型引数を渡すことも可能 } }
メソッド単体にもジェネリックは指定できる。動作はクラスの時とほとんど同じだが、その時には戻り値型より前に仮型を"
コンストラクタのジェネリック
class Sample{ public <T> Sample(T t) { System.out.println(t); } } class SubSample extends Sample{ public SubSample(String s) { super(s); } } public class Generic { public static void main(String[] args) { Sample s1 = new Sample("string"); Sample s2 = new SubSample("substring"); } }
制限付きジェネリック(extends)
import java.io.Serializable; public class Generic<T extends Number & Serializable> { public static void main(String[] args) { Generic<Integer> sample = new Generic<>(); sample.show(10); Generic.<Integer>show2(200); } void show(T t){ System.out.println(t.doubleValue()); } static <E extends Number> void show2(E e){ System.out.println(e.byteValue()); } }
仮型に入るクラスを制限できるようにする為に、"extends 型"というキーワードをつける。そうすることで、そのクラスを継承しているものしか使用することができなくなる。"型"の部分はクラス、インターフェースともに指定でき、両方ともextendsというキーワードを使う。クラス、インターフェースを複数指定する場合は、"&"でつなぐ。※ここでもクラスの多重継承はできない
ワイルドカードなジェネリック(?/extends/super)
import java.util.ArrayList; import java.util.List; public class Generic{ public static void main(String[] args) { Generic sample = new Generic(); List<Integer> intList = new ArrayList<>(); intList.add(1); intList.add(2); System.out.println(sample.showList(intList).get(0).getClass()); List<String> stringList = new ArrayList<>(); stringList.add("a"); stringList.add("b"); System.out.println(sample.showList(stringList).get(0).getClass()); List<Number> numberList = new ArrayList<>(); sample.showNumberList(numberList); sample.showNumberList2(numberList); List<Integer> IntegerList = new ArrayList<>(); sample.showNumberList(IntegerList); // sample.showNumberList2(IntegerList); // コンパイルエラー List<Object> objectList = new ArrayList<>(); // sample.showNumberList(objectList); // コンパイルエラー sample.showNumberList2(objectList); } List<?> showList(List<?> t){ return t; } List<? extends Number> showNumberList(List<? extends Number> t){ return t; } List<? super Number> showNumberList2(List<? super Number> t){ return t; } }
使い方がよくないが、実行時まで型が決まらないことはよくある。そのときに使うのが、ワイルドカード"?"である。その際に"extends"と"super"というキーワードが使用できる。"extends"はワイルドカードの型がextendsで指定された型、もしくはそのサブクラスでないとコンパイルエラーとなる。"super"はワイルドカードの型がsuperで指定された型、もしくはそのスーパークラスでないとコンパイルエラーとなる。
- | ||
Integer | ○ | コンパイルエラー |
Number | ○ | ○ |
Object | コンパイルエラー | ○ |
※ 継承関係:Object - Number - Integer