John Squirrels
Niveau 41
San Francisco

Type wissen

Gepubliceerd in de groep Willekeurig
Hoi! We vervolgen onze reeks lessen over generieke geneesmiddelen. We kregen eerder een algemeen idee van wat ze zijn en waarom ze nodig zijn. Vandaag leren we meer over enkele kenmerken van generieke geneesmiddelen en over het werken ermee. Laten we gaan! Typ wissen - 1In de vorige les hebben we het gehad over het verschil tussen generieke typen en onbewerkte typen . Een onbewerkt type is een generieke klasse waarvan het type is verwijderd.
List list = new ArrayList();
Hier is een voorbeeld. Hier geven we niet aan wat voor soort objecten er in onze List. Als we proberen zo'n a te maken Listen er enkele objecten aan toe te voegen, zien we een waarschuwing in IDEA:
"Unchecked call to add(E) as a member of raw type of java.util.List".
Maar we hadden het ook over het feit dat generieke geneesmiddelen alleen in Java 5 verschenen. Tegen de tijd dat deze versie werd uitgebracht, hadden programmeurs al een heleboel code geschreven met onbewerkte typen, dus deze functie van de taal kon niet stoppen met werken, en de mogelijkheid om onbewerkte typen maken in Java is bewaard gebleven. Het probleem bleek echter breder te zijn. Zoals u weet, wordt Java-code geconverteerd naar een speciaal gecompileerd formaat, bytecode genaamd, dat vervolgens wordt uitgevoerd door de virtuele Java-machine. Maar als we tijdens het conversieproces informatie over typeparameters in de bytecode zouden plaatsen, zou het alle eerder geschreven code breken, omdat er vóór Java 5 geen typeparameters waren! Bij het werken met generieke geneesmiddelen is er één heel belangrijk concept dat u moet onthouden. Het wordt typeverwijdering genoemd. Het betekent dat een klasse geen informatie bevat over een typeparameter. Deze informatie is alleen beschikbaar tijdens de compilatie en wordt gewist (wordt ontoegankelijk) vóór runtime. Als u probeert het verkeerde type object in uw , te plaatsen List<String>, genereert de compiler een fout. Dit is precies wat de makers van de taal wilden bereiken toen ze generieke versies creëerden: controles tijdens het compileren. Maar wanneer al uw Java-code verandert in bytecode, bevat deze geen informatie meer over typeparameters. In bytecode List<Cat>is uw kattenlijst niet anders dan List<String>strings. In bytecode zegt niets dat het catseen lijst met Catobjecten is. Dergelijke informatie wordt tijdens het compileren gewist — alleen het feit dat je een List<Object> catslijst hebt, komt terecht in de bytecode van het programma. Laten we eens kijken hoe dit werkt:
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();
   }
}
We hebben onze eigen generieke TestClassklasse gemaakt. Het is vrij eenvoudig: het is eigenlijk een kleine "verzameling" van 2 objecten, die direct worden opgeslagen wanneer het object wordt gemaakt. Het heeft 2 Tvelden. Wanneer de createAndAdd2Values()methode wordt uitgevoerd, moeten de twee doorgegeven objecten ( Object aen Object bworden gecast naar het Ttype en vervolgens toegevoegd aan het TestClassobject. In de main()methode maken we een TestClass<Integer>, dwz het Integertype argument vervangt de Integerparameter type. We geven ook a Doubleen a Stringdoor aan de createAndAdd2Values()methode. Denk je dat ons programma zal werken? We hebben tenslotte gespecificeerd Integerals het type argument, maar a Stringkan absoluut niet worden gecast naar een Integer! Laten we demain()methode en controleer. Console-uitvoer:
22.111
Test String
Dat was onverwachts! Waarom is dit gebeurd? Het is het resultaat van typeverwijdering. Informatie over het Integertype-argument dat werd gebruikt om ons TestClass<Integer> testobject te instantiëren, werd gewist toen de code werd gecompileerd. Het veld wordt TestClass<Object> test. Onze argumenten Doubleen Stringwerden gemakkelijk geconverteerd naar Objectobjecten (ze worden niet geconverteerd naar Integerobjecten zoals we hadden verwacht!) en stilletjes toegevoegd aan TestClass. Hier is nog een eenvoudig maar zeer onthullend voorbeeld van typeverwijdering:
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());

   }
}
Console-uitvoer:
true
true
Het lijkt erop dat we collecties hebben gemaakt met drie verschillende soorten argumenten - String, Integer, en onze eigen Catklasse. Maar tijdens de conversie naar bytecode worden alle drie de lijsten List<Object>, dus wanneer het programma wordt uitgevoerd, vertelt het ons dat we in alle drie de gevallen dezelfde klasse gebruiken.

Typ wissen bij het werken met arrays en generieke gegevens

Er is een heel belangrijk punt dat duidelijk moet worden begrepen bij het werken met arrays en generieke klassen (zoals List). U moet er ook rekening mee houden bij het kiezen van datastructuren voor uw programma. Generieke geneesmiddelen zijn onderhevig aan typeverwijdering. Informatie over typeparameters is niet beschikbaar tijdens runtime. Arrays kennen daarentegen informatie over hun gegevenstype en kunnen deze gebruiken wanneer het programma wordt uitgevoerd. Als u probeert een ongeldig type in een array te plaatsen, wordt er een uitzondering gegenereerd:
public class Main2 {

