検索
ホームページJava&#&ベースJava ジェネリックの詳細な紹介

Java ジェネリックの詳細な紹介

#1. ジェネリックの概念の導入 (ジェネリックが必要な理由)? (推奨: java ビデオ チュートリアル )

まず、次の短いコードを見てみましょう:

public class GenericTest {

    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("qqyumidi");
        list.add("corn");
        list.add(100);

        for (int i = 0; i < list.size(); i++) {
            String name = (String) list.get(i); // 1
            System.out.println("name:" + name);
        }
    }
}

は List 型のコレクションを定義します。2 つの値String 型の値が追加され、その後に Integer 型の値が続きます。リストのデフォルトのタイプはオブジェクトであるため、これは完全に許可されます。

後続のループでは、整数型の値を事前にリストに追加するのを忘れたり、その他のエンコードの理由により、//1 と同様のエラーが発生しやすくなります。コンパイル段階は正常ですが、実行時に「java.lang.ClassCastException」例外が発生するためです。したがって、このようなエラーはコーディング中に検出することが困難です。

上記のコーディング プロセス中に、2 つの主な問題があることがわかりました:

1. オブジェクトをコレクションに入れると、コレクションはオブジェクトの型を記憶しません。このオブジェクトが再びコレクションから取り出されると、オブジェクトのコンパイル済み型は Object 型に変わりますが、その実行時型は独自の型のままです。

2. そのため、//1のコレクション要素を取り出す際には、特定の対象型への人為的な強制型変換が必要となり、「java.lang.ClassCastException」例外が発生しやすくなります。

それでは、コレクション内の要素の型をコレクションが記憶できるようにし、コンパイル中に問題がない限り「java.lang.ClassCastException」例外が発生するという目標を達成する方法はありますか?実行時に発生しませんか?答えはジェネリック医薬品を使用することです。

2. ジェネリックとは何ですか?

ジェネリック、つまり「パラメータ化された型」。 パラメータに関して言えば、最もよく知られているのは、メソッドを定義するときに仮パラメータがあり、このメソッドを呼び出すときに実際のパラメータが渡されることです。

それでは、パラメータ化された型をどのように理解すればよいでしょうか?名前が示すように、型はメソッドの変数パラメータと同様に、元の特定の型からパラメータ化されますが、このとき、型もパラメータの形式で定義されます (これを型パラメータと呼ぶこともできます)。次に、型 (型引数) を使用または呼び出すときに、特定の型が渡されます。

少し複雑に思えますが、まず、一般的な記述を使用して上記の例を見てみましょう。

public class GenericTest {

    public static void main(String[] args) {
        /*
        List list = new ArrayList();
        list.add("qqyumidi");
        list.add("corn");
        list.add(100);
        */

        List<String> list = new ArrayList<String>();
        list.add("qqyumidi");
        list.add("corn");
        //list.add(100);   // 1  提示编译错误

        for (int i = 0; i < list.size(); i++) {
            String name = list.get(i); // 2
            System.out.println("name:" + name);
        }
    }
}

汎用的な書き込み方法を採用した後、//1 に Integer 型オブジェクトを追加しようとするとコンパイル エラーが発生します。List を通じて、リスト コレクションには String のみを含めることが直接制限されます。この時点でコレクションは要素の型情報を記憶でき、コンパイラはそれが String 型であることをすでに確認できるため、 //2 で強制的な型変換を実行する必要はありません。

上記の一般的な定義と組み合わせると、List では、String が型の実パラメータであることがわかります。つまり、対応する List インターフェイスには型パラメータが含まれている必要があります。また、 get() メソッドの戻り結果も、この仮パラメータの型 (つまり、対応する受信型の実パラメータ) がそのまま返されます。 List インターフェイスの具体的な定義を見てみましょう:

public interface List<E> extends Collection<E> {

    int size();

    boolean isEmpty();

    boolean contains(Object o);

    Iterator<E> iterator();

    Object[] toArray();

    <T> T[] toArray(T[] a);

    boolean add(E e);

    boolean remove(Object o);

    boolean containsAll(Collection<?> c);

    boolean addAll(Collection<? extends E> c);

    boolean addAll(int index, Collection<? extends E> c);

    boolean removeAll(Collection<?> c);

    boolean retainAll(Collection<?> c);

    void clear();

    boolean equals(Object o);

    int hashCode();

