CodeGym/Java-Blog/Random-DE/Geben Sie Löschung ein
Autor
Andrey Gorkovenko
Frontend Engineer at NFON AG

Geben Sie Löschung ein

Veröffentlicht in der Gruppe Random-DE
Hallo! Wir setzen unsere Lektionsreihe zum Thema Generika fort. Wir haben uns zuvor einen allgemeinen Überblick darüber verschafft, was sie sind und warum sie benötigt werden. Heute erfahren wir mehr über einige Funktionen von Generika und über die Arbeit mit ihnen. Lass uns gehen! Geben Sie Löschung ein – 1In der letzten Lektion haben wir über den Unterschied zwischen generischen Typen und Rohtypen gesprochen . Ein Rohtyp ist eine generische Klasse, deren Typ entfernt wurde.
List list = new ArrayList();
Hier ist ein Beispiel. Hier geben wir nicht an, welche Art von Objekten in unserem platziert werden List. Wenn wir versuchen, ein solches zu erstellen Listund einige Objekte hinzuzufügen, wird in IDEA eine Warnung angezeigt:
"Unchecked call to add(E) as a member of raw type of java.util.List".
Aber wir haben auch über die Tatsache gesprochen, dass Generika nur in Java 5 auftauchen. Als diese Version veröffentlicht wurde, hatten Programmierer bereits eine Menge Code mit Rohtypen geschrieben, sodass diese Funktion der Sprache nicht aufhören konnte, zu funktionieren, und die Möglichkeit, dies zu tun Das Erstellen von Rohtypen in Java wurde beibehalten. Es stellte sich jedoch heraus, dass das Problem weitreichender war. Wie Sie wissen, wird Java-Code in ein spezielles kompiliertes Format namens Bytecode konvertiert, das dann von der Java Virtual Machine ausgeführt wird. Wenn wir jedoch während des Konvertierungsprozesses Informationen über Typparameter in den Bytecode einfügen, würde dies den gesamten zuvor geschriebenen Code zerstören, da es vor Java 5 keine Typparameter gab! Bei der Arbeit mit Generika gibt es ein sehr wichtiges Konzept, das Sie beachten müssen. Dies wird als Typlöschung bezeichnet. Das bedeutet, dass eine Klasse keine Informationen über einen Typparameter enthält. Diese Informationen sind nur während der Kompilierung verfügbar und werden vor der Laufzeit gelöscht (nicht mehr zugänglich). Wenn Sie versuchen, den falschen Objekttyp in Ihre Datei einzufügen List<String>, generiert der Compiler einen Fehler. Das ist genau das, was die Entwickler der Sprache erreichen wollten, als sie Generika erstellten: Überprüfungen zur Kompilierungszeit. Aber wenn Ihr gesamter Java-Code in Bytecode umgewandelt wird, enthält er keine Informationen mehr über Typparameter. Im Bytecode List<Cat>unterscheidet sich Ihre Katzenliste nicht von List<String>Zeichenfolgen. Im Bytecode steht nichts dafür, dass es sich catsum eine Liste von CatObjekten handelt. Solche Informationen werden während der Kompilierung gelöscht – nur die Tatsache, dass Sie eine List<Object> catsListe haben, landet im Bytecode des Programms. Mal sehen, wie das funktioniert:
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();
   }
}
Wir haben unsere eigene generische TestClassKlasse erstellt. Es ist ganz einfach: Es handelt sich eigentlich um eine kleine „Sammlung“ von 2 Objekten, die sofort beim Erstellen des Objekts gespeichert werden. Es hat 2 TFelder. Wenn die Methode ausgeführt wird, müssen createAndAdd2Values()die beiden übergebenen Objekte ( Object aund in den Typ umgewandelt und dann dem Objekt hinzugefügt werden. In der Methode erstellen wir ein , d. h. das Typargument ersetzt den Typparameter. Wir übergeben auch a und a an die Methode. Glauben Sie, dass unser Programm funktionieren wird? Schließlich haben wir als Typargument angegeben, aber a kann definitiv nicht in ein umgewandelt werden ! Lassen Sie uns das ausführenObject bTTestClassmain()TestClass<Integer>IntegerIntegerDoubleStringcreateAndAdd2Values()IntegerStringIntegermain()Methode und Überprüfung. Konsolenausgabe:
22.111
Test String
Das war unerwartet! Warum ist das passiert? Es ist das Ergebnis der Typlöschung. Informationen über das IntegerTypargument, das zur Instanziierung unseres TestClass<Integer> testObjekts verwendet wurde, wurden beim Kompilieren des Codes gelöscht. Das Feld wird TestClass<Object> test. Unsere Doubleund- StringArgumente konnten problemlos in ObjectObjekte konvertiert werden (sie werden nicht Integerwie erwartet in Objekte konvertiert!) und stillschweigend zu hinzugefügt TestClass. Hier ist ein weiteres einfaches, aber sehr aufschlussreiches Beispiel für das Löschen von Typen:
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());

   }
}
Konsolenausgabe:
true
true
Anscheinend haben wir Sammlungen mit drei verschiedenen Argumenttypen erstellt – String, Integerund unserer eigenen CatKlasse. Aber während der Konvertierung in Bytecode werden alle drei Listen zu List<Object>, sodass uns das Programm bei der Ausführung mitteilt, dass wir in allen drei Fällen dieselbe Klasse verwenden.

