JavaSEの高度なプログラミングの概要と一般的なプログラミングのトレーニング2

JavaSEの高度なプログラミングの概要と一般的なプログラミングのトレーニング2

第四に、ジェネリックの実現原理

4.1、概要の原則

ジェネリックはJDK1.5でのみ作成されたため、それ以前は、JavaジェネリックはObjectオブジェクトを使用してすべてのタイプのオブジェクトを受け入れていました。ただし、JVMは下位互換性を維持する必要があります。つまり、下位バージョンのJDKは上位バージョンのJDKで実行できる必要があります。この互換性のため、ジェネリックはコンパイル後にJDK1.5以前のバイトコードと同じクラスファイルになる必要がある
ため、ジェネリックの原則は依然としてObject ~~に関連しています。

4.2、例を見てください

例1:共通ジェネリッククラスと共通ジェネリック関数


/**
 * @author jiangzl
 */
public class MyGeneric7 {
    
    

    public static <T> void getTemplateValue(MyNumber<T> myNumber){
    
    
        System.out.println(myNumber.getValue());
    }

    public static void main(String[] args) {
    
    
        MyNumber<Double> doubleMyNumber = new MyNumber<>(3.14);
        getTemplateValue(doubleMyNumber);
        MyNumber<Integer> integerMyNumber = new MyNumber<>(520);
        getTemplateValue(integerMyNumber);
    }
}

class MyNumber<T>{
    
    
    private T value;

    public MyNumber(T value){
    
    
        this.value = value;
    }

    public T getValue(){
    
    
        return value;
    }
}
例の出力を見てください。

ここに画像の説明を挿入

分解の結果を見てください。
最初:MyNumber.classの結果

ここに画像の説明を挿入

重要なポイントは、以下を参照してください:フィールド値

java / lang / Object ;! これは、分解後、この値がオブジェクトオブオブジェクトになることを示しています。その場合、構築メソッドもObjectです。invokespecialは、仮想関数ではなく特定の関数を意味するだけです~~

2番目:MyGeneric7.classの結果

ここに画像の説明を挿入

ジェネリックメソッドのパラメーターを見ると、逆アセンブル後、getValue()とフィールドパーツ、つまりパラメーターパーツはObjectオブジェクトになっています~~

それでは、ジェネリック型が具体的に呼び出されたときに、どのようにインスタンス化しますか?以下を見てください:〜
ここに画像の説明を挿入
ここに、メインメソッドの一部である一般的なインスタンス化があります。

#8と#9がコメント化された後、静的メソッドを呼び出すと、Objectオブジェクトに対してDouble.valueOf()とInteger.valueOf()が実行されます。〜これでインスタンス化が完了し、ジェネリックメソッドが呼び出されます。

例2:制限付きジェネリック

import java.security.Security;
import java.util.Comparator;
import java.util.Scanner;

/**
 * 泛型类型的限定
 * @author jiangzl
 */
public class MyGeneric3 {
    
    

    public static <T extends Comparable> T getMax(T... e){
    
    
        if(null == e || e.length <= 0){
    
    
            return null;
        }
        T ret = e[0];
        for(T var : e){
    
    
            if(ret.compareTo(var) < 0){
    
    
                ret = var;
            }
        }
        return ret;
    }

    public static void main(String[] args) {
    
    
        int n = 5;
        Student[] s = new Student[n];
        Scanner sc = new Scanner(System.in);
        for(int i = 0;i < n;++i){
    
    
            String name = sc.next();
            int score = sc.nextInt();
            s[i] = new Student(name, score);
        }
        sc.close();
        System.out.println(getMax(s));

        String Str[] = {
    
    "b", "a", "c", "d", "e"};
        System.out.println(getMax(Str));
    }
}

class Student implements Comparable<Student> {
    
    
    private String name;
    private int score;

    public Student(String name, int score){
    
    
        this.name = name;
        this.score = score;
    }

    @Override
    public String toString(){
    
    
        return new String("name = " + name + ", score = " + score);
    }

