Genel dönüş tipi üst sınır - arabirim ve sınıf - şaşırtıcı derecede geçerli kod


171

Bu, bir üçüncü taraf kütüphane API'sinden gerçek dünyadaki bir örnektir, ancak basitleştirilmiştir.

Oracle JDK 8u72 ile derlenmiştir

Bu iki yöntemi düşünün:

<X extends CharSequence> X getCharSequence() {
    return (X) "hello";
}

<X extends String> X getString() {
    return (X) "hello";
}

Her ikisi de "denetlenmeyen bir oyuncu kadrosu" uyarısı veriyor - nedenini anladım. Beni şaşırtan şey neden arayabilirim

Integer x = getCharSequence();

ve derler? Derleyici Integerbunun uygulanmadığını bilmelidir CharSequence. Çağrı

Integer y = getString();

hata veriyor (beklendiği gibi)

incompatible types: inference variable X has incompatible upper bounds java.lang.Integer,java.lang.String

Birisi bu davranışın neden geçerli sayıldığını açıklayabilir mi? Nasıl faydalı olur?

İstemci bu çağrının güvensiz olduğunu bilmiyor - istemcinin kodu uyarı vermeden derleniyor. Derleme neden bu konuda bir uyarı vermiyor / hata vermiyor?

Ayrıca, bu örnekten farkı nedir:

<X extends CharSequence> void doCharSequence(List<X> l) {
}

List<CharSequence> chsL = new ArrayList<>();
doCharSequence(chsL); // compiles

List<Integer> intL = new ArrayList<>();
doCharSequence(intL); // error

Geçmeye çalışmak List<Integer>beklendiği gibi bir hata veriyor:

method doCharSequence in class generic.GenericTest cannot be applied to given types;
  required: java.util.List<X>
  found: java.util.List<java.lang.Integer>
  reason: inference variable X has incompatible bounds
    equality constraints: java.lang.Integer
    upper bounds: java.lang.CharSequence

Bu bir hata olarak rapor edilirse neden Integer x = getCharSequence();olmasın?


15
ilginç! LHS dökümü Integer x = getCharSequence();derlenecek, ancak RHS dökümü Integer x = (Integer) getCharSequence();derlenemedi
pullar

Java derleyicisinin hangi sürümünü kullanıyorsunuz? Lütfen soruda bu bilgileri belirtin.
Federico Peralta Schaffner

@FedericoPeraltaSchaffner bunun neden önemli olduğunu göremiyor - bu doğrudan JLS hakkında bir soru.
Örümcek Boris

@BoristheSpider java8 için tür çıkarım mekanizması değiştiği için
Federico Peralta Schaffner

1
@FedericoPeraltaSchaffner - Soruyu zaten [java-8] ile etiketledim, ancak derleyiciyi şimdi yayına ekledim.
Adam Michalik

Yanıtlar:


184

CharSequencebir interface. Bu nedenle bileSomeClass , uygulamasaCharSequence bir sınıf yaratmak mükemmel bir şekilde mümkün olacaktır

class SubClass extends SomeClass implements CharSequence

Bu nedenle yazabilirsiniz

SomeClass c = getCharSequence();

çünkü çıkarım türü X kavşak türüdür SomeClass & CharSequence.

Bu durumda biraz garip Integerçünkü Integer, kesindir amafinal bu kurallarda herhangi bir rol oynamaz. Örneğin yazabilirsiniz

<T extends Integer & CharSequence>

Öte yandan, Stringbirinterface , bu yüzden SomeClassbir alt tipi almak için genişletmek imkansız olurdu String, çünkü java sınıflar için çoklu kalıtım desteklemiyor.

İle Listörneğin, sen jenerik ne kovaryant ne de kontravaryant olduğunu hatırlamak gerekir. Bu X, bir alt türü ise Y, List<X>ne bir alt tür ya da bir üst tür olmadığı anlamına gelir List<Y>. Yana IntegeruygulamıyorCharSequence , kullanmak olamaz List<Integer>senin içinde doCharSequenceyöntemle.

