ジェネリックは一種の「コード テンプレート」であり、一連のコードでさまざまなタイプに適用できます。
1. ジェネリック医薬品とは何ですか?
- ジェネリックとは、あらゆる型に対応するテンプレート コードを記述することです。
- ジェネリックスの利点は、使用時に型をキャストする必要がなく、コンパイラを通じて型をチェックすることです。
ArrayList<Integer>
ジェネリック型の継承関係に注意してください。上位に変換することはできますList<Integer>
(T
変更できません)。ただし、ArrayList<Integer>
上位に変換することはできませんArrayList<Number>
(T
親クラスになることはできません)。
ジェネリックとは何かを説明する前に、まずJava 標準ライブラリが提供するArrayList
ものを見てみましょう。配列よりも使いやすいため、「可変長」配列とみなすことができます。
実際、これはArrayList
内のObject[]
配列であり、現在割り当てられている長さを格納することで「変数配列」として機能します。
パブリッククラス ArrayList { プライベートObject[]配列; プライベートint サイズ; public void add(Object e) {...} public void Remove(int Index) {...} public Object get(int Index) {...} }
上記のArrayList
ストレージString
タイプを使用する場合、いくつかの欠点があります。
-
強制的な変換が必要です。
-
不便で間違いが起こりやすい。
たとえば、コードは次のように記述する必要があります。
ArrayList list = new ArrayList();
list.add("Hello");
// 获取到Object,必须强制转型为String:
String first = (String) list.get(0);
ClassCastException は「ミスキャスト」しやすいため、発生しやすくなります。
list.add(new Integer(123));
// ERROR: ClassCastException:
String second = (String) list.get(1);
上記の問題を解決するには、別のコードを書くことができます。String
ArrayList
パブリッククラスStringArrayList { プライベートString[]配列; プライベート int サイズ; public void add(String e) {...} public void Remove(int Index) {...} public String get(int Index) {...} }
この方法では、格納されるものString
と取り出されるものが同じである必要があります。String
コンパイラーは put in の型を強制的にチェックするため、強制的に変換する必要はありません。
StringArrayList list = new StringArrayList();
list.add("Hello");
String first = list.get(0);
// 编译错误: 不允许放入非String类型:
list.add(new Integer(123));
問題は一時的に解決されます。ただし、新たな問題は、それを保存したい場合は、別のファイルを作成するInteger
必要があることです。Integer
ArrayList
public class IntegerArrayList { private Integer[]配列; プライベート int サイズ; public void add(Integer e) {...} public void Remove(int Index) {...} public Integer get(int Index) {...} }
実際、他のクラスごとに個別に記述する必要がありますArrayList
。
- LongArrayList
- DoubleArrayList
- 人物配列リスト
- ...
これは不可能です。JDK には何千ものクラスがあり、他の人が作成したクラスを知りません。
新しい問題を解決するには、それをテンプレートに変換する必要がありますArrayList
ArrayList<T>
。コードは次のとおりです。
パブリック クラスArrayList<T> { プライベートT[]配列; プライベート int サイズ; public void add(T e) {...} public void Remove(int Index) {...} public T get(int Index) {...} }
T
任意のクラスを指定できます。このようにして、次のことを実現しました。テンプレートを作成すれば、あらゆる種類のテンプレートを作成できます。ArrayList
// 创建可以存储String的ArrayList:
ArrayList<String> strList = new ArrayList<String>();
// 创建可以存储Float的ArrayList:
ArrayList<Float> floatList = new ArrayList<Float>();
// 创建可以存储Person的ArrayList:
ArrayList<Person> personList = new ArrayList<Person>();
したがって、ジェネリックスはテンプレートとして定義され (例: ArrayList<T>
)、コンパイラが型をチェックするためArrayList<类型>
に。
ArrayList<String> strList = new ArrayList<String>();
strList.add("hello"); // OK
String s = strList.get(0); // OK
strList.add(new Integer(123)); // compile error!
Integer n = strList.get(0); // compile error!
このようにして、1 回の書き込みとユニバーサル マッチングを実現するだけでなく、コンパイラを通じて型の安全性も確保します。これはジェネリックです。
上向きの変革
Java 標準ライブラリにインターフェイスArrayList<T>
を実装しList<T>
、次のようにアップキャストできます。List<T>
public class ArrayList<T> implements List<T> {...}
List<String> list = new ArrayList<String>();
つまり、型ArrayList<T>
を にアップキャストできますList<T>
。
特に注意してください。をまたはArrayList<Integer>
に上向きに変換することはできません。ArrayList<Number>
List<Number>
どうしてこれなの?ArrayList<Integer>
上向きに変換できると仮定してArrayList<Number>
、コードを見てください。
// ArrayList<Integer> を作成します type:
ArrayList<Integer> integerList = new ArrayList<Integer>();
// Integer を追加します:
integerList.add(new Integer(123));
// ArrayList<Number > に「アップキャスト」します:
ArrayList<Number> numberList = integerList;
// Float も Number であるため、Float を追加します:
numberList.add(new Float(12.34));
// ArrayList<Integer> からインデックス 1 の要素を取得します (つまり、追加された Float ):
Integer n = integerList.get(1); // ClassCastException!
1 つを型ArrayList<Integer>
にキャストすると、これは のサブクラスであるため、その型を受け入れることができます。しかし、実際には、 と は同じオブジェクト、つまり型であり、型を受け入れることはできないので、それを取得するときに、ArrayList<Number>
ArrayList<Number>
Float
Float
Number
ArrayList<Number>
ArrayList<Integer>
ArrayList<Integer>
Float
Integer
ClassCastException。
実際、このエラーを回避するために、コンパイラはArrayList<Integer>
への変換を許可しませんArrayList<Number>
。ArrayList<Integer> と ArrayList<Number> には継承関係がまったくありません。
2. ジェネリック医薬品を使用する
- ジェネリックスを使用する場合は、ジェネリック パラメーターを必要なクラス タイプ(など
<T>
) に置き換えます。ArrayList<String>
ArrayList<Number>
- コンパイラが自動的に推論できる型は、次のように省略できます
List<String> list = new ArrayList<>();
。 - ジェネリック パラメーターの型が指定されていない場合、コンパイラーは警告を出し、それは型
<T>
としてのみみなされます。Object
- ジェネリック型はインターフェイスで定義でき、このインターフェイスを実装するクラスは正しいジェネリック型を実装する必要があります。
使用時にArrayList
ジェネリック型が定義されていない場合、ジェネリック型は実際には次のようになりますObject
。
// 编译器警告:
List list = new ArrayList();
list.add("Hello");
list.add("World");
String first = (String) list.get(0);
String second = (String) list.get(1);
<T>
現時点では、ジェネリックとしてのみ使用できObject
、ジェネリックの利点は活用されません。ジェネリック型を定義すると<String>
、List<T>
ジェネリック インターフェイスは厳密に型指定されます。List<String>
// 无编译器警告:
List<String> list = new ArrayList<String>();
list.add("Hello");
list.add("World");
// 无强制转型:
String first = list.get(0);
String second = list.get(1);
ジェネリック型を定義すると、ジェネリック インターフェイスは厳密に型指定されます。<Number>
List<T>
List<Number>
List<Number> list = new ArrayList<Number>();
list.add(new Integer(123));
list.add(new Double(12.34));
Number first = list.get(0);
Number second = list.get(1);
コンパイラがジェネリック型を自動的に推論できる場合は、後続のジェネリック型を省略できます。たとえば、次のコードの場合:
List<Number> list = new ArrayList<Number>();
コンパイラはジェネリック型を認識すると、後続のジェネリック型は である必要があるList<Number>
と自動的に推測できるため、コードは次のように省略できます。ArrayList<T>
ArrayList<Number>
// 可以省略后面的Number,编译器可以自动推断泛型类型:
List<Number> list = new ArrayList<>();
汎用インターフェース
ジェネリックスの使用に加えてArrayList<T>
、インターフェイスでもジェネリックスを使用できます。たとえば、Arrays.sort(Object[])
任意の配列を並べ替えることができますが、並べ替えられる要素はComparable<T>
次の汎用インターフェイスを実装する必要があります。
public interface Comparable<T> {
/**
* 返回负数: 当前实例比参数o小
* 返回0: 当前实例与参数o相等
* 返回正数: 当前实例比参数o大
*/
int compareTo(T o);
}
配列は直接ソートできますString
。
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
String[] ss = new String[] { "Orange", "Apple", "Pear" };
Arrays.sort(ss);
System.out.println(Arrays.toString(ss));
}
}
これは、インターフェース自体が実装されているためString
Comparable<String>
です。カスタムPerson
タイプに変更してみると
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
Person[] ps = new Person[] {
new Person("Bob", 61),
new Person("Alice", 88),
new Person("Lily", 75),
};
Arrays.sort(ps);
System.out.println(Arrays.toString(ps));
}
}
class Person {
String name;
int score;
Person(String name, int score) {
this.name = name;
this.score = score;
}
public String toString() {
return this.name + "," + this.score;
}
}
プログラムを実行すると、に変換できないことがわかりますClassCastException
Person
Comparable
。コードを変更してインターフェースをPerson
実装しますComparable<T>
class Person はComparable<Person> を実装します { ... }
上記のコードを実行すると、正しくname
並べ替えることができます。たとえば、score
高い順に並べ替えるなど、比較ロジックを変更することもできます。自分でテストを変更してください。
3. ジェネリックスの作成
- ジェネリックを作成するときは、ジェネリック型を定義する必要があります
<T>
。 - 静的メソッドはジェネリック型を参照できません。静的ジェネリック メソッドを実装するには
<T>
、他の型 (たとえば、 ) を定義する必要があります。<K>
- ジェネリックでは、複数の型を同時に定義できます (例: )
Map<K, V>
。
ジェネリック クラスの作成は、通常のクラスよりも複雑です。一般に、ジェネリック クラスはコレクション クラスでよく使用されます。たとえばArrayList<T>
、ジェネリック クラスを作成する必要があることはほとんどありません。本当にジェネリック クラスを作成する必要がある場合、どのように作成すればよいでしょうか? 以下の手順に従ってジェネリック クラスを作成できます。まず、特定の型に従ってString
クラスを作成します。たとえば、次のようになります。
public class Pair {
private String first;
private String last;
public Pair(String first, String last) {
this.first = first;
this.last = last;
}
public String getFirst() {
return first;
}
public String getLast() {
return last;
}
}
次に、特定のタイプをすべてマークします。これは次のとおりですString
。
public class Pair {
private String first;
private String last;
public Pair(String first, String last) {
this.first = first;
this.last = last;
}
public String getFirst() {
return first;
}
public String getLast() {
return last;
}
}
最後に、String
特定の型を次のように置き換えてT
宣言します。<T>
public class Pair<T> {
private T first;
private T last;
public Pair(T first, T last) {
this.first = first;
this.last = last;
}
public T getFirst() {
return first;
}
public T getLast() {
return last;
}
}
慣れてきたら、T
最初から直接書くこともできます。
静的メソッド
<T>
ジェネリック クラスを作成するときは、ジェネリック型を静的メソッドに使用できないという事実に特に注意してください。
パブリッククラスPair<T> { プライベートTが最初です。 プライベート T 最後; publicpai(T first, T last) { this.first = first; this.last = 最後; } public T getFirst() { ... } public T getLast() { ... }
// 静的メソッドで <T> を使用します:
public static Pain<T> create(T first, T last) { return new Pain<T>(first, last); }
}
上記のコードはコンパイル エラーを引き起こします。メソッド パラメータにジェネリック型を使用したり、静的メソッドの戻り値の型を使用したりすることはできません。create()
T
//
static
modifier の後に 1 つを追加する<T>
とコンパイルは成功しますが、実際には、これはtype<T>
とは何の関係もありません。Pair<T>
<T>
public static <T>ペア<T> create(T first, T last) { return new ペア<T>(first, last); }
静的メソッドの場合は、別の型を使用するだけで、個別に「ジェネリック」メソッドとして書き直すことができます。上記のcreate()
静的メソッドの場合、次のような別のジェネリック型に変更する必要があります<K>
。
// 静态泛型方法应该使用其他类型区分:
public static <K> Pair<K> create(K first, K last) {
return new Pair<K>(first, last);
}
これにより、静的メソッドのジェネリック タイプとインスタンス タイプのジェネリック タイプが明確に区別されます。
-
ジェネリック型はオブジェクトがインスタンス化されるときに決定され、静的メソッドはクラスがロードされるときにオブジェクト インスタンスを作成せずに直接呼び出すことができます。したがって、静的メソッドの戻り値とパラメーターはジェネリック型 <T> に依存できず、静的メソッドのジェネリック型はインスタンス型のジェネリック型と区別する必要があります。
-
さらに、静的メソッドは、クラスのインスタンスではなく、クラス自体に関連付けられます。したがって、クラスのインスタンスが作成された場合でも、静的メソッドはインスタンスのジェネリック型にアクセスできません。
複数のジェネリック型
ジェネリックでは複数の型を定義することもできます。たとえば、Pair
同じ型の 2 つのオブジェクトを常に保存したくない場合は、次の型を使用できます。<T, K>
public class Pair<T, K> {
private T first;
private K last;
public Pair(T first, K last) {
this.first = first;
this.last = last;
}
public T getFirst() { ... }
public K getLast() { ... }
}
使用する場合は、次の 2 つのタイプに注意する必要があります。
Pair<String, Integer> p = new Pair<>("test", 123);
Java 標準ライブラリは、Map<K, V>
2 つのジェネリック型を使用する例です。キーに 1 つのタイプを使用し、値に別のタイプを使用します。
4. 拭き取り方法
- Java のジェネリックスは、wipe メソッドを使用して実装されます。
- ワイプによりジェネリックが決定されます
<T>
。- たとえば、次のような基本タイプにすることはできません
int
。 - ジェネリック型を取得できません
Class
。例:Pair<String>.class
; - ジェネリック型を持つ型は判定できません。例:
x instanceof Pair<String>
; T
型はインスタンス化できません。例:new T()
。
- たとえば、次のような基本タイプにすることはできません
- ジェネリック メソッドでは、メソッドの繰り返し定義を避ける必要があります。たとえば、次のようになります
public boolean equals(T obj)
。 - サブクラスは親クラスのジェネリック型を取得できます
<T>
。
ジェネリックスは「テンプレート コード」に似た技術であり、異なる言語でのジェネリックスの実装は必ずしも同じではありません。Java 言語の一般的な実装方法は Type Erasure です。いわゆるワイプ方式は、仮想マシンが実際にはジェネリックについて何も知らず、すべての作業がコンパイラによって実行されることを意味します。
たとえば、ジェネリック クラスを作成しますPair<T>
。これはコンパイラが認識するコードです。
public class Pair<T> {
private T first;
private T last;
public Pair(T first, T last) {
this.first = first;
this.last = last;
}
public T getFirst() {
return first;
}
public T getLast() {
return last;
}
}
そして、仮想マシンはジェネリックについてまったく知りません。これは仮想マシンによって実行されるコードです
public class Pair {
private Object first;
private Object last;
public Pair(Object first, Object last) {
this.first = first;
this.last = last;
}
public Object getFirst() {
return first;
}
public Object getLast() {
return last;
}
}
したがって、Java はワイピングを使用してジェネリックスを実装し、次のような結果になります。
- コンパイラは型を;
<T>
として扱います。Object
- コンパイラは
<T>
安全なキャストを実装します。
ジェネリックスを使用する場合、作成するコードはコンパイラーが認識するコードでもあります。
Pair<String> p = new Pair<>("Hello", "world");
String first = p.getFirst();
String last = p.getLast();
仮想マシンによって実行されるコードにはジェネリックがありません。
Pair p = new Pair("Hello", "world");
String first = (String) p.getFirst();
String last = (String) p.getLast();
したがって、Java のジェネリックスはコンパイル時にコンパイラによって実装されます。コンパイラは常にすべての型を内部処理T
としてObject
扱います。ただし、変換が必要な場合、コンパイラはT
型に基づいて安全な強制変換を自動的に実行します。
Java ジェネリックの実装 (ワイピング メソッド) を理解すると、Java ジェネリックの制限がわかります。
制限 1:<T>
プリミティブ型にすることはできません。たとえば、int
実際の型は次のとおりであるため、型はプリミティブ型を保持できませんObject
Object
。
Pair<int> p = new Pair<>(1, 2); // compile error!
制限事項 2: ジェネリックスを取得できませんClass
。次のコードを観察してください。
public class Main {
public static void main(String[] args) {
Pair<String> p1 = new Pair<>("Hello", "world");
Pair<Integer> p2 = new Pair<>(123, 456);
Class c1 = p1.getClass();
Class c2 = p2.getClass();
System.out.println(c1==c2); // true
System.out.println(c1==Pair.class); // true
}
}
class Pair<T> {
private T first;
private T last;
public Pair(T first, T last) {
this.first = first;
this.last = last;
}
public T getFirst() {
return first;
}
public T getLast() {
return last;
}
}
はいT
、合計の型を取得するObject
と、同じ型、つまりクラスが取得されるためです。つまり、すべてのジェネリック インスタンスは、型に関係なく、すべてコンパイル後であるため、同じインスタンスを返します。Pair<String>
Pair<Integer>
Class
Class
Pair
Class
T
getClass()
Class
Pair<Object>
制限 3: ジェネリックスを使用して型を決定できない
Pair<Integer> p = new Pair<>(123, 456);
// Compile error:
if (p instanceof Pair<String>) {
}
理由は前述のように存在せずPair<String>.class
、ただ 1 つだけですPair.class
。
T
制限事項 4:型をインスタンス化できない
public class Pair<T> {
private T first;
private T last;
public Pair() {
// Compile error:
first = new T();
last = new T();
}
}
上記のコードは、コンストラクターの 2 行が原因でコンパイルに失敗します。
first = new T();
last = new T();
ワイプ後は実際には次のようになります。
first = new Object();
last = new Object();
このようにして、作成new Pair<String>()
と作成はnew Pair<Integer>()
すべて完了しObject
、明らかにコンパイラーはこの種の間違ったコードを防止したいと考えています。
T
型をインスタンス化するには、追加のClass<T>
パラメーターを使用する必要があります。
パブリッククラスPair<T> { プライベートTが最初です。 プライベート T 最後; public Pair( Class<T> clazz ) { first = clazz.newInstance(); 最後 = clazz.newInstance(); } }
上記のコードは、パラメーターとリフレクションを使用してtype をClass<T>
T
インスタンス化します。使用する場合は、それも に渡す必要がありますClass<T>
。例えば:
Pair<String> pair = new Pair<>(String.class);
インスタンスが渡されるため、それを利用して型をインスタンス化できます。Class<String>
String.class
String
不適切な上書き方法
場合によっては、正しく定義されているように見えるメソッドがコンパイルに失敗することがあります。例えば:
public class Pair<T> {
public boolean equals(T t) {
return this == t;
}
}
これは、コンパイラが継承元のメソッドを定義することで、ジェネリックequals(T t)
メソッド定義が実際にオーバーライドになるのをequals(Object t)
防ぐためです。Object
競合を避けるためにObject.equals(Object)
メソッド名を変更すると、正常にコンパイルできます。
public class Pair<T> {
public boolean same(T t) {
return this == t;
}
}
ジェネリック継承
クラスはジェネリック クラスから継承できます。例:親クラスの型はPair<Integer>
、サブクラスの型はIntPair
、次のように継承できます。
public class IntPair extends Pair<Integer> {
}
これを使用する場合、サブクラスにはIntPair
ジェネリック型がないため、通常どおり使用できます。
IntPair ip = new IntPair(1, 2);
前述したように、型を取得することはできませんPair<T>
。T
つまり、変数が与えられた場合、そこから型を取得することはできませんPair<Integer> p
。ただし、親クラスがジェネリック型の場合、コンパイラはその型(この場合は型) をサブクラスのクラス ファイルに保存する必要があります。保存しないと、コンパイラはこの型にしかアクセスできないことを認識できません。ジェネリック型を継承する場合、サブクラスは親クラスのジェネリック型を取得できます。たとえば、親クラスのジェネリック型を取得できます。親クラスのジェネリック型を取得するコードはさらに複雑です。p
Integer
T
IntPair
Integer
IntPair
Integer
IntPair
Integer
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
public class Main {
public static void main(String[] args) {
Class<IntPair> clazz = IntPair.class;
Type t = clazz.getGenericSuperclass();
if (t instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) t;
Type[] types = pt.getActualTypeArguments(); // 可能有多个泛型类型
Type firstType = types[0]; // 取第一个泛型类型
Class<?> typeClass = (Class<?>) firstType;
System.out.println(typeClass); // Integer
}
}
}
class Pair<T> {
private T first;
private T last;
public Pair(T first, T last) {
this.first = first;
this.last = last;
}
public T getFirst() {
return first;
}
public T getLast() {
return last;
}
}
class IntPair extends Pair<Integer> {
public IntPair(Integer first, Integer last) {
super(first, last);
}
}