Liigu peamise sisu juurde

Geneerilised liidesed

Sissejuhatus

Sarnaselt klassidele ja record'itele võivad ka liidesed omada tüübiparameetreid. Süntaks on antud juhul eelnevatele sama. Geneeriline liides määratleb lepingu, mis ei sõltu konkreetsest tüübist. Iga klass, mis seda liidest implementeerib, määrab tüübiparameetri ja realiseerib nõutud meetodid.

Geneerilise liidese deklareerimine

Geneerilisi liideseid deklareeritakse klassidega sarnaselt ehk nime järgi nurksulud, millse sisse lähevad tüübiparameetrid:

interface Container<T> {
void add(T item);
T get(int index);
int size();
}

Antud juhul T tähistab tüübiparameetrit. Liidest implementeeriv klass peab täpsustama, millist tüüpi T tähistab. Seda siis kas määrates sellele konkreetse tüübi või deklareerides selle omakorda oma tüübiparameetrina.

Liidese teostamine kindla tüübiga

Teostame eelnevalt loodud liidest. Kui liidest implementeeriv klass teab ette, millist tüüpi elemente konteiner hoiab, saab ta tüübiparameetri määrata konkreetse tüübina. Sellisel juhul ei ole klass ise enam geneeriline.

class StringList implements Container<String> {
private List<String> items = new ArrayList<>();

@Override
public void add(String item) {
items.add(item);
}

@Override
public String get(int index) {
return items.get(index);
}

@Override
public int size() {
return items.size();
}
}

Siin määratakse liidese tüübiparameetriks String, mistõttu StringList hoiab alati sõnesid. Kuna tüüp on juba implementeerimisel fikseeritud, ei ole klassi kasutamisel vaja tüübiparameetrit eraldi määrata:

StringList list = new StringList();
list.add("hello");
String s = list.get(0); // returns String directly

Liidese teostamine geneerilise klassina

Samas implementeeriv klass võib jätta tüübi ka avatuks, kandes tüübiparameetri edasi. Sellisel juhul on klass ise samuti geneeriline:

class SimpleContainer<T> implements Container<T> {
private List<T> items = new ArrayList<>();

@Override
public void add(T item) {
items.add(item);
}

@Override
public T get(int index) {
return items.get(index);
}

@Override
public int size() {
return items.size();
}
}

Siin ei määrata konkreetset tüüpi implementeerimise hetkel, vaid see jäetakse klassi kasutaja otsustada. Tüüp täpsustatakse objekti loomisel:

SimpleContainer<Integer> numbers = new SimpleContainer<>();
numbers.add(1);
numbers.add(2);
Integer n = numbers.get(0); // returns Integer

Mitu tüübiparameetrit

Geneerilisel liidesel võib olla mitu tüübiparameetrit, täpselt nagu geneerilisel klassil. Tüüpiline näide sellest on teisendaja, mis muudab ühe tüübi teiseks:

interface Converter<I, O> {
O convert(I input);
}

Siin tähistab I sisendtüüpi (input) ja O väljundtüüpi (output).

Konkreetne implementatsioon määrab mõlemad tüübid:

class StringToInteger implements Converter<String, Integer> {
@Override
public Integer convert(String input) {
return Integer.parseInt(input);
}
}

Võimalik on ka jätta mõlemad tüübid avatuks ning kanda tüübiparameetrid edasi:

class IdentityConverter<T> implements Converter<T, T> {
@Override
public T convert(T input) {
return input; // returns the input unchanged
}
}

Sellisel juhul määratakse konkreetne tüüp alles klassi kasutamisel.

Praktiline näide - Comparable

Java standardteegis olev Comparable<T> on geneeriline liides, millega oled tõenäoliselt juba kokku puutunud. See määratleb ühe meetodi objektide loomuliku järjestuse loomise jaoks:

interface Comparable<T> {
int compareTo(T other);
}

Kui klass implementeerib Comparable liidest, peab ta kirjeldama oma loomuliku järjestuse ehk kuidas sama tüüpi objekte sorteerida. Näiteks:

class Temperature implements Comparable<Temperature> {
private final double celsius;

public Temperature(double celsius) {
this.celsius = celsius;
}

@Override
public int compareTo(Temperature other) {
return Double.compare(this.celsius, other.celsius);
}
}

Siin on tüübiparameetriks Temperature, mistõttu saab compareTo argumendiks just Temperature tüüpi objekti. See muudab võrdlemise tüübikindlaks ehk seda meetodit ei saa kogemata välja kutsuda vale tüüpi argumendiga.

Kui liides on implementeeritud, saab seda klassi kasutada kõikjal, kus eeldatakse loomulikku järjestust:

List<Temperature> readings = new ArrayList<>();
readings.add(new Temperature(37.0));
readings.add(new Temperature(20.5));
readings.add(new Temperature(100.0));

Collections.sort(readings); // uses compareTo internally
info

Comparable<T> määrab objekti loomuliku järjestuse — selle, milline järjestus on selle tüübi jaoks vaikimisi mõistlik. Kui sama tüüpi objekte on vaja järjestada mitmel erineval viisil, tuleks kasutada liidest Comparator<T>.

Comparable liidese kohta saate rohkem lugeda siit.
Comparator liidese kohta saate rohkem lugeda siit.

Geneeriliste liideste laiendamine

Üks geneeriline liides võib laiendada teist. Laiendav liides võib tüübiparameetri edasi kanda või vajadusel uusi tüübiparameetreid lisada.

interface ReadableContainer<T> {
T get(int index);
int size();
}

interface WriteableContainer<T> extends ReadableContainer<T> {
void add(T item);
void remove(int index);
}

Antud näites kannab WriteableContainer<T> tüübiparameetri T edasi liidesest ReadableContainer<T>. Klass, mis teostab WriteableContainer<T> liidest, peab realiseerima kõik neli meetodit - kaks liidesest ReadableContainer ja kaks liidesest WriteableContainer.