JavaRush /Java-Blog /Random-DE /Löschtypen

Löschtypen

Veröffentlicht in der Gruppe Random-DE
Hallo! Wir setzen unsere Vortragsreihe zum Thema Generika fort. Zuvor haben wir allgemein herausgefunden, was es ist und warum es benötigt wird. Heute werden wir über einige Merkmale von Generika sprechen und uns einige Fallstricke bei der Arbeit mit ihnen ansehen. Gehen! Löscharten – 1In der letzten Vorlesung haben wir über den Unterschied zwischen generischen Typen und Rohtypen gesprochen . Falls Sie es vergessen haben: Raw Type ist eine generische Klasse, aus der der 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, eines 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 sprachen auch über die Tatsache, dass Generika nur in der Java 5-Version der Sprache auftauchten. Zum Zeitpunkt der Veröffentlichung hatten Programmierer eine Menge Code mit Rohtypen geschrieben, und damit es nicht aufhörte zu funktionieren, war die Fähigkeit dazu vorhanden Das Erstellen und Arbeiten mit Rohtypen in Java blieb erhalten. Es stellte sich jedoch heraus, dass dieses Problem viel umfassender war. Wie Sie wissen, wird Java-Code in speziellen Bytecode umgewandelt, der dann von der Java Virtual Machine ausgeführt wird. Und wenn wir während des Übersetzungsprozesses Informationen über Parametertypen in den Bytecode einfügen würden, würde dies den gesamten zuvor geschriebenen Code zerstören, da vor Java 5 keine Parametertypen existierten! Bei der Arbeit mit Generika gibt es eine sehr wichtige Funktion, die Sie beachten müssen. Man nennt es Typlöschung. Ihr Kern liegt darin, dass innerhalb der Klasse keine Informationen über ihren Parametertyp gespeichert werden. Diese Informationen sind nur in der Kompilierungsphase verfügbar und werden zur Laufzeit gelöscht (nicht mehr zugänglich). Wenn Sie versuchen, ein Objekt des falschen Typs in Ihre Datei einzufügen List<String>, gibt der Compiler einen Fehler aus. Genau das haben die Entwickler der Sprache durch die Erstellung von Generika erreicht – Überprüfungen in der Kompilierungsphase. Wenn jedoch der gesamte von Ihnen geschriebene Java-Code in Bytecode umgewandelt wird, gibt es keine Informationen über Parametertypen. Innerhalb des Bytecodes List<Cat>unterscheidet sich Ihre Katzenliste nicht von List<String>Zeichenfolgen. Nichts im Bytecode sagt aus, dass catses sich um eine Liste von Objekten handelt Cat. Informationen dazu werden beim Kompilieren gelöscht und nur die Information, dass Sie eine bestimmte Liste in Ihrem Programm haben, gelangt in den Bytecode List<Object> cats. Mal sehen, wie es 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 Klasse erstellt TestClass. Es ist ganz einfach: Im Wesentlichen handelt es sich um eine kleine „Sammlung“ von 2 Objekten, die sofort beim Erstellen des Objekts dort platziert werden. Es hat 2 Objekte als Felder T. Wenn die Methode ausgeführt wird, createAndAdd2Values()sollten die beiden übergebenen Objekte Object ain Object bunseren Typ umgewandelt werden T, wonach sie dem Objekt hinzugefügt werden TestClass. In der Methode , main()die wir schaffen TestClass<Integer>, das heißt in der Qualität, Tdie wir haben werden Integer. Aber gleichzeitig createAndAdd2Values()übergeben wir der Methode eine Zahl Doubleund ein Objekt String. Glauben Sie, dass unser Programm funktionieren wird? Schließlich haben wir den Typ als Parameter angegeben Integer, aber Stringer kann sicherlich nicht in umgewandelt werden Integer! Lassen Sie uns die Methode ausführen main()und überprüfen. Konsolenausgabe: 22.111 Testzeichenfolge Unerwartetes Ergebnis! Warum ist das passiert? Gerade wegen der Typlöschung. Während der Codekompilierung wurden Informationen über den Parametertyp Integerunseres Objekts TestClass<Integer> testgelöscht. Er verwandelte sich in TestClass<Object> test. Unsere Parameter wurden problemlos in umgewandelt Double( und nicht wie erwartet!) und stillschweigend zu hinzugefügt . Hier ist ein weiteres einfaches, aber sehr anschauliches Beispiel für das Löschen von Typen: StringObjectIntegerTestClass
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 Es sieht so aus, als hätten wir Sammlungen mit drei verschiedenen Parametertypen erstellt – String, Integerund der von uns erstellten Klasse Cat. Bei der Konvertierung in Bytecode wurden jedoch 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