Ancak bunu derlemek için alabilirsiniz

<T extends Integer & CharSequence> void foo(List<T> list) {
    doCharSequence(list);
}  

Bunun gibi bir yöntem döndüren bir yönteminiz varsa List<T>:

static <T extends CharSequence> List<T> foo() 

yapabilirsin

List<? extends Integer> list = foo();

Yine, bunun nedeni, çıkarım türünün Integer & CharSequence ve bunun bir alt türü olmasıdır Integer.

Birden fazla sınır belirttiğinizde (örn. <T extends SomeClass & CharSequence>) Kavşak türleri dolaylı olarak ortaya çıkar .

Daha fazla bilgi için buraya tip çalışmaları sınırlayan nasıl açıklıyor JLS bir parçasıdır. Birden fazla arayüz ekleyebilirsiniz, örn.

<T extends String & CharSequence & List & Comparator>

ancak yalnızca ilk sınır arabirim dışı olabilir.


62
&Genel bir tanım koyabileceğiniz hakkında hiçbir fikrim yoktu . +1
pullar

13
@flkes Birden fazla koyabilirsiniz, ancak yalnızca ilk argüman arabirim dışı olabilir. <T extends String & List & Comparator>tamam ama <T extends String & Integer>değil, çünkü Integerbir arayüz değil.
Paul Boddington

7
@PaulBoddington Bu yöntemler için bazı pratik kullanımlar vardır. Örneğin, tür saklanan veriler için gerçekten kullanılmıyorsa. Bunun örnekleri Collections.emptyList()de vardır Optional.empty(). Bunlar genel bir arayüzün uygulamalarını döndürür, ancak hiçbir şey saklamaz.
Stefan Dollase

6
Ve kimse finalderleme zamanında olan bir sınıfın finalçalışma zamanında olacağını söylemiyor .
Holger

7
@Federico Peralta Schaffner: Buradaki nokta, yöntem , arayanın ihtiyacı olan getCharSequence()her şeyi geri vermeyi vaat ediyor ; arayanın ihtiyacı varsa ve bu sözün altında, sonucun atanmasına izin vermek doğru olan Xbir türün geri döndürülmesini Integerve uygulanmasını içerir . Sözünü tutmadığı için kırılan yöntem bu, ancak derleyicinin hatası değil. CharSequenceIntegergetCharSequence()
Holger

59

İçin atanmadan önce derleyici tarafından çıkarılır tipi XDİR Integer & CharSequence. Bu tür garip geliyor , çünkü Integernihai, ancak Java'da mükemmel geçerli bir tür. Daha sonraInteger mükemmel bir şekilde tamamlanır.

İçin tam bir olası değer yoktur Integer & CharSequencetipi: null. Aşağıdaki uygulama ile:

<X extends CharSequence> X getCharSequence() {
    return null;
}

Aşağıdaki atama işe yarayacaktır:

Integer x = getCharSequence();

Bu olası değer nedeniyle, açıkça işe yaramasa bile, görevin yanlış olması için hiçbir neden yoktur. Bir uyarı faydalı olacaktır.

Asıl sorun çağrı sitesi değil API

Aslında, yakın zamanda bu API tasarımı anti deseni hakkında blog yazdım . (Neredeyse) asla rastgele türler döndürmek için genel bir yöntem tasarlamamalısınız, çünkü (neredeyse) hiçbir zaman çıkarılan türün teslim edileceğini garanti edemezsiniz. Bir istisna Collections.emptyList(), listenin boşluğunun (ve genel tip silinmesinin) herhangi bir çıkarımın <T>çalışmasının nedeni olduğu gibi yöntemlerdir :

public static final <T> List<T> emptyList() {
    return (List<T>) EMPTY_LIST;
}
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.