<Arasındaki fark nedir? Taban> ve <T Taban>>?


29

Bu örnekte:

import java.util.*;

public class Example {
    static void doesntCompile(Map<Integer, List<? extends Number>> map) {}
    static <T extends Number> void compiles(Map<Integer, List<T>> map) {}

    static void function(List<? extends Number> outer)
    {
        doesntCompile(new HashMap<Integer, List<Integer>>());
        compiles(new HashMap<Integer, List<Integer>>());
    }
}

doesntCompile() ile derlenemedi:

Example.java:9: error: incompatible types: HashMap<Integer,List<Integer>> cannot be converted to Map<Integer,List<? extends Number>>
        doesntCompile(new HashMap<Integer, List<Integer>>());
                      ^

süre compiles()derleyici tarafından kabul edilir.

Bu cevap , tek farkın aksine <? ...>, <T ...>daha sonra bu türden daha sonra referans vermenize izin verdiğini açıklıyor .

Arasındaki fark nedir <? extends Number>ve <T extends Number>bu durumda ve neden ilk derleme etmiyor?


Yorumlar uzun tartışmalar için değildir; bu görüşme sohbete taşındı .
Samuel Liew

[1] Java Genel türü: Liste <? Sayı> 'genişletir ve <T, Sayı'yı genişletir> aynı soruyu soruyor gibi görünmektedir, ancak ilgi çekici olsa da, bu gerçekten bir kopya değildir. [2] Bu iyi bir soru olmakla birlikte, başlık son cümlede sorulan belirli soruyu doğru bir şekilde yansıtmamaktadır.
skomisa


Buradaki açıklamaya ne dersiniz ?
Mart'ta

Yanıtlar:


14

Yöntemi aşağıdaki imzayla tanımlayarak:

static <T extends Number> void compiles(Map<Integer, List<T>> map) {}

ve şöyle çağırıyor:

compiles(new HashMap<Integer, List<Integer>>());

JLS olarak §8.1.2 biz bu (benim tarafımdan kalın ilginç kısmı) bulabilirsiniz:

Genel sınıf bildirimi , tür parametresi bölümünün tür bağımsız değişkenlerine göre olası her çağrılması için bir dizi parametrelenmiş tür (§4.5) tanımlar . Bu parametreli türlerin tümü, çalışma zamanında aynı sınıfı paylaşır.

Başka bir deyişle, tür Tgiriş türüyle eşleştirilir ve atanır Integer. İmza etkili bir şekilde olacak static void compiles(Map<Integer, List<Integer>> map).

Bu doesntCompileyöntem söz konusu olduğunda , jls alt tipleme kurallarını tanımlar ( §4.5.1 , kalın yazılan):

T2 tür argümanının, T2 tarafından belirtilen türler kümesi muhtemelen aşağıdaki kuralların dönüşlü ve geçişli kapanışı altında T1 tarafından belirtilen türler kümesinin bir alt kümesi ise, T2 <= T1 yazılan başka bir T2 tür argümanı içerdiği söylenir. burada <: alt türü belirtir (§4.10)):

  • ? T <=? T <: S ise S'yi genişletir

  • ? T <=?

  • ? süper T <=? süper S eğer S <: T

  • ? süper T <=?

  • ? süper T <=? Nesneyi genişletir

  • T <= T

  • T <=? T'yi genişletir

  • T <=? süper T

Bu, ? extends Numbergerçekten içerdiği Integerveya hatta List<? extends Number>içerdiği anlamına gelir, List<Integer>ancak Map<Integer, List<? extends Number>>ve için geçerli değildir Map<Integer, List<Integer>>. Bu konuda daha fazla bilgi bu SO iş parçacığında bulunabilir . ?Joker karakterli sürümü , aşağıdakilerin bir alt türünü beklediğinizi bildirerek çalıştırabilirsiniz List<? extends Number>:

public class Example {
    // now it compiles
    static void doesntCompile(Map<Integer, ? extends List<? extends Number>> map) {}
    static <T extends Number> void compiles(Map<Integer, List<T>> map) {}

    public static void main(String[] args) {
        doesntCompile(new HashMap<Integer, List<Integer>>());
        compiles(new HashMap<Integer, List<Integer>>());
    }
}

