Szia! Folytatjuk a generikumok leckesorozatát. Korábban általános képet kaptunk arról, hogy mik ezek, és miért van rájuk szükség. Ma többet megtudunk a generikumok egyes jellemzőiről és a velük való együttműködésről. Gyerünk! Az utolsó leckében
az általános típusok és a nyers típusok közötti különbségről beszéltünk . A nyers típus egy általános osztály, amelynek típusát eltávolították.
A dokumentáció ezt írja: "T - az osztály objektum által modellezett osztály típusa." Ha ezt a dokumentáció nyelvéről egyszerű beszédre fordítjuk, megértjük, hogy az

List list = new ArrayList();
Íme egy példa. Itt nem jelezzük, hogy milyen típusú objektumok lesznek elhelyezve a mi List
. Ha megpróbálunk létrehozni egy ilyet, List
és néhány objektumot hozzáadunk hozzá, akkor egy figyelmeztetést fogunk látni az IDEA-ban:
"Unchecked call to add(E) as a member of raw type of java.util.List".
De beszéltünk arról is, hogy a generikus szerek csak a Java 5-ben jelentek meg. Mire ez a verzió megjelent, a programozók már egy csomó kódot írtak nyers típusok segítségével, így a nyelvnek ez a funkciója nem tudott megállni, és A nyers típusok létrehozása Java nyelven megmaradt. A probléma azonban kiterjedtebbnek bizonyult. Mint tudják, a Java kódot egy speciális lefordított formátummá alakítják, amelyet bájtkódnak neveznek, és ezt a Java virtuális gép hajtja végre. De ha a konvertálás során a bájtkódba a típusparaméterekre vonatkozó információkat helyeznénk el, az megtörné az összes korábban írt kódot, mert a Java 5 előtt nem voltak típusparaméterek! Amikor generikus gyógyszerekkel dolgozik, van egy nagyon fontos fogalom, amelyet emlékeznie kell. Ezt típustörlésnek hívják. Ez azt jelenti, hogy egy osztály nem tartalmaz információt egy típusparaméterről. Ez az információ csak a fordítás során érhető el, és a futásidő előtt törlődik (elérhetetlenné válik). Ha nem megfelelő típusú objektumot próbál meg tenni a -ba List<String>
, a fordító hibát fog generálni. Pontosan ezt akarják elérni a nyelv készítői, amikor generikus termékeket készítettek: fordítási idő ellenőrzéseket. De amikor az összes Java-kód bájtkóddá változik, már nem tartalmaz információkat a típusparaméterekről. A bájtkódban a List<Cat>
macskák listája nem különbözik List<String>
a karakterláncoktól. A bájtkódban semmi sem mondja, hogy az objektumok cats
listája Cat
. Az ilyen információk törlődnek a fordítás során – csak az a tény, hogy van listája, List<Object> cats
a program bájtkódjába kerül. Lássuk, hogyan működik ez:
public class TestClass<T> {
private T value1;
private T value2;
public void printValues() {
System.out.println(value1);
System.out.println(value2);
}
public static <T> TestClass<T> createAndAdd2Values(Object o1, Object o2) {
TestClass<T> result = new TestClass<>();
result.value1 = (T) o1;
result.value2 = (T) o2;
return result;
}
public static void main(String[] args) {
Double d = 22.111;
String s = "Test String";
TestClass<Integer> test = createAndAdd2Values(d, s);
test.printValues();
}
}
Létrehoztuk saját általános osztályunkat TestClass
. Ez nagyon egyszerű: valójában egy kis "gyűjtemény" 2 objektumból, amelyek azonnal eltárolódnak az objektum létrehozásakor. 2 mezője van T
. A createAndAdd2Values()
metódus végrehajtásakor a két átadott objektumot ( Object a
és Object b
a típusba kell önteni T
, majd hozzá kell adni az TestClass
objektumhoz. A metódusban main()
létrehozunk egy TestClass<Integer>
, azaz a Integer
type argumentum helyettesíti a type paramétert. A-t és a- t Integer
is átadunk a metódus. Gondolod, hogy a programunk működni fog? Végül is típus argumentumként adtuk meg, de az a-t biztosan nem lehet egy -re leadni ! Futtassuk aDouble
String
createAndAdd2Values()
Integer
String
Integer
main()
módszert és ellenőrzést. Konzol kimenet:
22.111
Test String
Ez váratlan volt! Miért történt ez? Ez a típustörlés eredménye. Integer
Az objektumunk példányosításához használt type argumentum információi TestClass<Integer> test
törlésre kerültek a kód lefordításakor. A mező válik TestClass<Object> test
. A mi Double
és String
argumentumaink könnyen objektumokká konvertáltak Object
(nem úgy alakulnak objektummá, Integer
ahogy vártuk!), és csendesen hozzáadtuk a -hoz TestClass
. Íme egy másik egyszerű, de nagyon leleplező példa a típustörlésre:
import java.util.ArrayList;
import java.util.List;
public class Main {
private class Cat {
}
public static void main(String[] args) {
List<String> strings = new ArrayList<>();
List<Integer> numbers = new ArrayList<>();
List<Cat> cats = new ArrayList<>();
System.out.println(strings.getClass() == numbers.getClass());
System.out.println(numbers.getClass() == cats.getClass());
}
}
Konzol kimenet:
true
true
Úgy tűnik, három különböző típusú argumentummal hoztunk létre gyűjteményeket – String
, Integer
, és a saját Cat
osztályunkkal. Ám a bájtkódra való átalakítás során mindhárom lista - lesz List<Object>
, így a program futása során azt mondja, hogy mindhárom esetben ugyanazt az osztályt használjuk.
Írja be az erasing parancsot, ha tömbökkel és általánosokkal dolgozik
Van egy nagyon fontos pont, amelyet egyértelműen meg kell érteni, amikor tömbökkel és általános osztályokkal (példáulList
) dolgozik. Ezt is figyelembe kell vennie a program adatstruktúráinak kiválasztásakor. A generikus termékek típustörlés hatálya alá tartoznak. A típusparaméterekre vonatkozó információ futás közben nem érhető el. Ezzel szemben a tömbök ismerik és felhasználhatják az adattípusukat, amikor a program fut. Ha egy tömbbe érvénytelen típust próbálnak bevinni, az kivételt eredményez:
public class Main2 {
public static void main(String[] args) {
Object x[] = new String[3];
x[0] = new Integer(222);
}
}
Konzol kimenet:
Exception in thread "main" java.lang.ArrayStoreException: java.lang.Integer
Mivel olyan nagy különbségek vannak a tömbök és az általánosok között, kompatibilitási problémák adódhatnak. Mindenekelőtt nem hozhat létre általános objektumok tömbjét, vagy akár csak egy paraméterezett tömböt sem. Kicsit zavaróan hangzik? Lássuk. Például ezt nem teheti meg Java-ban:
new List<T>[]
new List<String>[]
new T[]
Ha objektumok tömbjét próbáljuk létrehozni List<String>
, fordítási hibát kapunk, amely az általános tömb létrehozására panaszkodik:
import java.util.List;
public class Main2 {
public static void main(String[] args) {
// Compilation error! Generic array creation
List<String>[] stringLists = new List<String>[1];
}
}
De miért történik ez? Miért nem engedélyezett az ilyen tömbök létrehozása? Mindez a típusbiztonságot szolgálja. Ha a fordító megengedi, hogy ilyen általános objektumtömböket hozzunk létre, rengeteg problémát okozhatunk magunknak. Íme egy egyszerű példa Joshua Bloch "Effective Java" című könyvéből:
public static void main(String[] args) {
List<String>[] stringLists = new List<String>[1]; // (1)
List<Integer> intList = Arrays.asList(42, 65, 44); // (2)
Object[] objects = stringLists; // (3)
objects[0] = intList; // (4)
String s = stringLists[0].get(0); // (5)
}
Képzeljük el, hogy egy tömbszerű létrehozása List<String>[] stringLists
megengedett, és nem generál fordítási hibát. Ha ez igaz lenne, itt van néhány dolog, amit tehetünk: Az 1. sorban létrehozunk egy listák tömbjét: List<String>[] stringLists
. Tömbünk tartalmaz egyet List<String>
. A 2. sorban számlistát készítünk: List<Integer>
. A 3. sorban List<String>[]
egy Object[] objects
változóhoz rendeljük a miünket. A Java nyelv ezt lehetővé teszi: egy objektumtömb X
tárolhat X
objektumokat és objektumokat az összes alosztályból X
. Ennek megfelelően bármit elhelyezhetsz egy tömbben Object
. objects()
A 4. sorban a tömb egyetlen elemét (a ) helyettesítjük List<String>
egy -re List<Integer>
. List<Integer>
Így a csak tárolásra szánt tömbbe tettünk egy-tList<String>
tárgyakat! Csak az 5. sor végrehajtásakor fogunk találkozni hibával. A ClassCastException
program futás közben fog dobni. Ennek megfelelően a Java-t betiltották az ilyen tömbök létrehozására. Ezzel elkerülhetjük az ilyen helyzeteket.
Hogyan kerülhetem meg a típustörlést?
Nos, megtanultuk a típustörlést. Próbáljuk meg becsapni a rendszert! :) Feladat: Van egy általánosTestClass<T>
osztályunk. createNewT()
Ehhez az osztályhoz egy metódust akarunk írni , amely egy új T
objektumot hoz létre és ad vissza. De ez lehetetlen, igaz? T
A fordítás során minden információ törlődik a típusról, és futás közben nem tudjuk meghatározni, hogy milyen típusú objektumot kell létrehoznunk. Valójában van ennek egy trükkös módja. Valószínűleg emlékszel arra, hogy a Java-nak van egy Class
osztálya. Használhatjuk bármely objektumunk osztályának meghatározására:
public class Main2 {
public static void main(String[] args) {
Class classInt = Integer.class;
Class classString = String.class;
System.out.println(classInt);
System.out.println(classString);
}
}
Konzol kimenet:
class java.lang.Integer
class java.lang.String
De itt van egy szempont, amiről nem beszéltünk. Az Oracle dokumentációjában látni fogja, hogy a Class osztály általános! 
https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html
Integer.class
objektum osztálya nem csak Class
, hanem Class<Integer>
. Az String.class
objektum típusa nem csak Class
, hanem Class<String>
stb. Ha még mindig nem világos, próbáljon meg egy type paramétert hozzáadni az előző példához:
public class Main2 {
public static void main(String[] args) {
Class<Integer> classInt = Integer.class;
// Compilation error!
Class<String> classInt2 = Integer.class;
Class<String> classString = String.class;
// Compilation error!
Class<Double> classString2 = String.class;
}
}
És most ezt a tudást felhasználva megkerülhetjük a típustörlést és teljesíthetjük a feladatunkat! Próbáljunk meg információt szerezni egy típusparaméterről. Típusargumentumunk a következő lesz MySecretClass
:
public class MySecretClass {
public MySecretClass() {
System.out.println("A MySecretClass object was created successfully!");
}
}
És így használjuk megoldásunkat a gyakorlatban:
public class TestClass<T> {
Class<T> typeParameterClass;
public TestClass(Class<T> typeParameterClass) {
this.typeParameterClass = typeParameterClass;
}
public T createNewT() throws IllegalAccessException, InstantiationException {
T t = typeParameterClass.newInstance();
return t;
}
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
TestClass<MySecretClass> testString = new TestClass<>(MySecretClass.class);
MySecretClass secret = testString.createNewT();
}
}
Konzol kimenet:
A MySecretClass object was created successfully!
Csak átadtuk a szükséges osztályargumentumot az általános osztályunk konstruktorának:
TestClass<MySecretClass> testString = new TestClass<>(MySecretClass.class);
Ez lehetővé tette számunkra, hogy elmentsük a típusargumentumra vonatkozó információkat, és megakadályozzuk, hogy azok teljesen törlődjenek. Ennek eredményeként tudtunk létrehozni aT
tárgy! :) Ezzel a mai lecke véget is ér. Mindig emlékeznie kell a típustörlésre, amikor általános gyógyszerekkel dolgozik. Ez a megoldás nem tűnik túl kényelmesnek, de meg kell értenie, hogy a generikus kifejezések nem voltak a Java nyelv részei annak létrehozásakor. Ezt a funkciót, amely segít nekünk paraméterezett gyűjtemények létrehozásában és a fordítás során előforduló hibák észlelésében, később alkalmaztuk. Néhány más nyelven, amely az első verzió általános kifejezéseit tartalmazza, nincs típustörlés (például C#-ban). Egyébként még nem végeztünk a generikumok tanulmányozásával! A következő leckében a generikumok néhány további jellemzőjével ismerkedhet meg. Egyelőre jó lenne megoldani pár feladatot! :)
GO TO FULL VERSION