    E get(int index);

    E set(int index, E element);

    void add(int index, E element);

    E remove(int index);

    int indexOf(Object o);

    int lastIndexOf(Object o);

    ListIterator<E> listIterator();

    ListIterator<E> listIterator(int index);

    List<E> subList(int fromIndex, int toIndex);
}

List インターフェイスで一般的な定義を採用した後、 の E が型パラメータを表し、特定の Type を受け取ることができることがわかります。このインターフェイス定義では、E が現れる場所はどこでも、外部から受け取った同じ型の引数を表します。

当然ながら、ArrayList は List インターフェイスの実装クラスであり、その定義形式は次のとおりです。

public class ArrayList<E> extends AbstractList<E> 
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    
    public E get(int index) {
        rangeCheck(index);
        checkForComodification();
        return ArrayList.this.elementData(offset + index);
    }
    
    //...省略掉其他具体的定义过程

}

これにより、ソース コードの観点から、なぜ追加時にコンパイル エラーが発生するのかがわかります。 //1 の整数型オブジェクト、 //2 の get() で取得した型はそのまま String 型です。

3. カスタマイズされたジェネリック インターフェイス、ジェネリック クラス、およびジェネリック メソッド

上記の内容から、ジェネリックの具体的な操作プロセスを誰もが理解できました。また、インターフェイス、クラス、メソッドもジェネリックスを使用して定義し、それに応じて使用できることもわかっています。はい、具体的な用途では、ジェネリック インターフェイス、ジェネリック クラス、ジェネリック メソッドに分けることができます。

カスタマイズされたジェネリック インターフェイス、ジェネリック クラス、およびジェネリック メソッドは、上記の Java ソース コードの List および ArrayList に似ています。以下に、ジェネリック クラスとジェネリック メソッドの最も単純な定義を見ていきます。

public class GenericTest {

    public static void main(String[] args) {

        Box<String> name = new Box<String>("corn");
        System.out.println("name:" + name.getData());
    }

}

class Box<T> {

    private T data;

    public Box() {

    }

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

    public T getData() {
        return data;
    }

}

ジェネリック インターフェイス、ジェネリック クラス、およびジェネリック メソッドの定義プロセスでは、同じものの T、E、K、V パラメータがよく見られます。フォームは、外部使用から渡される型引数を受け取るため、ジェネリック パラメーターを表すためによく使用されます。それでは、渡されたさまざまな型の引数に対して、生成される対応するオブジェクト インスタンスの型は同じなのでしょうか?

public class GenericTest {

    public static void main(String[] args) {

        Box<String> name = new Box<String>("corn");
        Box<Integer> age = new Box<Integer>(712);

        System.out.println("name class:" + name.getClass());      // com.qqyumidi.Box
        System.out.println("age class:" + age.getClass());        // com.qqyumidi.Box
        System.out.println(name.getClass() == age.getClass());    // true

    }

}

このことから、ジェネリック クラスを使用する場合、異なるジェネリック引数が渡されても、実際には異なる型が生成されないことがわかりました。メモリ内にはジェネリック クラスが 1 つだけあり、これが元の最も基本的な型 (この例ではボックス) もちろん、論理的には、これを複数の異なるジェネリック型として理解できます。

その理由は、Java におけるジェネリックの概念の目的は、コードのコンパイル段階にのみ影響することであるためです。コンパイル プロセス中、ジェネリックの結果が正しく検証された後、ジェネリックは関連情報が消去されます。つまり、正常にコンパイルされたクラス ファイルには一般的な情報が含まれていません。一般的な情報は実行時段階には入りません。

对此总结成一句话:泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型。 

四.类型通配符

接着上面的结论,我们知道,Box和Box实际上都是Box类型,现在需要继续探讨一个问题,那么在逻辑上,类似于Box和Box是否可以看成具有父子关系的泛型类型呢?

为了弄清这个问题,我们继续看下下面这个例子:

public class GenericTest {

    public static void main(String[] args) {

        Box<Number> name = new Box<Number>(99);
        Box<Integer> age = new Box<Integer>(712);

        getData(name);
        
        //The method getData(Box<Number>) in the type GenericTest is 
        //not applicable for the arguments (Box<Integer>)
        getData(age);   // 1

    }
    
    public static void getData(Box<Number> data){
        System.out.println("data :" + data.getData());
    }

}

