ジェネリックの基本的な使用法と機能を簡単に説明します。

序文

前回の記事では、ジェネリックスの概念、機能、使用シナリオ、ジェネリック コレクション、ジェネリック インターフェイス、ジェネリック クラスの使用法について説明しましたが、紙面の都合上、ジェネリックスの内容を完全に説明することはできませんでした。そこで、今日はジェネリックメソッド、ジェネリック消去、ワイルドカードなどについて学習していきます。皆さんも学習の準備を続けていただければ幸いです。


全文は約 [ 4600]語で、ナンセンスではなく、技術を学び原理を理解するための純粋な乾物です。この記事には豊富な事例と写真付きのビデオが含まれているため、記事内の技術的な概念をよりよく理解して使用することができ、十分な啓発的な思考を得ることができます。

1. 一般的な方法

1 はじめに

インターフェイスとクラスを定義するときにジェネリックスを使用できるため、インターフェイスとクラス内のすべてのメソッドとメンバー変数もジェネリックスを使用できます。しかし実際には、ジェネリックはクラス全体に適用することも、クラス内のメソッドにのみ適用することもできます。つまり、メソッドが存在するクラスはジェネリック クラスである場合とそうでない場合があります。メソッドにジェネリックがあるかどうかは、そのメソッドが属しているクラスにジェネリックがあるかどうかとは関係ありません。

ジェネリック メソッドは、メソッドの呼び出し時に型が決定されるメソッドで、クラスとは独立してメソッドを変更できますまた、静的静的メソッドはジェネリック クラスの型パラメーターにアクセスできないため、静的メソッドにジェネリック機能を持たせたい場合は、静的メソッドをジェネリック メソッドにする必要があります。

2. 文法

ジェネリック メソッドを定義するときは、メソッド名の前に型パラメータを追加する必要があります。ジェネリック メソッドを定義するための構文は次のとおりです。

[访问权限修饰符] [static] [final] <类型参数列表> 返回值类型 方法名([形式参数列表])

例えば:

public static <T> List showInfo(Class<T> clazz,int userId){
    
    }

一般に、ジェネリック メソッドを作成するときは、メソッド名の前に使用するジェネリック型を宣言する必要があります。複数のジェネリック型をカンマで区切って同時に宣言できます。次に、ジェネリック メソッドを定義し、ジェネリック メソッドの作成と使用について具体的に説明します。

3. コード例

ここでは、配列をソートし、要素の出力を走査するための汎用メソッドを定義します。コードは次のとおりです。

import java.util.Arrays;

public class Demo04 {
    
    
	
	//定义了一个静态的泛型方法,遍历数组中的每个元素
	public static <T> void printArray(T[] arr) {
    
    
		//先对数组进行排序
		Arrays.sort(arr);
		//再遍历数组元素
	    for (T t : arr) {
    
    
	        System.out.print(t + " ");
	    }
	    System.out.println();
	}
	
	public static void main(String[] args) {
    
    
		Integer[] nums= {
    
    100,39,8,200,65};
		//调用泛型方法
		printArray(nums);
	}
}

上記のコードでは、printArray()型パラメーター T が使用されるジェネリック メソッドです。そして、メソッドのパラメータで型パラメータ T を使用してジェネリック配列 T[] を定義し、配列をソートして走査します。このようにして、渡す配列の型に関係なく、型変換を行わずに並べ替えなどの機能を簡単に実装できるため、以前の要件を簡単に実現できます。

2. ワイルドカード

上記の使用法に加えて、ジェネリックスには非常に重要なワイルドカード関数もあります。次に、それがどのように機能するかを見てみましょう。

1 はじめに

ジェネリックスのワイルドカードは、実際には特別なジェネリック型であり、ワイルドカード型パラメータとも呼ばれます。ワイルドカード型パラメータを利用すると、実際の型を知らなくても、より汎用的なコードを作成できます。通常、特定の型パラメータを置き換えるには?を使用します。たとえば、List<?> は、List や ListなどのすべてのList<concrete type argument>クラスと論理的に同等になります。

この点に関して、なぜワイルドカードが必要なのか、興味を持つ友人もいるかもしれません。実際、ワイルドカードが表示される理由は主に開発中にあり、ジェネリック型が必要になる場合がありますが、具体的にどの型を使用すればよいかわかりません。この場合、ワイルドカード型パラメータを使用して、コードをより汎用的にすることができます。たとえば、任意の型を受け入れて最大の要素を返すコレクションを作成したい場合、現時点ではどの特定のコレクションを渡せばよいかわからない場合は、ワイルドカードを使用することをお勧めします。

2. ワイルドカードの形式