[1] Ne demek düşünüyorum ? extends Numberziyade ? extends Numeric. [2] "Liste <? Numarayı genişletiyor ve Liste <Integer> için durum böyle değil" iddianız yanlış. @VinceEmigh'in daha önce işaret static void demo(List<? extends Number> lst) { }ettiği gibi, bir yöntem oluşturabilir ve bunu şu demo(new ArrayList<Integer>());veya bu şekilde çağırabilirsiniz demo(new ArrayList<Float>());ve kod derleyip OK'yi çalıştırır. Yoksa söylediklerinizi yanlış mı yanlış anlıyorum?
skomisa

@ Her iki durumda da haklısın. İkinci noktanıza gelince, yanıltıcı bir şekilde yazdım. List<? extends Number>Tüm haritanın tip parametresi olarak kastediyorum , kendisi değil. Yorum için çok teşekkür ederim.
Andronicus

@skomisa aynı sebepten List<Number>içermiyor List<Integer>. Bir işlevin olduğunu varsayalım static void check(List<Number> numbers) {}. Onunla çağırırken derlemez check(new ArrayList<Integer>());, yöntemi olarak tanımlamanız gerekir static void check(List<? extends Number> numbers) {}. Harita ile aynı ama daha fazla yuvalama.
Andronicus

1
@skomisa Numberda listenin bir tür parametresidir ve ? extendsbunu kovaryant yapmak için eklemeniz gerekir List<? extends Number>, bir tür parametresidir Mapve aynı zamanda ? extendskovaryans için de gereklidir .
Mart'ta Andronicus

1
TAMAM. Çok seviyeli bir joker (diğer bir deyişle "iç içe joker" ?) Çözümünü sağladığınızdan ve ilgili JLS referansına bağlı olduğunuzdan, ödülünüzü alın.
skomisa

6

Çağrıda:

compiles(new HashMap<Integer, List<Integer>>());

T, Tamsayı ile eşleştirildiğinden, bağımsız değişkenin türü a şeklindedir Map<Integer,List<Integer>>. Bu yöntem için geçerli değildir doesntCompile: bağımsız değişken türü, çağrıdaki Map<Integer, List<? extends Number>>gerçek bağımsız değişken ne olursa olsun kalır ; ve bu öğeden atanamaz HashMap<Integer, List<Integer>>.

GÜNCELLEME

In doesntCompileyöntemle, hiçbir şey böyle bir şey yapmak önler:

static void doesntCompile(Map<Integer, List<? extends Number>> map) {
    map.put(1, new ArrayList<Double>());
}

Açıkçası, HashMap<Integer, List<Integer>>a'yı argüman olarak kabul edemez .


Öyleyse geçerli bir çağrı nasıl olurdu doesntCompile? Sadece merak ediyorum.
Xtreme Biker

1
@XtremeBiker doesntCompile(new HashMap<Integer, List<? extends Number>>());olduğu gibi çalışır doesntCompile(new HashMap<>());.
skomisa

@XtremeBiker, bu bile işe yarar, Harita <Tamsayı, Liste <? genişletir Sayı >> harita = yeni HashMap <Tamsayı, Liste <? Numarayı genişletir >> (); map.put (null, yeni ArrayList <Integer> ()); doesntCompile (harita);
Mayıs'ta

"Bu, hangi kişiden devredilemez HashMap<Integer, List<Integer>>" diye atanabilir.
Dev Null

@DevNull yukarıdaki güncellememi gör
Maurice Perry

2

Gösteri için basit örnek. Aynı örnek aşağıdaki gibi görselleştirilebilir.

static void demo(List<Pair<? extends Number>> lst) {} // doesn't work
static void demo(List<? extends Pair<? extends Number>> lst) {} // works
demo(new ArrayList<Pair<Integer>()); // works
demo(new ArrayList<SubPair<Integer>()); // works for subtype too

public static class Pair<T> {}
public static class SubPair<T> extends Pair<T> {}

List<Pair<? extends Number>>çok seviyeli bir joker karakter türüdür, List<? extends Number>standart bir joker karakter türüdür.

