Kovaryans, Değişmezlik ve Kontravaryans düz İngilizce ile açıklanıyor mu?


113

Bugün Java'da Covariance, Contravariance (ve Invariance) hakkında bazı makaleler okudum. İngilizce ve Almanca Wikipedia makalesini ve IBM'den bazı diğer blog gönderilerini ve makaleleri okudum.

Ama bunların tam olarak ne hakkında olduğu konusunda hala biraz kafam karıştı. Bazıları bunun türler ve alt türler arasındaki ilişki hakkında olduğunu söyler, bazıları bunun tür dönüştürme ile ilgili olduğunu söyler ve bazıları bir yöntemin geçersiz kılınmış mı yoksa aşırı yüklenmiş mi olduğuna karar vermek için kullanıldığını söylüyor.

Bu yüzden, yeni başlayanlara Kovaryans ve Kontravaryans'ın (ve Değişmezliğin) ne olduğunu gösteren basit bir İngilizce açıklama arıyorum. Artı, kolay bir örnek.


Lütfen bu gönderiye bakın, sizin için yardımcı olabilir: stackoverflow.com/q/2501023/218717
Francisco Alvarado

3
Belki de bir programcının yığın değişim türü sorusu daha iyidir. Orada gönderi yaparsanız, tam olarak ne anladığınızı ve özellikle neyin kafanızı karıştırdığını belirtmeyi düşünün, çünkü şu anda birinden sizin için bütün bir öğreticiyi yeniden yazmasını istiyorsunuz.
Hovercraft Full Of Eels

Yanıtlar:


289

Bazıları bunun türler ve alt türler arasındaki ilişkiyle ilgili olduğunu söyler, diğerleri bunun tür dönüştürme ile ilgili olduğunu söyler ve diğerleri bir yöntemin üzerine mi yoksa aşırı mı yükleneceğine karar vermek için kullanıldığını söyler.

Yukarıdakilerin hepsi.

Özünde, bu terimler alt tür ilişkisinin tür dönüşümlerinden nasıl etkilendiğini açıklar. Yani, eğer Ave Btürlerse, fbir tür dönüşümü ve sub alt tip ilişkisi (yani , bir alt türü olduğu A ≤ Banlamına gelir ), bizdeAB

  • fkovaryanttır eğer A ≤ Bima ediyorsaf(A) ≤ f(B)
  • faykırıdır, eğer bunu A ≤ Bima edersef(B) ≤ f(A)
  • f Yukarıdakilerin hiçbiri geçerli değilse değişmezdir

Bir örnek ele alalım. Let f(A) = List<A>nereye Listkadar beyan

class List<T> { ... } 

fEşdeğişken mi, aykırı mı yoksa değişmez mi? Kovaryant bir anlamına geleceğini List<String>bir alt tipi olan List<Object>bir olduğunu, kontravaryant List<Object>bir alt tipi olan List<String>, ne diğer bir alt tipi olduğu ve değişmez ie List<String>ve List<Object>değiştirilemez türleridir. Java'da ikincisi doğrudur, biz (biraz gayri resmi olarak) jeneriklerin değişmez .

