CodeGym /Java blog /Véletlen /Írja be a törlést
John Squirrels
Szint
San Francisco

Írja be a törlést

Megjelent a csoportban
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ébenTípustörlés – 1 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.

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 catslistá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> catsa 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 ba típusba kell önteni T, majd hozzá kell adni az TestClassobjektumhoz. A metódusban main()létrehozunk egy TestClass<Integer>, azaz a Integertype argumentum helyettesíti a type paramétert. A-t és a- t Integeris á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 aDoubleStringcreateAndAdd2Values()IntegerStringIntegermain()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. IntegerAz objektumunk példányosításához használt type argumentum információi TestClass<Integer> testtörlésre kerültek a kód lefordításakor. A mező válik TestClass<Object> test. A mi Doubleés Stringargumentumaink könnyen objektumokká konvertáltak Object(nem úgy alakulnak objektummá, Integerahogy 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 Catosztá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ául List) 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>[] stringListsmegengedett, é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[] objectsváltozóhoz rendeljük a miünket. A Java nyelv ezt lehetővé teszi: egy objektumtömb Xtárolhat Xobjektumokat é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 ClassCastExceptionprogram 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ános TestClass<T>osztályunk. createNewT()Ehhez az osztályhoz egy metódust akarunk írni , amely egy új Tobjektumot hoz létre és ad vissza. De ez lehetetlen, igaz? TA 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 Classosztá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! Típustörlés – 3

https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html

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 Integer.classobjektum osztálya nem csak Class, hanem Class<Integer>. Az String.classobjektum 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 aTtá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! :)
Hozzászólások
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION