ソフトウェアエンジニアの日常の雑記

日々思ったことをまとめます

Java:ジェネリックプログラミング

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
    }
}

クラス宣言時に""のように仮型を宣言しておく。そのクラスを"new"する時にそのクラスがどの型を使用するか指定でき、仮型に指定した型が補完される。"A"と"B"の中身の型がそれぞれ違うことが確認できる。

メソッドのジェネリック

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());  // 明示的に型引数を渡すことも可能
    }
}

メソッド単体にもジェネリックは指定できる。動作はクラスの時とほとんど同じだが、その時には戻り値型より前に仮型を" 戻り値型"のように宣言しておく。呼び出し側はクラスの時とはことなり特別なことはいらない。しかし、あたりまえのことだがその仮型はそのメソッドの中でしか使用できない。使いどころとしては"-- A" のようにコレクション等で使うのがいいかも。

コンストラクタジェネリック

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