Başka bir örnek. Let f(A) = A[]. Dır-dirfEşdeğişken mi, aykırı yoksa değişmez mi? Yani, String [] bir Object [] alt türü, Object [] bir String [] alt türü mü, yoksa ikisi de diğerinin bir alt türü değil mi? (Cevap: Java'da diziler eş değişkendir)

Bu hala oldukça soyuttu. Daha somut hale getirmek için, Java'da hangi işlemlerin alt tip ilişkisi açısından tanımlandığına bakalım. En basit örnek atamadır. İfade

x = y;

yalnızca eğer derlenir typeof(y) ≤ typeof(x). Yani, ifadelerin

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

Java'da derlenmeyecek, ancak

Object[] objects = new String[1];

niyet.

Alt tür ilişkisinin önemli olduğu başka bir örnek, bir yöntem çağırma ifadesidir:

result = method(a);

Gayri resmi konuşursak, bu ifade, değerinin ayöntemin ilk parametresine atanması, ardından yöntemin gövdesinin çalıştırılması ve ardından yöntemlerin dönüş değerinin atanması ile değerlendirilir result. Son örnekteki düz atama gibi, "sağ taraf", "sol tarafın" bir alt türü olmalıdır, yani bu ifade yalnızca typeof(a) ≤ typeof(parameter(method))ve ise geçerli olabilir returntype(method) ≤ typeof(result). Yani, yöntem şu şekilde bildirilirse:

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

aşağıdaki ifadelerin hiçbiri derlenmez:

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

fakat

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

niyet.

Alt tiplemenin önemli olduğu başka bir örnek. Düşünmek:

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

nerede

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

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

Gayri resmi olarak, çalışma zamanı bunu şu şekilde yeniden yazar:

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

İşaretli satırın derlenmesi için, geçersiz kılınan yöntemin yöntem parametresinin, geçersiz kılınan yöntemin yöntem parametresinin bir üst türü olması ve dönüş türünün, geçersiz kılınan yöntemin bir alt türü olması gerekir. Resmi olarak konuşursak, f(A) = parametertype(method asdeclaredin(A))en azından aykırı f(A) = returntype(method asdeclaredin(A))olmalı ve en azından ortak değişken olmalıdır.

Yukarıdaki "en az" a dikkat edin. Bunlar, herhangi bir makul statik tip güvenli nesne yönelimli programlama dilinin uygulayacağı minimum gereksinimlerdir, ancak bir programlama dili daha katı olmayı seçebilir. Java 1.4 durumunda, metotları geçersiz kılarken, yani geçersiz kılarken, parametre türleri ve yöntem dönüş türleri aynı olmalıdır (tür silme hariç) parametertype(method asdeclaredin(A)) = parametertype(method asdeclaredin(B)). Java 1.5'ten beri, geçersiz kılma sırasında kovaryant dönüş türlerine izin verilir, yani aşağıdakiler Java 1.5'te derlenir, ancak Java 1.4'te derlenmez:

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

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

Umarım her şeyi kapatmışımdır - ya da daha doğrusu yüzeyi çizmişimdir. Yine de, soyut, ancak önemli tür varyansı kavramını anlamaya yardımcı olacağını umuyorum.


1
Ayrıca, geçersiz kılma sırasında Java 1.5 çelişkili argüman türlerine izin verildiğinden. Sanırım bunu kaçırdın.
Brian Gordon

13
Öyle mi? Tutulmada denedim ve derleyici, geçersiz kılmak yerine aşırı yükleme yapmak istediğimi düşündü ve alt sınıf yöntemine bir @Override ek açıklaması yerleştirdiğimde kodu reddetti. Java'nın çelişkili argüman türlerini desteklediğine dair iddianız için herhangi bir kanıtınız var mı?
meriton

1
Ah, haklısın. Kendim kontrol etmeden birine inandım.
Brian Gordon

1
Çok fazla belge okudum ve bu konuyla ilgili birkaç konuşma izledim, ancak bu açık ara en iyi açıklama. Çok teşekkür ederim.
minzchickenflavor

1
Kesinlikle yalın ve basit olduğu için +1 A ≤ B. Bu gösterim, işleri çok daha basit ve anlamlı kılar. İyi okuma ...
Romeo Sierra

12

Java tipi sistemi ve ardından sınıfları almak:

Bazı T tipindeki herhangi bir nesne, T'nin alt türünün bir nesnesiyle değiştirilebilir.

TİP VARYANS - SINIF YÖNTEMLERİ AŞAĞIDAKİ SONUÇLARA SAHİPTİR

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;

Görülebileceği gibi:

  • T, alt tür S olmalıdır ( B, A'nın alt türü olduğundan kovaryant ).
  • V, U'nun süper türü olmalıdır ( kalıtımın tersi yön olarak karşıt değişken ).

Şimdi, B'nin A'nın alt tipi olmasıyla birlikte ve ters ilişkilidir. Aşağıdaki daha güçlü tipler, daha spesifik bilgilerle tanıtılabilir. Alt tipte.

Kovaryans (Java'da mevcuttur), birinin alt tipte daha spesifik bir sonuç döndürdüğünü söylemek için kullanışlıdır; özellikle A = T ve B = S olduğunda görülür. Contravariance, daha genel bir argümanı ele almaya hazır olduğunuzu söylüyor.


9

Varyans, farklı genel parametrelere sahip sınıflar arasındaki ilişkilerle ilgilidir. İlişkileri, onları seçebilmemizin sebebidir.

Co ve Contra varyansı oldukça mantıklı şeylerdir. Dil tipi sistemi bizi gerçek hayat mantığını desteklemeye zorlar. Örnek olarak anlamak kolaydır.

Kovaryans

Örneğin bir çiçek almak istiyorsunuz ve şehrinizde iki çiçekçi dükkanınız var: gül dükkanı ve papatya dükkanı.

Birine "çiçekçi nerede?" Diye sorarsan ve birisi sana gül dükkanının nerede olduğunu söyler, sorun olur mu? evet, çünkü gül bir çiçektir, eğer bir çiçek almak isterseniz bir gül satın alabilirsiniz. Aynısı, birisi size papatya dükkanının adresini vermişse de geçerlidir. Bu bir örnektir kovaryans : Eğer döküm için izin verilen A<C>için A<B>nerede, Cbir alt sınıfı olan Bise, A(işlevinden sonucunda döner) jenerik değerler üretir. Kovaryans, üreticilerle ilgilidir.

Türleri:

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();
    }
}

Soru "çiçekçi nerede?", Yanıt "orada gül dükkanı":

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

contravariance

Mesela kız arkadaşına çiçek hediye etmek istiyorsun. Kız arkadaşın herhangi bir çiçeği seviyorsa, onu gülleri seven biri olarak mı yoksa papatyaları seven biri olarak düşünebilir misin? evet, çünkü herhangi bir çiçeği seviyorsa hem gülü hem de papatyayı severdi. Bu bir örnektir contravariance : Eğer döküm için izin konum A<B>için A<C>, Calt sınıfını ise Beğer, Atükettiğini jenerik değer. Contravariance tüketicilerle ilgilidir.

Türleri:

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!");
    }

}

Herhangi bir çiçeği seven kız arkadaşınızı gülleri seven ve ona bir gül veren biri olarak düşünüyorsunuz:

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

En fazla bulabilirsiniz Kaynak .


@Peter, teşekkürler, bu adil bir nokta. Değişmezlik, farklı genel parametrelere sahip sınıflar arasında ilişki olmadığı zamandır, yani, B ve C arasındaki ilişki ne olursa olsun, A <B> 'yi A <C>' ye çeviremezsiniz.
VadzimV
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.