Liigu peamise sisu juurde

Tüübiparameetri piirangud

Sissejuhatus

Vaikimisi võib tüübiparameeter, näiteks <T>, tähistada suvalist tüüpi. Enamiku kasutusjuhtude puhul on see piisav ning täiendavaid piiranguid ei ole vaja seada.

Mõnikord on siiski vaja lubatud tüüpe piirata. Näiteks meetod, mille eesmärk on arvude summeerimine, peaks vastu võtma ainult arvutüüpe ning teiste tüüpide korral andma kompileerimisvea.

Selleks kasutatakse piiratud tüübiparameetreid (bounded type parameters), mis võimaldavad kehtestada tüübiparameetrile tingimusi, määrates, et see peab olema kindla klassi alamklass või implementeerima kindlat liidest.

Ülemine piirang

Tüübiparameetritele saab seada piiranguid kahel viisil, määrates ülemise või alumise piirangu.

Alumist piirangut saab seada ainult wildcard'idele (vt järgmine peatükk), mitte tüübiparameetritele otse.

Ülemine piirang (upper bound) määratakse märksõnaga extends, näiteks:

<T extends Number>

See tähendab, et T võib olla kas klass Number ise või mõni selle alamklass, näiteks Integer, Double või Long. Tüüp, mis ei ole Number-i alamklass, lükatakse kompilaatori poolt tagasi (ehk tekib tüübikonflikt ja selle tõttu kompileerimisviga).

Ilma piiranguta saab tüübiparameetril T kutsuda ainult neid meetodeid, mis on määratletud klassis Object (equals, hashCode, toString jne). Ülemise piirangu korral saab kasutada kõiki neid meetodeid, mis on määratletud piiravaks tüübiks olevas klassis.

public class NumericBox<T extends Number> {
private T value;

public NumericBox(T value) {
this.value = value;
}

public double doubleValue() {
return value.doubleValue(); // available because T extends Number
}
}

Antud näites on võimalik tüübiparameetri T puhul kasutada kõiki meetodeid, mis on Number klassis olemas.

Kasutamine:

NumericBox<Integer> intBox    = new NumericBox<>(42);
NumericBox<Double> doubleBox = new NumericBox<>(3.14);

System.out.println(intBox.doubleValue()); // 42.0
System.out.println(doubleBox.doubleValue()); // 3.14

// NumericBox<String> stringBox = new NumericBox<>("hello"); // compile error

Liidestega piiramine

Märksõna extends kasutatakse ka siis, kui piiranguks on liides, mitte implements (mis sest, et tegemist on liidestega). Näiteks:

<T extends Comparable<T>>

See tähendab, et T peab implementeerima Comparable<T> ehk T tüüpi objekt peab suutma võrrelda end teise sama tüüpi objektiga. Sellist piirangut kasutatakse sageli abimeetodites, kus on vaja elemente omavahel järjestada:

public static <T extends Comparable<T>> T max(T a, T b) {
return a.compareTo(b) >= 0 ? a : b;
}

See meetod töötab iga tüübiga, mis implementeerib Comparable-i, näiteks String, Integer, LocalDate või mõni enda loodud klass:

System.out.println(max(3, 7));          // 7
System.out.println(max("apple", "fig")); // fig

Mitu piirangut

Tüübiparameetrile saab seada ka mitu piirangut, eraldades need & märgiga. Sellisel juhul peab tüüp vastama kõikidele tingimustele korraga. Kui piirangute hulgas on klass, peab see olema esimesel kohal, liidesed järgnevad sellele, näiteks:

<T extends Animal & Swimmable & Flyable>

See tähendab, et T peab olema Animal-i alamklass ning samal ajal implementeerima nii Swimmable kui ka Flyable liidest.

Näiteks klass Duck:

interface Swimmable { void swim(); }
interface Flyable { void fly(); }

abstract class Animal {
public abstract String name();
}

class Duck extends Animal implements Swimmable, Flyable {
@Override public String name() { return "duck"; }
@Override public void swim() { System.out.println("Duck swims"); }
@Override public void fly() { System.out.println("Duck flies"); }
}

public static <T extends Animal & Swimmable & Flyable> void demonstrate(T animal) {
System.out.println(animal.name() + " can both swim and fly");
animal.swim();
animal.fly();
}

Mitut piirangut kasutatakse siis, kui meetodi sees on vaja kasutada mitme erineva “lepingu” võimalusi korraga. Samas praktikas on rohkem kui kahe piirangu kasutamine haruldane. Kui piiranguid tuleb lisada järjest juurde, viitab see sageli sellele, et koodi struktuuri saaks (ja tuleks) selgemaks muuta.

Piirangud geneerilistel klassidel

Piiranguid saab määrata ka klassi tasemel tüübiparameetritele:

public class SortedBox<T extends Comparable<T>> {
private T value;

public SortedBox(T value) {
this.value = value;
}

public boolean isGreaterThan(SortedBox<T> other) {
return this.value.compareTo(other.value) > 0;
}
}
SortedBox<Integer> a = new SortedBox<>(5);
SortedBox<Integer> b = new SortedBox<>(3);

System.out.println(a.isGreaterThan(b)); // true

Siinne piirang tagab, et ükskõik millist tüüpi väärtust kastis hoitakse, peab see toetama ka võrdlemist. Tänu sellele saab klass turvaliselt kasutada compareTo-meetodit.

nõuanne

Piiratud tüübiparameeter on vajalik siis, kui kood peab T tüüpi objektidel välja kutsuma konkreetseid meetodeid. Kui klass üksnes salvestab ja tagastab T väärtusi ilma nende meetodeid kasutamata, piisab piiranguta <T>-st.