概要
ジェネリクスは引数
だよってことです。
ジェネリクスについていまいちしっくり来てなかった時に、「あーなるほどー」って思えた考えをまとめときます。
参考にしたサイト
先人たちの知恵に感謝です。
http://d.hatena.ne.jp/Nagise/20101105/1288938415
Javaの環境
java version 1.8.0_181
Java(TM) SE Runtime Environment (build 1.8.0_181-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.181-b13, mixed mode)
src
└example
└java
└generics
├ Main.java
└ GenericsSample.java
値の引数といえばメソッドですよね
丸括弧 ()←これのことです
メソッドといえばこの丸括弧 ()
です。
この丸括弧()は、メソッド宣言の場所と、メソッド呼び出しの場所で同じ記号なんだけど違う意味で使ってます。
「メソッドで使う値」を示すための情報ですね。
package example.java.generics;
public class Main {
public static void main(String[] args) {
int result = plus(2, 5); // ここで 「()」は「この引数の値でメソッドを実行しますよ」という宣言(実引数)
System.out.println(result);
}
public static int plus(int T, int S) { // ここで 「()」は「この引数名で引数を受け取りますよ」という宣言(仮引数)
return T + S;
} // T, S の 範囲ここまで
}
型の引数といえばジェネリクスですよね
山括弧 <>←これのことです
じゃあ山括弧<>
といえば・・・?そう、ジェネリクスです。
この山括弧<>は、ジェネリクス宣言の場所と、ジェネリクス呼び出し(あえてこういう言い方してます)の場所で同じ記号なんだけど違う意味で使ってます。
「クラスやメソッドで使う型」を示すための情報ですね。
package example.java.generics;
public class GenericsSample {
// ここで 「<>」 は「この引数名で型を受け取りますよ」という宣言(仮引数)
public <T> void callGenerics(T value){
System.out.println(value); // 引数の値は何かな?
System.out.println(value.getClass().getName()); // 引数の型は何かな?
} // T の 範囲ここまで
}
package example.java.generics;
public class Main {
public static void main(String[] args) {
GenericsSample genericsSample = new GenericsSample();
// ここで 「<>」 は「この型の値でメソッドを実行しますよ」という宣言(実引数)
// ここで 「()」は「この引数の値でメソッドを実行しますよ」という宣言(実引数)
// 違う種類の実引数をそれぞれの記号で指定しているだけ
genericsSample.<Integer>callGenerics(1234);
}
}
型の値と、引数で渡した値の型(わかりづらいですね、、)が違う場合
package example.java.generics;
public class Main {
public static void main(String[] args) {
GenericsSample genericsSample = new GenericsSample();
genericsSample.<String>callGenerics(1234);
}
}
型 → 値の順番で書くよね
そもそもJavaでは
//型 変数の順番
String variable;
のように「型 → 順番」で書くので、型を表す引数のジェネリクス・値を表す引数の順番も「ジェネリクス → 値」になるのも納得感がある。
// 型 → 値
// voidの前に型を表すジェネリクスが来ているのは、戻り値の型がジェネリクスで指定した引数になる可能性(この場合は「T」)があるから(たぶん)
public <T> void callGenerics(T value)
// 型 → 値
genericsSample.<String>callGenerics(1234);
クラスに指定するジェネリクス
メソッドと同じく、クラスに「この引数名で型を受け取りますよ」という宣言(仮引数) がついているだけです。
package example.java.generics;
// ここで 「<>」 は「この引数名で型を受け取りますよ」という宣言(仮引数)
public class GenericsSample<T> {
// クラスで指定した型の引数T のスコープがクラス内のメソッドまで続いている
public void callGenerics(T value){
System.out.println(value); // 引数の値は何かな?
System.out.println(value.getClass().getName()); // 引数の型は何かな?
}
} // <T> の 範囲ここまで
package example.java.generics;
public class Main {
public static void main(String[] args) {
// ここで 「<>」 は「この型の値でクラスをインスタンス化しますよ」という宣言(実引数)
// new GenericsSample<Integer> は new GenericsSample<> に省略しても大丈夫(GenericsSample<Integer> なんだからIntegerなんでしょ?と推論してくれる)
GenericsSample<Integer> genericsSample = new GenericsSample<Integer>();
// ここで 「()」は「この引数の値でメソッドを実行しますよ」という宣言(実引数)
genericsSample.callGenerics(1234);
}
}
もちろん違う型をメソッドに指定すると。。。
package example.java.generics;
public class Main {
public static void main(String[] args) {
// ここで 「<>」 は「この型の値でクラスをインスタンス化しますよ」という宣言(実引数)
// new GenericsSample<Integer> は new GenericsSample<> に省略しても大丈夫(GenericsSample<Integer> なんだからIntegerなんでしょ?と推論してくれる)
GenericsSample<Integer> genericsSample = new GenericsSample<Integer>();
// ここで 「()」は「この引数の値でメソッドを実行しますよ」という宣言(実引数)
genericsSample.callGenerics("test");
}
}
<>がクラスにもメソッドにも同じ引数名でついてると。。
package example.java.generics;
// ここで 「<>」 は「この引数名で型を受け取りますよ」という宣言(仮引数)
public class GenericsSample<T> {
// ここで 「<>」 は「この引数名で型を受け取りますよ」という宣言(仮引数)
// クラスで指定した型の引数T のスコープがクラス内のメソッドまで続いている ... ??
public <T> void callGenerics(T value){
System.out.println(value); // 引数の値は何かな?
System.out.println(value.getClass().getName()); // 引数の型は何かな?
}
} // <T> の 範囲ここまで ... ??
package example.java.generics;
public class Main {
public static void main(String[] args) {
// ここで 「<>」 は「この型の値でクラスをインスタンス化しますよ」という宣言(実引数)
// new GenericsSample<Integer> は new GenericsSample<> に省略しても大丈夫(GenericsSample<Integer> なんだからIntegerなんでしょ?と推論してくれる)
GenericsSample<Integer> genericsSample = new GenericsSample<Integer>();
// ここで 「()」は「この引数の値でメソッドを実行しますよ」という宣言(実引数)
genericsSample.callGenerics("test");
}
}
この原因は、ここで 「<>」 は「この引数名で型を受け取りますよ」という宣言(仮引数) を
- クラス宣言時に
- メソッド宣言時に
と、同じ引数名で指定してしまったため。メソッド内では新しいローカルの型の変数で上書きしてしまった。
↓のような感じ。
package example.java.generics;
// ここで 「<>」 は「この引数名で型を受け取りますよ」という宣言(仮引数)
public class GenericsSample<T> {
// クラス内では、クラスのインスタンス時に指定したジェネリクスの実引数(new GenericsSample<Integer> のInteger)が有効
// クラスで指定した型の引数T のスコープがメソッド時に指定したジェネリクスの実引数(今回はString)で上書きされてしまった
// genericsSample.callGenerics("test"); の"test" から 「"test"はStringだから<T> のTはStringだ!」と推論された)
public <T> void callGenerics(T value){
System.out.println(value); // 引数の値は何かな?
System.out.println(value.getClass().getName()); // 引数の型は何かな?
}
}
staticなクラス宣言時には指定できないの?
できません。
staticじゃないクラスの時は
// ここで 「<>」 は「この型の値でクラスをインスタンス化しますよ」という宣言(実引数)
// new GenericsSample<Integer> は new GenericsSample<> に省略しても大丈夫(GenericsSample<Integer> なんだからIntegerなんでしょ?と推論してくれる)
とできましたが、staticクラスの時はクラスのインスタンス化とかできないですよね?
実引数を指定して作り出すタイミングがないので無理です。。
(でもstaticなメソッドの単位なら呼び出し時に実引数指定できるからジェネリクスの型引数を指定できるよ!)
結論
ただの引数だからあわてずに。