   public static void main(String[] args) {

       Object x[] = new String[3];
       x[0] = new Integer(222);
   }
}
Console-uitvoer:
Exception in thread "main" java.lang.ArrayStoreException: java.lang.Integer
Omdat er zo'n groot verschil is tussen arrays en generieke arrays, kunnen ze compatibiliteitsproblemen hebben. Bovendien kunt u geen reeks generieke objecten of zelfs maar een geparametriseerde reeks maken. Klinkt dat een beetje verwarrend? Laten we kijken. U kunt dit bijvoorbeeld niet doen in Java:
new List<T>[]
new List<String>[]
new T[]
Als we proberen een array van objecten te maken List<String>, krijgen we een compilatiefout die klaagt over het maken van een generieke array:
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];
   }
}
Maar waarom wordt dit gedaan? Waarom is het maken van dergelijke arrays niet toegestaan? Dit alles om typeveiligheid te bieden. Als de compiler ons zulke arrays van generieke objecten zou laten maken, zouden we onszelf een hoop problemen kunnen bezorgen. Hier is een eenvoudig voorbeeld uit het boek "Effective Java" van Joshua Bloch:
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)
}
Laten we ons voorstellen dat het maken van een array zoals List<String>[] stringListsis toegestaan ​​en geen compilatiefout zal genereren. Als dit waar zou zijn, zijn hier enkele dingen die we zouden kunnen doen: In regel 1 maken we een reeks lijsten: List<String>[] stringLists. Onze array bevat een List<String>. In regel 2 maken we een lijst met nummers: List<Integer>. In regel 3 kennen we our toe List<String>[]aan een Object[] objectsvariabele. De Java-taal staat dit toe: een array van objecten kan objecten en objecten van alle subklassen Xopslaan . Dienovereenkomstig kunt u alles in een array plaatsen. In regel 4 vervangen we het enige element van de array (a ) door een . We hebben dus a in een array geplaatst die alleen bedoeld was om op te slaanXXObjectobjects()List<String>List<Integer>List<Integer>List<String>voorwerpen! We zullen alleen een fout tegenkomen wanneer we regel 5 uitvoeren. Er ClassCastExceptionwordt tijdens runtime een gegenereerd. Dienovereenkomstig werd een verbod op het maken van dergelijke arrays aan Java toegevoegd. Zo kunnen we dergelijke situaties voorkomen.

Hoe kan ik typeverwijdering omzeilen?

Nou, we hebben geleerd over type wissen. Laten we proberen het systeem te misleiden! :) Taak: We hebben een generieke TestClass<T>klasse. We willen een methode voor deze klasse schrijven createNewT()die een nieuw Tobject zal maken en retourneren. Maar dit is toch onmogelijk? Alle informatie over het Ttype wordt tijdens de compilatie gewist en tijdens runtime kunnen we niet bepalen welk type object we moeten maken. Er is eigenlijk een lastige manier om dit te doen. U herinnert zich waarschijnlijk dat Java een Classklasse heeft. We kunnen het gebruiken om de klasse van elk van onze objecten te bepalen:
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);
   }
}
Console-uitvoer:
class java.lang.Integer
class java.lang.String
Maar hier is een aspect waar we het nog niet over hebben gehad. In de Oracle-documentatie ziet u dat de Class-klasse generiek is! Type wissen - 3

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

De documentatie zegt: "T - het type klasse gemodelleerd door dit Class-object." Als we dit vertalen van de taal van documentatie naar gewone spraak, begrijpen we dat de klasse van het Integer.classobject niet alleen Class, maar eerder is Class<Integer>. Het type van het String.classobject is niet alleen Class, maar eerder Class<String>, enz. Als het nog steeds niet duidelijk is, probeer dan een parameter type toe te voegen aan het vorige voorbeeld:
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;
   }
}
En nu, met behulp van deze kennis, kunnen we het wissen van typen omzeilen en onze taak volbrengen! Laten we proberen informatie te krijgen over een typeparameter. Ons typeargument zal zijn MySecretClass:
public class MySecretClass {

   public MySecretClass() {

       System.out.println("A MySecretClass object was created successfully!");
   }
}
En hier is hoe we onze oplossing in de praktijk gebruiken:
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();

   }
}
Console-uitvoer:
A MySecretClass object was created successfully!
We hebben zojuist het vereiste klassenargument doorgegeven aan de constructor van onze generieke klasse:
TestClass<MySecretClass> testString = new TestClass<>(MySecretClass.class);
Hierdoor konden we de informatie over het type-argument opslaan, waardoor werd voorkomen dat het volledig werd gewist. Hierdoor hebben we eenTvoorwerp! :) Daarmee komt de les van vandaag ten einde. U moet altijd het wissen van typen onthouden wanneer u met generieke geneesmiddelen werkt. Deze tijdelijke oplossing ziet er niet erg handig uit, maar u moet begrijpen dat generieke talen geen deel uitmaakten van de Java-taal toen deze werd gemaakt. Deze functie, die ons helpt bij het maken van geparametriseerde collecties en het opvangen van fouten tijdens het compileren, is later toegevoegd. In sommige andere talen die generieke talen uit de eerste versie bevatten, is er geen typeverwijdering (bijvoorbeeld in C#). Trouwens, we zijn nog niet klaar met het bestuderen van generieke geneesmiddelen! In de volgende les maak je kennis met nog enkele kenmerken van generieke geneesmiddelen. Voor nu zou het goed zijn om een ​​paar taken op te lossen! :)
Opmerkingen
  • Populair
  • Nieuw
  • Oud
Je moet ingelogd zijn om opmerkingen te kunnen maken
Deze pagina heeft nog geen opmerkingen