ジェネリックスは、オープンソースフレームワークであろうとJDKソースコードであろうと、Javaで非常に重要な位置を占めています。
ジェネリックは一般的な設計に不可欠な要素であると言っても過言ではないので、ジェネリックを正しく理解して使用するために必要なコースです。
まず、ジェネリックの性質
Javaジェネリック(ジェネリック)は、JDK 5で導入された新機能です。ジェネリックは、コンパイル時に不正な型を検出できるようにするコンパイル時の型安全性検出メカニズムを提供します。
ジェネリックスの本質は、型をパラメーター化すること、つまり、型のパラメーターを指定し、使用時にこのパラメーターの特定の値を指定することです。これにより、使用時に型を判別できます。このパラメータータイプは、クラス、インターフェイス、およびメソッドで使用できます。これらは、それぞれジェネリッククラス、ジェネリックインターフェイス、およびジェネリックメソッドと呼ばれます。
2.ジェネリックを使用する理由
ジェネリックスの利点は、コンパイル時に型の安全性がチェックされ、すべてのキャストが自動的かつ暗黙的に行われるため、コードの再利用が向上することです。
(1)型安全性が保証されています。
ジェネリックスの前は、コレクションから読み取られたすべてのオブジェクトを変換する必要があり、誤って間違ったタイプのオブジェクトを挿入した場合、変換プロセスは実行時に失敗していました。
例:ジェネリックなしのコレクションの使用:
public static void noGeneric() {
ArrayList names = new ArrayList();
names.add("mikechen的互联网架构");
names.add(123); //编译正常
}
ジェネリックスでコレクションを使用する:
public static void useGeneric() {
ArrayList<String> names = new ArrayList<>();
names.add("mikechen的互联网架构");
names.add(123); //编译不通过
}
ジェネリックスでは、add(123)のコンパイル時に、定義されたセット名のコンパイルに失敗します。
これは、各コレクションが受け取るオブジェクトのタイプをコンパイラに通知するのと同じです。コンパイラは、コンパイル時にタイプチェックを実行して、間違ったタイプのオブジェクトが挿入されているかどうかを通知し、プログラムをより安全にし、プログラムの堅牢性を高めます。
(2)強制改宗を排除する
ジェネリックスの副次的な利点は、ソースコード内の多くのキャストが排除されることです。これにより、コードが読みやすくなり、エラーが発生する可能性が低くなります。
例として、ジェネリックスのない次のコードスニペットにはキャストが必要です。
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0);
ジェネリックスを使用するように書き直す場合、コードをキャストする必要はありません。
List<String> list = new ArrayList<String>();
list.add("hello");
String s = list.get(0); // no cast
(3)不必要な梱包および開梱操作を回避し、プログラムのパフォーマンスを向上させます
非ジェネリックプログラミングでは、単一の型をObjectとして渡すと、Boxing操作とUnboxing操作が発生しますが、どちらもコストがかかります。ジェネリックスの導入後は、ボクシングとアンボクシングの操作を行う必要がないため、特に収集操作が頻繁に行われるシステムでは、操作効率が比較的高く、この機能によるパフォーマンスの向上がより顕著になります。
汎用変数には固定型があり、それらが使用されると、それが値型か参照型かをすでに認識しているため、不要なボックス化およびボックス化解除操作を回避できます。
object a=1;//由于是object类型,会自动进行装箱操作。
int b=(int)a;//强制转换,拆箱操作。这样一去一来,当次数多了以后会影响程序的运行效率。
ジェネリックを使用した後
public static T GetValue<T>(T a)
{
return a;
}
public static void Main()
{
int b=GetValue<int>(1);//使用这个方法的时候已经指定了类型是int,所以不会有装箱和拆箱的操作。
}
(4)コードの再利用性を向上させます。
3:ジェネリックの使用方法
ジェネリックスを使用するには、ジェネリッククラス、ジェネリックインターフェイス、ジェネリックメソッドの3つの方法があります。
1.ジェネリッククラス
ジェネリッククラス:クラスでジェネリックを定義する
定義形式:
public class 类名 <泛型类型1,...> {
}
注:ジェネリック型は参照型(非プリミティブデータ型)である必要があります
ジェネリッククラスを定義し、クラス名の後に山かっこを追加し、山かっこで型パラメーターを入力します。複数のパラメーターが存在する場合があり、複数のパラメーターはコンマで区切られます。
パブリッククラスGenericClass<ab、a、c> {}
もちろん、後者のパラメータータイプも標準化されており、上記のように任意にすることはできません。通常、タイプパラメーターには大文字の1文字を使用します。
T:任意类型 type
E:集合中元素的类型 element
K:key-value形式 key
V:key-value形式 value
示例代码:
ジェネリッククラス:
public class GenericClass<T> {
private T value;
public GenericClass(T value) {
this.value = value;
}
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
テストクラス:
//TODO 1:泛型类
GenericClass<String> name = new GenericClass<>("mikechen的互联网架构");
System.out.println(name.getValue());
GenericClass<Integer> number = new GenericClass<>(123);
System.out.println(number.getValue());
演算結果:
ジェネリックメソッドの概要:メソッドでジェネリックを定義する
定義形式:
public <泛型类型> 返回类型 方法名(泛型类型 变量名) {
}
-
注意点:
-
メソッド宣言で定義された仮パラメーターはメソッドでのみ使用できますが、インターフェースとクラス宣言で定義された型パラメーターはインターフェースとクラス全体で使用できます。fun()メソッドを呼び出すと、渡された実際のオブジェクトに従って、コンパイラーは型パラメーターTで表される実際の型を判別します。
-
public interface GenericInterface<T> {
void show(T value);}
}
public class StringShowImpl implements GenericInterface<String> {
@Override
public void show(String value) {
System.out.println(value);
}}
public class NumberShowImpl implements GenericInterface<Integer> {
@Override
public void show(Integer value) {
System.out.println(value);
}}
注:ジェネリックを使用する場合、前後に定義されたジェネリック型は一貫している必要があります。そうでない場合、コンパイル例外が発生します。
GenericInterface<String> genericInterface = new NumberShowImpl();//编译异常
または、単にタイプを指定しないでください。そうすれば、どのタイプのnewでも問題ありません。
GenericInterface g1 = new NumberShowImpl();
GenericInterface g2 = new StringShowImpl();
3.一般的な方法
ジェネリックメソッドは、メソッドを呼び出すときにジェネリックの特定のタイプを指定することです。
-
定義形式:
修飾子<ジェネリック型を表す変数>戻り値型メソッド名(パラメーター){}
例えば:
/**
*
* @param t 传入泛型的参数
* @param <T> 泛型的类型
* @return T 返回值为T类型
* 说明:
* 1)public 与 返回值中间<T>非常重要,可以理解为声明此方法为泛型方法。
* 2)只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。
* 3)<T>表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。
* 4)与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E等形式的参数常用于表示泛型。
*/
public <T> T genercMethod(T t){
System.out.println(t.getClass());
System.out.println(t);
return t;
}
public static void main(String[] args) {
GenericsClassDemo<String> genericString = new GenericsClassDemo("helloGeneric"); //这里的泛型跟下面调用的泛型方法可以不一样。
String str = genericString.genercMethod("hello");//传入的是String类型,返回的也是String类型
Integer i = genericString.genercMethod(123);//传入的是Integer类型,返回的也是Integer类型
}
class java.lang.String
hello
class java.lang.Integer
123
ここでは、入力パラメーターのタイプが異なるため、ジェネリックメソッドが異なるタイプを取得していることがわかります。ジェネリックメソッドを使用すると、クラスに関係なくメソッドを変更できます。
4:一般的なワイルドカード
Javaジェネリックのワイルドカードは、主に次の3つのカテゴリで、ジェネリック間で参照を渡す問題を解決するために使用される特別な構文です。
// 1:表示类型参数可以是任何类型
public class Apple<?>{}
// 2:表示类型参数必须是A或者是A的子类
public class Apple<T extends A>{}
// 3: 表示类型参数必须是A或者是A的超类型
public class Apple<T supers A>{}
1.リスト<?>など
の<?>である無制限のワイルドカード無制限のワイルドカードの主な機能は、ジェネリックが未知のタイプのデータを受け入れることを可能にすることです。
2. <?extends E>の形式の上限ワイルドカード(上限ワイルドカード)
上限が固定されたワイルドカードジェネリックを使用すると、指定されたクラスとそのサブクラスタイプのデータを受け入れることができます。
このクラスのワイルドカードの使用を宣言するには、<?extends E>の形式を使用します。ここで、Eはジェネリック型の上限です。
注:ここではextendsキーワードが使用されていますが、親クラスEを継承するサブクラスに限定されるものではなく、インターフェースEを表示するクラスを参照することもできます。
3. <?super E>の形式の下限ワイルドカード(下限ワイルドカード)
下限が固定されたワイルドカードジェネリックは、指定されたクラスとそのスーパークラスタイプのデータを受け入れることができます。
このクラスのワイルドカードの使用を宣言するには、<?super E>の形式を使用します。ここで、Eはジェネリック型の下限です。
注:ジェネリックには上限または下限を指定できますが、両方を指定することはできません。
5:ジェネリック医薬品におけるKTVEの意味
JDKのいくつかのジェネリッククラスのソースコードをクリックすると、次のコードが表示されます。
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
...
}
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
...
}
上記のジェネリッククラス定義のジェネリックパラメーターE、K、Vはどういう意味ですか?
実際、これらのパラメーター名は、メソッドのパラメーター名と同じように任意に指定できますが、通常、意味のある名前を付けて、他の人に一目でそれが何を意味するかを知らせます。
一般的な一般的なパラメータ名は次のとおりです。
E:要素(コレクションは要素を格納するため、コレクションで使用されます)
T:タイプ(Javaクラス)
K:キー(キー)
V:値(値)
N:数値(数値タイプ)
?:不確定なJavaタイプを表します
6:ジェネリック医薬品の実現原理
ジェネリックスの本質は、データ型をパラメーター化することです。これは、消去によって実現されます。つまり、コンパイラーはコンパイル中にジェネリック構文を「消去」し、それに応じていくつかの型変換アクションを実行します。
次のような例を見るのは明らかです:
public class Caculate<T> {
private T num;
}
ジェネリッククラスを定義し、属性メンバーを定義します。メンバーのタイプはジェネリックタイプであり、Tのタイプはわかりません。これは、タイプを修飾するためにのみ使用されます。
このCaculateクラスを逆コンパイルします:
public class Caculate{
public Caculate(){}
private Object num;
}
コンパイラがCaculateクラスの後に2つの山括弧を消去し、numの型をObject型に定義したことがわかりました。
では、すべてのジェネリック型はObjectで消去されますか?ほとんどの場合、ジェネリック型はObjectに置き換えられますが、1つの場合には置き換えられません。これは、次のような拡張構文とスーパー構文を使用する有界型です:
public class Caculate<T extends String> {
private T num;
}
この場合のジェネリック型の場合、numはObjectではなくStringに置き換えられます。
これはタイプ修飾構文であり、TをStringまたはStringのサブクラスに制限します。つまり、Caculateインスタンスを作成する場合、TをStringまたはStringのサブクラスにのみ制限できるため、タイプに関係なく制限すると、Stringは親クラスであり、型の不一致の問題は発生しないため、Stringを型の消去に使用できます。
実際、コンパイラーは通常、ジェネリックスが使用されている場所で消去をコンパイルして入力し、インスタンスを返します。ただし、これに加えて、ジェネリックインスタンスを構築するときにジェネリック構文を使用すると、コンパイラはインスタンスにマークを付け、インスタンスの後続のすべてのメソッド呼び出しに注意を払い、各呼び出しの前にセキュリティチェックを実行します。メソッドを正常に呼び出すことはできません。
実際、コンパイラはジェネリックメソッドの呼び出しに注意を払うだけでなく、戻り値が修飾されたジェネリック型である一部のメソッドの型変換も実行します。型の消去により、戻り値がジェネリック型であるメソッドは次のようになります。これらのメソッドが呼び出されると、コンパイラは型変換のためにチェックキャスト命令の追加行を挿入します。このプロセスは「ジェネリック変換」と呼ばれます。
セブン:最後
上記では、Javaジェネリックの本質から、ジェネリックの使用、ジェネリックの実装原理まで、6つの側面について完全かつ詳細に説明しました。お役に立てば幸いです。