Joker tip Geçerli beton örneklemi List<? extends Number>kapsar Numberve herhangi alt tipleri Numberdurumunda ise bir List<Pair<? extends Number>>türünü argüman bir tür argümanı ve kendisi jenerik tipte bir somut örneğinin vardır.

Jenerikler değişmez, bu nedenle Pair<? extends Number>joker kart türü sadece kabul edebilir Pair<? extends Number>>. İç tip ? extends Numberzaten kovaryanttır. Kovaryansa izin vermek için kapalı türü kovaryant yapmanız gerekir.


Nasıl <Pair<Integer>>çalışmıyor <Pair<? extends Number>>ama birlikte <T extends Number> <Pair<T>>nasıl çalışıyor ?
jaco0646

@ jaco0646 Esasen OP ile aynı soruyu soruyorsunuz ve Andronicus'un yanıtı kabul edildi. Bu yanıttaki kod örneğine bakın.
skomisa

@skomisa, evet, aynı soruyu birkaç nedenden dolayı soruyorum: birincisi, bu cevabın aslında OP'nin sorusunu ele almıyor gibi görünüyor; ama ikincisi bu cevabı daha kolay kavramak. Potansiyel müşteriler beni olmayan iç içe jenerik vs iç içe, hatta anlamak için ben herhangi bir şekilde Andronicus'ta cevabını takip edemez Tvs ?. Sorunun bir kısmı, Andronicus açıklamasının hayati noktasına ulaştığında, sadece önemsiz örnekleri kullanan başka bir iş parçacığını savunuyor olmasıdır. Burada daha net ve eksiksiz bir cevap almayı umuyordum.
jaco0646

1
@ jaco0646 Tamam. Angelika Langer'ın "Java Generics SSS - Tür Bağımsız Değişkenleri" adlı dokümanda çok seviyeli (yani iç içe) joker karakterler ne anlama geliyor? . OP'nin sorusunda ortaya çıkan sorunları açıklamak için bildiğim en iyi kaynak bu. İç içe joker karakterler için kurallar ne basit ne de sezgiseldir.
skomisa

1

Genel joker karakterlerin belgelerine, özellikle joker karakter kullanımına ilişkin yönergeleri incelemenizi öneririz.

Açıkçası yönteminizi #doesntCompile

static void doesntCompile(Map<Integer, List<? extends Number>> map) {}

ve şöyle çağır

doesntCompile(new HashMap<Integer, List<Integer>>());

Temelde yanlış

Yasal uygulamayı ekleyelim :

    static void doesntCompile(Map<Integer, List<? extends Number>> map) {
        List<Double> list = new ArrayList<>();
        list.add(0.);
        map.put(0, list);
    }

Gerçekten iyi, çünkü Double Sayı'yı uzatıyor, bu yüzden koymak List<Double>kesinlikle iyi List<Integer>değil, değil mi?

Ancak, yine de örneğinizden buraya geçmenin yasal olduğunu düşünüyor new HashMap<Integer, List<Integer>>()musunuz?

Derleyici böyle düşünmüyor ve bu tür durumlardan kaçınmak için elinden gelenin en iyisini yapıyor.

Yöntem #compile ile aynı uygulamayı yapmaya çalışın ve derleyici açıkça haritaya çiftler listesi koymak için izin vermez.

    static <T extends Number> void compiles(Map<Integer, List<T>> map) {
        List<Double> list = new ArrayList<>();
        list.add(10.);
        map.put(10, list); // does not compile
    }

Temelde hiçbir şey koyamazsınız List<T>, bu yüzden bu yöntemi new HashMap<Integer, List<Integer>>()veya new HashMap<Integer, List<Double>>()veya new HashMap<Integer, List<Long>>()veya ile çağırmak güvenlidir new HashMap<Integer, List<Number>>().

Kısacası, derleyici ile hile yapmaya çalışıyorsunuz ve bu hile karşı oldukça savunuyor.

Not: Maurice Perry tarafından gönderilen cevap kesinlikle doğrudur. Sadece yeterince açık olduğundan emin değilim, bu yüzden daha kapsamlı yazı eklemek için denedim (gerçekten umuyorum).

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.