我们发现,在代码//1处出现了错误提示信息:The method getData(Box) in the t ype GenericTest is not applicable for the arguments (Box)。显然,通过提示信息,我们知道Box在逻辑上不能视为Box的父类。那么,原因何在呢?

public class GenericTest {

    public static void main(String[] args) {

        Box<Integer> a = new Box<Integer>(712);
        Box<Number> b = a;  // 1
        Box<Float> f = new Box<Float>(3.14f);
        b.setData(f);        // 2

    }

    public static void getData(Box<Number> data) {
        System.out.println("data :" + data.getData());
    }

}

class Box<T> {

    private T data;

    public Box() {

    }

    public Box(T data) {
        setData(data);
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

}

这个例子中,显然//1和//2处肯定会出现错误提示的。在此我们可以使用反证法来进行说明。

假设Box在逻辑上可以视为Box的父类,那么//1和//2处将不会有错误提示了,那么问题就出来了,通过getData()方法取出数据时到底是什么类型呢?Integer? Float? 还是Number?且由于在编程过程中的顺序不可控性,导致在必要的时候必须要进行类型判断,且进行强制类型转换。显然,这与泛型的理念矛盾,因此,在逻辑上Box不能视为Box的父类。

好,那我们回过头来继续看“类型通配符”中的第一个例子,我们知道其具体的错误提示的深层次原因了。那么如何解决呢?总部能再定义一个新的函数吧。

这和Java中的多态理念显然是违背的,因此,我们需要一个在逻辑上可以用来表示同时是Box和Box的父类的一个引用类型,由此,类型通配符应运而生。

类型通配符一般是使用 ? 代替具体的类型实参。注意了,此处是类型实参,而不是类型形参!且Box>在逻辑上是Box、Box...等所有Box的父类。由此,我们依然可以定义泛型方法,来完成此类需求。

public class GenericTest {

    public static void main(String[] args) {

        Box<String> name = new Box<String>("corn");
        Box<Integer> age = new Box<Integer>(712);
        Box<Number> number = new Box<Number>(314);

        getData(name);
        getData(age);
        getData(number);
    }

    public static void getData(Box<?> data) {
        System.out.println("data :" + data.getData());
    }

}

有时候,我们还可能听到类型通配符上限和类型通配符下限。具体有是怎么样的呢?

在上面的例子中,如果需要定义一个功能类似于getData()的方法,但对类型实参又有进一步的限制:只能是Number类及其子类。此时,需要用到类型通配符上限。

public class GenericTest {

    public static void main(String[] args) {

        Box<String> name = new Box<String>("corn");
        Box<Integer> age = new Box<Integer>(712);
        Box<Number> number = new Box<Number>(314);

        getData(name);
        getData(age);
        getData(number);
        
        //getUpperNumberData(name); // 1
        getUpperNumberData(age);    // 2
        getUpperNumberData(number); // 3
    }

    public static void getData(Box<?> data) {
        System.out.println("data :" + data.getData());
    }
    
    public static void getUpperNumberData(Box<? extends Number> data){
        System.out.println("data :" + data.getData());
    }

}

此时,显然,在代码//1处调用将出现错误提示,而//2 //3处调用正常。

类型通配符上限通过形如Box extends Number>形式定义,相对应的,类型通配符下限为Box super Number>形式,其含义与类型通配符上限正好相反,在此不作过多阐述了。

更多java知识请关注java基础教程栏目。

以上がJava ジェネリックの詳細な紹介の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明
この記事は博客园で複製されています。侵害がある場合は、admin@php.cn までご連絡ください。

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

AI Hentai Generator

AI Hentai Generator

AIヘンタイを無料で生成します。

ホットツール

PhpStorm Mac バージョン

PhpStorm Mac バージョン

最新(2018.2.1)のプロフェッショナル向けPHP統合開発ツール

SAP NetWeaver Server Adapter for Eclipse

SAP NetWeaver Server Adapter for Eclipse

Eclipse を SAP NetWeaver アプリケーション サーバーと統合します。

SublimeText3 英語版

SublimeText3 英語版

推奨: Win バージョン、コードプロンプトをサポート!

AtomエディタMac版ダウンロード

AtomエディタMac版ダウンロード

最も人気のあるオープンソースエディター

Dreamweaver Mac版

Dreamweaver Mac版

ビジュアル Web 開発ツール