Es gibt einen sehr wichtigen Punkt, der bei der Arbeit mit Arrays und Generika (z. B. List) klar verstanden werden muss. Dies ist auch eine Überlegung wert, wenn Sie eine Datenstruktur für Ihr Programm auswählen. Generika unterliegen der Typlöschung. Informationen zum Parametertyp sind während der Programmausführung nicht verfügbar. Im Gegensatz dazu kennen Arrays Informationen über ihren Datentyp und können diese während der Programmausführung verwenden. Der Versuch, einen Wert des falschen Typs in ein Array einzufügen, löst eine Ausnahme aus:
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 zwischen Arrays und Generika ein so großer Unterschied besteht, kann es zu Kompatibilitätsproblemen kommen. Erstens können Sie kein Array generischer Objekte oder auch nur ein typisiertes Array erstellen. Klingt etwas verwirrend? Lass uns genauer hinschauen. In Java ist beispielsweise nichts davon möglich:
new List<T>[]
new List<String>[]
new T[]
Wenn wir versuchen, ein Array von Listen zu erstellen List<String>, erhalten wir einen generischen Kompilierungsfehler bei der Array-Erstellung:
import java.util.List;

public class Main2 {

   public static void main(String[] args) {

       //ошибка компиляции! Generic array creation
       List<String>[] stringLists = new List<String>[1];
   }
}
Aber warum wurde das gemacht? Warum ist die Erstellung solcher Arrays verboten? Dies alles dient der Gewährleistung der Typsicherheit. Wenn der Compiler es uns erlauben würde, solche Arrays aus generischen Objekten zu erstellen, könnten wir in große Schwierigkeiten geraten. 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 die Array-Erstellung List<String>[] stringListszulässig wäre und der Compiler sich nicht beschweren würde. Folgendes könnten wir in diesem Fall tun: In Zeile 1 erstellen wir ein Array von Blättern 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 unser Array List<String>[]einer Variablen zu Object[] objects. Die Java-Sprache ermöglicht Ihnen dies: XSie können sowohl Objekte Xals auch Objekte aller untergeordneten Klassen in ein Array von Objekten einfügen Х. Dementsprechend Objectskönnen Sie alles in das Array einfügen. In Zeile 4 ersetzen wir das einzelne Element des Arrays objects (List<String>)durch eine Liste List<Integer>. Als Ergebnis haben wir es List<Integer>in unser Array aufgenommen, das nur zum Speichern gedacht war List<String>! Wir werden nur dann auf einen Fehler stoßen, wenn der Code Zeile 5 erreicht. Während der Programmausführung wird eine Ausnahme ausgelöst ClassCastException. Daher wurde in der Java-Sprache das Verbot der Erstellung solcher Arrays eingeführt – so 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 zu betrügen! :) Aufgabe: Wir haben eine generische Klasse TestClass<T>. Wir müssen darin eine Methode erstellen createNewT(), die ein neues Objekt vom Typ erstellt und zurückgibt Т. Aber das ist unmöglich, oder? Alle Informationen über den Typ Тwerden während der Kompilierung gelöscht und während das Programm läuft, können wir nicht herausfinden, welchen Objekttyp wir erstellen müssen. Tatsächlich gibt es einen kniffligen Weg. Sie erinnern sich wahrscheinlich, dass es in Java eine Klasse gibt Class. Damit können wir die Klasse jedes unserer Objekte ermitteln:
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 eine Funktion, über die wir nicht gesprochen haben. In der Oracle-Dokumentation sehen Sie, dass Class eine generische Klasse ist! Löscharten – 3In der Dokumentation heißt es: „T ist der Typ der Klasse, die von diesem Klassenobjekt modelliert wird.“ Wenn wir dies aus der Dokumentationssprache in die menschliche Sprache übersetzen, bedeutet dies, dass die Klasse für ein Objekt Integer.classnicht nur Class, sondern ist Class<Integer>. Der Objekttyp string.classist nicht nur Class, 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;
       //ошибка компиляции!
       Class<String> classInt2 = Integer.class;


       Class<String> classString = String.class;
       //ошибка компиляции!
       Class<Double> classString2 = String.class;
   }
}
Und jetzt können wir mit diesem Wissen die Typlöschung umgehen und unser Problem lösen! Versuchen wir, Informationen über den Parametertyp zu erhalten. Seine Rolle wird von der Klasse übernommen MySecretClass:
public class MySecretClass {

   public MySecretClass() {

       System.out.println("Объект секретного класса успешно создан!");
   }
}
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:

Объект секретного класса успешно создан!
Wir haben einfach den erforderlichen Klassenparameter an den Konstruktor unserer generischen Klasse übergeben:
TestClass<MySecretClass> testString = new TestClass<>(MySecretClass.class);
Dadurch haben wir Informationen über den Parametertyp gespeichert und vor dem Löschen geschützt. Als Ergebnis konnten wir ein Objekt erstellen T! :) Damit ist der heutige Vortrag abgeschlossen. Bei der Arbeit mit Generika ist das Löschen von Typen immer zu beachten. Das sieht nicht sehr praktisch aus, aber Sie müssen verstehen, dass Generika nicht Teil der Java-Sprache waren, als sie erstellt wurde. Dies ist eine später hinzugefügte Funktion, die uns hilft, typisierte Sammlungen zu erstellen und Fehler in der Kompilierungsphase zu erkennen. Einige andere Sprachen, in denen es seit Version 1 Generika gibt, verfügen nicht über die Typlöschung (z. B. C#). Allerdings sind wir mit dem Studium von Generika noch nicht fertig! In der nächsten Vorlesung lernen Sie einige weitere Funktionen der Arbeit mit ihnen kennen. In der Zwischenzeit wäre es schön, ein paar Probleme zu lösen! :) :)
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION