in

Kotlin jenerikliği

Bu makale, Kotlin’deki Jenerik ve Varyans kavramlarını kapsar ve Java ile karşılaştırır. Kotlin Generics, Java’nın kullanıcıların alt-yazarak ilişkilerinde nasıl davranacaklarını nasıl tanımlayabildiklerinden farklıdır. Java’nın aksine, Kotlin, bildirim alanında varyansı tanımlamaya izin verirken, Java yalnızca kullanım alanı varyansını bilir.

Bu makale, Kotlin’deki Jenerik ve Varyans kavramlarını kapsar ve Java ile karşılaştırır. Kotlin Generics, Java’nın kullanıcıların alt-yazarak ilişkilerinde nasıl davranacaklarını nasıl tanımlayabildiklerinden farklıdır. Java’nın aksine, Kotlin, bildirim alanında varyansı tanımlamaya izin verirken, Java yalnızca kullanım alanı varyansını bilir.

Kotlin Generics – Varyans Nedir?

Pek çok programlama dili, “A cat IS-an animal” gibi ilişkileri temsil eden hiyerarşilerin uygulanmasına izin veren alt yazma kavramını destekler. Java’da extendsmevcut bir sınıfın (kalıtımsal) davranışını değiştirmek / genişletmek için anahtar kelimeyi kullanabilir veya implementsbir arayüz için uygulamalar sağlamak için kullanabiliriz . Liskov’e göre en değiştirme prensibine , bir sınıfın her örneği Akendi alt tipi örnekleri tarafından ikame edilmiş olabilir B. Genellikle matematikte de ifade edilen varyans kelimesi, yöntem geri dönüş tipleri, tür bildirimleri, genel türler veya diziler gibi karmaşık yönlerde alt tiplemenin, ilgili sınıfların kalıtım derecesi ile nasıl ilişkili olduğunu açıklamak için kullanılır.. Dikkate almamız gereken üç terim var: Kovaryans, Kontravaryans ve Değişmezlik.

Uygulamadaki Varyans (Java)

Kovaryans

Java’da, geçersiz kılma yönteminin geri dönüş türünde kovaryant olması gerekir ; yani, geçersiz kılınan geri dönüş türleri ve geçersiz kılma yöntemi, ilgili sınıfların kalıtım ağacının yönü ile aynı hizada olmalıdır. Bir yöntem olup treat(): Animalsınıfı AnimalDoctorile geçersiz kılınabilir treat(): Catsınıfında CatDoctoruzanan, AnimalDoctor. Başka bir kovaryans örneği, burada gösterilen tip dönüşümüdür:

public class Hayvanlar {}
public class Kedi extends Hayvan {}
(Hayvan) new Kedi() //Çalışıyor
(Kedi) new Hayvan() //Çalışmıyor

Kalıtım ağacında alt sınıflar oluşturabiliriz; Değişken bildirimlerine bakarsak, durum budur. Bir Cattür değişkeni örneği atamak sorun değil Animal, bunun tersini yapmak başarısızlığa neden olur.

contravariance

Öte yandan, kontravaryans tam tersini tanımlar. Kontravaryans Java’da kullanılamaz. Bunu açıklığa kavuşturmak için, geçersiz kılma yöntemlerinde çelişkili yöntem argümanlarına izin veren başka bir programlama dili düşünebiliriz (Java’da bunun yerine aşırı yüklenmiş bir yöntem olabilir). Üst bir sınıf var diyelim ClassBbaşka bir sınıfını genişleten ClassAve orijinal parametre türünü değiştirerek bir yöntemi geçersiz kılar T'onun süper tipli T.