    @Override
    public int compareTo(Student s) {
    
    
        if(score != s.score){
    
    
            return score - s.score;
        }
        else{
    
    
            return s.name.compareTo(name);
        }
    }
}

ここで、ジェネリックメソッドをインスタンス化できるクラスは、Comparableインターフェイスを実装するクラスである必要があります。

分解の結果を見てみましょう:(内容が多すぎます、抜粋のみ)

ここに画像の説明を挿入
ここでは、ジェネリックメソッド(public static <T extends Comparable> T getMax(T... e))が逆アセンブルされると、パラメーターがComparable型になることがわかります。

まず、extendsのクラスとインターフェースにクラスがある場合、そのクラスを前に書く必要があり、このクラスの型は逆アセンブル後の型です!
第二:拡張のためのクラスがなく、インターフェースがある場合、それは問題ではありません最初の(拡張に最も近い)クラスがジェネリック型として使用されます!

5、共分散と反分散

5.1。共分散と反分散とは何ですか、そしてその関係は何ですか?

A <= BがAがBのサブクラスであることを意味する場合、f(x)は特定のクラスxが特定のデータ構造にカプセル化されることを示します。配列、コレクションなど カプセル化後、f(A)とf(B)に継承関係がある場合、関係があるといいます〜

関係は次のとおりです。

最初:

A <= Bかつf(A)<= f(B)の場合、それは共分散と呼ばれます。つまり、AはBのサブタイプであり、カプセル化後もAはBのサブタイプです。通常、配列があります。たとえば、AはBのサブタイプであり、配列A []は依然としてB []〜のサブタイプです。

第二:

A <= Bおよびf(A)> = f(B)の場合、これは反変と呼ばれます。つまり、AはBのサブタイプであり、カプセル化後のBはAのサブタイプです。通常、これはワイルドカードによって制限され、下限によって制限されます〜

第三:

A <= Bの場合、f(A)<> f(B)は変更されていないと見なされます。つまり、AはBのサブクラスです。カプセル化後、AとBは継承関係を持ちません。通常、ジェネリック、無制限のジェネリックがあり、List <A>やList <B>などのジェネリックの後のコレクションは、〜とは関係ありません。

これははっきりしないかもしれません、それを示すために写真を使用してください:
ここに画像の説明を挿入

5.2関数の戻り値の共分散とリヒター置換の原理

リヒター置換の原則によれば、関数に渡される実際のパラメーターは、仮パラメーターで受け取ることができるはずです。つまり、タイプ(実パラメータ)<=タイプ(仮パラメータ)です。これは、実パラメータが仮パラメータ自体のタイプまたはサブタイプであることを意味します。設定された戻り値は、呼び出しの戻り値、つまりtype(call)> = type(return)で受信できる必要があります。

例を見てください:



/**
 * @author jiangzl
 */
public class MyGeneric7 {
    
    
    public static void main(String[] args) {
    
    
        ClassFather classFather = new ClassSon();
        System.out.println(classFather.method(3.14));
    }
}

class ClassFather{
    
    
    public Number method(Number value){
    
    
    	System.out.println(value.getClass());
        System.out.println("father");
        return value;
    }
}

class ClassSon extends ClassFather{
    
    
    @Override
    public Double method(Number value){
    
    
        System.out.println("son");
        return Double.valueOf(value.toString());
    }
}

JDK1.5から、サブクラスが親メソッドを書き換えることができる場合、共変(オーバーライドされた親メソッドの戻り値タイプのサブクラス)が戻り値として使用されます。しかし、このコードの出力を見てみましょう。

ここに画像の説明を挿入

結論に達しました:

JDK1.5以降、サブクラスはスーパークラスメソッドをオーバーライドして共分散を許可します。つまり、戻り値はスーパークラスメソッドよりも特定の型にすることができます。

5.3、配列の共分散

まずサンプルコードを見てください:

モジュール1:クラスの設計
class FirstClass{
    
    
    public FirstClass(){
    
    
        System.out.println("FirstClass is constructed!!");
    }

