9 Haziran 2020 Salı

Generics ve Ata Sınıftaki Nesneyi İstenilen Tipe Çevirmek

Giriş
Bazen elimizdeki nesneyi bu nesneden kalıtan bir başka tipe çevirmek isteriz. Özellikle generics kullanan kodlar bu çevrim işi karışık bir hal alabiliyor.

Çevrim için iki temel yöntem var.
1. Sol tarafta değişken yoksa yani metod getter() tarzı bir şey değilse, mecburen kullanmak istediğimiz tipi metoda geçmek gerekiyor.

2. Sol tarafta değişken varsa yani metod getter() tarzı bir şeyse, kullanmak istediğimiz tip değişkenin tipinden çıkarılabiliyor.

3. Sol tarafta değişken varsa ve girdi alıyorsa yani metod calculate() tarzı bir şeyse, kullanmak istediğimiz tip değişkenin tipinden çıkarılabiliyor.

1. Sol Taraftaki Değişken Yoksa
Bu örneklerde elimizde ata sınıf referansı var. Onu kalıtan bir başka sınıfa cast ediyoruz. Bu biraz Type Witness kullanımına benziyor

Örnek
Elimizde şöyle bir kod olsun
public class Animal {
  private Map<String,Animal> friends = new HashMap<>();

  public void addFriend(String name, Animal animal){
    friends.put(name,animal);
  }

  public Animal callFriend(String name){
    return friends.get(name);
  }
}
Normal kullanım şöyle. Ancak return type'ı belirtilen tipe cast olmadan çevirmek istiyoruz.
Mouse jerry = new Mouse();
jerry.addFriend("spike", new Dog());
jerry.addFriend("quacker", new Duck());

((Dog) jerry.callFriend("spike")).bark();
((Duck) jerry.callFriend("quacker")).quack();
Çözüm 1
callFriend metodunu şöyle değiştiririz
@SuppressWarnings("unchecked")
public <T extends Animal> T callFriend(String name) {
    return (T)friends.get(name);
}
Kullanmak için şöyle yaparız.
jerry.<Dog>callFriend("spike").bark();
Çözüm 2 - Return Type Girdi Olarak Alınır
Ancak şu çözüm daha iyi. Daha detaylı örmekler aşağıda. Elimizde şöyle bir kod olsun.
public <T extends Animal> void addFriend(String name, Class<T> type, T animal);
public <T extends Animal> T callFriend(String name, Class<T> type);
Metodun içi şöyle olsun.
public <T extends Animal> T callFriend(String name, Class<T> type) {
  return type.cast(friends.get(name));
}
Çağırmak için şöyle yaparız.
jerry.callFriend("spike", Dog.class).bark();
jerry.callFriend("quacker", Duck.class).quack();
2. Return Type Girdi Olarak Alınır
Bu örneklerde elimizde ata sınıf referansı var. Onu kalıtan bir başka sınıfa cast ediyoruz.

Örnek
Şöyle yaparız. Bu örnekte girdi yok. Sadece değişkenimiz istenilen tipe çevriliyorr.
public getAs(Class<T> clazz){
  return castTo(myField,clazz);
}


protected <E> E castTo(Object object, Class<E> clazz){
  if (clazz.isInstance(object) {
    return (E) object;
  }
  return null;
}
3. Sol Taraftaki Değişken Tipine Göre Girdi Var

Örnek
Bu örnekte girdi ile sol taraftaki değişken farklı tiplerdir. Şöyle yaparız.
public static <T, U extends T> T foo(U u) { ... }
Ve bu kod şöyle kullanabiliyoruz
// Baz is just the containing class of foo()
Number n = Baz.foo(1);
Aslında aynı kod şöyle olsaydı da çalışırdı.
public static <T> T foo(T t) { ... }
Ancak ikinci örnek girdi ve sol taraftaki değişkeni hep aynı tip kabul ediyor. Yani Şu kod her zaman derlenir.
Integer integer = null;
Number number = null;

// Always works:
integer = fooTrivial(integer);
number = fooTrivial(number);
number = fooTrivial(integer);


public static <T, U extends T> T fooTrivial(U u) {
  return u;
}
Ancak şu kodlar bazen derlenmez çünkü List<Number> ile List<Integer> farklı şeylerdir.
List<Number> numberList = null;
List<Integer> integerList = null;


numberList = withList(numberList);
//numberList = withList(integerList); // Does not work


public static <T> List<T> withList(List<T> u) {
  List<T> result = new ArrayList<T>();
  result.add(u.get(0));
  return result;
}
Düzeltmek için şöyle yaparız.
List<Number> numberList = null;
List<Integer> integerList = null;


// Both work:
numberList = withListAndBound(numberList);
numberList = withListAndBound(integerList);

public static <T, U extends T> List<T> withListAndBound(List<U> u) {
  List<T> result = new ArrayList<T>();
  result.add(u.get(0));
  return result;
}
4. Arayüz Kullanıyorsak
Arayüz ile Class kullanımında nesneyi istenile tipe çevirmek aynı şey gibi. Sadece arayüz kullanırken mutlaka kalıtım olacağı için karşımıza farklı bir iki kullanım daha çıkıyor.


4.1 Arayüzün İmzasını Değiştirmek
Bu aslında Covariant Return Type. Yine de not almak istedim.

Örnek
Elimizde şöyle bir kod olsun.
interface AbstractToolbox {
  public List<? extends AbstractItem> getItems();
}
Kalıtan sınıf ? extends kullanan metodun arayüzünü değiştirebilir. Şöyle yaparız.
public ExpensiveToolbox implements AbstractToolbox {
  private List<SharpItem> items = new ArrayList()<SharpItems>;
  public List<SharpItem> getItems() { return this.items; }
}
Eğer arayüze kod kodlama yaparsak arayüzün imzasını kullanmak gerekir. Şöyle yaparız.
AbstractToolbox absTB = ...;
List<? extends AbstractItem> items1 = absTB.getItems(); //fine
Şu kod derlenmez.
List<SharpItem> items2 = absTB.getItems(); //compile error
4.2 Arayüze T Generic Parametresi Geçmek 
Örnek
Elimizde şöyle bir kod olsun
public interface AbstractToolbox<T extends AbstractItem> {
  public List<T> getItems();
}
public ExpensiveToolbox implements AbstractToolbox<SharpItem> {
  public List<SharpItem> getItems() {...}
}
Kalıtan sınıfa göre kodlarsak kalıtan sınıfın imzasını kullanmak gerekir  . Şöyle yaparız.
ExpensiveToolbox toolbox = ...
List<SharpItem> items3= toolbox.getItems(); //fine
Eğer arayüze göre kodlarsak arayüzün imzasını kullanmak gerekir. Şöyle yaparız.
AbstractToolbox absTB = ...;

List<? extends AbstractItem> items1 = absTB.getItems(); //fine
4.3 Sol Tarafta Değişken Varsa
Eğer dışarıdan gelen parametreyi önemsemeden sadece döndürdüğümüz tipi dikkate alırsak şöyle yaparız.
MyClass<? extends Number> foo() {
  return new MyClass<Integer>();
}
Eğer dışarıdan gelen parametre tipini dikkate almak istersek şöyle yaparız.
<R extends Number> MyClass<R> foo() {
  return new MyClass<Integer>();
}
Bu durumda şöyle kodlarız.
MyClass<Integer> d = foo();

Hiç yorum yok:

Yorum Gönder