共分散、不変性、反変性は平易な英語で説明されていますか?


113

今日、私はJavaの共分散、反変(および不変性)に関するいくつかの記事を読みました。私は英語とドイツ語のウィキペディアの記事、およびIBMからの他のいくつかのブログ投稿と記事を読みました。

しかし、私はまだこれらが正確に何であるかについて少し混乱していますか?型とサブタイプの関係に関するもの、型変換に関するもの、メソッドがオーバーライドされるかオーバーロードされるかを決定するために使用されると言う人もいます。

だから私は平易な英語で簡単な説明を探しています。それは初心者に共分散と反変(そして不変性)が何であるかを示しています。簡単な例としてプラスポイント。


この投稿を参考にしてください。参考になるかもしれません
Francisco Alvarado

3
おそらく、プログラマーのスタック交換タイプの質問の方が良いでしょう。そこに投稿する場合は、理解していること、特に混乱していることだけを述べることを検討してください。現在、あなたは誰かにチュートリアル全体を書き直すように頼んでいるからです。
ホバークラフトフルウナギ

回答:


288

型とサブタイプの間の関係についてであると言う人もいれば、型変換についてであると言う人もいれば、メソッドを上書きするかオーバーロードするかを決定するために使用する人もいます。

上記のすべて。

基本的に、これらの用語は、サブタイプ関係がタイプ変換によってどのように影響を受けるかを説明します。つまり、AおよびBがタイプでfあり、タイプ変換であり、≤サブタイプの関係(つまり、のサブタイプであるA ≤ Bことを意味する)である場合、AB

  • fそれがA ≤ B意味する場合、共変ですf(A) ≤ f(B)
  • fA ≤ B意味する場合、反変ですf(B) ≤ f(A)
  • f 上記のいずれも成立しない場合は不変

例を考えてみましょう。f(A) = List<A>どこListで宣言されているかみましょう

class List<T> { ... } 

あるf共変、反変、または不変は?共変とは、a List<String>がのサブタイプ、aがサブタイプでList<Object>ある反変List<Object>List<String>およびどちらも他のサブタイプではない不変、つまり変換不可能なタイプList<String>List<Object>あることを意味します。Javaでは後者が真実であり、ジェネリックは不変であると(ある程度非公式に)言います。

もう一つの例。みましょうf(A) = A[]。あるf共変、反変、または不変は?つまり、String []はObject []のサブタイプ、Object []はString []のサブタイプですか、それとも他のサブタイプでもありませんか?(回答:Javaでは、配列は共変です)

これはまだかなり抽象的なものでした。より具体的にするために、Javaのどの操作がサブタイプの関係で定義されているかを見てみましょう。最も簡単な例は代入です。ステートメント

x = y;

コンパイルされるのはtypeof(y) ≤ typeof(x)。つまり、ステートメントが

ArrayList<String> strings = new ArrayList<Object>();
ArrayList<Object> objects = new ArrayList<String>();

Javaでコンパイルされませんが、

Object[] objects = new String[1];

意志。

サブタイプの関係が重要なもう1つの例は、メソッド呼び出し式です。

result = method(a);

非公式に言えば、このステートメントはa、メソッドの最初のパラメータにの値を割り当て、次にメソッドの本体を実行し、メソッドの戻り値をに割り当てることによって評価されresultます。最後の例では、プレーンの割り当てと同様に、「右側には、」もしこの文は唯一の有効なことができ、すなわち、「左側」のサブタイプでなければならないtypeof(a) ≤ typeof(parameter(method))returntype(method) ≤ typeof(result)。つまり、メソッドが次のように宣言されている場合:

Number[] method(ArrayList<Number> list) { ... }

次の式はいずれもコンパイルされません。

Integer[] result = method(new ArrayList<Integer>());
Number[] result = method(new ArrayList<Integer>());
Object[] result = method(new ArrayList<Object>());

だが

Number[] result = method(new ArrayList<Number>());
Object[] result = method(new ArrayList<Number>());

意志。

サブタイプが重要なもう1つの例は、オーバーライドです。考慮してください:

Super sup = new Sub();
Number n = sup.method(1);

どこ

class Super {
    Number method(Number n) { ... }
}

class Sub extends Super {
    @Override 
    Number method(Number n);
}

非公式には、ランタイムはこれを次のように書き換えます。

class Super {
    Number method(Number n) {
        if (this instanceof Sub) {
            return ((Sub) this).method(n);  // *
        } else {
            ... 
        }
    }
}

マークされた行をコンパイルするには、オーバーライドするメソッドのメソッドパラメーターがオーバーライドされるメソッドのメソッドパラメーターのスーパータイプであり、戻り値の型がオーバーライドされるメソッドのサブタイプである必要があります。正式に言えf(A) = parametertype(method asdeclaredin(A))ば、少なくとも反変でf(A) = returntype(method asdeclaredin(A))なければならず、少なくとも共変でなければなりません。

上記の「少なくとも」に注意してください。これらは、静的で型保証された適切なオブジェクト指向プログラミング言語が適用する最小限の要件ですが、プログラミング言語をより厳密にすることもできます。Java 1.4の場合、メソッドをオーバーライドするparametertype(method asdeclaredin(A)) = parametertype(method asdeclaredin(B))とき、つまりオーバーライドするとき、パラメーターの型とメソッドの戻り値の型は(型の消去を除いて)同一でなければなりません。Java 1.5以降では、オーバーライド時に共変の戻り値の型が許可されています。つまり、以下はJava 1.5ではコンパイルされますが、Java 1.4ではコンパイルされません。

