Szia! A mai leckében folytatjuk a generikumok tanulmányozását. Megtörténhet, hogy ez egy nagy téma, de nem lehet megkerülni – ez a nyelv rendkívül fontos része :) Amikor áttanulmányozza az Oracle generikusokról szóló dokumentációját vagy online oktatóanyagokat olvas, találkozni fog a nem újraírható típusok kifejezésekkel , reifikálható típusok . Az újraértékelhető típus olyan típus, amelyre vonatkozó információk futás közben teljes mértékben rendelkezésre állnak. A Java-ban az ilyen típusok közé tartoznak a primitívek, a nyers típusok és a nem általános típusok. Ezzel szemben a nem újraírható típusok olyan típusok, amelyek információi törlődnek, és futás közben elérhetetlenné válnak. Ezek általánosságok –
A halomszennyezés a következő esetekben fordulhat elő:
List<String>
, List<Integer>
, stb.
Egyébként emlékszel, mi az a varargs?
Ha elfelejtette, ez egy változó hosszúságú argumentum. Olyan helyzetekben hasznosak, amikor nem tudjuk, hány érvet adhatunk át a módszerünknek. Például, ha van egy számológép-osztályunk, amelynek vansum
metódusa. A sum()
módszer fogadhat 2 számot, vagy 3-at, vagy 5-öt, vagy annyit, amennyit csak akar. Nagyon furcsa lenne túlterhelni a sum()
metódust minden lehetséges számú argumentum esetén. Ehelyett ezt tehetjük:
public class SimpleCalculator {
public static int sum(int...numbers) {
int result = 0;
for(int i : numbers) {
result += i;
}
return result;
}
public static void main(String[] args) {
System.out.println(sum(1,2,3,4,5));
System.out.println(sum(2,9));
}
}
Konzol kimenet:
15
11
Ez azt mutatja, hogy van néhány fontos funkció, amikor a varargokat generikusokkal kombináljuk. Nézzük a következő kódot:
import javafx.util.Pair;
import java.util.ArrayList;
import java.util.List;
public class Main {
public static <E> void addAll(List<E> list, E... array) {
for (E element : array) {
list.add(element);
}
}
public static void main(String[] args) {
addAll(new ArrayList<String>(), // This is okay
"Leonardo da Vinci",
"Vasco de Gama"
);
// but here we get a warning
addAll(new ArrayList<Pair<String, String>>(),
new Pair<String, String>("Leonardo", "da Vinci"),
new Pair<String, String>("Vasco", "de Gama")
);
}
}
A addAll()
metódus bemenetként a List<E>
és tetszőleges számú E
objektumot vesz fel, majd hozzáadja ezeket az objektumokat a listához. A metódusban kétszer main()
hívjuk meg a módszerünket. addAll()
Az első esetben két közönséges karakterláncot adunk a List
. Itt minden rendben van. A második esetben két Pair<String, String>
objektumot adunk a List
. De itt váratlanul kapunk egy figyelmeztetést:
Unchecked generics array creation for varargs parameter
Az mit jelent? Miért kapunk figyelmeztetést, és miért említik a array
? Végül is a kódunkban nincs array
! Kezdjük a második esettel. A figyelmeztetés egy tömböt említ, mert a fordító a változó hosszúságú argumentumot (varargs) tömbbé alakítja. Más szavakkal, módszerünk aláírása a addAll()
következő:
public static <E> void addAll(List<E> list, E... array)
Valójában így néz ki:
public static <E> void addAll(List<E> list, E[] array)
Vagyis a metódusban main()
a fordító a kódunkat erre konvertálja:
public static void main(String[] args) {
addAll(new ArrayList<String>(),
new String[] {
"Leonardo da Vinci",
"Vasco de Gama"
}
);
addAll(new ArrayList<Pair<String,String>>(),
new Pair<String,String>[] {
new Pair<String,String>("Leonardo","da Vinci"),
new Pair<String,String>("Vasco","de Gama")
}
);
}
Egy String
tömb jó. De egy Pair<String, String>
tömb nem az. A probléma az, hogy Pair<String, String>
egy nem visszafizethető típus. A fordítás során a típusargumentumok (<String, String>) összes információja törlődik. Nem újraírható típusú tömbök létrehozása nem engedélyezett a Java-ban . Ezt láthatja, ha manuálisan próbál létrehozni egy Pair<String, String> tömböt
public static void main(String[] args) {
// Compilation error Generic array creation
Pair<String, String>[] array = new Pair<String, String>[10];
}
Az ok nyilvánvaló: típusbiztonság. Amint emlékszel, egy tömb létrehozásakor feltétlenül meg kell adni, hogy a tömb mely objektumokat (vagy primitíveket) tárolja.
int array[] = new int[10];
Egyik korábbi leckénkben részletesen megvizsgáltuk a típustörlést. Ebben az esetben a típustörlés hatására elveszítjük azokat az információkat, amelyeket az Pair
objektumok párokat tárolnak <String, String>
. A tömb létrehozása nem lenne biztonságos. Ha varargokat és generikus módszereket használ , ne felejtse el a típustörlést és annak működését. Ha teljesen biztos az általad írt kódban, és tudja, hogy az nem okoz problémát, akkor a megjegyzések segítségével kikapcsolhatja a varargs-szal kapcsolatos figyelmeztetéseket . @SafeVarargs
@SafeVarargs
public static <E> void addAll(List<E> list, E... array) {
for (E element : array) {
list.add(element);
}
}
Ha hozzáadja ezt a megjegyzést a metódusához, akkor a korábban tapasztalt figyelmeztetés nem jelenik meg. Egy másik probléma, amely a varargok generikus gyógyszerekkel történő használatakor előfordulhat, a halomszennyezés. 
import java.util.ArrayList;
import java.util.List;
public class Main {
static List<String> polluteHeap() {
List numbers = new ArrayList<Number>();
numbers.add(1);
List<String> strings = numbers;
strings.add("");
return strings;
}
public static void main(String[] args) {
List<String> stringsWithHeapPollution = polluteHeap();
System.out.println(stringsWithHeapPollution.get(0));
}
}
Konzol kimenet:
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
Egyszerűen fogalmazva, halomszennyezésről beszélünk, amikor egy típusú objektumnak A
a kupacban kell lennie, de a típusú objektumok B
a típusbiztonsággal kapcsolatos hibák miatt kerülnek oda. Példánkban pontosan ez történik. Először létrehoztuk a nyers numbers
változót, és hozzárendeltünk egy általános gyűjteményt ( ArrayList<Number>
). Aztán hozzáadtuk a számot 1
a gyűjteményhez.
List<String> strings = numbers;
Ezen a vonalon a fordító megpróbált figyelmeztetni minket a lehetséges hibákra az " Unchecked assignment... " figyelmeztetéssel, de figyelmen kívül hagytuk. A végén egy általános típusú változót kapunk, List<String>
amely egy általános típusgyűjteményre mutat ArrayList<Number>
. Nyilvánvaló, hogy ez a helyzet bajhoz vezethet! És így is van. Az új változónk segítségével egy karakterláncot adunk a gyűjteményhez. Most halom szennyezés van – hozzáadtunk egy számot, majd egy karakterláncot a parametrizált gyűjteményhez. A fordító figyelmeztetett minket, de mi figyelmen kívül hagytuk a figyelmeztetését. ClassCastException
Ennek eredményeként a csak a program futása közben kapunk . Szóval mi köze ennek a varargokhoz? A varargok generikus gyógyszerekkel való használata könnyen halomszennyezéshez vezethet. Íme egy egyszerű példa:
import java.util.Arrays;
import java.util.List;
public class Main {
static void polluteHeap(List<String>... stringsLists) {
Object[] array = stringsLists;
List<Integer> numbersList = Arrays.asList(66,22,44,12);
array[0] = numbersList;
String str = stringsLists[0].get(0);
}
public static void main(String[] args) {
List<String> cars1 = Arrays.asList("Ford", "Fiat", "Kia");
List<String> cars2 = Arrays.asList("Ferrari", "Bugatti", "Zaporozhets");
polluteHeap(cars1, cars2);
}
}
Mi folyik itt? A típustörlés miatt a változó hosszúságú argumentumunk
List<String>...stringsLists
listák tömbjévé válik, azaz List[]
ismeretlen típusú objektumok tömbjévé (ne felejtsük el, hogy a varargs a fordítás során szabályos tömbbé válik). Emiatt könnyen hozzárendelhetjük a Object[] array
metódus első sorában lévő változóhoz – a listánkban szereplő objektumok típusa törölve lett! És most van egy Object[]
változónk, amihez bármit hozzáadhatunk, hiszen a Java-ban minden objektum örökli Object
! Eleinte csak a karakterláncok listáinak tömbje áll rendelkezésünkre. De a típustörlésnek és a varargok használatának köszönhetően könnyen hozzáadhatunk egy számlistát, amit meg is teszünk. Ennek eredményeként különböző típusú tárgyak keverésével szennyezzük a kupacot. Az eredmény még más lesz, ClassCastException
amikor megpróbálunk kiolvasni egy karakterláncot a tömbből. Konzol kimenet:
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
Ilyen váratlan következményeket okozhat a varargok használata, egy egyszerűnek tűnő mechanizmus :) És ezzel a mai lecke is véget ér. Ne felejts el megoldani néhány feladatot, és ha van időd és energiád, tanulj meg néhány további olvasmányt. Az " effektív Java " nem olvassa be magát! :) A következő alkalomig!
GO TO FULL VERSION