KözéphaladóOlvasási idő: ~14 perc

Wildcardok

Korlátlan wildcard, felső korlát, alsó korlát és PECS elv

Korlátlan, felső korlátos és alsó korlátos wildcardok — valamint a PECS szabály, amely a valódi generikus API-kat használhatóvá teszi.

1. Definíció

Mi ez?

A wildcard egy ismeretlen generikus típust reprezentál.

Ahelyett, hogy azt mondanád, „ez pontosan egy List<String>”, a wildcard lehetővé teszi, hogy azt mondd: „ez valamilyen List, de itt nem tudom a pontos elemtípust”.

A leggyakoribb formák:

  • <?>
  • <? extends T>
  • <? super T>

A wildcardok azért fontosak, mert a Java generics invariáns.

Ez azt jelenti, hogy a List<Integer> nem lesz a List<Number> altípusa, hiába altípusa az Integer a Number-nek.

Miért létezik?

Wildcardok nélkül sok újrahasznosítható API vagy túl szigorú lenne, vagy nem lenne biztonságos.

Például egy metódus, amely csak olvasni akar numerikus listából, ne csak pontosan List<Number>-t fogadjon.

Fogadnia kellene List<Integer>-t, List<Double>-t és más numerikus listákat is.

A wildcard ezt a kapcsolatot fejezi ki biztonságosan.

Hova illeszkedik?

A wildcardok fogalmilag a generics alapok és a type erasure közé esnek nehézségben.

Kiemelten fontosak itt:

  • library API design
  • kollekciós utilityk
  • stream és comparator segédek
  • framework extension pointok
  • interjúkérdések varianciáról és helyettesíthetőségről

Ha a generics alapok arról szólnak, hogyan paraméterezel egy típust, akkor a wildcardok arról, hogyan tudnak együttműködni a különböző paraméterezett típusok.

2. Alapfogalmak

2.1 Először az invariancia

2.1.1 Kulcsszavak és szerződések, amelyeket itt explicit módon ki kell mondani

A wildcardok sokkal tisztábbak, ha a mögöttük lévő szerződési szavakat tudatosan néven nevezzük:

  • invariance — a List<Integer> nem altípusa a List<Number>-nek.
  • unbounded wildcard<?>, vagyis ismeretlen típus, főleg Object-ként olvasható szemantikával.
  • upper-bounded wildcard<? extends T>, amikor az értékeket főleg olvasod a struktúrából.
  • lower-bounded wildcard<? super T>, amikor az értékeket főleg beleírod a struktúrába.
  • producer — olyan paraméter, amelyből főleg olvasol.
  • consumer — olyan paraméter, amelybe főleg írsz.
  • PECS — a Producer Extends, Consumer Super rövid szabálya.
  • wildcard capture — compiler-technika, amely helper metódusban egy ismeretlen wildcardot konzisztens belső típusváltozóként kezelhetővé tesz.

Ezek a szavak a szignatúra szerződésének részei. Egy jó wildcardos API megmutatja a hívónak, hogy a paraméter olvasásra, írásra vagy mindkettőre szolgál-e.

A wildcardok előtt az invarianciát kell érteni.

List<Integer> integers = List.of(1, 2, 3);
// List<Number> numbers = integers; // fordítási hiba

A Java ezt azért tiltja, mert a List<Number> nézeten keresztül történő írás nem lenne biztonságos.

Ha ez megengedett lenne, be lehetne tenni egy Double-t egy eredetileg List<Integer> listába.

A wildcard kontrollált rugalmasságot ad anélkül, hogy feláldozná a biztonságot.

2.2 Korlátlan wildcard ``

A <?> azt jelenti, hogy „valamilyen ismeretlen típus”.

Akkor hasznos, ha:

  • a kódnak csak konténerként kell tekintenie az objektumra
  • csak olyan metódusokat hívsz, amelyek nem függnek a konkrét elemtípustól
  • főleg Object-ként olvasol ki értékeket
public static void printAll(List<?> values) {
    for (Object value : values) {
        System.out.println(value);
    }
}

A List<?> listából Object-ként biztonságosan olvashatsz.

Tetszőleges elemet viszont nem tudsz biztonságosan hozzáadni, mert az elemtípus ismeretlen.

2.3 Felső korlátos wildcard ``

Ez a forma azt jelenti, hogy „valamilyen ismeretlen T altípus”.

