01 Generics neden var?
Generics olmasaydı genel amaçlı bir kutuyu şöyle yazabilirdik:
JAVApublic class Box {
private Object value;
public void set(Object value) {
this.value = value;
}
public Object get() {
return value;
}
}JAVABox box = new Box();
box.set("Java");
String value = (String) box.get();Bu yaklaşımda iki önemli problem vardır:
get()sonucu için cast gerekir.- Kutunun içine yanlışlıkla her tip konabilir.
JAVAbox.set(42);
String value = (String) box.get();
// Runtime'da ClassCastExceptionGeneric sürümünde kabul edilen tip önceden belirlenir:
JAVApublic class Box<T> {
private T value;
public void set(T value) {
this.value = value;
}
public T get() {
return value;
}
}JAVABox<String> box = new Box<>();
box.set("Java");
String value = box.get();
box.set(42);
// Compile error02 T nedir?
JAVApublic class Box<T> {}Buradaki T, bir type parameter, yani tip parametresidir. T özel bir keyword değildir; aynı sınıfı X ile de yazabilirdik. Ancak Java’da yaygın isimlendirmeler vardır.
Type
Element
Key
Value
Result
Number
HashMap kabaca iki tip parametresi kullanır:
JAVAMap<K, V>
Map<String, Integer> ages = new HashMap<>();
// K → String
// V → IntegerGeneric class kullanımı
JAVABox<String> stringBox = new Box<>();
Box<Integer> integerBox = new Box<>();
Box<User> userBox = new Box<>();
stringBox.set("Java");
integerBox.set(42);
userBox.set(new User("Ali"));Aynı Box kodu farklı tiplerle kullanılabilir. Her nesne kendi belirlenen tipiyle çalışır.
03 Generic class yazmak
Birden fazla tip parametresi taşıyan immutable Pair örneği:
JAVAimport java.util.Objects;
public class Pair<K, V> {
private final K key;
private final V value;
public Pair(K key, V value) {
this.key = Objects.requireNonNull(key);
this.value = Objects.requireNonNull(value);
}
public K key() {
return key;
}
public V value() {
return value;
}
}JAVAPair<String, Integer> age =
new Pair<>("Ali", 29);
String name = age.key();
Integer value = age.value();
Pair<Long, User> userEntry =
new Pair<>(100L, new User("Ali"));Type parameter sınıf içinde farklı konumlarda kullanılabilir:
JAVApublic class Container<T> {
private T value; // Field tipi
public Container(T value) { // Parametre tipi
this.value = value;
}
public T get() { // Return tipi
return value;
}
public void set(T value) { // Metot parametresi
this.value = value;
}
}04 Diamond operator <>
new Box<String>()Eski ve tekrarlı kullanım.
new Box<>()Modern diamond operator kullanımı.
JAVABox<String> box = new Box<>();Sağ taraftaki tip sol taraftan çıkarılabilir. Buna type inference denir. Compiler diamond içindeki T’nin String olması gerektiğini anlar.
JAVAvar first = new Box<String>();
// Tip bilgisi olmadığı için çoğunlukla Box<Object>
var second = new Box<>();Box<String> first = new Box<>(); ve var second = new Box<String>(); açık ve güvenlidir.05 Raw type nedir?
Generic bir sınıfı tip parametresi vermeden kullanırsak raw type oluşur. Bu kullanım geriye dönük uyumluluk için vardır; yeni kodda kullanılmamalıdır.
JAVABox box = new Box();
box.set("Java");
box.set(42);
box.set(new User("Ali"));
// Compiler: unchecked call uyarısıDoğrusu:
JAVABox<String> box = new Box<>();06 Generic tiplerde primitive kullanılamaz
JAVABox<int> box = new Box<>();
// Compile errorGenerics yalnızca reference type’larla çalışır. Primitive yerine wrapper class kullanılır.
JAVABox<Integer> integerBox = new Box<>();
Box<Double> doubleBox = new Box<>();
Box<Boolean> booleanBox = new Box<>();Autoboxing ve unboxing
JAVABox<Integer> box = new Box<>();
box.set(10);
// Compiler: box.set(Integer.valueOf(10));
int number = box.get();
// Integer → int unboxingWrapper null olduğunda unboxing yapılamaz:
JAVABox<Integer> box = new Box<>();
Integer wrapper = box.get(); // null
int primitive = box.get(); // NullPointerException07 Generic method
Sınıfın tamamını generic yapmak zorunda değiliz. Tek bir metot kendi tip parametresine sahip olabilir.
JAVApublic class Printer {
public static <T> void print(T value) {
System.out.println(value);
}
}JAVAPrinter.print("Java");
Printer.print(42);
Printer.print(new User("Ali"));<T>, return type’tan önce yazılır: public static <T> void print(...).JAVApublic static <T> T first(T first, T second) {
return first;
}
String result = first("Java", "Spring");
Integer number = first(10, 20);Compiler parametrelere ve beklenen dönüş tipine bakarak T’yi çıkarır. İstersek tipi açıkça verebiliriz ancak çoğunlukla gerekmez.
JAVAString result = Printer.<String>first(
"Java",
"Spring"
);08 Generic class ile generic method farkı
Generic class
JAVApublic class Box<T> {
private T value;
public void set(T value) {
this.value = value;
}
}
Box<String> box = new Box<>();T, nesne oluşturulurken belirlenir. Bu box için T, nesnenin kullanım ömrü boyunca String’dir.
Generic method
JAVApublic static <T> T identity(T value) {
return value;
}
String text = identity("Java"); // T = String
Integer number = identity(42); // T = IntegerGeneric classTip, nesnenin kullanım ömrü boyunca belirlenir.
Generic methodTip, her metot çağrısında yeniden belirlenir.
09 Static üyeler ve class type parameter
Şu kod geçersizdir:
JAVApublic class Box<T> {
private static T value;
// Compile error
}Static alan sınıfa aittir, belirli bir nesneye ait değildir. Aynı sınıftan Box<String> veBox<Integer> oluşturulabildiğine göre tek bir static value’nun hangi tipe ait olacağı belirsizdir.
JAVApublic class Box<T> {
public static T getSomething() {
// Compile error
}
}Static metot kendi bağımsız generic parametresini tanımlayabilir:
JAVApublic class Box<T> {
public static <U> U identity(U value) {
return value;
}
}Buradaki U, sınıfın T parametresinden bağımsızdır.
10 Neden bounds gerekir?
JAVApublic static <T> double doubleValue(T value) {
return value.doubleValue();
// Compile error
}Compiler T hakkında hiçbir şey bilmez. T; String, User, LocalDate veya Thread olabilir ve hepsinde doubleValue metodu yoktur. Compiler’a bir garanti vermek için bound kullanılır.
JAVApublic static <T extends Number>
double doubleValue(T value) {
return value.doubleValue();
}<T extends Number>, “T, Number veya Number’ın alt türlerinden biri olmalı” anlamına gelir.JAVAdoubleValue(10); // Integer
doubleValue(5.5); // Double
doubleValue(100L); // Long
doubleValue("Java");
// Compile error11 Upper bound
<T extends Number> ile yazılan sınır upper bound olarak adlandırılır.
Object
└── Number
├── Integer
├── Long
├── Double
├── Float
├── Short
└── ByteT, Number sınırının aşağısındaki tiplerden biri olabilir. Bound sayesinde metot içinde Number’ın metotları çağrılabilir.
JAVApublic static <T extends Number>
double calculate(T value) {
double first = value.doubleValue();
int second = value.intValue();
return first + second;
}Compiler value değişkeninin en azından Number olduğunu bilir.
12 Interface ile bound
extends yalnızca sınıflar için kullanılmaz. Interface bound için de extends yazılır.
JAVApublic static <T extends Comparable<T>>
T greater(T first, T second) {
if (first.compareTo(second) >= 0) {
return first;
}
return second;
}JAVASystem.out.println(greater(10, 20));
// 20
System.out.println(greater("Java", "Spring"));
// SpringComparable garantisi sayesinde first.compareTo(second)çağrılabilir. Bound olmasaydı compiler compareTo metodunun varlığını bilemezdi.
13 Multiple bounds
Bir tipin hem bir sınıfı genişletmesini hem de interface’leri uygulamasını isteyebiliriz.
JAVApublic static
<T extends Number & Comparable<T>>
T max(T first, T second) {
return first.compareTo(second) >= 0
? first
: second;
}
Integer result = max(10, 20);Multiple bound kuralları:
- En fazla bir sınıf bound olabilir.
- Sınıf varsa ilk sırada yazılmalıdır.
- Sonrasında birden fazla interface gelebilir.
JAVA<T extends SomeClass
& FirstInterface
& SecondInterface><T extends Comparable<T> & Number>Hatalı: class bound ilk sırada değil.
<T extends Number & Comparable<T>>Doğru sıralama.
14 Generic bir Stack yazalım
JAVAimport java.util.ArrayList;
import java.util.EmptyStackException;
import java.util.List;
public class Stack<T> {
private final List<T> elements =
new ArrayList<>();
public void push(T value) {
elements.add(value);
}
public T pop() {
if (elements.isEmpty()) {
throw new EmptyStackException();
}
return elements.remove(
elements.size() - 1
);
}
public T peek() {
if (elements.isEmpty()) {
throw new EmptyStackException();
}
return elements.get(
elements.size() - 1
);
}
public boolean isEmpty() {
return elements.isEmpty();
}
public int size() {
return elements.size();
}
}JAVAStack<String> stack = new Stack<>();
stack.push("Java");
stack.push("Spring");
String top = stack.pop();
// Spring
Stack<Integer> numbers = new Stack<>();
numbers.push(10);
numbers.push(20);
int number = numbers.pop();
// 20Aynı algoritma farklı tiplerle çalışır ancak tip güvenliği korunur.
15 Generics invariant’tır
Integer, Number’ın alt tipi olsa da List<Integer>,List<Number>ın alt tipi değildir.
JAVAList<Integer> integers = List.of(1, 2, 3);
List<Number> numbers = integers;
// Compile errorNeden izin verilmediğini düşünelim:
JAVAList<Integer> integers =
new ArrayList<>();
List<Number> numbers = integers;
numbers.add(3.14);
// List<Integer> içine Double eklenmiş olurduInteger, Number’ın alt tipidir. AncakList<Integer>, List<Number>ın;Box<Integer> da Box<Number>ın alt tipi değildir.Bu ihtiyaç wildcard ile çözülür: List<? extends Number>ve List<? super Integer>. Bu, roadmap’in dördüncü konusu olan Wildcards, PECS ve type erasure bölümünde ele alınacak.
16 Generics’in önemli kısıtlamaları
new T() oluşturamazsın
JAVApublic class Factory<T> {
public T create() {
return new T();
// Compile error
}
}Runtime’da T’nin somut olarak ne olduğunu doğrudan bilemeyiz. Bir çözüm factory veya Supplier almaktır.
JAVAimport java.util.function.Supplier;
public class Factory<T> {
private final Supplier<T> supplier;
public Factory(Supplier<T> supplier) {
this.supplier = supplier;
}
public T create() {
return supplier.get();
}
}
Factory<ArrayList<String>> factory =
new Factory<>(ArrayList::new);
ArrayList<String> list = factory.create();Generic array oluşturamazsın
JAVAT[] values = new T[10];
// Compile errorinstanceof T kullanamazsın
JAVAif (value instanceof T) {
// Compile error
}T.class kullanamazsın
JAVAClass<T> type = T.class;
// Compile error17 Ne zaman hangisini kullanmalı?
Generic class kullan
JAVAclass Repository<T> {}
class Stack<T> {}
class Box<T> {}
class Cache<K, V> {}Nesnenin genel davranışı belirli bir tipe bağlıysa sınıfı generic yap.
Generic method kullan
JAVAstatic <T> T first(List<T> values)
static <T> void swap(...)
static <T> T identity(T value)Yalnızca tek bir işlem tipten bağımsız çalışacaksa metodu generic yap.
Bound kullan
JAVA<T extends Number>
<T extends Comparable<T>>Metodun T üzerinde belirli operasyonları yapabilmesi gerekiyorsa sınır koy.
18 En önemli zihinsel model
Her şeyi kabul eder; tip bilgisi kaybolur, cast ve runtime hatası doğabilir.
Tip kontrollüdür; bilgi korunur ve hata compile-time’da yakalanır.
T serbest değildir; compiler hangi davranışlara sahip olduğunu bilir.
Zihinsel modeli kontrol et
Box<Integer>, Box<Number>ın alt tipi değildir.
Metodun kendi tip parametresini tanımlaması gerekir.
T üzerinde Number metotlarının çağrılabileceğini garanti eder.
Raw type kullanımı compile-time tip güvenliğini kaybettirir.
Basitten bounded generics’e ilerle
Box<T>: set, get ve isEmptyPair<K, V>: immutable iki değerStack<T>: push, pop ve peek<T> T first(List<T>): generic method<T extends Number> double sum(List<T>)<T extends Comparable<T>> T max(T, T)