Javaを書いていたら<T>こんな感じのコードが出てきて戸惑ったことはありませんか?
私は初めてこの記号のようなものを見て、「は?」となったのを覚えています。
<>といのはダイヤモンド演算子というもので、そしてTは型パラメータというものとなっています。
名前だけ聞いてもわからないと思いますので、この記事でさらに詳しく解説していきます。
総称型(ジェネリクス)とは?
先ほど紹介したダイヤモンド演算子などを使用して実現する仕組みとしてジェネリクスというものがあります。
これはデータの型を実行時に臨機応変に決定することができる仕組みです。
実際のコードを見ていただいた方が分かりやすいと思います。
【コード】
public class Gene<T> {
private T originalString;
public Gene(T original) {
this.originalString=original;
}
public T getString() {
return originalString;
}
}
public class MainString<T> {
public static void main(String[] args) {
Gene<String> ex= new Gene<String>("文字列を与えます");
System.out.println(ex.getString());
}
}
【実行結果】
文字列を与えます
上記はGeneクラスでは基本的に型をTで指定してることが分かるかと思います。
そしてmainメソッドでGeneクラスをインスタンス化する際にString型を指定しています。
これによりGeneクラスで指定したTという文字全てがString型に置き換わるというイメージを持っていただければ大丈夫です。
なぜこんな面倒なことをするのか?と疑問に感じた方もいるのではないでしょうか?
これをすることでこの臨機応変に型を変更することができるからです。
上記のmainメソッドを下記のように変更した場合、エラー等発生せずに出力が問題なく実施されることが分かるかと思います。
【コード】
public class MainString<T> {
public static void main(String[] args) {
Gene<Integer> ex= new Gene<Integer>(100);
System.out.println(ex.getString());
}
}
【実行結果】
100
このように参照元のクラスを変更することなく、実行時に型を指定することができるのがジェネリクスというものです。
またそれ以外にもList型を見ていただいたらよりこのジェネリクスの利点が分かっていただけるかと思います。
List list = new ArrayList();
上記のようにList型のクラスを宣言したとします。
この変数に仮に100、”文字列”、12.4といった3つの型の変数を代入した場合、複数の型が混在したList型となってしまいます。
これではList内の要素を取り出す際に下記のように間違った型で取り出しを行って例外が発生してしまう可能性があります。
【コード】
import java.util.ArrayList;
import java.util.List;
public class MainList {
public static void main(String[] args) {
List list=new ArrayList();
list.add(100);
list.add("文字列");
list.add(12.4);
int a1=(int)list.get(0);
int a2=(int)list.get(1);
int a3=(int)list.get(2);
System.out.println(a1);
System.out.println(a2);
System.out.println(a3);
}
}
【実行結果】
Exception in thread "main" java.lang.ClassCastException:
こういったことを防ぐ目的としてジェネリクスという機能があり、下記のようにダイヤモンド演算子などを追加するだけで、型が間違っていた場合はコンパイルエラーが発生するため早急に間違いに気づくことができます。
List<Integer> list=new ArrayList<>();
型推論について
次に型推論について解説していきます。
型推論というのは先ほどの解説でも少し使用していましたが、型パラメータを記述しない方法です。
List<Integer> list=new ArrayList<Integer>();
上記のような記述方式を下記のように変更数方法です。
List<Integer> list=new ArrayList<>();
これの何がいいのかというと単純にコードの記述量が減るというものです。
変数宣言で使用する型を基に、インスタンス化時の型を推測してくれるため変数の宣言時のみ型を記述するだけでいいというものです。
必ずこういった記載方法をしなければならないというわけではありませんが、こういった機能があるということを覚えて置いたらいざ他人が書いたコードを見た際に慌てなくて済みます。
総称型の注意点
最後に総称型のクラスを宣言する際の注意点のようなものを解説しておきます。
通常の変数ですと下記のように親クラスの型の変数にサブクラス型でインスタンス化を実施することは可能でした。
List list=new ArrayList();
しかしジェネリクスではこれが少し異なってきます。
Aクラスを継承したBクラスというものがあったとします。
そして下記のようにインスタンス化を実施したとします。
List<A> list=new ArrayList<B>();
一見問題なく実行できそうですが、実行結果は例外がスローされます。
ジェネリクスはサブクラス型の代入を実施することができない非変性という性質があります。
なぜこのような性質があるかというと、仮に下記のようにクラスA、B、Cとあったとします。
public class A {ならんかの処理}
public class B extends A {何らかの処理}
public class C extends A {何らかの処理}
このクラスを使用して下記のようにインスタンス化を実施したとします。
List<A> list = new ArrayList<B>();
この変数listにC型の変数を追加すると実行時に例外が発生することとなります。
理由としてクラスCはクラスAを継承はしていますが、クラスBを継承していないことが原因です。
そのため継承関係のないクラスBにクラスCの値を追加しようとしたため実行時に例外が発生すると考えられます。
これらの理由から基本的にジェネリクスの型パラメータは同じものしか代入することができないようになっています。
まとめ
この記事ではJavaの総称型(ジェネリクス)について解説してきました。
- 総称型(ジェネリクス)とは?
- 型推論について
- 総称型の注意点
<>はダイヤモンド演算子といい、総称型のクラスを宣言する際に使用します。
これによりクラス作成時には型を決めなくても、そのクラスをインスタンス化する際などに型を決めることが可能となります。
見た目はよくわからない記号ですが、何度か使用していると慣れてきますので、ぜひこれから使い慣れていってください。
以上で今回の解説は終了とします。
コメント