ホームページ >Java >&#&チュートリアル >Java 配列共分散と一般不変性の知識の紹介 (コード付き)
この記事では、Java 配列の共分散と一般的な不変性についての知識を紹介します (コード付き)。必要な方は参考にしていただければ幸いです。
可変性は OOP 言語の不変性の大きな落とし穴であり、Java の配列の共分散は古い落とし穴の 1 つです。最近踏んだのでメモ。ところで、パラダイムの退化についても触れておこう。
配列の共分散を説明する前に、まず、共分散、不変、反変という 3 つの関連する概念を明確にします。
1. 共分散、不変性、反変性
レストラン用にこのようなコードを書いたとします
#
class Soup<T> { public void add(T t) {} } class Vegetable { } class Carrot extends Vegetable { }
汎用クラス Soup8742468051c85b06f0a0af9e3e506b5c があり、これは材料 T で作られたスープを表します。そのメソッド add(T t) は、スープに材料を追加することを表します。材料Tを加えます。 Vegetable クラスは野菜を表し、キャロット クラスはニンジンを表します。もちろん、キャロットは野菜のサブクラスです。
それでは、スープ とスープ の関係は何でしょうか? 最初の反応は、にんじんスープは明らかに野菜のスープであるため、スープc5a68faf2386114a42af4ce42f5c283a はスープ3e6024b0c696400d14fe0f28f1c8c3c9 のサブカテゴリであるべきであるというものです。その場合は、以下のコードを見てください。このうち、Tomato はトマトを意味します。これは、 Vegetable のもう 1 つのサブクラスです。 , したがって、Soupc5a68faf2386114a42af4ce42f5c283a のインスタンスを変数スープに割り当てることができます。 2 番目の文は問題ありません。スープは Soupab702d7b9a6cb2354171f9714e875283 タイプとして宣言されており、その add メソッドは Vegetable タイプのパラメーターを受け取り、トマトは Vegetable であり、正しいタイプを持っているからです。 しかし、2 つの文を組み合わせるときに問題が発生します。実際のスープのタイプは Soupc5a68faf2386114a42af4ce42f5c283a で、トマトのインスタンスをその add メソッドに渡しました。つまり、トマトでにんじんのスープを作ろうとしたら、絶対に作れません。したがって、スープc5a68faf2386114a42af4ce42f5c283aをスープ3e6024b0c696400d14fe0f28f1c8c3c9のサブクラスとみなすのは論理的には正しいのですが、使用時には欠陥があります。 それでは、スープc5a68faf2386114a42af4ce42f5c283aとスープ3e6024b0c696400d14fe0f28f1c8c3c9の関係は何でしょうか?言語が異なれば、理解と実装も異なります。要約すると、3つの状況があります。 (1) Soupc5a68faf2386114a42af4ce42f5c283a が Soupab702d7b9a6cb2354171f9714e875283 の場合、汎用 Soup8742468051c85b06f0a0af9e3e506b5c は共変であると言われます。 ; が 2 つの無関係なクラスである場合、汎用 Soup8742468051c85b06f0a0af9e3e506b5c は不変であると言われます。逆の変化。 (ただし、反変性は一般的ではありません) 共変性、不変性、反変性の概念を理解した後、Java の実装を見てみましょう。 Java の一般的なジェネリックは不変です。つまり、Soupab702d7b9a6cb2354171f9714e875283 と Soup7ee71479a1181d69b4eb73c1ca11e6f7 は無関係な 2 つのクラスであり、一方のクラスのインスタンスを他方のクラスの変数に割り当てることはできません。したがって、トマトを使用してニンジンスープを作成する上記のコードは、実際にはまったくコンパイルできません。 2. 配列の共分散共変です。つまり、キャロット[]は野菜[]のサブクラスです。前のセクションの例では、共分散が問題を引き起こす場合があることを示しました。たとえば、次のコードは
Soup<Vegetable> soup = new Soup<Carrot>(); soup.add(new Tomato());配列は共変であるため、コンパイラは キャロット[10] をタイプ Vegetable[] の変数に割り当てることを許可します。コードは正常にコンパイルできます。何か大きな問題が発生するのは、実行時、つまり JVM が実際にニンジンの山にトマトを挿入しようとするときだけです。したがって、上記のコードは実行時に java.lang.ArrayStoreException 型の例外をスローします。
配列共分散は、Java の有名な歴史的重荷の 1 つです。配列を使用する場合は注意してください。
例の配列をリストに置き換えると、状況は異なります。このように、Vegetable[] vegetables = new Carrot[10]; vegetables[0] = new Tomato(); // 运行期错误ArrayList はジェネリック クラスであり、不変です。したがって、ArrayList と ArrayList の間には継承関係がなく、このコードはコンパイル中にエラーを報告します。 どちらのコードもエラーを報告しますが、通常はコンパイル時エラーの方が実行時エラーよりも処理が簡単です。 3. ジェネリクスが共分散と反変性も必要とする場合 ジェネリクスは不変ですが、一部のシナリオでは共変性が期待されます。たとえば、体重を減らすために毎日野菜スープを飲んでいる若い女性がいます。
ArrayList<Vegetable> vegetables = new ArrayList<Carrot>(); // 编译期错误 vegetables.add(new Tomato());
我们希望drink方法可以接受各种不同的蔬菜汤,包括Soup48d44884ab571fbfd69a92f91c198045和Soup8ad9f592c4c8b60fa486d2a1b7041744。但受到不变性的限制,它们无法作为drink的参数。
要实现这一点,应该采用一种类似于协变性的写法
public void drink(Soup<? extends Vegetable> soup) {}
意思是,参数soup的类型是泛型类Soup8742468051c85b06f0a0af9e3e506b5c,而T是Vegetable的子类(也包括Vegetable自己)。这时,小姐姐终于可以愉快地喝上胡萝卜汤和西红柿汤了。
但是,这种方法有一个限制。编译器只知道泛型参数是Vegetable的子类,却不知道它具体是什么。所以,所有非null的泛型类型参数均被视为不安全的。说起来很拗口,其实很简单。直接上代码
public void drink(Soup<? extends Vegetable> soup) { soup.add(new Tomato()); // 错误 soup.add(null); // 正确}
方法内的第一句会在编译期报错。因为编译器只知道add方法的参数是Vegetable的子类,却不知道它具体是Carrot、Tomato、或者其他的什么类型。这时,传递一个具体类型的实例一律被视为不安全的。即使soup真的是Soup8ad9f592c4c8b60fa486d2a1b7041744类型也不行,因为soup的具体类型信息是在运行期才能知道的,编译期并不知道。
但是方法内的第二句是正确的。因为参数是null,它可以是任何合法的类型。编译器认为它是安全的。
同样,也有一种类似于逆变的方法
public void drink(Soup<? super Vegetable> soup) {}
这时,Soup8742468051c85b06f0a0af9e3e506b5c中的T必须是Vegetable的父类。
这种情况就不存在上面的限制了,下面的代码毫无问题
public void drink(Soup<? super Vegetable> soup) { soup.add(new Tomato()); }
Tomato是Vegetable的子类,自然也是Vegetable父类的子类。所以,编译期就可以确定类型是安全的。
以上がJava 配列共分散と一般不変性の知識の紹介 (コード付き)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。