Java Generics, Type Erasure ve Geleceğin Reified Generics’i: Derinlemesine Bir Bakış
Java geliştiricileri olarak kodumuzu daha güvenli, okunabilir ve esnek hale getirmek için sürekli yeni yollar ararız. İşte tam da bu noktada Java Generics (Genel Tipler) devreye girer. Ancak Generics’in sunduğu tüm bu avantajların arkasında, çoğu zaman farkında bile olmadığımız bir mekanizma yatar: Type Erasure (Tip Silme). Bu makalede, Java Generics’in temellerinden başlayarak, Tip Silme’nin nasıl çalıştığını, getirdiği sınırlamaları ve bu sınırlamaları aşmak için kullanılan yöntemleri inceleyeceğiz. Son olarak, geliştiricilerin uzun süredir hayalini kurduğu, Type Erasure’ın getirdiği kısıtlamaları ortadan kaldırabilecek potansiyel bir çözüm olan Reified Generics (Yeniden Somutlaştırılmış Generics) kavramını ve neden henüz Java ekosistemine dahil olmadığını tartışacağız. Hazırsanız, Java’nın tip sisteminin derinliklerine bir yolculuğa çıkalım.
Java Generics Nedir ve Neden Hayat Kurtarır?
Java Generics, kodun derleme zamanında tip güvenliği sağlamak ve aynı zamanda kodun yeniden kullanılabilirliğini artırmak için tasarlanmış güçlü bir özelliktir. Temel olarak, sınıflara, arayüzlere ve metotlara parametre olarak tiplerin geçirilmesine olanak tanır. Bu sayede, farklı tiplerle çalışabilen ancak tip güvenliğinden ödün vermeyen yapılar oluşturabiliriz. Generics’in olmadığı bir dünyayı hayal ettiğimizde, kodumuzun ne kadar kırılgan ve hata potansiyeli yüksek olabileceğini daha iyi anlayabiliriz. Özellikle büyük ve karmaşık projelerde, tip güvenliği hataları tespit edilmesi zor ve maliyetli sorunlara yol açabilir. Generics, bu tür sorunların önüne geçerek geliştiricilerin hayatını önemli ölçüde kolaylaştırır.
Generics Olmasaydı Hayatımız Nasıl Olurdu?
Generics’in Java 5 ile hayatımıza girmesinden önce, koleksiyonlar gibi yapıları kullanırken ciddi tip güvenliği sorunlarıyla karşılaşıyorduk. Örneğin, bir ArrayList içine farklı tiplerde nesneler ekleyebilir ve bu durum derleme zamanında herhangi bir uyarıya neden olmazdı. Her şey Object tipi üzerinden yürürdü. Ancak bu durum, nesneleri koleksiyondan çekerken manuel olarak tip dönüşümü yapmamız gerektiği anlamına geliyordu. İşte bu tip dönüşümleri sırasında, beklenmeyen bir tipte nesneyle karşılaştığımızda ClassCastException (Sınıf Dönüşüm İstisnası) hatası alırdık. Bu tür hatalar genellikle çalışma zamanında ortaya çıktığı için, geliştirme sürecinde tespit edilmeleri zordu ve uygulamanın beklenmedik bir şekilde çökmesine neden olabilirdi.
Düşünün ki, bir e-ticaret uygulamasında sepetinizdeki ürünleri tutmak için ArrayList kullanıyorsunuz. Generics olmasaydı, kodunuz şöyle görünebilirdi:
List sepet = new ArrayList();
sepet.add("Laptop"); // String
sepet.add(1200.0); // Double (fiyat)
sepet.add(new Object()); // Yanlışlıkla eklenen bir Object
// Sepetten ürünleri çekerken
for (Object item : sepet) {
if (item instanceof String) {
String urunAdi = (String) item;
System.out.println("Ürün Adı: " + urunAdi);
} else if (item instanceof Double) {
Double fiyat = (Double) item;
System.out.println("Fiyat: " + fiyat);
} else {
System.err.println("Beklenmeyen tipte ürün: " + item.getClass().getName());
// Burada bir ClassCastException riski var, eğer yanlış bir dönüşüm yaparsak
}
}
Bu örnekte bile, doğru tip kontrolünü yapmak için instanceof operatörünü kullanmak zorunda kaldık. Ancak, yanlışlıkla bir Integer ekleseydik ve Double olarak dönüştürmeye çalışsaydık, çalışma zamanında bir hata alırdık. Bu durum, kodun hem daha karmaşık hem de daha hataya açık olmasına neden oluyordu. Generics, bu tür senaryolarda bize derleme zamanında tip güvenliği sağlayarak, potansiyel hataları daha kod yazarken fark etmemizi sağlar.
Generics ile Güvenli ve Esnek Kod Yazmak
Generics, tip parametreleri sayesinde kodu daha güvenli ve esnek hale getirir. Bir koleksiyon oluştururken hangi tipleri içereceğini belirterek, derleyicinin bu kısıtlamayı uygulamasını sağlayabiliriz. Bu, yanlış tiplerde nesnelerin eklenmesini derleme zamanında engeller ve böylece ClassCastException riskini ortadan kaldırır.
Yukarıdaki sepet örneğini Generics ile yeniden yazarsak:
List urunSepeti = new ArrayList<>();
urunSepeti.add("Laptop"); // String ekleyebiliriz
// urunSepeti.add(1200.0); // Derleme hatası! Double ekleyemeyiz
// urunSepeti.add(new Object()); // Derleme hatası! Object ekleyemeyiz
for (String urunAdi : urunSepeti) {
System.out.println("Ürün Adı: " + urunAdi);
}
Gördüğünüz gibi, List tanımı sayesinde, urunSepeti listesine sadece String tipli nesneler ekleyebileceğimizi derleyiciye bildirmiş oluyoruz. Bu, hem kodun okunabilirliğini artırır hem de potansiyel hataları derleme zamanında yakalayarak geliştirme sürecini hızlandırır. Ayrıca, tip dönüşümlerine gerek kalmadığı için kod daha temiz ve anlaşılır hale gelir. Generics sadece koleksiyonlarla sınırlı değildir; kendi özel sınıflarınızı veya metotlarınızı da genel (generic) hale getirebilirsiniz. Örneğin, iki değeri değiştiren genel bir metot yazabiliriz:
public class Takasci {
private T deger1;
private T deger2;
public Takasci(T d1, T d2) {
this.deger1 = d1;
this.deger2 = d2;
}
public void takasEt() {
T gecici = this.deger1;
this.deger1 = this.deger2;
this.deger2 = gecici;
System.out.println("Değerler takas edildi: " + this.deger1 + ", " + this.deger2);
}
public T getDeger1() { return deger1; }
public T getDeger2() { return deger2; }
public static void main(String[] args) {
Takasci kelimeTakas = new Takasci<>("Merhaba", "Dünya");
kelimeTakas.takasEt(); // Çıktı: Dünya, Merhaba
Takasci sayiTakas = new Takasci<>(10, 20);
sayiTakas.takasEt(); // Çıktı: 20, 10
}
}
Bu Takasci sınıfı, T tip parametresi sayesinde herhangi bir tipteki iki değeri güvenli bir şekilde takas edebilir. Bu, kodun yeniden kullanılabilirliğini artırırken, tip güvenliğinden ödün vermememizi sağlar. Generics, modern Java programlamasının vazgeçilmez bir parçasıdır ve tip güvenliği ile esnekliği bir arada sunarak geliştiricilere büyük kolaylıklar sağlar.
Type Erasure (Tip Silme): Java Generics'in Perde Arkası
Java Generics'in bize sunduğu tüm bu avantajlara rağmen, bu mekanizmanın nasıl çalıştığını anlamak, bazı sınırlamalarını ve beklenmedik davranışlarını açıklığa kavuşturmak için kritik öneme sahiptir. Generics'in arkasındaki temel prensip, Type Erasure (Tip Silme) olarak bilinir. Bu kavram, Java'nın geriye dönük uyumluluğunu korumak ve JVM'in (Java Sanal Makinesi) eski sürümleriyle bile Generic kodun çalışmasını sağlamak amacıyla tasarlanmıştır. Ancak bu tasarım kararı, Generics'in bazı durumlarda beklediğimizden farklı davranmasına neden olur. Tip silme, derleme zamanında Generic tip bilgilerinin silinmesi ve bunların yerine "ham tipler" (raw types) veya Object tipi gibi daha genel tiplerin kullanılması anlamına gelir. Bu süreç, derleyici tarafından otomatik olarak gerçekleştirilir ve çalışma zamanında Generic tip bilgilerinin mevcut olmamasına yol açar.
Tip Silme Nedir ve Nasıl Çalışır?
Tip silme, Java Generics'in temel bir özelliğidir. Java derleyicisi, Generic kodunuzu derlerken, tüm tip parametrelerini (örneğin, List içindeki ) kaldırır ve bunları, tip parametresinin üst sınırı (bound) ile değiştirir. Eğer bir üst sınır belirtilmemişse (yani List gibi), varsayılan olarak Object ile değiştirilir. Bu işlemden sonra, Generic kodunuz aslında Generic olmayan, "ham" (raw) bir kod gibi görünür.
Örneğin, aşağıdaki Generic kodu ele alalım:
List stringList = new ArrayList<>();
List integerList = new ArrayList<>();
stringList.add("Merhaba");
integerList.add(123);
Derleme zamanında, derleyici bu kod parçasını şu şekilde dönüştürür:
List stringList = new ArrayList(); // bilgisi silindi
List integerList = new ArrayList(); // bilgisi silindi
stringList.add("Merhaba"); // Derleyici otomatik olarak Object'e dönüştürür
integerList.add(Integer.valueOf(123)); // Derleyici otomatik olarak Object'e dönüştürür
Dikkat ederseniz, derlenmiş kodda veya gibi tip bilgileri kalmamıştır. Tüm List referansları List (ham tip) olarak ele alınır ve eklenen elemanlar Object olarak kabul edilir. Ancak, Generics'in sağladığı tip güvenliği hala derleme zamanında geçerlidir. Derleyici, stringList'e bir Integer eklemeye çalıştığınızda hata verecektir. Tip silme, derleme sonrası bytecode'un Generic tipler hakkında bilgi içermemesini sağlar. Bu, JVM'in eski sürümleriyle bile uyumluluğu korumak için hayati öneme sahiptir, çünkü bu JVM'ler Generics kavramını bilmezlerdi. Derleyici, tip silme işlemini gerçekleştirirken aynı zamanda gerekli tip dönüşümlerini (casting) de otomatik olarak ekler, böylece çalışma zamanında doğru tiplerle çalışıldığından emin olunur. Örneğin, bir List'den bir eleman çektiğinizde, derleyici otomatik olarak bir (String) dönüşümü ekler.
Tip Silmenin Getirdiği Sınırlamalar Nelerdir?
Tip silme, Java'nın Generics'i uygulamasında bazı önemli sınırlamalara yol açar. Bu sınırlamaları bilmek, Generic kod yazarken karşılaşılan bazı "neden böyle oluyor?" sorularına cevap bulmamızı sağlar.
1. Generic Tiplerden Nesne Oluşturulamaması: Çalışma zamanında tip bilgisi silindiği için, bir Generic tipin doğrudan bir örneğini oluşturamazsınız. Örneğin, new T() veya new T[boyut] gibi ifadeler kullanamazsınız.
public class Ornek {
public T yarat() {
// return new T(); // Derleme hatası!
return null;
}
}
Bu durum, T'nin çalışma zamanında hangi tip olacağının bilinmemesinden kaynaklanır.
2. instanceof Operatörü ile Generic Tipleri Kontrol Edememe: Benzer şekilde, instanceof operatörünü Generic tiplerle doğrudan kullanamazsınız.
public void tipKontrol(List liste) {
// if (liste instanceof ArrayList) { // Derleme hatası!
// System.out.println("Bu bir String listesidir.");
// }
if (liste instanceof ArrayList) { // Sadece ham tipi kontrol edebiliriz
System.out.println("Bu bir ArrayList'tir.");
}
}
ArrayList ve ArrayList'ın çalışma zamanında her ikisi de sadece ArrayList olarak bilindiği için, derleyici bu ayrımı yapamaz.
3. Generic Tiplerden Diziler Oluşturulamaması: new T[boyut] ifadesinin yanı sıra, new List[boyut] gibi Generic tiplerden diziler de oluşturamazsınız.
// List[] listeler = new ArrayList[10]; // Derleme hatası!
Bunun nedeni, Java'da dizilerin çalışma zamanında tip bilgilerini korumasıdır (covariant array). Eğer Generic tiplerden dizi oluşturulabilseydi, tip güvenliği ihlalleri meydana gelebilirdi. Örneğin, List[] dizisine List atanabilseydi, bu bir çalışma zamanı hatasına yol açardı.
4. Static Metotlarda Generic Tipleri Kullanamama (Sınıf Seviyesinde): Bir sınıfın static metotları veya static alanları, sınıfın tip parametrelerini kullanamaz. Çünkü static üyeler, sınıfın belirli bir örneğine bağlı değildir ve dolayısıyla belirli bir tip parametresine de bağlı olamazlar.
public class StaticOrnek {
// private static T statikDeger; // Derleme hatası!
// public static T getStatikDeger() { return statikDeger; } // Derleme hatası!
}
Ancak, static metotlar kendi Generic tip parametrelerini tanımlayabilirler:
public class StaticOrnek {
public static U staticMetot(U deger) {
return deger;
}
}
Bu sınırlamalar, tip silmenin doğasından kaynaklanır ve Generic kod yazarken dikkate alınması gereken önemli noktalardır.
Vaka Analizi: Gerçek Dünyada Tip Silmenin Etkileri
Tip silmenin getirdiği sınırlamalar, özellikle yansıma (reflection), serileştirme (serialization) ve daha karmaşık tip güvenliği gerektiren durumlarda kendisini gösterir.
Yansıma (Reflection) ve Tip Bilgisinin Kaybı:
Java'da yansıma, çalışma zamanında sınıfların, metotların ve alanların incelenmesine ve değiştirilmesine olanak tanır. Ancak tip silme nedeniyle, bir Generic alanın veya metodun gerçek tip parametrelerini doğrudan yansıma yoluyla öğrenmek zordur. Örneğin, List türündeki bir alanın String tip parametresini doğrudan elde edemeyiz; sadece List olduğunu bilebiliriz.
public class VeriKapsayici {
List isimler;
Map kullaniciBilgileri;
public VeriKapsayici() {
this.isimler = new ArrayList<>();
this.kullaniciBilgileri = new HashMap<>();
}
}
// main metodu veya başka bir yerde
// VeriKapsayici kapsayici = new VeriKapsayici();
// Field field = kapsayici.getClass().getDeclaredField("isimler");
// System.out.println(field.getType()); // Çıktı: interface java.util.List
// // Burada bilgisini doğrudan alamayız.
Bu durum, özellikle JSON veya XML serileştirme/deserileştirme kütüphaneleri gibi, çalışma zamanında tip bilgilerine ihtiyaç duyan kütüphaneler için bir zorluk teşkil eder. Bu kütüphaneler, tip silmenin üstesinden gelmek için genellikle "Super Type Token" (Süper Tip Jetonu) gibi desenleri kullanır.
Serileştirme/Deserileştirme ve Generic Tipler:
Bir nesneyi serileştirip daha sonra deserileştirmek istediğinizde, Generic tip bilgisi eksikliği sorunlara yol açabilir. Örneğin, bir List'i JSON'a serileştirdiniz ve geri deserileştirmek istiyorsunuz. Eğer sadece List.class bilgisini verirseniz, kütüphane içindeki elemanları Object olarak deserileştirebilir ve doğru MyObject tipine dönüştürmek için ek bir çaba sarf etmeniz gerekebilir. Bu, özellikle karmaşık Generic tiplerle (örneğin Map>) çalışırken daha da karmaşık hale gelir. Gson veya Jackson gibi popüler serileştirme kütüphaneleri, bu sorunu aşmak için TypeToken veya ParameterizedTypeReference gibi özel yapılar sunar. Bu yapılar, tip silme nedeniyle kaybolan Generic tip bilgisini yakalamak için yansıma ve anonim iç sınıfların birleşimini kullanır.
ORM (Object-Relational Mapping) Kütüphanelerinde Generic Tipler:
Hibernate gibi ORM kütüphaneleri, veritabanı tablolarını Java sınıflarına eşler. Bir Generic DAO (Data Access Object) deseni kullandığınızda, örneğin GenericDao, T'nin çalışma zamanında hangi varlık tipi olduğunu bilmek isteyebilirsiniz. Tip silme nedeniyle, T'nin ne olduğunu doğrudan öğrenemezsiniz. Bu nedenle, genellikle DAO'nun yapıcı metoduna Class tipinde bir parametre geçirmeniz gerekir: public GenericDao(Class entityClass). Bu, kütüphanenin T tipinin çalışma zamanı bilgisini elde etmesinin bir yoludur.
Tüm bu vaka analizleri, tip silmenin Java Generics'in güçlü bir özelliği olmasına rağmen, bazı senaryolarda geliştiricilerin ekstra çaba sarf etmesini gerektirdiğini göstermektedir. Bu sınırlamaları anlamak, daha sağlam ve hatasız Generic kod yazmamıza yardımcı olur.
Mevcut Java'da Tip Bilgisini Korumak Mümkün mü?
Tip silmenin getirdiği sınırlamalara rağmen, Java geliştiricileri bu kısıtlamaları aşmak ve çalışma zamanında Generic tip bilgilerine erişmek için çeşitli yaratıcı desenler geliştirmiştir. Bu desenler, genellikle yansıma (reflection) mekanizmasını kullanarak, derleme zamanında kaybolan tip bilgilerini dolaylı yoldan yakalamaya çalışır. En bilinen ve yaygın kullanılan yöntemlerden biri "Süper Tip Token" (Super Type Token) desenidir. Bu desen, özellikle serileştirme kütüphaneleri ve diğer tip bağımlı API'ler tarafından sıklıkla tercih edilir.
Süper Tip Token (Super Type Token) Deseni
Süper Tip Token deseni, tip silme nedeniyle kaybolan tam Generic tip bilgisini çalışma zamanında yakalamak için kullanılan bir tekniktir. Bu desen, anonim bir iç sınıfın Generic süper tipini kullanarak tip parametrelerini yakalar. Anonim iç sınıflar, derleyici tarafından gerçek tipleriyle birlikte derlenir ve bu sayede, yansıma kullanılarak süper tipin Generic parametrelerine erişilebilir.
Temel fikir şudur: Class nesnesi, T'nin tek başına Generic tip parametrelerini içermez. Ancak, TypeReference> gibi bir anonim sınıf oluşturduğumuzda, bu anonim sınıfın süper tipi (yani TypeReference>) tam Generic tip bilgisini korur. Bu bilgiye yansıma yoluyla erişebiliriz.
İşte basit bir TypeReference sınıfı örneği:
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
public abstract class TypeReference {
private final Type type;
protected TypeReference() {
Type superclass = getClass().getGenericSuperclass();
if (superclass instanceof Class) {
throw new RuntimeException("Missing type parameter.");
}
ParameterizedType parameterized = (ParameterizedType) superclass;
this.type = parameterized.getActualTypeArguments()[0];
}
public Type getType() {
return type;
}
}
Bu TypeReference sınıfını kullanarak, bir List için tip bilgisini nasıl yakalayacağımızı görelim:
// main metodu veya başka bir yerde
Type stringListType = new TypeReference>() {}.getType();
System.out.println(stringListType); // Çıktı: java.util.List
Type stringMapType = new TypeReference>() {}.getType();
System.out.println(stringMapType); // Çıktı: java.util.Map
Bu çıktı, stringListType ve stringMapType değişkenlerinin, List ve Map gibi tam Generic tip bilgilerini içerdiğini gösterir. Bu bilgiye, Type arayüzü ve onun alt arayüzleri (ParameterizedType, GenericArrayType, TypeVariable, WildcardType) aracılığıyla erişilebilir ve daha sonra bu bilgiyi kullanarak çalışma zamanında tip güvenli işlemler yapılabilir. Gson ve Jackson gibi JSON kütüphaneleri, bu deseni kullanarak karmaşık Generic tiplerin serileştirilmesi ve deserileştirilmesini mümkün kılar. Örneğin, Gson'da TypeToken sınıfı tam olarak bu prensiple çalışır.
Sınıf Literal Kullanımı ve Sınırlamaları
Bir diğer yaygın yöntem ise, metotlara Class tipinde bir parametre geçirmektir. Bu, özellikle bir Generic sınıfın veya metodun, çalışma zamanında T'nin hangi somut sınıf olduğunu bilmesi gerektiği durumlarda kullanışlıdır.
public class Factory {
private Class tipSinifi;
public Factory(Class tipSinifi) {
this.tipSinifi = tipSinifi;
}
public T yeniNesneYarat() throws InstantiationException, IllegalAccessException, NoSuchMethodException, java.lang.reflect.InvocationTargetException {
// Sadece varsayılan yapıcı metot (no-arg constructor) varsa çalışır.
return tipSinifi.getDeclaredConstructor().newInstance();
}
public static void main(String[] args) throws Exception {
Factory stringFactory = new Factory<>(String.class);
String yeniString = stringFactory.yeniNesneYarat();
System.out.println("Yeni String nesnesi: " + yeniString); // Çıktı: Yeni String nesnesi:
Factory integerFactory = new Factory<>(Integer.class);
// Integer'ın no-arg constructor'ı olmadığı için hata fırlatır
// Integer yeniInteger = integerFactory.yeniNesneYarat();
}
}
Bu yaklaşım, T'nin somut Class nesnesini çalışma zamanında elde etmemizi sağlar. Ancak önemli bir sınırlaması vardır: sadece "ham" (raw) sınıf bilgisini sağlar, yani Class bilgisini alabiliriz, ancak Class> gibi tam Generic tip bilgisini elde edemeyiz. String.class veya Integer.class gibi sınıf literalleri, Generic tiplerin parametrelerini içermezler. Örneğin, List.class dediğimizde, bu sadece java.util.List sınıfını temsil eder, içindeki Generic parametreleri (, gibi) içermez. Bu nedenle, bu yöntem sadece tek bir tip parametresinin somut sınıfını bilmek istediğimizde işe yarar. List gibi karmaşık Generic tiplerin tam bilgisini elde etmek için yeterli değildir. Süper Tip Token deseni, bu sınırlamayı aşarak daha karmaşık Generic tip bilgilerini yakalamak için daha güçlü bir yol sunar. Her iki desen de, tip silmenin yarattığı boşluğu doldurmaya çalışır ve Java geliştiricilerine daha esnek ve tip güvenli kod yazma imkanı sunar.
Reified Generics (Yeniden Somutlaştırılmış Generics): Hayal mi Gerçek mi?
Java'da tip silmenin getirdiği sınırlamalar, uzun yıllardır geliştiriciler arasında tartışılan ve üzerinde iyileştirme beklenen bir konudur. Kotlin gibi modern dillerde Reified Generics'in (Yeniden Somutlaştırılmış Generics) varlığı, Java topluluğunda da bu özelliğe olan talebi artırmıştır. Reified Generics, basitçe, Generic tip bilgilerinin çalışma zamanında da korunması anlamına gelir. Bu, geliştiricilerin tip silme nedeniyle karşılaştıkları birçok sorunu ortadan kaldırabilecek potansiyel bir çözümdür. Ancak, bu özelliği Java'ya dahil etmek, özellikle geriye dönük uyumluluk ve JVM mimarisi göz önüne alındığında, önemli teknik zorlukları beraberinde getirmektedir.
Reified Generics Nedir ve Ne İşe Yarar?
Reified Generics, Java'daki tip silme (Type Erasure) davranışının aksine, Generic tip parametrelerinin çalışma zamanında da erişilebilir olmasını sağlayan bir kavramdır. Yani, List oluşturduğunuzda, çalışma zamanında bu listenin elemanlarının String tipinde olduğunu doğrudan sorgulayabilirsiniz. Mevcut Java'da List ve List çalışma zamanında sadece List olarak görünürken, Reified Generics ile her ikisi de kendi spesifik tiplerini koruyacaktır.
Reified Generics'in temel işlevi, geliştiricilerin tip silme nedeniyle başvurdukları karmaşık ve bazen hata eğilimli geçici çözümlere (örneğin Super Type Token deseni) olan ihtiyacı ortadan kaldırmasıdır. Bu sayede kod daha temiz, daha anlaşılır ve daha güvenli hale gelir. Örneğin, Kotlin'de inline metotlarla birlikte Reified Generics kullanarak, bir Generic tipin Class nesnesine doğrudan erişebilir ve bu nesne üzerinden yeni örnekler oluşturabilir veya instanceof kontrolü yapabiliriz:
// Kotlin örneği
inline fun createInstance(): T {
return T::class.java.getDeclaredConstructor().newInstance()
}
// Kullanım
val myString: String = createInstance()
val myInteger: Int = createInstance() // Bu Kotlin'de hata verir, Int'in no-arg constructor'ı yok.
// Java'daki Integer.class örneğine benzer.
Bu örnekte, reified T anahtar kelimesi, T tip parametresinin çalışma zamanında da somutlaştırılacağını belirtir. Bu