Typlöschung beim Arbeiten mit Arrays und Generika

Bei der Arbeit mit Arrays und generischen Klassen (z. B. ) muss ein sehr wichtiger Punkt klar verstanden werden List. Sie sollten dies auch bei der Auswahl der Datenstrukturen für Ihr Programm berücksichtigen. Generika unterliegen der Typlöschung. Informationen zu Typparametern sind zur Laufzeit nicht verfügbar. Im Gegensatz dazu kennen Arrays Informationen über ihren Datentyp und können diese verwenden, wenn das Programm ausgeführt wird. Der Versuch, einen ungültigen Typ in ein Array einzufügen, führt zu einer Ausnahme:
public class Main2 {

   public static void main(String[] args) {

       Object x[] = new String[3];
       x[0] = new Integer(222);
   }
}
Konsolenausgabe:
Exception in thread "main" java.lang.ArrayStoreException: java.lang.Integer
Da es einen so großen Unterschied zwischen Arrays und Generika gibt, kann es zu Kompatibilitätsproblemen kommen. Vor allem können Sie kein Array generischer Objekte oder auch nur ein parametrisiertes Array erstellen. Klingt das etwas verwirrend? Lass uns einen Blick darauf werfen. In Java ist beispielsweise nichts davon möglich:
new List<T>[]
new List<String>[]
new T[]
Wenn wir versuchen, ein Array von List<String>Objekten zu erstellen, erhalten wir einen Kompilierungsfehler, der sich über die generische Array-Erstellung beschwert:
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];
   }
}
Aber warum wird das gemacht? Warum ist die Erstellung solcher Arrays nicht erlaubt? Dies alles dient der Typensicherheit. Wenn der Compiler uns erlauben würde, solche Arrays generischer Objekte zu erstellen, könnten wir uns eine Menge Probleme bereiten. Hier ist ein einfaches Beispiel aus Joshua Blochs Buch „Effective Java“:
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)
}
Stellen wir uns vor, dass das Erstellen eines Arrays wie List<String>[] stringListserlaubt ist und keinen Kompilierungsfehler erzeugt. Wenn das wahr wäre, könnten wir Folgendes tun: In Zeile 1 erstellen wir ein Array von Listen: List<String>[] stringLists. Unser Array enthält eine List<String>. In Zeile 2 erstellen wir eine Liste mit Zahlen: List<Integer>. In Zeile 3 weisen wir our List<String>[]einer Object[] objectsVariablen zu. Die Java-Sprache ermöglicht dies: Ein Array von Objekten kann Objekte und Objekte aller Unterklassen Xspeichern . Dementsprechend kann man in einem Array alles Mögliche unterbringen . In Zeile 4 ersetzen wir das einzige Element des Arrays (a ) durch a . Deshalb haben wir a in ein Array eingefügt, das nur zum Speichern gedacht warXXObjectobjects()List<String>List<Integer>List<Integer>List<String>Objekte! Wir werden nur dann auf einen Fehler stoßen, wenn wir Zeile 5 ausführen. A ClassCastExceptionwird zur Laufzeit geworfen. Dementsprechend wurde in Java ein Verbot der Erstellung solcher Arrays hinzugefügt. Dadurch können wir solche Situationen vermeiden.

