Bunun nedenleri Java'nın jenerikleri nasıl uyguladığına dayanmaktadır.
Bir Diziler Örneği
Dizilerle bunu yapabilirsiniz (diziler kovaryanttır)
Integer[] myInts = {1,2,3,4};
Number[] myNumber = myInts;
Ancak, bunu yapmaya çalışırsanız ne olur?
myNumber[0] = 3.14; //attempt of heap pollution
Bu son satır iyi derlenebilir, ancak bu kodu çalıştırırsanız, bir ArrayStoreException
. Çünkü bir tamsayı dizisine bir çift koymaya çalışıyorsunuz (sayı başvurusundan erişilmesine bakılmaksızın).
Bu, derleyiciyi kandırabileceğiniz anlamına gelir, ancak çalışma zamanı türü sistemini kandıramazsınız. Bu böyledir çünkü diziler yeniden kullanılabilir tipler olarak adlandırdığımız şeydir . Bu, çalışma zamanında Java'nın bu dizinin gerçekte bir tür başvurusu aracılığıyla erişilen bir tamsayılar dizisi olarak başlatıldığını bildiği anlamına gelir Number[]
.
Gördüğünüz gibi, bir şey nesnenin gerçek türü ve başka bir şey ona erişmek için kullandığınız referansın türü, değil mi?
Java Generics ile İlgili Sorun
Java genel türleriyle ilgili sorun, tür bilgisinin derleyici tarafından atılması ve çalışma zamanında kullanılamamasıdır. Bu işleme tür silme adı verilir . Java'da böyle jenerikleri uygulamak için iyi bir neden vardır, ancak bu uzun bir hikaye ve diğer şeylerin yanı sıra, önceden var olan kodla ikili uyumluluk ile yapılması gerekir (bkz . Sahip olduğumuz jenerikleri nasıl elde ettik ).
Ancak buradaki önemli nokta, çalışma zamanında tür bilgisi olmadığı için yığın kirliliğini sağlamadığımızdan emin olmamızın bir yolu yoktur.
Örneğin,
List<Integer> myInts = new ArrayList<Integer>();
myInts.add(1);
myInts.add(2);
List<Number> myNums = myInts; //compiler error
myNums.add(3.14); //heap pollution
Java derleyicisi bunu yapmanızı engellemezse, çalışma zamanı türü sistemi de sizi durduramaz, çünkü çalışma zamanında, bu listenin yalnızca tamsayıların bir listesi olması gerektiğini belirlemenin bir yolu yoktur. Java çalışma zamanı, yalnızca tamsayı içermesi gerektiğinde, istediğiniz her şeyi bu listeye koymanıza izin verir, çünkü oluşturulduğunda, tamsayıların bir listesi olarak bildirilir.
Bu nedenle, Java tasarımcıları derleyiciyi kandıramayacağınızdan emin oldular. Derleyiciyi kandıramazsanız (dizilerde yapabileceğimiz gibi), çalışma zamanı türü sistemini de kandıramazsınız.
Bu itibarla , jenerik tiplerin yeniden kullanılamaz olduğunu söylüyoruz .
Açıkçası, bu polimorfizmi engelleyecektir. Aşağıdaki örneği düşünün:
static long sum(Number[] numbers) {
long summation = 0;
for(Number number : numbers) {
summation += number.longValue();
}
return summation;
}
Şimdi şöyle kullanabilirsiniz:
Integer[] myInts = {1,2,3,4,5};
Long[] myLongs = {1L, 2L, 3L, 4L, 5L};
Double[] myDoubles = {1.0, 2.0, 3.0, 4.0, 5.0};
System.out.println(sum(myInts));
System.out.println(sum(myLongs));
System.out.println(sum(myDoubles));
Ancak, aynı kodu genel koleksiyonlarla uygulamaya çalışırsanız, başarılı olmazsınız:
static long sum(List<Number> numbers) {
long summation = 0;
for(Number number : numbers) {
summation += number.longValue();
}
return summation;
}
Eğer denerseniz derleyici hatalar alacaktı ...
List<Integer> myInts = asList(1,2,3,4,5);
List<Long> myLongs = asList(1L, 2L, 3L, 4L, 5L);
List<Double> myDoubles = asList(1.0, 2.0, 3.0, 4.0, 5.0);
System.out.println(sum(myInts)); //compiler error
System.out.println(sum(myLongs)); //compiler error
System.out.println(sum(myDoubles)); //compiler error
Çözüm, kovaryans ve kontravaryans olarak bilinen Java jeneriklerinin iki güçlü özelliğini kullanmayı öğrenmektir.
Kovaryans
Kovaryans ile bir yapıdaki öğeleri okuyabilirsiniz, ancak içine hiçbir şey yazamazsınız. Bütün bunlar geçerli beyanlardır.
List<? extends Number> myNums = new ArrayList<Integer>();
List<? extends Number> myNums = new ArrayList<Float>();
List<? extends Number> myNums = new ArrayList<Double>();
Ve şunları okuyabilirsiniz myNums
:
Number n = myNums.get(0);
Gerçek liste ne olursa olsun, bir sayıya yükseltilebileceğinden emin olabilirsiniz (sayıyı genişleten her şeyden sonra bir sayıdır, değil mi?)
Ancak, kovaryant bir yapıya hiçbir şey koymanıza izin verilmez.
myNumst.add(45L); //compiler error
Java, genel yapıdaki nesnenin gerçek türünün ne olduğunu garanti edemediğinden buna izin verilmez. Number'ı genişleten herhangi bir şey olabilir, ancak derleyici emin olamaz. Böylece okuyabilirsiniz, ama yazamazsınız.
contravariance
Karşıtlık ile tam tersini yapabilirsiniz. Bir şeyleri genel bir yapıya koyabilirsiniz, ancak ondan okuyamazsınız.
List<Object> myObjs = new List<Object>();
myObjs.add("Luke");
myObjs.add("Obi-wan");
List<? super Number> myNums = myObjs;
myNums.add(10);
myNums.add(3.14);
Bu durumda, nesnenin gerçek doğası bir Nesneler Listesi'dir ve karşıtlık yoluyla, Temelde tüm sayıların ortak ataları olarak Nesne olduğu için Sayılar koyabilirsiniz. Bu nedenle, tüm Sayılar nesnedir ve bu nedenle bu geçerlidir.
Ancak, bir sayı alacağınızı varsayarak, bu karşıt varyanttan hiçbir şeyi güvenle okuyamazsınız.
Number myNum = myNums.get(0); //compiler-error
Gördüğünüz gibi, derleyici bu satırı yazmanıza izin veriyorsa, çalışma zamanında bir ClassCastException alırsınız.
Get / Put Prensibi
Bu nedenle, yalnızca bir yapının genel değerlerini almak istediğinizde kovaryans kullanın, yalnızca bir yapıya genel değerler koymak istediğinizde karşıtlık kullanın ve her ikisini de yapmak istediğinizde tam genel türü kullanın.
Elimdeki en iyi örnek, herhangi bir numarayı bir listeden başka bir listeye kopyalayan aşağıdaki gibidir. Yalnızca kaynaktan öğeleri alır ve yalnızca öğeleri hedefe koyar .
public static void copy(List<? extends Number> source, List<? super Number> target) {
for(Number number : source) {
target(number);
}
}
Kovaryans ve karşıtlık güçleri sayesinde bu böyle bir vaka için çalışır:
List<Integer> myInts = asList(1,2,3,4);
List<Double> myDoubles = asList(3.14, 6.28);
List<Object> myObjs = new ArrayList<Object>();
copy(myInts, myObjs);
copy(myDoubles, myObjs);