class Collection {
    Iterator iterator() { ... }
}

class List extends Collection {
    @Override 
    ListIterator iterator() { ... }
}

私はすべてをカバーしたいと思っています。それでも、抽象、しかし重要な型分散の概念を理解するのに役立つことを願っています。


1
また、Java 1.5の反変引数型はオーバーライド時に許可されています。あなたはそれを逃したと思います。
ブライアンゴードン

13
彼らは?私はそれを日食で試したところ、コンパイラはオーバーライドではなくオーバーロードするつもりであると考え、@ Overrideアノテーションをサブクラスメソッドに配置するとコードを拒否しました。Javaが反変引数型をサポートしているという主張の証拠はありますか?
メリトン2013年

1
ああ、そうです。私は自分でチェックせずに誰かを信じました。
ブライアンゴードン

1
私はたくさんのドキュメントを読み、このトピックに関するいくつかの話を見ましたが、これは断然最良の説明です。Thnxたくさん。
minzchickenflavor

1
+1を使用すると、完全にレマンでシンプルになりA ≤ Bます。その表記により、物事がはるかにシンプルで意味のあるものになります。朗読...
Romeo Sierra

12

Javaの型システム、そしてクラスを取る:

あるタイプTのオブジェクトは、サブタイプTのオブジェクトで置き換えることができます。

タイプの差異-クラスメソッドには次の結果があります

class A {
    public S f(U u) { ... }
}

class B extends A {
    @Override
    public T f(V v) { ... }
}

B b = new B();
t = b.f(v);
A a = ...; // Might have type B
s = a.f(u); // and then do V v = u;

次のことがわかります。

  • TはサブタイプSでなければなりません(BはAのサブタイプであるため、共変です)。
  • VはUのスーパータイプでなければなりません(継承方向としての反変)。

ここで、BがAのサブタイプであることに共および逆の関係があります。以下のより強い型付けが、より具体的な知識とともに導入される場合があります。サブタイプ。

共分散(Javaで使用可能)は便利です。つまり、サブタイプでより具体的な結果を返すということです。特にA = TおよびB = Sの場合に見られます。逆分散は、より一般的な議論を処理する準備ができていると言います。


8

分散とは、ジェネリックパラメーターが異なるクラス間の関係のことです。それらの関係は、私たちがそれらをキャストできる理由です。

CoとContraの分散はかなり論理的なものです。言語型システムは、現実の論理をサポートすることを強制します。例で理解しやすいです。

共分散

たとえば、花を購入したいのですが、市内に2つのフラワーショップがあります。ローズショップとデイジーショップです。

「花屋はどこですか」と尋ねると 誰かがローズショップの場所を教えてくれても大丈夫でしょうか?はい、バラは花なので、花を買いたいならバラを買うことができます。デイジーショップの住所を誰かが返信した場合も同様です。これはの例である共分散:あなたはキャストに許可されているA<C>A<B>、どこCのサブクラスであるB場合、A一般的な値(関数から結果としてリターン)を生成します。共分散とは生産者に関するものです。

タイプ:

class Flower {  }
class Rose extends Flower { }
class Daisy extends Flower { }

interface FlowerShop<T extends Flower> {
    T getFlower();
}

class RoseShop implements FlowerShop<Rose> {
    @Override
    public Rose getFlower() {
        return new Rose();
    }
}

class DaisyShop implements FlowerShop<Daisy> {
    @Override
    public Daisy getFlower() {
        return new Daisy();
    }
}

質問は「フラワーショップはどこですか」、回答は「ローズショップはそこ」です。

static FlowerShop<? extends Flower> tellMeShopAddress() {
    return new RoseShop();
}

反変

たとえば、あなたはあなたのガールフレンドに花を贈りたいです。彼女が花を愛するなら、バラを愛する人、またはヒナギクを愛する人として彼女を考えることができますか?そうです、もし彼女が花を愛するなら、彼女はバラとデイジーの両方を愛するでしょう。これはの例ですcontravariance:あなたはキャストに許可されているA<B>A<C>CのサブクラスをされBた場合、A消費一般的な値です。逆分散は消費者についてです。

タイプ:

interface PrettyGirl<TFavouriteFlower extends Flower> {
    void takeGift(TFavouriteFlower flower);
}

class AnyFlowerLover implements PrettyGirl<Flower> {
    @Override
    public void takeGift(Flower flower) {
        System.out.println("I like all flowers!");
    }

}

花を愛するあなたのガールフレンドをバラを愛する誰かと考えて、彼女にバラを与えます:

PrettyGirl<? super Rose> girlfriend = new AnyFlowerLover();
girlfriend.takeGift(new Rose());

あなたはソースでより多くを見つけることができます。


@ピーター、ありがとう、それは公平な点です。不変性は、異なるジェネリックパラメーターを持つクラス間に関係がない場合です。つまり、BとCの関係が何であれ、A <B>をA <C>にキャストできません。
VadzimV
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.