検索

Java ジェネリックス

Dec 19, 2016 pm 02:54 PM
ジェネリック

1. ジェネリックの概念の導入 (なぜジェネリックが必要なのか)

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

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 つの文字列型の値をそれに追加し、次に Integer 型の値を追加します。リストのデフォルトのタイプはオブジェクトであるため、これは完全に許可されます。後続のループでは、前に整数型の値をリストに追加するのを忘れたり、その他のエンコードの理由により、//1 と同様のエラーが発生しやすくなります。コンパイル段階は正常ですが、実行時に「java.lang.ClassCastException」例外が発生するためです。したがって、このようなエラーはコーディング中に検出することが困難です。

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

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

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

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

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

ジェネリック、つまり「パラメータ化された型」。パラメーターに関して最もよく知られているのは、メソッドを定義するときに仮パラメーターがあり、メソッドが呼び出されるときに実際のパラメーターが渡されることです。では、パラメーター化された型をどのように理解すればよいでしょうか?名前が示すように、型はメソッド内の変数パラメーターと同様に、元の特定の型からパラメーター化されます。このとき、型はパラメーター形式 (型パラメーターと呼ぶこともできます) としても定義されます。 type (type 引数) を使用または呼び出すときに、特定の型が渡されます。

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

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 型の要素のみを含めることが直接制限されるため、 /2 で強制的な型変換を実行する必要はありません。この時点で、コレクションは要素の型情報を記憶でき、コンパイラはそれが String 型であることをすでに確認できるからです。

上記の一般的な定義と組み合わせると、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 が型パラメータを表し、特定の型の実パラメータを受け取ることができることがわかります。そして、このインターフェイス定義では、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 に Integer 型オブジェクトを追加するとコンパイルエラーが発生する理由がソースコードの観点から理解できます。 get( at //2 ) は直接 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 つだけ存在することがわかりました。最も基本的な型 (この例では Box) もちろん、論理的には複数の異なるジェネリック型として理解できます。

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

これは 1 つの文に要約できます。ジェネリック型は論理的には複数の異なる型と見なすことができますが、実際にはすべて同じ基本型です。

4. ワイルドカードを入力します

接着上面的结论,我们知道,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泛型相关文章请关注PHP中文网!

声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
JVMはオペレーティングシステムAPIの違いをどのように処理しますか?JVMはオペレーティングシステムAPIの違いをどのように処理しますか?Apr 27, 2025 am 12:18 AM

JVMは、JavanativeInterface(JNI)およびJava Standard Libraryを介してオペレーティングシステムのAPIの違いを処理します。1。JNIでは、Javaコードがローカルコードを呼び出し、オペレーティングシステムAPIと直接対話できます。 2. Java Standard Libraryは統一されたAPIを提供します。これは、異なるオペレーティングシステムAPIに内部的にマッピングされ、コードがプラットフォーム間で実行されるようにします。

Java 9で導入されたモジュール性は、プラットフォームの独立性にどのように影響しますか?Java 9で導入されたモジュール性は、プラットフォームの独立性にどのように影響しますか?Apr 27, 2025 am 12:15 AM

modularitydoesnotdirectlyectlyectjava'splatformindepensence.java'splatformendepenceismaindainededainededainededaindainedaindained bythejvm、butmodularityinfluencesApplucationStructure andmanagement、間接的なインパクチャプラット形成依存性.1)

ByteCodeとは何ですか?また、Javaのプラットフォームの独立性とどのように関係していますか?ByteCodeとは何ですか?また、Javaのプラットフォームの独立性とどのように関係していますか?Apr 27, 2025 am 12:06 AM

bytecodeinjavaisthe intermediaterepresentationthateNablesplatformindepence.1)javacodeis compiledintobytecodestoredin.classfiles.2)thejvminterpretsorcompilesthisbytecodeintomachinecodeatime、

Javaがプラットフォームに依存しない言語と見なされるのはなぜですか?Javaがプラットフォームに依存しない言語と見なされるのはなぜですか?Apr 27, 2025 am 12:03 AM

javaachievesplatformedenceTheTheTheJavavirtualMachine(JVM)、これは、javacodeisisisisisissompiledIntobytecode.2)javaCodeisisisisissompiledevedevicetecode.2)

グラフィカルユーザーインターフェイス(GUI)は、Javaのプラットフォーム独立性の課題をどのように提示できますか?グラフィカルユーザーインターフェイス(GUI)は、Javaのプラットフォーム独立性の課題をどのように提示できますか?Apr 27, 2025 am 12:02 AM

Javagui開発におけるプラットフォームの独立性は課題に直面していますが、Swing、Javafx、統一外観、パフォーマンス最適化、サードパーティライブラリ、クロスプラットフォームテストを使用することで対処できます。 Javaguiの開発は、クロスプラットフォームの一貫性を提供することを目的としたAWTとSwingに依存していますが、実際の効果はオペレーティングシステムごとに異なります。ソリューションには以下が含まれます。1)SwingおよびJavafxをGUIツールキットとして使用します。 2)uimanager.setlookandfeel()を介して外観を統合します。 3)さまざまなプラットフォームに合わせてパフォーマンスを最適化します。 4)ApachepivotやSWTなどのサードパーティライブラリを使用する。 5)一貫性を確保するために、クロスプラットフォームテストを実施します。

Java開発のどの側面がプラットフォームに依存していますか?Java開発のどの側面がプラットフォームに依存していますか?Apr 26, 2025 am 12:19 AM

javadevelopmentisnotentirelylylypratform-IndopentDuetoseveralfactors.1)jvmvariationsaffectperformanceandbehavioracrossdifferentos.2)nativeLibrariesviajniintroducePlatform-specificissues.3)giaiasystemsdifferbeTioneplateplatifflics.4)

さまざまなプラットフォームでJavaコードを実行するときにパフォーマンスの違いはありますか?なぜ?さまざまなプラットフォームでJavaコードを実行するときにパフォーマンスの違いはありますか?なぜ?Apr 26, 2025 am 12:15 AM

Javaコードは、さまざまなプラットフォームで実行するときにパフォーマンスの違いがあります。 1)JVMの実装と最適化戦略は、OracleJDKやOpenJDKなどとは異なります。 2)メモリ管理やスレッドスケジューリングなどのオペレーティングシステムの特性もパフォーマンスに影響します。 3)適切なJVMを選択し、JVMパラメーターとコード最適化を調整することにより、パフォーマンスを改善できます。

Javaのプラットフォームの独立性の制限は何ですか?Javaのプラットフォームの独立性の制限は何ですか?Apr 26, 2025 am 12:10 AM

java'splatformindepentedencehaslimitationsincludingporformanceoverhead、versioncompatibulisisues、changleSwithnativeLibraryIntegration、プラットフォーム固有の機能、およびjvminStallation/maintenation。

See all articles

ホット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衣類リムーバー

Video Face Swap

Video Face Swap

完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

ホットツール

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

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

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

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

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

WebStorm Mac版

WebStorm Mac版

便利なJavaScript開発ツール

DVWA

DVWA

Damn Vulnerable Web App (DVWA) は、非常に脆弱な PHP/MySQL Web アプリケーションです。その主な目的は、セキュリティ専門家が法的環境でスキルとツールをテストするのに役立ち、Web 開発者が Web アプリケーションを保護するプロセスをより深く理解できるようにし、教師/生徒が教室環境で Web アプリケーションを教え/学習できるようにすることです。安全。 DVWA の目標は、シンプルでわかりやすいインターフェイスを通じて、さまざまな難易度で最も一般的な Web 脆弱性のいくつかを実践することです。このソフトウェアは、