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) {
sum += ((Account) account).getAmount();
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-BeziehungenHier 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;
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;
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 ThemasEin 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);
Die AntwortSollte 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);
Die AntwortSie 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);
<?> 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) {
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;
Wildcard-Erfassung
Versuchen wir nun, eine Methode zu implementieren, die die Elemente einer Liste in umgekehrter Reihenfolge permutiert.
public static void reverse(List<?> list);
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 AntwortIn 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 AntwortJeder 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 {
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;
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;
Betrachten Sie ein anderes Beispiel:
ArrayList<String> strings = new ArrayList<>(); ArrayList arrayList = new ArrayList(); arrayList = strings;
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<>();
ArrayList
–
List<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());
JDK 7 compile-time error. JDK 8 . JEP-101, — . JDK 8 — :
List.cons(42, List.<Integer>nil());
JEP-101 , , :
String s = List.nil().head();
, . , 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
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
,
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.