ClassA::someMethod(t: T')
ClassB::someMethod(t: T)

Method parametresinin t hiyerarşisinin, etrafındaki sınıfların hiyerarşisine aykırı olduğunu görebilirsiniz. Ağacın karşısında someMethod, parametre türünde çelişkilidir.

değişmezlik

Son fakat en az değil, en kolay olanı: Değişmezlik. Java’daki yöntemleri geçersiz kılmayı düşündüğümüzde, daha önceki örnekte gördüğümüz gibi bu kavramı gözlemleyebiliriz. Geçersiz kılma yöntemi yalnızca geçersiz kılma yöntemiyle aynı parametreleri kabul etmelidir. Örneğin metot parametrelerinin üst ve alt tipte farklı olması durumunda değişmezlikten söz ediyoruz .

Java’da koleksiyon türlerinin varyansı

Dikkate almak istediğimiz bir diğer husus, diziler ve diğer genel koleksiyon türleri. Java’daki diziler, türlerinde kovaryanttır; bu, bir Strings dizisinin, bir değişkene atanabileceği anlamına gelir Object [].

Object [] arr = new String [] {"hello", "world"};

Fakat daha önemlisi, diziler tuttukları türde kovaryanttır. Bu, bir Nesneye Tamsayı, Dizeleri veya ne tür bir Nesne ekleyebileceğiniz anlamına gelir [].

Object [] arr = new Object [2];
arr[0] = 1;
arr[1] = "2";

Kovariant dizileri oldukça kullanışlı görünebilir ancak çalışma zamanında hatalara neden olabilir. Bir tür değişken Object []muhtemelen bir nesneye başvurabilir String []. Değişkeni bir Nesneler dizisi bekleyen bir yönteme iletirsek ne olur? Bu yöntem, parametrenin türünde olması beklendiğinden yasal gibi görünen diziye bir Nesne eklemek isteyebilir Object []. Sorun, arayan kişinin, ArrayStoreExceptionbu basitleştirilmiş örnekte gördüğümüz bir çalışma zamanına neden olabilecek dizisine koymak için hangi yöntemin muhtemel olacağı hakkında hiçbir fikrinin olmamasıdır :

Object [] arr = new String [] {"hello", "world"};
arr[1] = new Object(); //throws: java.lang.ArrayStoreException: java.lang.Object 

Genel Koleksiyonlar

Java 1.5 itibarıyla jenerik (yani elemanları özel bir koleksiyon örneğin depolanabilir gerekiyordu derleyici bilgilendirmek için kullanılabilir ListMapQueueSet, vs.). Dizilerin aksine, genel koleksiyonlar varsayılan olarak parametreleştirilmiş türlerinde değişmez . Bu, a List<Animal>ile değiştiremeyeceğiniz anlamına gelir List<Cat>. Derleme bile olmaz. Sonuç olarak, genel koleksiyonları kovaryant dizilerden ( Etkili Java ) daha güvenli kılan beklenmeyen çalışma zamanı hatalarıyla karşılaşmak mümkün değildir . Bir dezavantajı olarak, ilk önce koleksiyonların alt tiplenmesi konusunda esnek değiliz. Neyse ki, kullanıcı generics kullanırken açıkça tür parametrelerinin varyansını belirleyebilir. Bu kullanım alanı varyansı diyoruz .

Kovariant koleksiyonları

Aşağıdaki kod örneği, bir kovaryant listesini nasıl ilan ettiğimizi Animalve bir listesini nasıl atadığımızı gösterir Cat.

List<Cat> cats = new ArrayList<>();
List<? extends Animal> animals = cats;

Bunun gibi bir kovaryant liste, bir diziden hala farklıdır çünkü kovaryans, type parametresinde kodlanmıştır, bu da okuyucu için daha belirgin hale getirir. Sadece listeden okuyabiliriz, oysa derleyici öğe eklemeyi yasaklar. Liste bir olduğu söylenir Yapımcı ait Animals. Genel tür ? extends Animal?“Joker” karakteridir) sadece liste bir üst sınır ile her tür içerdiğini gösterir Animalbir liste anlamına gelebilir, CatDogya da başka bir hayvan. Bu yaklaşım, kovaryant dizilerde karşılaşılan çalışma zamanı hatasını tercih edilen derleme hatasına dönüştürür:

public void processAnimals(List<? extends Animal> collection){
    Animal a = collection.get(0);
    Cat c = collection.get(0); //will not compile
    collection.add(new Dog()) //will not compile
}

Şimdi, eğer arayanlar processAnimalslistesiyle Catçağırdıysanız, işlevin buna hiçbir şey eklemeyeceğinden emin olabilirsiniz. İşlev, yalnızca Animalkoleksiyondakileri alabilir , çünkü joker karakter sözdiziminin gösterdiği şey budur.

Kontravariant koleksiyonlar