Akkor ideális, ha a struktúra értékeket termel a számodra.

public static double sum(List<? extends Number> values) {
    double total = 0;
    for (Number value : values) {
        total += value.doubleValue();
    }
    return total;
}

A metódus elfogadja például:

  • List<Integer>
  • List<Long>
  • List<Double>
  • List<Number>

A lényegi megkötés az írásnál jön elő.

Egy List<? extends Number> listához általában nem adhatsz hozzá értékeket, mert a tényleges altípus nem ismert.

2.4 Alsó korlátos wildcard ``

Ez a forma azt jelenti, hogy „valamilyen ismeretlen T szupertípus”.

Akkor ideális, ha a struktúra fogyasztja a tőled érkező értékeket.

public static void addDefaults(List<? super Integer> target) {
    target.add(0);
    target.add(1);
}

A metódus elfogadja például:

  • List<Integer>
  • List<Number>
  • List<Object>

Integer értékeket biztonságosan hozzáadhatsz.

Visszaolvasáskor viszont a compiler csak Object-et tud garantálni, mert a pontos tárolt típus nem ismert.

2.5 PECS

A PECS jelentése:

  • Producer Extends
  • Consumer Super

Ez egy memóriasegéd, nem formális nyelvi szabály.

Az extends akkor jó, ha:

  • főleg olvasol
  • a forrásstruktúra értékeket termel az algoritmusodnak

A super akkor jó, ha:

  • főleg írsz
  • a célstruktúra a te algoritmusod által előállított T értékeket fogyasztja

Interjún ez talán a legfontosabb wildcard szabály.

2.6 Wildcard capture

Előfordul, hogy a wildcardos publikus API kívülről helyes, de a belső implementációhoz konkrét típusparaméter kell.

Ilyenkor jön jól a saját típusparaméterrel rendelkező helper metódus.

public static void swapFirstTwo(List<?> list) {
    swapHelper(list);
}

private static <T> void swapHelper(List<T> list) {
    T first = list.get(0);
    list.set(0, list.get(1));
    list.set(1, first);
}

Ezt nevezik wildcard capture-nek.

A helper lehetővé teszi, hogy a compiler az ismeretlen wildcardot egy konzisztens, névvel ellátott típusként kezelje.

2.7 Miért nem wildcard a `List`?

A List<Object> egy konkrét paraméterezés.

Nem azt jelenti, hogy „bármilyen lista”.

Azt jelenti, hogy „egy olyan lista, amely ezen a nézeten keresztül bármilyen Object altípust fogadhat”.

Ez más szerződés, mint a List<?>, amely egyetlen ismeretlen elemtípust jelent.

Ez a különbség sok félreértést megszüntet.

3. Gyakorlati használat

Read-only kollekciós API-k

Ha a metódusod csak olvas a kollekcióból, általában jobb a ? extends, mint a pontos típus.

Példák:

  • statisztika numerikus értékekre
  • logolás vagy formázás
  • elemek validálása
  • másolás forráskollekcióból

Ez újrahasznosíthatóbbá teszi az API-t a hívók számára.

Write-focused API-k

Ha a metódusod értékeket ír egy célstruktúrába, általában ? super kell.

Példák:

  • batch insert segédek
  • map-elt eredmények másolása hívó által adott kollekcióba
  • event dispatch regisztráció szélesebb handler bucketbe

Ez tisztán kifejezi a szándékot: a metódus T értékeket termel.

A copy minta

A klasszikus példa egyszerre mutatja mindkét oldalt:

public static <T> void copy(List<? extends T> source, List<? super T> target) {
    for (T item : source) {
        target.add(item);
    }
}

Ez az egy metódus jobban megmutatja a varianciát, mint sok bekezdés.

Azt is jól demonstrálja, miért praktikus a PECS.

Framework és library használat

Wildcardokat gyakran látsz itt:

  • Comparator<? super T>
  • Class<? extends Annotation>
  • Consumer<? super T>
  • Supplier<? extends T>
  • Stream<? extends T> jellegű helper szignatúrák

Egy jó interjúválasz legalább egy valós JDK példát megemlít.

Wildcard vagy típusparaméter?

Wildcardot akkor használj, ha a metódusnak nem kell néven neveznie a pontos típust.