汎用ワイルドカードを特に使用する場合、次の 3 つの実装形式があります。

  • 修飾されていないワイルドカード (?) : ? は、不明なタイプのワイルドカードを表します
  • 上限ワイルドカード (? extends T) : ? 型の上限のワイルドカードを示します。T はクラスまたはインターフェイスです
  • 下限ワイルドカード (? super T) : ? はタイプの下限のワイルドカードを表し、T はクラスまたはインターフェイスです

次に、上記 3 つの形式について、いくつかのケースを通してその使用方法を説明します。

3. 修飾されていないワイルドカード (?)

修飾されていないワイルドカード (?) は、型パラメータが必要な場合に使用できる不明な型のワイルドカードです。ただし、制限がないため、コレクション内のイテレータや戻り値の型がジェネリックであるメソッドなど、単純な場合にのみ使用できます。簡単な例を次に示します。

import java.util.ArrayList;
import java.util.List;

public class Demo05 {
    
    

	public static void main(String[] args) {
    
    
		List<String> names = new ArrayList<String>();
		List<Integer> ages = new ArrayList<Integer>();
		List<Number> numbers = new ArrayList<Number>();

		names.add("一一哥");
		names.add("秦始皇");
		ages.add(28);
		ages.add(50);
		ages.add(28);
		numbers.add(100);
		numbers.add(800);

		printElement(names);
		printElement(ages);
		printElement(numbers);
	}

	//未限定通配符的使用
	public static void printElement(List<?> data) {
    
    
		for(int i=0;i<data.size();i++) {
    
    
			//data.getClass().getSimpleName():用于获取某个类的类名
			System.out.println(data.getClass().getSimpleName()+"--data: " + data.get(i));
		}
	}

}

この例では、printElement() メソッドは不明な型の List コレクションを受け入れるため、名前、年齢、数字をすべてこのメソッドの実際のパラメーターとして使用できます。これが無制限のワイルドカードの役割です。

4. PECS の原則

PECS は、Producer Extends Consumer Super の略称で、Java ジェネリックスに関する設計原則です。この原則は、T を返す必要がある場合はプロデューサー (Producer) であり、extends ワイルドカードを使用する必要があり、T を書き込む必要がある場合はコンシューマ (Consumer) であり、スーパー ワイルドカードを使用する必要があることを意味します。この原則は、ジェネリックスを使用するときに型パラメーターの上限と下限を定義する方法をガイドします。

ジェネリックスを使用する場合、型パラメーターの上限と下限を定義する必要がある場合があります。

たとえば、いくつかのコレクション型を処理するメソッドを作成したいが、これらのコレクションにどのような型の要素が含まれているかがわからない場合は、すべてのコレクション型を処理するための型パラメーターを定義できます。一般に、extends を使用してジェネリックの上限を設定し、super を使用してジェネリックの下限を設定できます。続きまして、その下の5番目と6番目のまとめで、後発品の上限と下限をどのように実現するかということについてご説明させていただきますので、引き続き検討していただければと思います。

5. 上限ワイルドカード (? は T を拡張します)

上限ワイルドカード (? extends T) は型の上限を示すワイルドカードです。T はクラスまたはインターフェイスであり、ジェネリック クラスの型は T インターフェイスまたはクラスを実装または継承する必要があります。使用できる型の上限を指定し、主に入力パラメータの型を制限するために使用されます。

import java.util.ArrayList;
import java.util.List;

/**
 * @author 一一哥Sun 
 */
public class Demo06 {
    
    

	public static void main(String[] args) {
    
    
		List<String> names = new ArrayList<String>();
		List<Integer> ages = new ArrayList<Integer>();
		List<Number> numbers = new ArrayList<Number>();

		names.add("一一哥");
		names.add("秦始皇");
		ages.add(28);
		ages.add(50);
		ages.add(28);
		numbers.add(100);
		numbers.add(800);
		
		//String等非Number类型就不行
		//printElementUpbound(names);
		
		//泛型值只能是Number及其子类类型,所以Integer/Double/Float等类型都可以,但String就不行
		printElementUpbound(ages);
		printElementUpbound(numbers);
	}

	//上限通配符的使用,这里的泛型值只能是Number及其子类类型
	public static void printElementUpbound(List<? extends Number> data) {
    
    
		for(int i=0;i<data.size();i++) {
    
    
			//data.getClass().getSimpleName():用于获取某个类的类名
			System.out.println(data.getClass().getSimpleName()+"--data: " + data.get(i));
		}
	}
}

この例では、printElementUpbound メソッドのコレクション ジェネリック型は Number クラスまたはそのサブクラスにすることができますが、他の型は許可されませんつまり、型パラメーターとして使用できるのは Number またはそのサブクラスのみであり、ジェネリック型の上限は Number であり、これが上限ワイルドカードの意味です。