Genel bir tür parametresi ? super Animal(alt tür sınırı Animal) ile bildirdiğimiz çelişkili koleksiyonlarla çalışmak da mümkündür . Böyle bir liste tipte olabilir List<Animal>kendisi ya da herhangi bir süper tip bir listesi Animalbile Object. Çok değişkenli listelerde olduğu gibi, listenin gerçekte hangi türü temsil ettiğini bilemeyiz (yine joker karakter tarafından belirtilir). Aradaki fark, çelişkili bir listeden okuyamayacağımızdır, zira Animalsadece s elde edip etmeyeceğimiz belirsizdir Object. Orijinal arayanın Animalherhangi bir alt türü eklemeyi mümkün kılan ya da onun süper tiplerini beklediğini bilmemize rağmen listeye yazmaya izin verilir Animal. Listesi gibi davranmaktadır Tüketicinin ait Animals:

public void addAnimals(List<? super Animal> collection) {
    collection.add(new Cat());
    collection.add(new Dog());
}

Yukarıda gösterilen fonksiyon, çelişkili bir Animals listesini kabul eder . Bu işlevin bir arayanı olarak bir List<Animal>ve ayrıca List<Object>veya diğer olası alt tiplerin listelerini geçebilirsiniz Animal. Koleksiyonunuza yalnızca hayvanlar veya alt türlerinin ekleneceğinden emin olabilirsiniz; bu, yöntem çağrısından sonra bile hayvanları listeden almak için güvenli olduğunuz anlamına gelir:

List<Animal> animals = new ArrayList<>();
addAnimals(animals);
//here we can still safely get Animals from the list, we don't care which subtype it actually has
Animal someAnimal = animals.get(0);

addAnimals(new ArrayList<Object>());

Joshua Bloch onun fantastik kitapta Pratik bir kural oluşturdu Etkili Java : “Yapımcı-uzanır tüketici süper (PECS)” kovaryant üretici ve arasındaki ilişkiyi ezberlemek yardımcı extendsanahtar kelime yanı sıra kontravaryant tüketiciler arasındaki superanahtar kelime.

Kotlin Generics ile Varyans

Genel olarak varyansın ne anlama geldiğine ve Java’nın bu kavramları nasıl kullandığına bir göz attıktan sonra, şimdi Kotlin’in nasıl işlediğine bir göz atmak istiyoruz. Kotlin, jeneriklere gelince, dizilerle de kombinasyon halinde, birkaç yönden Java’dan farklıdır ve ilk bakışta deneyimli bir Java geliştiricisine garip gelebilir. İlk ve belki de en rahat fark şudur: Kotlin’deki diziler değişmezdir . Sonuç olarak, Java’nın aksine Array<String>, türünde bir referans değişkenine atamak mümkün değildir Array<Object>. Bu, derleme zamanı güvenliği sağladığı ve Java’da kovaryant dizileriyle karşılaşabileceğiniz çalışma zamanı hatalarını önlediği için harikadır. Ancak, daha sonra inceleyeceğimiz alt sıralı dizilerle veya kursla çalışmanın başka bir yolu olması gerekir.

Beyanname Site Varyansı

