Kam, sah, verallgemeinerte: eingetaucht in Java Generics

Java Generics ist eine der wichtigsten Änderungen in der Geschichte der Java-Sprache. Mit Java 5 verfügbare Generika haben die Verwendung des Java Collection Framework einfacher, bequemer und sicherer gemacht. Fehler, die mit einer falschen Verwendung von Typen verbunden sind, werden jetzt in der Kompilierungsphase erkannt. Ja, und die Java-Sprache selbst ist noch sicherer geworden. Trotz der offensichtlichen Einfachheit generischer Typen haben viele Entwickler Schwierigkeiten, sie zu verwenden. In diesem Beitrag werde ich über die Funktionen der Arbeit mit Java Generics sprechen, damit Sie weniger von diesen Schwierigkeiten haben. Nützlich, wenn Sie kein generischer Guru sind und dabei helfen, viele Schwierigkeiten beim Eintauchen in das Thema zu vermeiden.



Arbeiten Sie mit Sammlungen


Angenommen, eine Bank muss die Höhe der Einsparungen auf Kundenkonten berechnen. Vor dem Aufkommen von „Generika“ sah die Methode zur Berechnung der Summe folgendermaßen aus:

public long getSum(List accounts) {   long sum = 0;   for (int i = 0, n = accounts.size(); i < n; i++) {       Object account = accounts.get(i);       if (account instanceof Account) {           sum += ((Account) account).getAmount();       }   }   return sum; } 