Névvel ellátott típusparamétert akkor, ha a metódusnak több helyen is ugyanazt a típust kell összekötnie.

Például:

  • List<?> elég, ha csak kiírod az értékeket
  • <T> T first(List<T>) már névvel ellátott T-t igényel, mert a visszatérési típus függ az elemtípustól

Ez a tervezési ítélőképesség fontosabb, mint a puszta szintaxis memorizálása.

Gyakorlati ellenőrzőlista

Wildcard választás előtt kérdezd meg:

  1. Csak olvasok, csak írok vagy mindkettő történik?
  2. A hívónak kell rugalmasság egy típuscsaládon belül?
  3. Kell ugyanaz a névvel ellátott típusváltozó több paraméterhez vagy a visszatérési értékhez?
  4. Tisztább lesz ettől az API, vagy inkább eltakarja a szándékot?
  5. Szükség lesz wildcard capture helperre?

4. Kód példák

1. példa — Korlátlan wildcard

public static void debug(List<?> values) {
    for (Object value : values) {
        System.out.println(value);
    }
}

Ez helyes, mert a metódusnak csak Object-ként kell olvasnia.

2. példa — Producer `extends`-szel

public static double average(List<? extends Number> values) {
    if (values.isEmpty()) {
        throw new IllegalArgumentException("values must not be empty");
    }

    double total = 0;
    for (Number value : values) {
        total += value.doubleValue();
    }
    return total / values.size();
}

Itt a lista Number-szerű értékeket termel az algoritmusnak.

3. példa — Consumer `super`-rel

public static void addIds(List<? super Integer> target) {
    target.add(1001);
    target.add(1002);
}

A metódus Integer értékeket állít elő, és a célstruktúra ezeket fogyasztja.

4. példa — Copy PECS-szel

public static <T> void copy(List<? extends T> source, List<? super T> target) {
    for (T item : source) {
        target.add(item);
    }
}

Ez az a szignatúra, amelyet a legtöbb interjúztató szeretne, ha világosan el tudnál magyarázni.

5. példa — Wildcard capture helper

public static void swapFirstTwo(List<?> list) {
    swapHelper(list);
}

private static <T> void swapHelper(List<T> list) {
    T first = list.get(0);
    list.set(0, list.get(1));
    list.set(1, first);
}

Helper nélkül a közvetlen módosítás kényelmetlen, mert a ? ismeretlen.

5. Trade-offok

Szempont Előny Költség / kockázat
Rugalmasság Szélesebb generikus bemeneteket fogad A szignatúra kezdőknek nehezebben olvasható
Biztonság Varianciás helyzetekben is megőrzi a típusbiztonságot A túlhasznált wildcard elfedheti a szándékot
API reuse A utility metódusok sokkal általánosabban használhatók A rossz bound (extends vs super) rontja a használhatóságot
Dokumentáció Megmutatja, hogy az API olvas, ír vagy mindkettő Invariancia és PECS mentális modell kell hozzá
Karbantarthatóság A jó szignatúra később megelőzi az unsafe castokat A rossz szignatúra zavaros compiler üzenetekhez vezet

A fő trade-off itt az érthetőség és a rugalmasság közötti egyensúly.

A wildcard akkor jó, ha valódi szerződést fejez ki.

Akkor rossz, ha csak azért kerül a kódba, mert a szerző „haladó” szignatúrát akar.

6. Gyakori hibák

1. Azt hinni, hogy a `List` a `List` altípusa

Nem az.

Ez a wildcardok létezésének egyik alapoka.

2. `extends` használata, majd írási kísérlet

Az extends producer oldalon általában read-only nézetet jelent.

Az írási kísérlet klasszikus kezdő hiba.

3. `super` használata, majd pontos olvasási típust várni

A List<? super Integer> listából kiolvasva a compiler gyakorlatilag csak Object-et tud biztosan mondani.

Ez sokakat meglep.

4. Minden típusparaméter wildcardra cserélése

Nem minden generikus szignatúrának kell ?.

Ha több argumentumot vagy a visszatérési típust is ugyanaz a típus köti össze, névvel ellátott típusparaméter kell.

5. A `List` összekeverése a `List`-tel

A két szerződés más.

A List<Object> specifikus.

A List<?> ismeretlen.

6. A PECS elfelejtése tervezéskor

Ha nem döntöd el, hogy az adott paraméter producer vagy consumer, könnyen rossz boundot választasz.