6. 下限ワイルドカード(?スーパーT)

下限ワイルドカード (? super T) は、型の下限を表すワイルドカードです。ここで、T はクラスまたはインターフェイスです。使用できる型の下限を指定します。主に出力パラメータの型を制限するために使用されます簡単な例を次に示します。

import java.util.ArrayList;
import java.util.List;

public class Demo07 {
    
    

	public static void main(String[] args) {
    
    
		List<String> names = new ArrayList<String>();
		List<Integer> ages = new ArrayList<Integer>();
		List<Double> numbers = new ArrayList<Double>();

		names.add("一一哥");
		names.add("秦始皇");
		ages.add(28);
		ages.add(50);
		ages.add(28);
		numbers.add(100.0);
		numbers.add(800.9);
		
		//String等非Number类型就不行
		//printElementUpbound(names);
		
		//此时Double类型也不行
		//printElementDownbound(numbers);
		
		//泛型值只能是Integer及其父类类型,所以Double/Float/String等类型都不可以
		printElementDownbound(ages);
		
	}

	//下限通配符的使用,这里的泛型值只能是Integer及其父类类型
	public static void printElementDownbound(List<? super Integer> data) {
    
    
		for(int i=0;i<data.size();i++) {
    
    
			System.out.println(data.getClass().getSimpleName()+"--data: " + data.get(i));
		}
	}
}

この例では、printElementDownboundメソッドのコレクション ジェネリック型は Integer またはその親型にすることができます。つまり、型の下限は Integer であり、他の型は許可されませんつまり、型パラメーターとして使用できるのは Integer またはそのスーパークラスのみであり、ジェネリック型の下限は Integer であり、これが下限ワイルドカードの意味です。

7. <? extends T> と <? super T> の違い

<? extends T>和<? super T>ここで、違いを要約して説明したいと思います。

  • <? extends T> T get() などの読み取りメソッドを呼び出して T オブジェクトへの参照を取得できるようにしますが、 set(T) などの書き込みメソッドでTへの参照を渡すことは許可しません ( nullを渡す場合を除く) );
  • <? super T> set(T) などの書き込みメソッドを呼び出して T オブジェクトへの参照を渡すことは許可されますが、T get() などの読み取りメソッドを呼び出して T オブジェクトへの参照を取得することは許可されません。 Tオブジェクト ( Objectの取得を除く)。
  • つまり、 <? extends T> は 読み取りを許可しますが、書き込みは許可しません。また 、<? super T> は 書き込みを許可しますが、読み取りを許可しません。

無制限のワイルドカード、上限ワイルドカード、下限ワイルドカードのいずれであっても、メソッド内、クラスまたはインターフェイス内で使用できることに注意してください。

3. 一般的な消去

ジェネリックスを学習するときは、上記のジェネリック クラス、ジェネリック インターフェイス、ジェネリック メソッド、およびワイルドカードを習得することに加えて、ジェネリック消去についても学ぶ必要があります。では、一般的な消去とは何でしょうか? 次へ移りましょう。

1 はじめに

いわゆる型消去 (Type Erasure) とは、コンパイル時に JVM コンパイラーがすべてのジェネリック情報を消去し、元の型に変換することを意味します。一般に、ジェネリック型パラメーターは特定の型に置き換えられます。上限または下限 (上限が指定されていない場合は、デフォルトでオブジェクトになります)。

つまり、コード内ではジェネリックを使用していますが、コンパイル後はすべてのジェネリック型が消去され、代わりに対応するプリミティブ型が使用されます。これは、Java ジェネリックスの基本的な実装原則です。この設計の目的は、古い JDK バージョンとの互換性を確保することです。これにより、Java の下位互換性が向上し、古い非汎用コードは変更を加えることなく直接汎用クラス ライブラリを使用できるようになります。同時に、Java はジェネリック型を操作するためのリフレクション メカニズムも提供するため、場合によってはジェネリック型を引き続き取得できるため、ジェネリックの消去が発生した場合でも、Java 仮想マシンの実行時間の効率に大きな影響を与えることはありません。 。

たとえば、ジェネリック クラスを定義するときは、次の例のように、特定の型ではなくジェネリック型パラメーターを使用します。
ここに画像の説明を挿入

2. 汎用消去による制限事項

コンパイル後、このジェネリック クラスの型パラメーター T は消去され、対応するプリミティブ型 Object になります。

