Skip to content

3. Jenerik sınıf ve yöntem imzaları

  

RxJava kütüphanesi, parametre olarak jenerik arayüz örneklerini kabul eden birçok yönteme sahiptir. Bazen bu yöntemlerin imzaları karmaşıktır. İşte birkaç örnek:


public final <R> Observable<R> map(Func1<? super T,? extends R> func)

public final <R> Observable<R> flatMap(Func1<? super T,? extends Observable<? extends R>> func)

Bu iki yöntem, T ve R adlı iki jenerik tür kullanır. Peki, örneğin map yönteminin parametre tanımı olan Func1<? super T,? extends R> func ?

Jeneriklerle ilgili bilgiler URL [https://docs.oracle.com/javase/tutorial/java/generics/] adresinde bulunabilir. Aşağıdaki bazı bilgiler bu URL kaynağından alınmıştır. Jenerikler, Java 1.5 sürümüyle ortaya çıkmıştır.

Farklı türlerde bilgiler sunan bir web hizmeti düşünelim. Web hizmeti bazen bu bilgileri sunmada başarısız olabilir ve bu durumda müşterisine bir hata bildirmesi gerekir. Bu durumda, web hizmetinin yanıtını şu şekilde standartlaştırabiliriz:


public class Response<T> {

    // ----------------- özellikler
    // işlemin durumu
    private int status;
    // olası hata mesajları
    private List<String> messages;
    // yanıt gövdesi
    private T body;
...
}
  • 1. satır: [Response] sınıfı, T türü ile tanımlanmıştır;
  • 9. satır: T türü, yanıt gövdesinin türüdür; bu, müşterinin gerçekte beklediği şeydir;
  • satır 5: bir hata kodu; hata yoksa 0;
  • 7. satır: hatayı açıklayan mesajlar; hata yoksa null;

İstemci tarafında ise şöyle yazılabilir:


Response<Product> product=getDataFromWebService(...) ;

veya


Response<List<Product>>=getDataFromWebService(...) ;

Biçimsel T türü, burada [Product] veya [List<Product>] gibi bir etkin türle değiştirilir. [Response<T>] jenerik sınıfı, standart bir yanıtla çalışmaya olanak tanıdığı için burada kullanışlıdır.

new operatörüyle [Response] türünü oluşturmak için şu kod yazılır:


Response<List<Product>> response=new Response<List<Product>> (...) ;

Java 1.8 sürümünden itibaren, yukarıdaki komut daha basit bir şekilde yazılabilir:


Response<List<Product>> response=new Response<> (...) ;

Artık = işaretinin sağında jenerik parametrelerin etkin türünü belirtmeye gerek yoktur: = işaretinin solunda belirtilen biçimsel tür kullanılır. Buna tür çıkarım denir: derleyici, bağlama göre jenerik parametrelerin etkin türünü kendisi bulabilir. Bu özellik, bir yöntemin jenerik parametrelerinin etkin türünün sıklıkla atlandığı lambda işlevlerinde yaygın olarak kullanılır.

Şimdi istemci tarafında, [getDataFromWebService] yöntemi şu imzaya sahip olabilir:


<T1,T2> Response<T1>  getDataFromWebService(String urlWebService, String httpMethod, T2 post)

Burada, T1 ve T2 olmak üzere iki türle parametreleştirilmiş genel bir yöntem bulunmaktadır:

  • T1, beklenen yanıt gövdesinin türüdür;
  • T2, POST işlemine gönderilen değerin türüdür; null ise GET işlemine gönderilen değerin türüdür;
  • urlWebService: web hizmetinin URL'sidir;
  • httpMethod: Bu URL'i sorgulamak için kullanılacak HTTP, GET veya POST yöntemidir;

İstemci tarafında şu çağrı yapılabilir:


Long id=... ;
Response<Product> product=this.<Product,Long>getDataFromWebService('http://localhost:8080/rest/product','POST',id) ;

Bu durumda T1=Product ve T2=Long olacaktır.

Java 8 ile tip çıkarımını kullanabiliriz ve daha basit bir şekilde şöyle yazabiliriz:


Long id=... ;
Response<Product> product=getDataFromWebService('http://localhost:8080/rest/product','POST',id) ;

İşte başka bir olası çağrı:


Long[] ids=... ;
Response<List<Product>> product=getDataFromWebService('http://localhost:8080/rest/products','POST',ids) ;

Bu durumda T1=List<Product> ve T2=Long[] olur.

Genel sınıf veya yöntem, genel parametrelerine kısıtlamalar getirebilir. RxJava'in [map] yönteminin tanımına geri dönelim:


public final <R> Observable<R> map(Func1<? super T,? extends R> func)

Yöntem, T ve R olmak üzere iki parametre türü kabul eder. [Func1] türü, genel bir arayüzdür:

 

Func1 ise işlevsel bir arayüz tanımlar; yani, tek bir yönteme sahip bir arayüzdür. İşlevsel arayüzler, Java 8'deki lambda işlevlerinin temelini oluşturur. Burada, arayüzün yöntemi şu şekilde tanımlanmıştır:

R call(T t)

Dolayısıyla T, [call]'e geçirilen parametrenin türü, R ise elde edilen sonucun türüdür. [map] yönteminin tanımına geri dönelim:


public final <R> Observable<R> map(Func1<? super T,? extends R> func)
  • [map] yöntemi, [Func1<? super T,? extends R>] türünde tek bir parametre bekler ve [Observable<R>] türünde bir sonuç döndürür;
  • ? süper T: [Func1.call] yöntemine geçirilen parametre, T türü veya T'nin üst türü olmalıdır;
  • ? extends R: [call] yönteminin döndürdüğü sonucun türü, R bir sınıf ise R türü veya R’den türetilmiş bir tür olmalı; R bir arayüz ise R’yi uygulamalıdır;

Yukarıdaki iki kısıtlamayı daha iyi anlamak için, [https://docs.oracle.com/javase/tutorial/java/generics/bounded.html] referansından alınan örnekleri inceleyelim:


package tests;

public class Box<T> {

    private T t;

    public void set(T t) {
        this.t = t;
    }

    public T get() {
        return t;
    }

    public <U extends Number> void inspect(U u) {
        System.out.println("T: " + t.getClass().getName());
        System.out.println("U: " + u.getClass().getName());
    }

    public static void main(String[] args) {
        Box<Integer> integerBox = new Box<>();
        integerBox.set(new Integer(10));
        integerBox.inspect(new Long(20));
        integerBox.inspect(new Double(-1.78));
        //integerBox.inspect("some text"); // hata: bu hala bir String!
    }
}
  • 3. satır: [Box] sınıfı, 5. satırdaki alanın türü olan T türü ile parametrelenmiştir;
  • 15. satır: [inspect] yöntemi, U türünde bir parametre kabul eder. Bir yöntem de jenerik parametrelere sahip olabilir. Bu durumda, yöntem şu şekilde tanımlanır:
<T1, T2, ..., Tn> TResult méthode(paramètres ...)

Burada U, <U> ile yöntemin jenerik türü olarak tanımlanmıştır, ancak <U extends Number> kısıtlaması vardır; yani U, [Number] sınıfını genişletmelidir. Bu kısıtlama nedeniyle derleyici, 25. satırda bir hata bildirir;

  • 23-24. satırlar: [inspect] yöntemine, [Number]'ten türetilen türlerle yapılan çağrılar;

Aşağıdaki sonuçlar elde edilir:

T: java.lang.Integer
U: java.lang.Long
T: java.lang.Integer
U: java.lang.Double

Not: Sınıfı IntelliJ'de çalıştırmak için şu adımları izleyin: [1, 2]:

 

Şimdi şu örneği ele alalım:


    public void addNumbers(List<? super Integer> list) {
        for (int i = 1; i <= 3; i++) {
            list.add(i);
        }
}

[addNumbers] yöntemi, T'nin [Integer] sınıfının üst sınıfı olduğu bir List<T> türünü parametre olarak kabul eder. Bu kısıtlama nedeniyle, 1 ile 3 arasındaki int tamsayıları listeye eklenebilir (2-4. satırlar) ve yöntem şu şekilde çağrılabilir:


    List<Number> numbers=new ArrayList<>();
    // Double türü Number ekleniyor <-- Double
    numbers.add(7.8);
    // Long Number ekleniyor <-- Long
    numbers.add(1L);
    // List<Number>'a Number eklenmesi <-- Integer
    addNumbers(numbers);
    // List<Number> gösterimi
    for(Number number : numbers){
      System.out.println(number.getClass().getName());
}

Böylece şu sonuç elde edilir:

1
2
3
4
5
java.lang.Double
java.lang.Long
java.lang.Integer
java.lang.Integer
java.lang.Integer

Box<T> sınıfını genişletmek için şunu yazacağız:


class OtherBox<T> extends Box<T> {

}

Alt sınıfın kendisi de yeni jenerik parametreler ekleyebilir:


class AnotherBox<T, T1> extends Box<T> {
    void consumer(T1 t1) {
        System.out.printf("%s%n", t1);
    }
}

Bir arayüz de jenerik parametreler kullanabilir:


interface I<T> {
    void doSomethingWith(T t);
}

Bunu uygulamak için aşağıdaki sözdizimi kullanılır:


class A<T> implements I<T> {

    @Override
    public void doSomethingWith(T t) {
        System.out.printf("%s%n", t);
    }

}

Artık jenerikler hakkında lambda fonksiyonlarını ele alabilecek kadar bilgi sahibiyiz.