Bu gerçekten ilginç bir soru. Korkarım cevap karmaşık.
tl; Dr.
Farkı çözmek, Java'nın tür çıkarım şartnamesinin oldukça derinlemesine okunmasını içerir , ancak temelde buna dayanır:
- Diğer tüm şeyler eşit olduğunda, derleyici elinden gelenin en iyisini yapar .
- Bulabildiği Ancak, bir bütün gereksinimleri karşılar, sonra derleme başarılı olacağı bir tür parametresi için ikame Ancak muğlak ikamesi olarak çıkıyor.
- Çünkü
with
aşağıdaki şartları yerine getiren (kuşkusuz belirsiz) bir ikame vardır R
:Serializable
- Çünkü
withX
, ek tür parametresinin eklenmesi F
, derleyiciyi R
ilk önce kısıtlamayı dikkate almadan çözümlemeye zorlar F extends Function<T,R>
. R
(çok daha spesifik) olarak karar verir, String
bu da çıkarımın F
başarısız olduğu anlamına gelir .
Bu son mermi noktası en önemlisi, aynı zamanda en dalgalı olanıdır. İfadenin daha iyi ve kısa bir yolunu düşünemiyorum, bu yüzden daha fazla ayrıntı istiyorsanız, aşağıdaki açıklamanın tamamını okumanızı öneririz.
Bu amaçlanan davranış mı?
Burada bir uzuv çıkacağım ve hayır diyeceğim .
Spesifikasyonda bir hata olduğunu öne sürmüyorum, daha fazla (durumda withX
) dil tasarımcıları ellerini kaldırdı ve "tip çıkarımın çok zorlaştığı bazı durumlar var, bu yüzden sadece başarısız olacağız" dedi . Derleyicinin bu konudaki davranışı withX
istediğiniz gibi görünse de, bunun olumlu bir tasarım kararı yerine mevcut spesifikasyonun tesadüfi bir yan etkisi olduğunu düşünürüm.
Bu önemlidir, çünkü soruyu bilgilendirir Uygulama tasarımımda bu davranışa güvenmeli miyim? Yapmamalısınız, çünkü dilin gelecekteki sürümlerinin bu şekilde davranmaya devam edeceğini garanti edemezsiniz.
Dil tasarımcılarının spec / design / derleyicilerini güncellediklerinde varolan uygulamaları kırmamaya çalıştıkları doğru olsa da, sorun güvenmek istediğiniz davranışın derleyicinin başarısız olduğu (yani mevcut bir uygulama değil ) olmasıdır. Dil güncellemeleri derleme yapmayan kodu her zaman derleme koduna dönüştürür. Örneğin, aşağıdaki kod olabilir garantili Java 7 derlemeye değil, ama olur Java 8'de derlemek:
static Runnable x = () -> System.out.println();
Kullanım durumunuz farklı değil.
Metodunuzu kullanma konusunda dikkatli olmamın bir başka nedeni withX
de F
parametrenin kendisidir. Genellikle, bir yöntemdeki (dönüş türünde görünmeyen) genel tür parametresi , imzanın birden çok bölümünün türlerini birbirine bağlamak için vardır. Diyor ki:
Ne olduğu umrumda değil T
, ama nerede kullanırsam kullanalım T
aynı tipte olduğundan emin olmak istiyorum .
Mantıksal olarak, her tür parametresinin bir yöntem imzasında en az iki kez görünmesini bekleriz, aksi takdirde "hiçbir şey yapmaz". F
imzanızda withX
yalnızca bir kez görünür, bu da bana dilin bu özelliğinin amacı ile satır içi olmayan bir tür parametresi kullanımını önerir .
Alternatif bir uygulama
Bunu biraz daha "amaçlanan davranış" biçiminde uygulamanın bir yolu, with
yönteminizi 2 zincirine ayırmak olacaktır :
public class Builder<T> {
public final class With<R> {
private final Function<T,R> method;
private With(Function<T,R> method) {
this.method = method;
}
public Builder<T> of(R value) {
// TODO: Body of your old 'with' method goes here
return Builder.this;
}
}
public <R> With<R> with(Function<T,R> method) {
return new With<>(method);
}
}
Bu daha sonra aşağıdaki gibi kullanılabilir:
b.with(MyInterface::getLong).of(1L); // Compiles
b.with(MyInterface::getLong).of("Not a long"); // Compiler error
Bu, yaptığınız gibi yabancı bir tür parametresi withX
içermez. Yöntemi iki imzaya bölerek, bir tür güvenlik açısından ne yapmaya çalıştığınızın amacını daha iyi ifade eder:
- İlk yöntem , yöntem başvurusuna dayalı olarak türü tanımlayan bir class (
With
) kurar .
- Scond method (
of
) yöntemi , önceden ayarladığınız yöntemle uyumlu olacak türün sınırlandırırvalue
.
Dilin gelecekteki bir versiyonunun bunu derleyebilmesinin tek yolu, uygulanan tam ördek yazması, olası görünmüyorsa.
Tüm bu şeyleri alakasız hale getirmek için son bir not: Sanırım Mockito (ve özellikle de stubbing işlevselliği) "tip güvenli jenerik kurucunuz" ile elde etmeye çalıştığınız şeyi zaten yapabilir. Belki de bunun yerine kullanabilirsiniz.
Tam (ish) açıklaması
Hem ve hem de türünü çıkarma yordamı ile çalışacağım . Bu oldukça uzun, bu yüzden yavaşça alın. Uzun olmasına rağmen, hala çok fazla ayrıntı bıraktım. Kendinizi haklı olduğum konusunda ikna etmek için daha fazla ayrıntı için spesifikasyonlara başvurmak isteyebilirsiniz (bağlantıları takip edin) (bir hata yapmış olabilirim).with
withX
Ayrıca, işleri biraz basitleştirmek için, daha az kod örneği kullanacağım. Temel fark o swapları olmasıdır Function
için Supplier
, bu yüzden daha az türleri ve oyunda parametreler vardır. Açıkladığınız davranışı yeniden üreten tam bir snippet:
public class TypeInference {
static long getLong() { return 1L; }
static <R> void with(Supplier<R> supplier, R value) {}
static <R, F extends Supplier<R>> void withX(F supplier, R value) {}
public static void main(String[] args) {
with(TypeInference::getLong, "Not a long"); // Compiles
withX(TypeInference::getLong, "Also not a long"); // Does not compile
}
}
Her bir yöntem çağrısı için tür uygulanabilirlik çıkarımı ve tür çıkarımı prosedürü üzerinde çalışalım :
with
Sahibiz:
with(TypeInference::getLong, "Not a long");
Başlangıç sınır kümesi B 0 :
Tüm parametre ifadeleri uygulanabilirlikle ilgilidir .
Dolayısıyla, uygulanabilirlik çıkarımı için ayarlanan ilk kısıtlama , C , şöyledir:
TypeInference::getLong
ile uyumlu Supplier<R>
"Not a long"
ile uyumlu R
Bu azaltır bağlı grubu için B 2 arasında:
R <: Object
( B 0'dan itibaren )
Long <: R
(ilk kısıtlamadan itibaren)
String <: R
(ikinci kısıtlamadan)
Bu sınır 'ihtiva etmediğinden false ' ve (ı varsayalım) çözünürlüğü ait R
başarılı (vererek Serializable
), sonra çağırma uygulanabilir.
Bu nedenle, çağırma türü çıkarımına geçiyoruz .
İlişkili giriş ve çıkış değişkenleriyle yeni kısıt kümesi C :
TypeInference::getLong
ile uyumlu Supplier<R>
- Giriş değişkenleri: yok
- Çıktı değişkenleri:
R
Bu arasında bağımlılıklar içeren giriş ve çıkış böylece edilebilir değişken azaltılmış , tek bir aşamada ve son bağlı seti, B 4 , aynı B 2 . Bu nedenle, çözünürlük önceki gibi başarılı olur ve derleyici rahat bir nefes alır!
withX
Sahibiz:
withX(TypeInference::getLong, "Also not a long");
Başlangıç sınır kümesi B 0 :
R <: Object
F <: Supplier<R>
Sadece ikinci parametre ifadesi uygulanabilirlikle ilgilidir . İlki ( TypeInference::getLong
) değildir, çünkü aşağıdaki koşulu karşılar:
Eğer m
genel bir yöntem olup, yöntem çağırma açık tür bağımsız değişkenleri, bir açık yazılmış lambda ifade ya da denk düşen hedef türü (imza elde edilen gibi olduğu için tam bir yöntem, referans ifade sağlamaz m
) bir tür parametresidir m
.
Dolayısıyla, uygulanabilirlik çıkarımı için ayarlanan ilk kısıtlama , C , şöyledir:
"Also not a long"
ile uyumlu R
Bu azaltır bağlı grubu için B 2 arasında:
R <: Object
( B 0'dan itibaren )
F <: Supplier<R>
( B 0'dan itibaren )
String <: R
(kısıtlamadan)
Bu bağlı 'içermediği Yine false ' ve çözünürlük ait R
başarır (vererek String
), sonra çağırma uygulanabilir.
Çağrı türü çıkarımı bir kez daha ...
Bu kez, ilişkili giriş ve çıkış değişkenleriyle yeni kısıt kümesi C :
TypeInference::getLong
ile uyumlu F
- Girdi değişkenleri:
F
- Çıktı değişkenleri: yok
Yine, girdi ve çıktı değişkenleri arasında karşılıklı bağımlılığımız yoktur . Ancak bu sefer, orada olan bir giriş değişkeni ( F
biz gerekir böylece), çözümlemek denemeden önce bu azalmayı . Böylece, bağlı setimiz B 2 ile başlayacağız .
Bir altkümeyi V
aşağıdaki gibi belirleriz :
Çözülmesi gereken bir takım çıkarım değişkenleri göz önüne alındığında V
, bu kümenin birleşmesi ve bu kümedeki en az bir değişkenin çözünürlüğünün bağlı olduğu tüm değişkenler olsun.
Bağlanmış ikinci By B 2 , çözünürlüğü F
bağlıdır R
yüzden V := {F, R}
.
V
Kurala göre bir alt küme seçiyoruz :
Izin vermek , i) herkes için , bir değişkenin çözünürlüğüne bağlı ise , o zaman bir örnekleme vardır ya da böyle bir şey var, örneklenmemiş { α1, ..., αn }
değişkenlerin boş olmayan bir alt kümesi ; ve ii) bu özelliğin boş olmayan uygun bir alt kümesi mevcut değildir .V
i (1 ≤ i ≤ n)
αi
β
β
j
β = αj
{ α1, ..., αn }
V
Bu özelliği karşılayan tek alt küme {R}
.
Üçüncü bound ( String <: R
) kullanarak bunu örnekliyoruz R = String
ve bağlı setimize dahil ediyoruz. R
Şimdi çözüldü ve ikinci sınır etkili bir şekilde olur F <: Supplier<String>
.
(Gözden geçirilmiş) ikinci sınırı kullanarak, somutlaştırırız F = Supplier<String>
. F
şimdi çözüldü.
Şimdi bu F
çözüldü, yeni kısıtlamayı kullanarak azaltmaya devam edebiliriz :
TypeInference::getLong
ile uyumlu Supplier<String>
- ...
Long
ile uyumludur String
- ... yanlış olana
... derleyici hatası alıyoruz!
'Genişletilmiş Örnek' hakkında ek notlar
Sorudaki Genişletilmiş Örnek , doğrudan yukarıdaki çalışmalarla kapsanmayan birkaç ilginç duruma bakar:
- Değer türü, yöntem döndürme türünün ( ) bir alt türü olduğunda
Integer <: Number
- Fonksiyonel arayüzün çıkarım tipinde (yani,
Consumer
yerine Supplier
) çelişkili olduğu durumlarda
Özellikle, verilen çağrılardan 3 tanesi, açıklamalarda açıklanandan 'farklı' derleyici davranışını potansiyel olarak önermektedir:
t.lettBe(t::setNumber, "NaN"); // Does not compile :-)
t.letBeX(t::getNumber, 2); // !!! Does not compile :-(
t.lettBeX(t::setNumber, 2); // Compiles :-)
Olarak bu 3 ikinci tam olarak aynı çıkarım işlemi geçeceği withX
(sadece yerine yukarıda Long
olan Number
ve String
birlikte Integer
). Bu, sınıf tasarımınız için bu başarısız tip çıkarım davranışına güvenmemeniz için başka bir nedeni göstermektedir, çünkü burada derlenememe arzu edilen bir davranış değildir .
Diğer 2 (ve aslında Consumer
üzerinde çalışmak istediğiniz bir şeyi içeren diğer çağrılardan ) için, yukarıdaki yöntemlerden biri için (örneğin with
, birincisi withX
için) üçüncü). Dikkat etmeniz gereken sadece küçük bir değişiklik var:
- İlk parametre (üzerindeki kısıtlama
t::setNumber
uyumlu Consumer<R>
eder) azaltmak için R <: Number
yerine Number <: R
bu için olduğu gibi Supplier<R>
. Bu, indirgeme ile ilgili bağlantılı belgelerde açıklanmaktadır.
Okuyucunun, bu ek bilgi parçasına sahip yukarıdaki prosedürlerden birini dikkatlice çalışması için, belirli bir çağrının neden derlendiğini veya derlemediğini kendilerine göstermesini bir egzersiz olarak bırakıyorum.