Java Generics: Tip Güvenliği ve Esneklik Bir Arada

Özge Odabaş
4 min readJun 18, 2023

Java dili explicit casting gerektirmeden çeşitli veri türleri üzerinde çalışabilen generic sınıflar ve metotlar yazmak için destek sağlar. Generic yapı, bir sınıfı formal tip parametreleri olarak tanımlamamızı ve bu parametrelerin daha sonra sınıf içinde değişkenlerin, parametrelerin ve return değerlerinin tipi olarak kullanmamızı sağlar.

public class Pair<A,B> { //generic formal type parameters
A first; // as variable type
B second; // as variable type

// constructor
public Pair(A a, B b) { // as paramater type
first = a;
second = b;
}

public A getFirst( ) { return first; } // as return type
public B getSecond( ) { return second;} // as return type
}

Yukarıda ki kodda görüldüğü gibi sınıfın imzasında generic olarak belirtilen A ve B tipleri daha sonra sınıf içerisinde değişken tipi, parametre tipi ve return tipi olarak kulanıldı.

Burada formal tip olarak kullandığımız A ve B değerlerinin ne olacağı, program içerisinde Pair class’ını kullandığımız anda belirlenir.

Az önce örnek olarak verdiğimiz kodla aynı işlevselliğe sahip bir metod generic yapısı kullanmadan da yazılabilir. Fakat bu kodu klasik yöntemle yazmak kodun çeşitli yerlerinde explicit cast yapmamızı zorunlu kılar.

public class ObjectPair {

Object first;
Object second;

public ObjectPair(Object a, Object b) { // constructor
first = a;
second = b;
}

public Object getFirst( ) { return first; }
public Object getSecond( ) { return second;}
}

Şuan gördüğünüz ObjectPair sınıfı da generic kullanılarak yazılan Pair sınıfı ile aynı işlevselliği saplayabilir. Fakat bir de kullanımına bakalım. Örneğin ObjectPair sınıfından bir String bir de Double değer tutacak bir nesne üretmek isteseydik:

ObjectPair bid = new ObjectPair("ORCL", 32.07); //bu şekilde bir nesne üretebiliriz.

Bu kullanımda ilk nesnemizin (“ORCL” olarak belirtilen) String tipinde olduğunu bilsek de aşağıdaki kullanımı yapamıyoruz:

String stock = bid.getFirst( ); //compile error

Bu durumda explicit casting ile narrowing conversion yapmak zorunda kalıyoruz.

String stock = (String) bid.getFirst( ); //Object to String

Klasik yöntem yerine ilk başka yazıdığımı generic Pair sınıfını kullansaydık casting işlemine gerek kalmadan yazabilirdik:

Pair<String,Double> bid;
bid = new Pair<>("ORCL", 32.07); //type inference
//or
//bid = new Pair<String,Double>("ORCL", 32.07); //with giving explicit types

//We can make these assignments without the need for the explicit casting
//process that we have to do in the classical method
String stock = bid.getFirst( );
double price = bid.getSecond( );

Generic Yapısı ve Diziler

Generic yapısını sınıflar ile kullanabildiğimiz gibi diziler ve metodlar ile de kullanabiliriz.

Diziler ile birlikte generic yapısını kullnmak istediğimiz karşımıza Javanın tip güvenliği sorunu çıkıyor. Bu sorunla iki durumda karşılaşabiliriz:

  1. Generic sınfın dışında başka bir yerde, generic sınıftan üretilmiş nesneleri tutan bir dizi oluşturmak istediğimizde.
  2. Generic sınıfın içinde generic olarak belirttiğimiz tipte nesneleri tutan bir dizi oluşturmak istediğimizde.

İlk maddeye bakacak olursak:

Pair<String,Double>[ ] holdings;
holdings = new Pair<String,Double>[25]; // illegal; compile error
holdings = new Pair[25]; // correct, but warning about unchecked cast
holdings[0] = new Pair<>("ORCL", 32.07); // valid element assignment

Java, tip güvenliğini sağlamak için generic tiplere sahip dizilerin doğrudan oluşturulmasına izin vermez. (2.satırda gördüğümüz gibi)

Bunun nedeni aslında generic tipler runtime esnasında silinir (type erasure), bu nedenle compiletime’da generic tiplerle ilişkili bir dizi oluşturmak mümkün değildir.

Mesela aşağıdaki gibi bir kod yazdığımızda:

public static  <T> boolean containsElement(T [] elements, T element){
for (T e : elements){
if(e.equals(element)){
return true;
}
}
return false;
}

Compiler tip güvenliğini sağlamak ve runtime errorlarından korunmak için T tipini Object tipi ile değiştirir:

public static boolean containsElement(Object [] elements, Object element){
for (Object e : elements){
if(e.equals(element)){
return true;
}
}
return false;
}

İkinci maddeye örnek verecek olursak:

public class Portfolio<T> {
T[ ] data;

public Portfolio(int capacity) {
data = new T[capacity]; // illegal; compiler error
data = (T[ ]) new Object[capacity]; // legal, but compiler warning
}

public T get(int index) { return data[index]; }
public void set(int index, T element) { data[index] = element; }
}

Bu kodda 4. satırdaki gibi direkt olarak T tipinde veri tutan bir array oluşturmak mümkün değildir. Onun yerine 5. satırda olduğu gibi, Object tipinde veri tutan bir array oluşturup T tipine cast edilir.

Özetle, Java Generics güçlü ve esnek bir dil özelliği olarak programcılara büyük avantajlar sağlar. Generic tipler, veri türünden bağımsız olarak kodun tekrar kullanılabilirliğini artırırken, aynı zamanda tür güvenliği sağlar. Bu sayede, daha okunabilir ve bakımı kolay kodlar yazabiliriz.

Generic sınıflar ve metotlar, farklı veri türleriyle çalışan bileşenlerin oluşturulmasını ve kullanılmasını kolaylaştırır. Tür silinimi (type erasure) nedeniyle bazı kısıtlamalar olsa da, Java Generics programcılara daha güçlü, esnek ve genel programlar oluşturma imkanı sunar.

Generics, Java’nın nesne yönelimli programlama yeteneklerini genişletir ve daha az hata yapma ve daha verimli kodlama deneyimi sağlar. Java programlama dünyasında Generics, birçok proje ve kütüphane için temel bir yapı taşı haline gelmiştir ve her programcının bilmesi gereken önemli bir konudur.

--

--

Özge Odabaş

Merhaba! Ben Özge. Junior Java Developerım. Kendimi geliştirirken edindiğim bilgileri yazıyorum. Keyifli okumalar.