7. Mélymerülés

Miért use-site variance van Java-ban?

Néhány nyelv declaration-site variance-t használ, ahol maga a típus deklarációja mondja meg a kovarianciát vagy kontravarianciát.

A Java ehelyett a használati helyen ad varianciát wildcardokkal.

Ez zajosabbá teheti a szignatúrát, de egyszerűbbé teszi a generikus típusdeklarációkat és jobban illeszkedett a visszafelé kompatibilitáshoz.

Tömbök és generics összehasonlítása

A Java tömbök kovariánsak.

Ezért az Integer[] hozzárendelhető Number[]-hez.

De ez csak részben biztonságos, és futásidőben ArrayStoreException lehet belőle.

A generics tanult ebből a kompromisszumból.

Szigorúbb fordítási biztonságot választott.

Senior interjún ez kifejezetten erős összehasonlítás.

Wildcard capture mint implementációs technika

A jó publikus API gyakran wildcardokat használ.

A belső helper metódusok viszont névvel ellátott típusparaméterrel dolgoznak.

Ez a kettősség kívül rugalmas, belül precíz megoldást ad.

A compiler szándékának olvasása

Amikor a Java azt mondja, hogy két generikus típus inkompatibilis, gyakran egy lehetséges mutációs lyuktól véd.

Ahelyett, hogy a compiler ellen harcolnál, inkább kérdezd meg:

  • ki termel értéket?
  • ki fogyaszt értéket?
  • tényleg kell itt egy közös névvel ellátott típusváltozó?

Ez a szemlélet a zavaros generics hibákat tervezési visszajelzéssé alakítja.

Senior interjús nézőpont

Egy senior wildcard válasz nem áll meg a PECS-nél.

Azt is elmagyarázza:

  • miért létezik invariancia
  • miért rontja a rossz bound az API ergonomiáját
  • hogyan oldja meg a wildcard capture az implementációs súrlódást
  • miért jó összehasonlítási pont a tömbök kovarianciája

8. Interjúkérdések

Miért nem a `List` a `List` altípusa?

Mert ha az lenne, a List<Number> nézeten keresztül be lehetne rakni egy Double-t, és ezzel megsérülne az eredeti List<Integer> típushelyessége.

Mikor használsz `? extends T`-t?

Akkor, amikor a struktúra valamilyen T altípust termel a számodra olvasásra.

Mikor használsz `? super T`-t?

Akkor, amikor a struktúra T értékeket fogyaszt, amelyeket a te kódod akar beleírni.

Mit jelent a PECS?

Producer Extends, Consumer Super.

Ez egy gyakorlati memóriaszabály a wildcard boundok kiválasztásához.

Mikor rosszabb a wildcard, mint a névvel ellátott típusparaméter?

Amikor a metódusnak több helyet is ugyanazzal a pontos típussal kell összekötnie, például argumentum és visszatérési típus kapcsolatánál.

9. Szószedet

Fogalom Jelentés
invariancia A List<A> nem altípusa a List<B>-nek akkor sem, ha A altípusa B-nek
wildcard Ismeretlen generikus típushelykitöltő, például ?
felső korlát ? extends T
alsó korlát ? super T
PECS Producer Extends, Consumer Super
wildcard capture Ismeretlen wildcard névvel ellátott típusváltozóvá alakítása helper metódussal
producer Olyan struktúra, amelyből főleg olvasol
consumer Olyan struktúra, amelybe főleg írsz

10. Cheatsheet

  • Invarianciával indulj; a wildcard csak ezután érthető.
  • A <?> ismeretlen típus, jellemzően Object-ként olvasható.
  • A <? extends T> producer oldalra való.
  • A <? super T> consumer oldalra való.
  • A PECS a leggyorsabb interjús heurisztika.
  • A List<Object> nem ugyanaz, mint a List<?>.
  • Ha a return type és az argumentumok össze vannak kötve, inkább névvel ellátott típusparaméter kell.
  • A wildcard capture helper metódusok belső implementációhoz kellenek.
  • A tömbök kovarianciája jó összehasonlítási pont.
  • Mindig azt a boundot válaszd, amelyik a szerződést fejezi ki, nem azt, amelyik “haladóbban” néz ki.

🎮 Játékok

10 kérdés

© 2026 Mester Ádám