Daha önce de gösterildiği gibi, Java, jenerik ürününü çeşitlendirmek için “joker tip” olarak adlandırılan jantları kullanmaktadır; bu, [Java’nın tür sisteminin en zor kısmı] olduğu söylenmektedir (http://kotlinlang.org/docs/reference/generics .html # tip-projeksiyonları). Belirli genel türlerin kullanıcısı, belirli bir tür her kullanıldığında varyansı işlemesi gerektiğinden, bunu kullanım alanı varyansı olarak adlandırırız . Kotlin joker karakter kullanmıyor. Bunun yerine, Kotlin’de bildirim alanı varyansı kullanıyoruz . İlk problemi tekrar hatırlayalım: Hayal edin, ReadableList<E>basit bir üretici yöntemiyle bir sınıfımız olduğunu düşünün get(): T. Java, bir ReadableList<String>tür değişkenin örneğinin atanmasını yasaklar ReadableList<Object>çünkü genel türler varsayılan olarak değişmezdir. Bunu düzeltmek için kullanıcı değişken türünü olarak değiştirebilir.ReadableList<? extends Object>ve her şey iyi çalışıyor. Kotlin bu konuya farklı yaklaşıyor. Türü Tyalnızca outanahtar kelime ile oluşturulmuş olarak işaretleyebiliriz, böylece derleyici derhal anlayabilecektir: ReadableListhiçbir zaman tükenmeyecek T, bu onu kovariant yapan T.

abstract class ReadableList<out T> {
    abstract fun get(): T
}

fun workWithReadableList(strings: ReadableList<String>) {
    val objects: ReadableList<Any> = strings // This is OK, since T is an out-parameter
    // ...
}

Gördüğünüz Tgibi outtür, bildirim alanı varyansı aracılığıyla tür olarak açıklanmıştır – varyans açıklaması olarak da adlandırılır. Derleyici Tkovaryant tip olarak kullanılmasını yasaklamaz . Standart kütüphanedeki kovaryant koleksiyonun harika bir örneği List<T>:

val ints: List<Int> = listOf(1, 2, 3, 4)

fun takeNumbers(nums: List<Number>) {
    val number: Number = nums[0]
     nums.add(1) // add is not visible
}

takeNumbers(ints)

Bir üreticiyi List<Int>kabul eden bir işleve kolayca geçebilirsiniz, List<Number>çünkü Listyalnızca bir üreticidir Tve takeNumberssınıfa asla bir şey eklemez. Öte yandan, parametre numstürüyle tanımlanmışsa MutableList<Number>, işlem addiyi sonuç verirdi, ancak arayan kişi artık geçemedi List<Int>ve yöntemin içine girilen listeye malzeme ekleyebileceği açıktı.

Tabii ki, onları kontravaryant yapmak, yani tüketiciler gibi jenerik türlerini işaretlemek için karşılık gelen bir açıklama da var: in. Millet, sunulan yaklaşımı C # ile birkaç yıldır başarıyla kullanıyor.

Kotlin ezberlemek için kural: Üretici, Tüketici

Kullanım alanı Varyansı, Tür projeksiyonları

Ne yazık ki, bir tür parametresini Tya outda olarak bildirme fırsatına sahip olmak her zaman yeterli değildir in. Mesela sadece dizileri düşünün. Bir dizi, nesneler eklemek ve almak için yöntemler sunar, bu yüzden kendi type parametresinde kovariant veya contravariant olamaz. Bir alternatif olarak, Kotlin da zaten tanımlanmış anahtar kelimeler kullanarak uygulayabilirsiniz kullanım yerinde varyansı verir inve out:

Array<in String>Java’nın tekabül Array<? super String>ve Array<out String>Java en tekabülArray<? extends Object>

fun copy(from: Array<out Any>, to: Array<Any>) {
 // ...
}

Örnek, fromkendi türünde bir üretici olarak nasıl ilan edildiğini gösterir ve bu nedenle kopya yöntemi Diziye ekleme gibi kötü şeyler yapamaz. Bu kavram, dizi yöntemlerinde kısıtlandığı için tip projeksiyonu olarak adlandırılır : yalnızca tip parametresini döndüren yöntemler Tçağrılabilir.

Birleştirilmiş Türleri

Java’da olduğu gibi, genel türler yalnızca derleme zamanında kullanılabilir ve çalışma zamanında silinir . Tip güvenliğini sağlamak için jenerik kullandığımız için bu çoğu kullanım için yeterince iyidir. Bazen, çalışma zamanında gerçek tür bilgisini de alabilmek harikadır. Neyse ki, Kotlin , çalışma zamanında genel türler üzerinde çalışmayı mümkün kılan (örneğin bir tür kontrolü gerçekleştirme), birleştirilmiş türler adı verilen bir özellikle birlikte gelir . Bu istifleme makalesinde bu özelliği ayrıntılı olarak açıkladım ve bununla ilgili bir blog yazısı yayınladım .

Alt çizgi

Bu makalede, jenerik bağlamında varyansın oldukça karmaşık yönlerine baktık. Java’yı kovaryans, kontravaryans ve değişmezlik kavramlarını göstermek için kullandık ve Kotlin ile karşılaştırdık. Kotlin, bildirim alanı varyansı gibi farklı yaklaşımları kullanarak jenerikliği basitleştirmeye çalışır ve ayrıca daha belirgin anahtar kelimeler sunar ( in,out). Bence Kotlin, jenerik ilaçların kullanımını gerçekten geliştirir ve basitleştirir ve ayrıca kovaryant diziler sorununu da ortadan kaldırır. Beyanname site varyansı, Java’nın joker karakter sözdiziminde bilindiği gibi karmaşık bildirimler kullanmasını sağlayarak müşteri kodunu çok basitleştirir. Ayrıca, kullanım alanı değişkenliğine geri dönmemiz gerekse bile, sözdizimi daha net ve anlaşılması daha kolay görünür. Bu konunun en basit konu olmadığını biliyorum, ama umarım, bu makalede bazı hususlar biraz daha netleştirilmiştir.

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Yazar Cem Y.

Yazılımın Önemi

Kotlin Koleksiyonlar