これは、実行時にジェネリックの実際の型パラメータを取得できないことも意味するため、ジェネリック消去の使用にはいくつかの制限があります。まず、ジェネリック型パラメータが消去されるため、実行時にジェネリック型パラメータの情報を取得することができません。たとえば、List 型の変数がある場合、実行時にこの List コレクション内の要素の型が Integer であることを取得できません。もう 1 つの制限は、ジェネリック型を使用する場合は、型の安全性にも注意する必要があることです。コンパイル段階では、コンパイラはジェネリック型の型安全性をチェックしますが、実行段階ではジェネリック型のパラメータが消去されるため、型安全性は保証できません。一般的な消去の制限は、主に次の側面に現れます。

型パラメータはプリミティブ型でインスタンス化できません。

実行時にジェネリック型情報を取得できません。

ジェネリック型パラメーターは静的変数または静的メソッドでは使用できません。

型 T はインスタンス化できません。

次に、これらの制限を詳細に分析します。

2.1 型パラメータはプリミティブ型でインスタンス化できない

Java ジェネリックスの型パラメーターはプリミティブ型にすることはできません。クラス型またはインターフェイス型のみを使用できます。たとえば、次のコードはコンパイル中のエラーによりコンパイルに失敗します。

List<int> list = new ArrayList<int>();

正しい書き方は、以下に示すように、基本型に対応するラッパー型を使用することです。

List<Integer> list = new ArrayList<Integer>();

2.2 実行時にジェネリック型情報を取得できない

ジェネリック消去が存在するため、プログラム実行中にジェネリック型の情報を取得することはできません。たとえば、次のコードは実行時に List の要素タイプを取得できません。

List<String> list = new ArrayList<String>(); 
Class<?> clazz = list.getClass(); 
Type type = clazz.getGenericSuperclass(); 
// 输出:class java.util.ArrayList<E>
System.out.println(type); 

出力結果では、ArrayList の型情報のみが取得できますが、コレクション内の特定のジェネリック型情報、つまり String の情報は取得できません。ただし、実行時にジェネリックの実際の型パラメーターを取得したいだけの場合は、実際にそれを実現する次の方法を参照できます。

public class Box<T> {
    
    
    private T content;

    public Box(T content) {
    
    
        this.content = content;
    }

    public T getContent() {
    
    
        return content;
    }

    public void setContent(T content) {
    
    
        this.content = content;
    }

    public Class<?> getContentType() {
    
    
        return content.getClass();
    }
}

上記のコードでは、ジェネリック型パラメーターの実際のバイトコード型を返すために使用される新しいメソッド getContentType() を追加しました。後ほど、このメソッドを呼び出すことでジェネリック型の情報を間接的に取得できます。

2.3 ジェネリック型パラメータは静的変数または静的メソッドでは使用できません

ジェネリック型パラメーターはオブジェクトがインスタンス化されるときに決定されるため、ジェネリック型パラメーターを静的変数または静的メソッドで使用することはできません。たとえば、次のコードはコンパイルできません。

public class MyClass<T> {
    
       
    //这样的代码编译不通过
    private static T value;   
    
    public static void setValue(T value) {
    
            
        MyClass.value = value;     
    } 
}

正しい記述方法は、ジェネリック型パラメーターの代わりに実際の型を使用することです。

public class MyClass {
    
         
    private static String value;          
    public static void setValue(String value) {
    
             
        MyClass.value = value;     
    } 
}

2.4 T 型はインスタンス化できない

たとえば、次のような場合です。

public class MyClass<T> {
    
    
    private T first;
    private T last;
    
    public MyClass() {
    
    
        first = new T();
        last = new T();
    }
}

コンストラクターに次の 2 行があるため、上記のコードはコンパイルできません。

first = new T(); 
last = new T(); 

ワイプ後は実際には次のようになります。

first = new Object(); 
last = new Object(); 

このようにして、作成new MyClass<String>()と作成はnew MyClass<Integer>()オブジェクトになり、コンパイラはこの種の間違ったコードを防止します。ジェネリック T をインスタンス化する場合は、Class<T> パラメーターを使用し、それを実現するためのリフレクション テクノロジを設定する必要があります。また、それを使用するときに Class<T> も渡す必要があります。


4. 結論

ただし、ジェネリック消去にはいくつかの制限がありますが、ジェネリックは依然として、コードの可読性と保守性を向上させる強力なプログラミング ツールです。ジェネリックスを適切に使用することで、コンパイル時に型チェックを実行し、型変換エラーや実行時例外を回避できるため、コードのセキュリティと信頼性が向上します。同時に、実際のアプリケーションで正しい決定を下すためには、Java の汎用消去の制限についても理解する必要があります。

ビデオチュートリアル:ビデオチュートリアル、直接クリックしてください

おすすめ

転載: blog.csdn.net/GUDUzhongliang/article/details/131001245