【Java】総称型、汎用プログラミング

2018年3月11日

本稿は、総称型を利用し、汎用プログラミングを実現する方法を説明します。

総称型を利用すると、一つの機能でいろんな型を扱えるクラスやメソッドを定義できます。

突き詰めると、非常に難易度が高く、わけのわからないプログラムができやすいですが、利用できるようになると、

ArrayList等の可変型の配列やJava8から導入された、StreamAPIの利用方法がある程度わかるようになってきます。

初心者の方も総称型クラスの利用までは理解しておいたほうがいいでしょう。

総称型クラスの定義

総称型クラスは、クラス名<汎用型名>と定義します。とりあえずサンプルを見てください。

class Setter<T> {
	T data;
}

この定義では型をTという名前で定義しました。

T型のdataという変数名を定義しました。

このT型というのは、勝手につけた名前であり、呼び出し方によってどんな型にでも変化する型になります。別に名前はEでもDでも構いません。

総称型クラスの利用

総称型クラスの利用は「クラス名<使いたい型> 変数名 = new クラス名<使いたい型>();」と定義しましょう。

「クラス名<使いたい型> 変数名 = new クラス名<>();」と省略することもできます。

public class Test{
	public static void main(String[] args){
		//String型で利用してみる
		Setter<String> s = new Setter<String>();
		s.data = "10";
		System.out.println(s.data);
		
		//Boolean型で利用してみる
		Setter<Boolean> s2 = new Setter<Boolean>();
		s2.data = true;
		System.out.println(s2.data);
	}
}

class Setter<T> {
	T data;
}
実行結果

10
true

この例では、T型をString型として扱うパターンとBoolean型として利用するパターンを試していますが、他のどのような参照型でも使用することができます。

基本型は、通常利用することができないのですが、各基本型に対応するラッパークラスを利用することで対応可能になります。

総称型の基本はこれだけです。

CollectionAPIやStreamAPIはこの機能をふんだんに利用して、できているのですね。

メソッドに対して総称型を利用する

メソッドのみに総称型を定義することもできます。

「<汎用型名> 戻り値 メソッド名(引数)」と定義しましょう。

引数には汎用型を指定可能になります。

汎用メソッドの呼び出しは、「変数(staticのときはクラス名).<汎用型名>メソッド()」と定義しましょう。

また、引数に汎用型を使用している場合は型推論してくれるので、省略可能です。

省略可能な場合は通常のメソッド呼び出しと変わりません。

public class Test{
	public static void main(String[] args){
		proc(350);
		proc(true);
	}
	
	static <T> void proc(T data){
		System.out.println(data);
	}
}
実行結果

350
true

総称型の複数定義

クラスやメソッドの総称型は複数定義が可能です。

複数の汎用型を扱う場合は「<汎用型名,汎用型名…>」とカンマ区切りで定義します。

それではサンプルをご覧ください。

public class Test{
	public static void main(String[] args){
		Object num = 350;
		Integer data = Test.<Object,Integer>proc(num);
		System.out.println(data);
	}
	
	static <T,E> E proc(T data){
		return (E)data;
	}
}
実行結果

350

少し複雑さが増しましたが、汎用型を二つ使って、特定の型を別の型に変換する処理を実行します。

<T,E>を定義していますが、T型をE型に変換する機能を実現しているわけです。

今回の例だと、Object型をInteger型に変換しようとしています。

総称型クラス指定制限

総称型クラスを定義する場合に「T extends クラス名」と定義することで、指定できる型の制限が可能です。

「T extends クラス名」はT型に指定できる型は右辺クラスか右辺クラスを継承したクラスのみということを指定できます。

それでは例を見てみましょう。

public class Test{
	public static void main(String[] args){
		//new Data<GameObject>();コンパイルエラー
		new Data<Chara>();
		new Data<PlayerChara>();
	}
}
class GameObject{
}
class Chara extends GameObject{
}
class PlayerChara extends Chara{
}
class Data<T extends Chara>{
}

new Data<GameObject>();のパターンではコンパイルエラーが発生しました。

この例のDataクラスの総称型は<T extends Chara>と定義しているため、必ずCharaクラスかCharaクラスを継承したクラスでなければなりません。

GameObjectクラスはChara型を継承しているわけではないのでコンパイルエラーとなってしまうのです。

ワイルドカード、総称型使用時の指定制限

総称型クラスを宣言する際にワイルドカードを指定可能です。

「<?>」と記述することで総称型指定をあいまいにできます。

例えば、Data<GameObject> obj = new Data<Chara>();

ではエラーになるのですが、?を利用することで代入可能になります。

public class Test{
	public static void main(String[] args){
		Data<?> data = new Data<GameObject>();
		data = new Data<Chara>();
		data = new Data<PlayerChara>();
	}
}
class GameObject{
}
class Chara extends GameObject{
}
class PlayerChara extends Chara{
}
class Data<T>{
}

宣言時に総称型指定制限をすることができます。

「? extends クラス名」と指定することで、指定できるクラスを右辺のクラスか右辺のクラスを継承したクラスに制限できます。

public class Test{
	public static void main(String[] args){
		//Data<? extends Chara> data1 = new Data<GameObject>();コンパイルエラー
		Data<? extends Chara> data2 = new Data<Chara>();
		Data<? extends Chara> data3 = new Data<PlayerChara>();
	}
}
class GameObject{
}
class Chara extends GameObject{
}
class PlayerChara extends Chara{
}
class Data<T>{
}

「? super クラス名」と指定することで、指定できるクラスを右辺のクラスか右辺のクラスの親クラスに制限できます。

public class Test{
	public static void main(String[] args){
		Data<? super Chara> data1 = new Data<GameObject>();
		Data<? super Chara> data2 = new Data<Chara>();
		//Data<? super Chara> data3 = new Data<PlayerChara>();コンパイルエラー
	}
}
class GameObject{
}
class Chara extends GameObject{
}
class PlayerChara extends Chara{
}
class Data<T>{
}

総称型はこねくり回されるとソースが一気に見にくくなってしまいますが、落ち着いて一つ一つ見ていけばわかるので、恐れる必要はありません。

・・・と言ってみたはいいものの、自分も多すぎると訳が分からなくなるんだよねぇ・・・

5,6個以上指定とか総称型の3重以上のネストは勘弁してほしいですねぇ・・・

Java

Posted by nompor