Wir haben iteriert, die Liste der Konten durchgesehen und überprüft, ob das Element aus dieser Liste wirklich eine Instanz der Account Klasse ist, dh das Konto des Benutzers. Der Typ unseres Objekts der Account Klasse und die getAmount Methode wurden getAmount , wodurch der Betrag in diesem Konto zurückgegeben wurde. Dann fassten sie alles zusammen und gaben den Gesamtbetrag zurück. Es waren zwei Schritte erforderlich:
 if (account instanceof Account) { // (1) 

 sum += ((Account) account).getAmount(); // (2) 

Wenn Sie ( instanceof ) nicht auf Zugehörigkeit zur Account Klasse prüfen, ist in der zweiten Phase eine ClassCastException möglich, ClassCastException ein Programmabsturz. Daher war eine solche Überprüfung obligatorisch.

Mit dem Aufkommen von Generics ist die Notwendigkeit der Typprüfung und des Castings verschwunden:
 public long getSum2(List<Account> accounts) {  long sum = 0;  for (Account account : accounts) {      sum += account.getAmount();  }  return sum; } 

Jetzt Methode
 getSum2(List<Account> accounts) 
akzeptiert als Argumente nur eine Liste von Objekten der Klasse Account . Diese Einschränkung wird in der Methode selbst angegeben, in ihrer Signatur kann der Programmierer einfach keine andere Liste übertragen - nur die Liste der Client-Konten.

Wir müssen den Typ der Elemente aus dieser Liste nicht überprüfen: Dies wird durch die Typbeschreibung des Methodenparameters impliziert
 List<Account> accounts 
(Kann als Account gelesen werden). Und der Compiler gibt einen Fehler aus, wenn etwas schief geht - das heißt, wenn jemand versucht, eine Liste anderer Objekte als der Account Klasse an diese Methode zu übergeben.

In der zweiten Zeile der Prüfung verschwand auch die Notwendigkeit. Falls erforderlich, wird das casting in der Kompilierungsphase durchgeführt.

Substitutionsprinzip


Das Substitutionsprinzip von Barbara Liskov ist eine spezifische Definition eines Subtyps in der objektorientierten Programmierung. Liskovs Idee eines „Subtyps“ definiert das Konzept der Substitution: Wenn S ein Subtyp von T , können Objekte vom Typ T in einem Programm durch Objekte vom Typ S ohne dass die gewünschten Eigenschaften dieses Programms geändert werden.

Typ
Subtyp
Nummer
Ganzzahl
Liste <E>
ArrayList <E>
Sammlung <E>
Liste <E>
Iterable <E>
Sammlung <E>

Beispiele für Typ / Subtyp-Beziehungen

Hier ist ein Beispiel für die Verwendung des Substitutionsprinzips in Java:
 Number n = Integer.valueOf(42); List<Number> aList = new ArrayList<>(); Collection<Number> aCollection = aList; Iterable<Number> iterable = aCollection; 

Integer ist ein Subtyp von Number . Daher kann der Variablen n Typ Number der Wert zugewiesen werden, den die Methode Integer.valueOf(42) zurückgibt.

Kovarianz, Kontravarianz und Invarianz


Zunächst eine kleine Theorie. Kovarianz ist die Beibehaltung der Vererbungshierarchie von Quelltypen in abgeleiteten Typen in derselben Reihenfolge. Wenn die Katze beispielsweise ein Subtyp von Tieren ist , ist die Gruppe von <Katzen> ein Subtyp der Gruppe von <Tieren> . Unter Berücksichtigung des Substitutionsprinzips kann daher folgende Zuordnung vorgenommen werden:

Viele <Tiere> = Viele <Katzen>

Kontravarianz ist die Umkehrung der Hierarchie der Quelltypen in abgeleiteten Typen. Wenn die Katze beispielsweise ein Subtyp der , ist das Set <Tiere> ein Subtyp des Sets der <Katzen> . Unter Berücksichtigung des Substitutionsprinzips kann daher folgende Zuordnung vorgenommen werden:

Viele <Katzen> = Viele <Tiere>

Invarianz - fehlende Vererbung zwischen abgeleiteten Typen. Wenn die Katze ein Subtyp von Tieren ist , ist der Satz von <Katzen> kein Subtyp des Satzes von <Tieren> und der Satz von <Tieren> ist kein Subtyp des Satzes von <Katzen> .

Arrays in Java sind kovariant . Typ S[] ist ein Subtyp von T[] wenn S ein Subtyp von T Zuordnungsbeispiel:
 String[] strings = new String[] {"a", "b", "c"}; Object[] arr = strings; 

Wir haben der Variablen arr einen Link zu einem Array von Zeichenfolgen zugewiesen, dessen Typ « » . Wenn Arrays nicht kovariant wären, könnten wir dies nicht tun. Mit Java können Sie dies tun, das Programm wird kompiliert und fehlerfrei ausgeführt.

 arr[0] = 42; // ArrayStoreException.       

Wenn wir jedoch versuchen, den Inhalt des Arrays über die Variable arr zu ändern und dort die Nummer 42 zu schreiben, erhalten wir in der Programmausführungsphase eine ArrayStoreException , da 42 keine Zeichenfolge, sondern eine Zahl ist. Dies ist der Nachteil der Kovarianz von Java-Arrays: Wir können in der Kompilierungsphase keine Überprüfungen durchführen, und möglicherweise läuft bereits zur Laufzeit etwas kaputt.

"Generika" sind unveränderlich. Hier ist ein Beispiel:
 List<Integer> ints = Arrays.asList(1,2,3); List<Number> nums = ints; // compile-time error.      nums.set(2, 3.14); assert ints.toString().equals("[1, 2, 3.14]"); 

Wenn Sie eine Liste von Ganzzahlen verwenden, handelt es sich weder um einen Subtyp vom Typ Number noch um einen anderen Subtyp. Er ist nur ein Subtyp von sich. Das heißt, List <Integer> ist eine List<Integer> und sonst nichts. Der Compiler stellt sicher, dass die als Liste der Objekte der Integer- Klasse ints Variable ints nur Objekte der Integer Klasse und sonst nichts enthält. In der Kompilierungsphase wird eine Überprüfung durchgeführt, und in unserer Laufzeit fällt nichts.

Platzhalter


Sind Generika immer invariant? Nein. Ich werde Beispiele geben:
 List<Integer> ints = new ArrayList<Integer>(); List<? extends Number> nums = ints; 

Das ist Kovarianz. List<Integer> - Subtyp von List<? extends Number> List<? extends Number>

 List<Number> nums = new ArrayList<Number>(); List<? super Integer> ints = nums; 

Das ist Kontravarianz. List<Number> ist ein Subtyp von List<? super Integer> List<? super Integer> .

Ein Datensatz wie "? extends ..." oder "? super ..." wird als Platzhalter oder Platzhalter mit einer Obergrenze ( extends ) oder einer Untergrenze ( super ) bezeichnet. List<? extends Number> List<? extends Number> kann Objekte enthalten, deren Klasse Number oder von Number erbt. List<? super Number> List<? super Number> kann Objekte enthalten, deren Klasse Number oder deren Number ein Erbe ist (Supertyp von Number ).


erweitert B - Wildcard mit Obergrenze
Super B - Platzhalter mit einer Untergrenze
wobei B - die Grenze darstellt

Eine Aufzeichnung der Form T 2 <= T 1 bedeutet, dass die durch T 2 beschriebene Menge von Typen eine Teilmenge der durch T 1 beschriebenen Menge von Typen ist

d.h.
Nummer <=? erweitert Objekt
? erweitert Nummer <=? erweitert Objekt
und
? Superobjekt <=? Super Nummer


Mehr mathematische Interpretation des Themas

Ein paar Aufgaben zum Testen von Wissen:

1. Warum tritt der Kompilierungsfehler im folgenden Beispiel auf? Welchen Wert kann ich der nums hinzufügen?
 List<Integer> ints = new ArrayList<Integer>(); ints.add(1); ints.add(2); List<? extends Number> nums = ints; nums.add(3.14); // compile-time error 

Die Antwort
Sollte der Container mit Platzhalter deklariert werden ? extends ? extends , können Sie nur die Werte lesen. Der Liste kann nur null hinzugefügt werden. Um der Liste ein Objekt hinzuzufügen, benötigen wir einen anderen Platzhaltertyp - ? super ? super


2. Warum kann ich keinen Artikel aus der folgenden Liste erhalten?
 public static <T> T getFirst(List<? super T> list) {  return list.get(0); // compile-time error } 

Die Antwort
Sie können einen Artikel aus einem Container mit Platzhalter nicht lesen ? super ? super , bis auf ein Objekt der Klasse Object

 public static <T> Object getFirst(List<? super T> list) {  return list.get(0); } 



Das Get and Put-Prinzip oder PECS (Producer Extends Consumer Super)


Die Platzhalterfunktion mit Ober- und Untergrenze bietet zusätzliche Funktionen für die sichere Verwendung von Typen. Sie können nur von einem Variablentyp lesen, nur in einen anderen schreiben (die Ausnahme ist die Möglichkeit, null für Erweiterungen zu schreiben und Object für super lesen). Damit Sie sich leichter merken können, wann welcher Platzhalter verwendet werden soll, gibt es das PECS-Prinzip - Producer Extends Consumer Super.

  • Wenn wir einen Platzhalter mit Extended deklariert haben, ist dies der Produzent . Er "produziert" nur, liefert ein Element aus dem Container und akzeptiert nichts.
  • Wenn wir einen Platzhalter mit Super angekündigt haben, dann ist dies der Verbraucher . Er akzeptiert nur, kann aber nichts liefern.

Verwenden Sie Wildcard und das PECS-Prinzip am Beispiel der Kopiermethode in der Klasse java.util.Collections.

 public static <T> void copy(List<? super T> dest, List<? extends T> src) { … } 

Die Methode kopiert Elemente aus der ursprünglichen src Liste in die dest Liste. src - mit Platzhalter deklariert ? extends ? extends und ist der Produzent, und dest wird mit Platzhalter deklariert ? super ? super und ist ein Verbraucher. Angesichts der Kovarianz und Kontravarianz von Platzhaltern können Sie Elemente aus der ints Liste in die nums Liste nums :
 List<Number> nums = Arrays.<Number>asList(4.1F, 0.2F); List<Integer> ints = Arrays.asList(1,2); Collections.copy(nums, ints); 


Wenn wir versehentlich die Parameter der nums ints und versuchen, von der nums in die ints Liste zu ints , erlaubt uns der Compiler dies nicht:
 Collections.copy(ints, nums); // Compile-time error 


<?> und Raw-Typen


Unten finden Sie einen Platzhalter mit einem unbegrenzten Platzhalter. Wir setzen einfach <?> , Ohne die super oder extends Keywords:
 static void printCollection(Collection<?> c) {  // a wildcard collection  for (Object o : c) {      System.out.println(o);  } } 


Tatsächlich ist ein solcher "unbegrenzter" Platzhalter von oben immer noch begrenzt. Collection<?> Ist auch ein Platzhalter wie " ? extends Object ". Ein Datensatz des Formulars Collection<?> Entspricht Collection<? extends Object> Collection<? extends Object> , was bedeutet, dass die Auflistung Objekte jeder Klasse enthalten kann, da alle Klassen in Java von Object erben - die Substitution wird daher als unbegrenzt bezeichnet.

Wenn wir zum Beispiel die Typangabe weglassen, wie hier:
 ArrayList arrayList = new ArrayList(); 

dann sagen sie, dass ArrayList der Raw Typ der parametrisierten ArrayList <T> ist . Mit Raw-Typen kehren wir in die Ära der Generika zurück und geben bewusst alle Merkmale auf, die parametrisierten Typen inhärent sind.

Wenn wir versuchen, eine parametrisierte Methode für den Raw-Typ aufzurufen, gibt der Compiler die Warnung "Unchecked call" aus. Wenn wir versuchen, einem Typ einen Verweis auf einen parametrisierten Raw-Typ zuzuweisen, gibt der Compiler die Warnung "Nicht aktivierte Zuordnung" aus. Das Ignorieren dieser Warnungen kann, wie wir später sehen werden, zu Fehlern bei der Ausführung unserer Anwendung führen.
 ArrayList<String> strings = new ArrayList<>(); ArrayList arrayList = new ArrayList(); arrayList = strings; // Ok strings = arrayList; // Unchecked assignment arrayList.add(1); //unchecked call 


Wildcard-Erfassung


Versuchen wir nun, eine Methode zu implementieren, die die Elemente einer Liste in umgekehrter Reihenfolge permutiert.

 public static void reverse(List<?> list); // ! public static void reverse(List<?> list) { List<Object> tmp = new ArrayList<Object>(list); for (int i = 0; i < list.size(); i++) {   list.set(i, tmp.get(list.size()-i-1)); // compile-time error } } 

Ein Kompilierungsfehler ist aufgetreten, weil die reverse Methode eine Liste mit einem unbegrenzten Platzhalterzeichen <?> Als Argument verwendet.
<?> bedeutet dasselbe wie <? extends Object> <? extends Object> . Nach dem PECS-Prinzip ist die list daher producer . Und der producer produziert nur Elemente. Und wir rufen in der for Schleife die set() -Methode auf, d.h. versuchen, in die list zu schreiben. Wir lehnen uns also gegen den Java-Schutz ab, der es uns nicht erlaubt, einen Wert nach Index festzulegen.

Was zu tun ist? Das Wildcard Capture Muster hilft uns dabei. Hier erstellen wir eine generische rev Methode. Es wird mit einer Variablen vom Typ T deklariert T Diese Methode akzeptiert eine Liste von T Typen, und wir können eine Menge erstellen.
 public static void reverse(List<?> list) { rev(list); } private static <T> void rev(List<T> list) { List<T> tmp = new ArrayList<T>(list); for (int i = 0; i < list.size(); i++) {   list.set(i, tmp.get(list.size()-i-1)); } } 

Jetzt wird alles mit uns kompiliert. Hier wurde die Wildcard-Erfassung erfasst. Wenn die reverse(List<?> list) Methode aufgerufen wird reverse(List<?> list) , wird eine Liste einiger Objekte (z. B. Zeichenfolgen oder Ganzzahlen) als Argument übergeben. Wenn wir den Typ dieser Objekte erfassen und einer Variablen vom Typ X zuweisen können, können wir daraus schließen, dass T X

Weitere Wildcard Capture zu Wildcard Capture hier und hier .

Fazit


Wenn Sie aus dem Container lesen müssen, verwenden Sie einen Platzhalter mit dem oberen Rand " ? extends ". Wenn Sie in den Container schreiben müssen, verwenden Sie einen Platzhalter mit dem unteren Rand von " ? super ". Verwenden Sie keinen Platzhalter, wenn Sie aufnehmen und lesen müssen.

Verwenden Sie keine Raw Typen! Wenn das Typargument nicht definiert ist, verwenden Sie den Platzhalter <?> .

Geben Sie Variablen ein


Wenn wir den Bezeichner in spitzen Klammern notieren, z. B. <T> oder <E> wenn wir eine Klasse oder Methode deklarieren, erstellen wir eine Typvariable . Eine Typvariable ist ein nicht qualifizierter Bezeichner, der als Typ im Hauptteil einer Klasse oder Methode verwendet werden kann. Eine Typvariable kann oben begrenzt werden.
 public static <T extends Comparable<T>> T max(Collection<T> coll) { T candidate = coll.iterator().next(); for (T elt : coll) {   if (candidate.compareTo(elt) < 0) candidate = elt; } return candidate; } 

In diesem Beispiel erweitert der Ausdruck T extends Comparable<T> T (eine Typvariable), die oben durch den Typ Comparable<T> . Im Gegensatz zu Platzhaltern können Typvariablen nur oben begrenzt werden (nur erweitert). Kann nicht super schreiben. Außerdem hängt T in diesem Beispiel von sich selbst ab und wird als recursive bound - eine rekursive Grenze.

Hier ist ein weiteres Beispiel aus der Enum-Klasse:
 public abstract class Enum<E extends Enum<E>>implements Comparable<E>, Serializable 

Hier wird die Enum-Klasse durch den Typ E parametrisiert, der ein Subtyp von Enum<E> .

Mehrere Grenzen


Multiple Bounds - mehrere Einschränkungen. Es wird durch das Zeichen " & " geschrieben, dh wir sagen, dass der Typ, der durch eine Variable vom Typ T , von oben durch die Object Klasse und die Comparable Schnittstelle begrenzt werden sollte.

 <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll) 

Object & Comparable<? super T> Object & Comparable<? super T> bildet den Schnittpunkttyp Multiple Bounds . Die erste Einschränkung - in diesem Fall Object - wird zum erasure , dem Prozess des Überschreibens von Typen. Es wird vom Compiler in der Kompilierungsphase ausgeführt.

Fazit


Eine Typvariable kann nur auf einen oder mehrere Typen beschränkt werden. Bei mehreren Einschränkungen wird beim Maischen der linke Rand (die erste Einschränkung) verwendet (Typ Löschen).

Geben Sie Löschen ein


Typlöschung ist eine Zuordnung von Typen (möglicherweise einschließlich parametrisierter Typen und Typvariablen) zu Typen, die niemals parametrisierte Typen oder Variablentypen sind. Wir schreiben Typ T als |T| .

Die Maischeanzeige ist wie folgt definiert:
  • Das Mischen des parametrisierten Typs G < T1 , ..., Tn > ist | G |
  • Das Mischen eines verschachtelten Typs TC ist | T |. C.
  • Das Mischen des Array-Typs T [] ist | T | []
  • Beim Mischen einer Typvariablen wird der linke Rand gemischt
  • Das Mischen eines anderen Typs ist dieser Typ selbst


Während der Ausführung von Type Erasure (Type Mashing) führt der Compiler die folgenden Aktionen aus:
  • Fügt bei Bedarf Typguss hinzu, um Typensicherheit zu gewährleisten
  • generiert Bridge-Methoden zur Aufrechterhaltung des Polymorphismus


T (Typ)
| T | (Maischetyp)
List <Integer>, List <String>, List <List <String >>
Liste
Liste <Integer> []
Liste []
Liste
Liste
int
int
Ganzzahl
Ganzzahl
<T erweitert Vergleichbares <T>>
Vergleichbar
<T erweitert Object & Comparable <? Super T >>
Objekt
LinkedCollection <E> .Node
LinkedCollection.Node

Diese Tabelle zeigt, was aus den verschiedenen Typen während des Maischvorgangs wird. Typ Löschen.

Im folgenden Screenshot sehen Sie zwei Beispiele für das Programm:


Der Unterschied zwischen beiden besteht darin, dass links ein Fehler bei der Kompilierung auftritt und rechts alles fehlerfrei kompiliert wird. Warum?

Die Antwort
In Java können zwei verschiedene Methoden nicht dieselbe Signatur haben. Beim Type Erasure-Prozess fügt der Compiler die Bridge-Methode public int compareTo(Object o) . Die Klasse enthält jedoch bereits eine Methode mit einer solchen Signatur, dass beim Kompilieren ein Fehler auftritt.

Kompilieren Sie die Name-Klasse, indem Sie die Methode compareTo(Object o) entfernen, und compareTo(Object o) den resultierenden Bytecode mit javap:
 # javap Name.class Compiled from "Name.java" public class ru.sberbank.training.generics.Name implements java.lang.Comparable<ru.sberbank.training.generics.Name> { public ru.sberbank.training.generics.Name(java.lang.String); public java.lang.String toString(); public int compareTo(ru.sberbank.training.generics.Name); public int compareTo(java.lang.Object); } 

Wir sehen, dass die Klasse eine int compareTo(java.lang.Object) Methode int compareTo(java.lang.Object) , obwohl wir sie aus dem Quellcode entfernt haben. Dies ist die Bridge-Methode, die der Compiler hinzugefügt hat.


Reifizierbare Typen


In Java sagen wir, dass ein Typ reifiable wenn seine Informationen zur Laufzeit vollständig reifiable sind. Zu den überprüfbaren Typen gehören:
  • Primitive Typen ( int , long , boolean )
  • Nicht parametrisierte (nicht generische) Typen ( String , Integer )
  • Parametrisierte Typen, deren Parameter als unbegrenzter Platzhalter (unbegrenzte Platzhalterzeichen) dargestellt werden ( Liste <?> , Sammlung <?> )
  • Rohe (nicht geformte) Typen ( List , ArrayList )
  • Arrays, deren Komponenten reifizierbare Typen sind ( int [] , Number [] , List <?> [] , List [ )


Warum sind Informationen zu einigen Typen verfügbar, andere jedoch nicht? Tatsache ist, dass aufgrund des Prozesses des Überschreibens von Typen durch den Compiler Informationen über einige Typen verloren gehen können. Wenn es verloren geht, kann dieser Typ nicht mehr überprüft werden. Das heißt, es ist zur Laufzeit nicht verfügbar. Falls verfügbar - jeweils nachprüfbar.

Die Entscheidung, nicht alle generischen Typen zur Laufzeit verfügbar zu machen, ist eine der wichtigsten und widersprüchlichsten Entwurfsentscheidungen im Java-Typsystem. Dies geschieht zunächst aus Gründen der Kompatibilität mit vorhandenem Code. Ich musste für die Migrationskompatibilität bezahlen - die vollständige Zugänglichkeit eines Systems generischer Typen zur Laufzeit ist nicht möglich.

Welche Arten sind nicht überprüfbar:
  • Typvariable ( T )
  • Parametrisierter Typ mit dem angegebenen Parametertyp ( List <Number> ArrayList <String> , List <List <String>> )
  • Ein parametrisierter Typ mit der angegebenen Ober- oder Untergrenze ( List <? Extends Number>, Comparable <? Super String> ). Aber hier ist eine Reservierung: Liste <? erweitert Objekt> - nicht überprüfbar, aber Liste <?> - überprüfbar


Und noch eine Aufgabe. Warum kann im folgenden Beispiel keine parametrisierte Ausnahme erstellt werden?

 class MyException<T> extends Exception {  T t; } 

Die Antwort
Jeder catch-Ausdruck in try-catch überprüft den Typ der empfangenen Ausnahme während der Programmausführung (was der Instanz von entspricht). Der Typ muss also überprüfbar sein. Daher können Throwable und seine Untertypen nicht parametrisiert werden.

 class MyException<T> extends Exception {// Generic class may not extend 'java.lang.Throwable'  T t; } 



Deaktivierte Warnungen


Beim Kompilieren unserer Anwendung wird möglicherweise die sogenannte Unchecked Warning - eine Warnung, dass der Compiler die Sicherheitsstufe für die Verwendung unserer Typen nicht korrekt bestimmen konnte. Dies ist kein Fehler, sondern eine Warnung, sodass Sie sie überspringen können. Es ist jedoch ratsam, alles zu beheben, um Probleme in Zukunft zu vermeiden.

Haufenverschmutzung


Wie bereits erwähnt, führt das Zuweisen eines Verweises auf einen Raw-Typ zu einer Variablen eines parametrisierten Typs zur Warnung „Nicht aktivierte Zuordnung“. Wenn wir es ignorieren, ist eine Situation namens " Heap Pollution " (Heap Pollution) möglich. Hier ist ein Beispiel:
 static List<String> t() {  List l = new ArrayList<Number>();  l.add(1);  List<String> ls = l; // (1)  ls.add("");  return ls; } 

In Zeile (1) warnt der Compiler vor "Nicht aktivierte Zuweisung".

Wir müssen ein weiteres Beispiel für „Haufenverschmutzung“ geben - wenn wir parametrisierte Objekte verwenden. Das folgende Codefragment zeigt deutlich, dass parametrisierte Typen nicht als Argumente für eine Methode mit Varargs verwendet werden Varargs . In diesem Fall ist der Methodenparameter m List<String>… , d. H. Tatsächlich ein Array von Elementen vom Typ List<String> . Angesichts der Regel, dass Typen während des Maischens stringLists , wird der Typ stringLists zu einem Array von stringLists ( List[] ), d. H. Zuweisung kann erfolgen Object[] array = stringLists; und schreiben Sie dann ein anderes Objekt als die Liste der Zeichenfolgen (1) in ein array , wodurch ClassCastException in Zeichenfolge (2) ClassCastException .

 static void m(List<String>... stringLists) {  Object[] array = stringLists;  List<Integer> tmpList = Arrays.asList(42);  array[0] = tmpList; // (1)  String s = stringLists[0].get(0); // (2) } 


Betrachten Sie ein anderes Beispiel:
 ArrayList<String> strings = new ArrayList<>(); ArrayList arrayList = new ArrayList(); arrayList = strings; // (1) Ok arrayList.add(1); // (2) unchecked call 

Java erlaubt die Zuordnung in Zeile (1). Dies ist aus Gründen der Abwärtskompatibilität erforderlich. Wenn wir jedoch versuchen, die add Methode in Zeile (2) auszuführen, erhalten wir eine Warnung für Unchecked call - der Compiler warnt uns vor einem möglichen Fehler. Tatsächlich versuchen wir, der Liste der Zeichenfolgen eine Ganzzahl hinzuzufügen.

Reflexion


Obwohl parametrisierte Typen während der Kompilierung einem Löschvorgang unterzogen werden, können wir mithilfe von Reflection einige Informationen abrufen.

  • Alle nachprüfbaren sind über den Reflexionsmechanismus verfügbar.
  • Informationen über den Typ der Klassenfelder, Methodenparameter und die von ihnen zurückgegebenen Werte sind über Reflection verfügbar.

Reflection Reifiable , . , , , - , :
 java.lang.reflect.Method.getGenericReturnType() 

Generics java.lang.Class . :
 List<Integer> ints = new ArrayList<Integer>(); Class<? extends List> k = ints.getClass(); assert k == ArrayList.class; 


ints List<Integer> ArrayList< Integer> . ints.getClass() Class<ArrayLis> , List<Integer> List . Class<ArrayList> k Class<? extends List> , ? extends . ArrayList.class Class<ArrayList> .

Fazit


, Reifiable. Reifiable : , , , Raw , reifiable.

Unchecked Warnings « » .

Reflection , Reifiable. Reflection , .

Type Inference


« ». () . :
 List<Integer> list = new ArrayList<Integer>(); 

- Java 7 ArrayList :
 List<Integer> list = new ArrayList<>(); 

ArrayListList<Integer> . type inference .

Java 8 JEP 101.
Type Inference. :
  • (reduction)
  • (incorporation)
  • (resolution)

: , , — .
, . JEP 101 .

, :
 class List<E> {  static <Z> List<Z> nil() { ... };  static <Z> List<Z> cons(Z head, List<Z> tail) { ... };  E head() { ... } } 

List.nil() :
 List<String> ls = List.nil(); 

, List.nil() String — JDK 7, .

, , , :
 List.cons(42, List.nil()); //error: expected List<Integer>, found List<Object> 

JDK 7 compile-time error. JDK 8 . JEP-101, — . JDK 8 — :
 List.cons(42, List.<Integer>nil()); 


JEP-101 , , :
 String s = List.nil().head(); //error: expected String, found Object 

, . , JDK , :
 String s = List.<String>nil().head(); 


JEP 101 StackOverflow . , , 7- , 8- – ? :
 class Test {  static void m(Object o) {      System.out.println("one");  }  static void m(String[] o) {      System.out.println("two");  }  static <T> T g() {      return null;  }  public static void main(String[] args) {      m(g());  } } 


- JDK1.8:
   public static void main(java.lang.String[]);   descriptor: ([Ljava/lang/String;)V   flags: ACC_PUBLIC, ACC_STATIC   Code:     stack=1, locals=1, args_size=1        0: invokestatic  #6   // Method g:()Ljava/lang/Object;        3: checkcast     #7   // class "[Ljava/lang/String;"        6: invokestatic  #8   // Method m:([Ljava/lang/String;)V        9: return     LineNumberTable:       line 15: 0       line 16: 9 


0 g:()Ljava/lang/Object; java.lang.Object . , 3 («») , java.lang.String , 6 m:([Ljava/lang/String;) , «two».

- JDK1.7 – Java 7:
   public static void main(java.lang.String[]);   flags: ACC_PUBLIC, ACC_STATIC   Code:     stack=1, locals=1, args_size=1        0: invokestatic  #6   // Method g:()Ljava/lang/Object;        3: invokestatic  #7   // Method m:(Ljava/lang/Object;)V        6: return            LineNumberTable:       line 15: 0       line 16: 6 


, checkcast , Java 8, m:(Ljava/lang/Object;) , «one». Checkcast – , Java 8.

, Oracle JDK1.7 JDK 1.8 , Java, , .

, Java 8 , Java 7, :

 public static void main(String[] args) { m((Object)g()); } 


Fazit


Java Generics . , :


  • Bloch, Joshua. Effective Java. Third Edition. Addison-Wesley. ISBN-13: 978-0-13-468599-1

, Java Generics.

Source: https://habr.com/ru/post/de416413/


All Articles