Wie kann ich die Typlöschung umgehen?

Nun, wir haben etwas über das Löschen von Schriftarten gelernt. Versuchen wir, das System auszutricksen! :) Aufgabe: Wir haben eine generische TestClass<T>Klasse. Wir möchten für diese Klasse eine Methode schreiben createNewT(), die ein neues Objekt erstellt und zurückgibt T. Aber das ist unmöglich, oder? Alle Informationen über den TTyp werden während der Kompilierung gelöscht und zur Laufzeit können wir nicht bestimmen, welchen Objekttyp wir erstellen müssen. Es gibt tatsächlich eine knifflige Möglichkeit, dies zu tun. Sie erinnern sich wahrscheinlich daran, dass Java eine Klasse hat Class. Wir können es verwenden, um die Klasse jedes unserer Objekte zu bestimmen:
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);
   }
}
Konsolenausgabe:
class java.lang.Integer
class java.lang.String
Aber hier ist ein Aspekt, über den wir noch nicht gesprochen haben. In der Oracle-Dokumentation sehen Sie, dass die Class-Klasse generisch ist! Geben Sie Löschung ein – 3

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

In der Dokumentation heißt es: „T – der Typ der von diesem Klassenobjekt modellierten Klasse.“ Wenn wir dies aus der Sprache der Dokumentation in einfache Sprache übersetzen, verstehen wir, dass die Klasse des Integer.classObjekts nicht einfach Class, sondern vielmehr ist Class<Integer>. Der Typ des String.classObjekts ist nicht nur Class, sondern Class<String>usw. Wenn es immer noch nicht klar ist, versuchen Sie, einen Typparameter zum vorherigen Beispiel hinzuzufügen:
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;
   }
}
Und jetzt können wir mit diesem Wissen die Typlöschung umgehen und unsere Aufgabe erfüllen! Versuchen wir, Informationen über einen Typparameter zu erhalten. Unser Typargument wird sein MySecretClass:
public class MySecretClass {

   public MySecretClass() {

       System.out.println("A MySecretClass object was created successfully!");
   }
}
Und so nutzen wir unsere Lösung in der Praxis:
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();

   }
}
Konsolenausgabe:
A MySecretClass object was created successfully!
Wir haben gerade das erforderliche Klassenargument an den Konstruktor unserer generischen Klasse übergeben:
TestClass<MySecretClass> testString = new TestClass<>(MySecretClass.class);
Dadurch konnten wir die Informationen über das Typargument speichern und verhindern, dass es vollständig gelöscht wird. Dadurch konnten wir eine erstellenTObjekt! :) Damit ist die heutige Lektion zu Ende. Bei der Arbeit mit Generika müssen Sie immer an die Typlöschung denken. Diese Problemumgehung sieht nicht sehr praktisch aus, Sie sollten sich jedoch darüber im Klaren sein, dass Generika bei ihrer Erstellung kein Teil der Java-Sprache waren. Diese Funktion, die uns hilft, parametrisierte Sammlungen zu erstellen und Fehler während der Kompilierung zu erkennen, wurde später ergänzt. In einigen anderen Sprachen, die Generika aus der ersten Version enthielten, gibt es keine Typlöschung (z. B. in C#). Übrigens sind wir mit dem Studium von Generika noch nicht fertig! In der nächsten Lektion lernen Sie einige weitere Funktionen von Generika kennen. Vorerst wäre es gut, ein paar Aufgaben zu lösen! :) :)
Kommentare
  • Beliebt
  • Neu
  • Alt
Du musst angemeldet sein, um einen Kommentar schreiben zu können
Auf dieser Seite gibt es noch keine Kommentare