目次
Generics(ジェネリクス・総称型)とは
Genericsとは、クラスやメソッドなどの型を、パラメータとして定義できるようにした機能のことです。
「<>」の中に具体的な型名を付けて利用できます。
Genericsクラスを定義する方法
Genericsクラスを定義するには、「パラメータ化された型」と「型変数」の2つを使います。
パラメータ化された型とは、型パラメータリストをもったクラスのことです。
クラスの宣言時に、クラス名の後ろに「< 型変数 >」という形で、型パラメータを記述します。
記述方法は次の通りです。
class クラス名< 型変数 > { //処理 }
パラメータ化されたクラスを使う場合は、インスタンス化する時に型の指定が必要です。
例えば、String型にする場合は、以下のように記述できます。
GenericsClass gc = new GenericsClass();
Genericsメソッドを定義する方法
ジェネリクスメソッドを定義するには、Genericsクラスで定義した型変数を、引数や戻り値に利用しましょう。
先ほどの、GenericsClassで言えば次のように記述可能です。
public class GenericsClass<String> { private String type; public GenericsClass(String type) { this.type = type; } public String getType() { return type; } }
Genericsの使い方
ここまで、Genericsの定義について紹介しました。
言葉で説明されても難しいと思うので、実際に使い方を見ていきましょう。
例えば、次のように記述できます。
■記述例 public class Main { public static void main(String[] args) { GenericsClass<String> gcStr = new GenericsClass<String>("PotepanStyle"); String str = gcStr.getT(); System.out.println(str); GenericsClass<Integer> gcInt = new GenericsClass<Integer>(123456); Integer i = gcInt.getT(); System.out.println(i); } } class GenericsClass<T>{ private T t; public GenericsClass(T t){ this.t = t; } public T getT(){ return t; } }
GenericsClassクラスのクラス名の後に「<T>」と記述しました。
<T>を指定すると、データ型の指定が「Object型」から「T型(型変数)」へ変更できます。
そして、Mainクラスのmainメソッドで、GenericsClassクラスをインスタンス化するときに、データ型をString型とInteger型で指定しています。
Genericsを使うので、GenericsClassクラスのオブジェクトを呼び出す「getTメソッド」をキャストすることなく、指定した方で値の代入が可能です。
上記のプログラムを実行すると、次の結果を取得できます。
■実行結果 mbp:Desktop potepan$ java Main PotepanStyle 123456
それぞれ、String型とInteger型で使えているのがわかります。
Genericsの「T」とは?
前章のプログラムで「<T>」という表現がありました。
この T とは、Generics型の変数名に使われるネーミングです。
T は Type の略称です。
T以外にも、次のようなネーミングがよく使われます。
- E:Element
- K:Key
- V:Value
- T:Type
- N:Number
- S,U:2、3番目
慣習的に使われるものなので、覚えておくと良いでしょう。
実際の現場やプロジェクトでも、共通のネーミングを使うことで混乱を避けられるはずです!
Genericsのワイルドカード型について
ここでは、Genericsのワイルドカード型について見ていきましょう。
例として、以下のプログラムを用意しました。
■記述例 import java.util.ArrayList; import java.util.Collection; import java.util.List; public class Main { public static void main(String[] args) { List list = new ArrayList<>(); list.add("Potepan"); list.add("Style"); printStr(list); } public static void printStr(Collection<String> col) { for (String s : col) { System.out.print(s); } System.out.println(); } } ■実行結果 mbp:Desktop potepan$ java Main PotepanStyle
printStrメソッドで、Collection型のコレクションを受け取った上で、その要素を出力しているプログラムです。
Collectionは、Genericsを使って型の指定を行っています。
しかし、使う際にはGenericsの型であるTごとに使う必要があり、少し不便です。
仮に、Integer型で扱いたい場合は、それぞれ別個で扱わないといけません。
そこで、便利なのがワイルドカード型を使った方法です。
ワイルドカードを「?」で表現し、「Collection<?>」します。
そうすることで、任意の型のコレクションとして扱えるのです。
先ほどのプログラムを書き換えて実行してみます。
■記述例 import java.util.ArrayList; import java.util.Collection; import java.util.List; public class Main { public static void main(String[] args) { List listStr = new ArrayList<>(); listStr.add("Potepan"); listStr.add("Style"); printStr(listStr); List listInt = new ArrayList<>(); listInt.add(12345); listInt.add(67890); printStr(listInt); } public static void printStr(Collection<?> col) { for (Object o : col) { System.out.print(o); } System.out.println(); } } ■実行結果 mbp:Desktop potepan$ java Main PotepanStyle 1234567890
このように、String型でもInteger型でも使えるようになります!
Genericsでextendsを使う方法
Genericsでは、型パラメータを宣言するときに、「extends」が使えます。
extendsを使うと、データ型として指定できるものを制限可能です。
例えば、<T extends P>と宣言した場合、データ型として指定できる型は、クラスPを継承したデータ型となります。
extendsで指定できるクラスは1つだけです。
複数指定する際は「&」でつなげることでインタフェースを指定できます。
class クラス名<型パラメーター extends クラスorインタフェース & インタフェース & ・・・> { クラス本体 }
では、実際にプログラムを実行してみましょう!
次のように記述できます。
■記述例 public class Main { public static void main(String[] args) { GenericsClass<Integer> gc = new GenericsClass<Integer>(); gc.setValue(123456); Integer i = gc.getValue(); System.out.println(i); } } class GenericsClass<T extends Number> { private T t; public void setValue (T t) { this.t = t; } public T getValue() { return t; } }
上記の例では、TはNumber型を継承したデータ型だけを扱えるようになります。
そのため、Integer型は使えますが、String型は使えません。
String型を指定した場合、コンパイルエラーになります。
上記のプログラムを実行した結果は次の通りです。
■実行結果 mbp:Desktop potepan$ java Main 123456
Integer型として出力されているのがわかります。
このように、扱えるデータ型を制限したい場合に、extendsは便利な機能です!
Genericsでsuperを使う方法
Genericsでsuperを使うことで、extendsとは逆の動作を実現することも可能です。
例えば、次のように記述できます。
■記述例 public class Main { public static void main(String[] args) { GenericsClass<Number> gc1 = new GenericsClass<Number>(123456); GenericsClass<? super Integer> gc2; gc2 = gc1; Object obj = gc2.getValue(); System.out.println(obj); } } class GenericsClass<T> { private T t; public GenericsClass(T t) { this.t = t; } public T getValue() { return t; } }
gs1というNumber型で指定したインスタンスと、<? super Integer>で指定したgs2インスタンスを用意します。
そして、gs1をgs2に代入する際に、Object型の変数「obj」を使いました。
上記のプログラムを実行すると、次のように表示されます。
■実行結果 mbp:Desktop potepan$ java Main 123456
Genericsでメソッドで使う方法
「Genericsメソッドを定義する方法」を紹介したように、メソッドでもGenericsは使用可能です。
使う際は、次のように記述しましょう。
■記述例 public class Main { public static void main(String[] args) { String str = getT("PotepanStyle"); System.out.println(str); Integer i = getT(123456); System.out.println(i); } private static <T> T getT(T t){ return t; } } ■実行結果 mbp:Desktop potepan$ java Main PotepanStyle 123456
Genericsクラスと同じように、String型とInteger型で取得できているのがわかります!
まとめ
JavaのGenericsについて、クラス・メソッドの定義方法や使い方を解説しました。
Genericsは、Integer型やString型といったさまざまなデータ型で、同じ処理を実行したい場合に便利です。
プログラム実行時のエラーが発生する危険がなくなるのも、メリットと言えます。
ぜひこの記事を参考に、Genericsの使い方を覚えてください!