    public void sayHello(){
    
    
        System.out.println("Helo World!");
    }
}

class SecondClass extends FirstClass{
    
    
    public SecondClass(){
    
    
        System.out.println("SecondClass is constructed!!");
    }
}

class LastClass extends SecondClass{
    
    
    public LastClass(){
    
    
        System.out.println("LastClass is constructed!!");
    }
}
モジュール2:配列の共分散
		FirstClass[] firstClasses = new SecondClass[2];
        firstClasses[0] = new SecondClass();
        try {
    
    
            firstClasses[1] = new FirstClass();
            firstClasses[1].sayHello();
        }
        catch (Exception e){
    
    
            System.out.println("Runtime error!");
        }

        System.out.println();
        SecondClass[] secondClasses = new SecondClass[2];
        firstClasses = secondClasses;
        firstClasses[0] = new LastClass();
        try {
    
    
            firstClasses[1] = new FirstClass();
            firstClasses[1].sayHello();
        }
        catch (Exception e){
    
    
            System.out.println("Runtime error!");
        }

ここでは、mainメソッドにある配列の共変部分に関するコードのみをインターセプトしました。実行結果は次のとおりです。
ここに画像の説明を挿入
ここから、配列が実際に共変であることがわかりますが、新しい問題も発生します。

親クラスのコンストラクターが実行されるまで、サブクラスが親クラスのオブジェクトをインスタンス化するのはなぜですか?

コンパイルはここを通過し、例外は親クラスのコンストラクターが終了するまでスローされません!なぜJVMはそれほど後知恵なのですか?知っている人は、メッセージを残すか、プライベートメッセージを送ってください、ありがとう!

5.4、一般的な共分散

例を見てみましょう:

import java.util.ArrayList;

/**
 * @author jiangzl
 */
public class MyGeneric6 {
    
    
    public static void main(String[] args) {
    
    
        int nA = 3, nB = 4, nC = 5;
        ArrayList<A> asA = new ArrayList<>();
        for(int i = 0;i < nA;++i){
    
    
            asA.add(new A());
        }
        ArrayList<B> asB = new ArrayList<>();
        for(int i = 0;i < nB;++i){
    
    
            asB.add(new B());
        }
        ArrayList<C> asC = new ArrayList<>();
        for(int i = 0;i < nC;++i){
    
    
            asC.add(new C());
        }

        sayName(asA, asB, asC);
    }

    public static void sayName(ArrayList<? extends A>... arrayList){
    
    
        int len = arrayList.length;
        for(int i = 0;i < len;++i){
    
    
            int size = arrayList[i].size();
            for(int j = 0;j < size;++j){
    
    
                arrayList[i].get(j).sayName();
            }
        }
    }
}

class A{
    
    
    public void sayName(){
    
    
        System.out.println("my name is A");
    }
}

class B extends A{
    
    
    @Override
    public void sayName(){
    
    
        System.out.println("my name is B");
    }
}

class C extends A{
    
    
    @Override
    public void sayName(){
    
    
        System.out.println("my name is C");
    }
}

出力を見てください:

ここに画像の説明を挿入

5.後ろに書きます:

一般的な共分散と反分散は、実際には前のセクションですでに述べられていますが、注意すべき点は次のとおりです。

共分散、つまり上限?Extends Classはジェネリックを定義しますが、ジェネリックは取得のみが可能で、設定はできません!

つまり、スーパークラスの下限はジェネリックを制限するため、設定のみが可能で、取得はできません。

ジェネリックの基本を理解していない場合は、前回の記事を参照してください。

JavaSE Advanced Programming-Generic Foundationの概要1

また、この記事には疑問が2つありますが、私の能力は限られているため、誤字や誤解がある場合は、ぜひ指摘していただきたいと思います。批判をありがとう!疑問も2つありますが、何か説明があれば、お気軽に教えてください!再度、感謝します!

おすすめ

転載: blog.csdn.net/qq_44274276/article/details/108098107