Java Language
Merkloos product
Zoeken…
Invoering
Generiek is een faciliteit voor generieke programmering die het type systeem van Java uitbreidt zodat een type of methode op verschillende soorten objecten kan werken, terwijl het tegelijkertijd compileer-type veiligheid biedt. In het bijzonder ondersteunt het Java-collectiesframework generieken om het type objecten op te geven dat in een collectie-instantie is opgeslagen.
Syntaxis
- class ArrayList <E> {} // een generieke klasse met typeparameter E
- class HashMap <K, V> {} // een generieke klasse met twee typeparameters K en V
- <E> void print (E-element) {} // een generieke methode met typeparameter E
- ArrayList <String> namen; // verklaring van een generieke klasse
- ArrayList <?> Objecten; // verklaring van een generieke klasse met een parameter van het onbekende type
- nieuwe ArrayList <String> () // instantiëring van een generieke klasse
- nieuwe ArrayList <> () // instantiatie met type inferentie "diamond" (Java 7 of hoger)
Opmerkingen
Generics worden geïmplementeerd in Java via Type wissen, wat betekent dat tijdens runtime de Type-informatie die is opgegeven in de instantiatie van een generieke klasse niet beschikbaar is. Bijvoorbeeld, de instructie List<String> names = new ArrayList<>();
produceert een lijstobject waarvan het elementtype String
niet kan worden hersteld tijdens runtime. Echter, wanneer deze wordt opgeslagen in een veld van het type List<String>
, of doorgegeven aan een werkwijze / constructor parameter van hetzelfde type of terug van een werkwijze die retourneringstype dient het gehele type-informatie kan worden teruggewonnen op runtime via de Java Reflection API.
Dit betekent ook dat bij het casten naar een generiek type (bijvoorbeeld: (List<String>) list
), de cast een ongecontroleerde cast is . Omdat de parameter <String>
is gewist, kan de JVM niet controleren of een cast van een List<?>
Naar een List<String>
correct is; de JVM ziet alleen een cast voor List
naar List
tijdens runtime.
Een generieke klasse maken
Generics maken klassen, interfaces en methoden mogelijk om andere klassen en interfaces als typeparameters te nemen.
Dit voorbeeld gebruikt de generieke klasse Param
om een enkele type parameter T
, gescheiden door punthaken ( <>
):
public class Param<T> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
Om deze klasse te instantiëren, geeft u een typeargument op in plaats van T
Bijvoorbeeld Integer
:
Param<Integer> integerParam = new Param<Integer>();
Het argument type kan elk referentietype zijn, inclusief arrays en andere generieke typen:
Param<String[]> stringArrayParam;
Param<int[][]> int2dArrayParam;
Param<Param<Object>> objectNestedParam;
In Java SE 7 en hoger kan het type argument worden vervangen door een lege set type argumenten ( <>
) genaamd de diamant :
Param<Integer> integerParam = new Param<>();
In tegenstelling tot andere ID's hebben typeparameters geen naamgevingsbeperkingen. Hun namen zijn echter meestal de eerste letter van hun doel in hoofdletters. (Dit geldt zelfs in de officiële JavaDocs.)
Voorbeelden zijn T
voor "type" , E
voor "element" en K
/ V
voor "sleutel" / "waarde" .
Een generieke klasse uitbreiden
public abstract class AbstractParam<T> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
AbstractParam
is een abstracte klasse die wordt gedeclareerd met de parameter type T
Wanneer deze klasse wordt uitgebreid, kan die parameter type worden vervangen door een typeargument dat in <>
is geschreven, of de parameter type kan ongewijzigd blijven. In de eerste en tweede voorbeelden hieronder vervangen String
en Integer
de parameter type. In het derde voorbeeld blijft de parameter type ongewijzigd. Het vierde voorbeeld gebruikt helemaal geen generieken, dus het lijkt op of de klasse een parameter Object
had. De compiler waarschuwt dat AbstractParam
een onbewerkt type is, maar compileert de klasse ObjectParam
. Het vijfde voorbeeld heeft 2 typeparameters (zie "meervoudige typeparameters" hieronder), waarbij de tweede parameter wordt gekozen als de typeparameter die aan de superklasse wordt doorgegeven.
public class Email extends AbstractParam<String> {
// ...
}
public class Age extends AbstractParam<Integer> {
// ...
}
public class Height<T> extends AbstractParam<T> {
// ...
}
public class ObjectParam extends AbstractParam {
// ...
}
public class MultiParam<T, E> extends AbstractParam<E> {
// ...
}
Het volgende is het gebruik:
Email email = new Email();
email.setValue("[email protected]");
String retrievedEmail = email.getValue();
Age age = new Age();
age.setValue(25);
Integer retrievedAge = age.getValue();
int autounboxedAge = age.getValue();
Height<Integer> heightInInt = new Height<>();
heightInInt.setValue(125);
Height<Float> heightInFloat = new Height<>();
heightInFloat.setValue(120.3f);
MultiParam<String, Double> multiParam = new MultiParam<>();
multiParam.setValue(3.3);
Merk op dat in de klasse Email
de methode T getValue()
werkt alsof deze een handtekening van String getValue()
heeft en dat de methode void setValue(T)
werkt alsof deze void setValue(String)
is verklaard void setValue(String)
.
Het is ook mogelijk om te instantiëren met anonieme binnenklasse met lege accolades ( {}
):
AbstractParam<Double> height = new AbstractParam<Double>(){};
height.setValue(198.6);
Merk op dat het gebruik van de diamant met anonieme binnenklassen niet is toegestaan.
Meerdere typeparameters
Java biedt de mogelijkheid om meer dan één typeparameter in een generieke klasse of interface te gebruiken. Meerdere typeparameters kunnen in een klasse of interface worden gebruikt door een door komma's gescheiden lijst met typen tussen de punthaken te plaatsen. Voorbeeld:
public class MultiGenericParam<T, S> {
private T firstParam;
private S secondParam;
public MultiGenericParam(T firstParam, S secondParam) {
this.firstParam = firstParam;
this.secondParam = secondParam;
}
public T getFirstParam() {
return firstParam;
}
public void setFirstParam(T firstParam) {
this.firstParam = firstParam;
}
public S getSecondParam() {
return secondParam;
}
public void setSecondParam(S secondParam) {
this.secondParam = secondParam;
}
}
Het gebruik kan worden gedaan zoals hieronder:
MultiGenericParam<String, String> aParam = new MultiGenericParam<String, String>("value1", "value2");
MultiGenericParam<Integer, Double> dayOfWeekDegrees = new MultiGenericParam<Integer, Double>(1, 2.6);
Een generieke methode verklaren
Methoden kunnen ook generieke typeparameters hebben.
public class Example {
// The type parameter T is scoped to the method
// and is independent of type parameters of other methods.
public <T> List<T> makeList(T t1, T t2) {
List<T> result = new ArrayList<T>();
result.add(t1);
result.add(t2);
return result;
}
public void usage() {
List<String> listString = makeList("Jeff", "Atwood");
List<Integer> listInteger = makeList(1, 2);
}
}
Merk op dat we geen feitelijk typeargument moeten doorgeven aan een generieke methode. De compiler leidt het typeargument voor ons af op basis van het doeltype (bijvoorbeeld de variabele waaraan we het resultaat toewijzen), of op basis van de typen van de feitelijke argumenten. Het zal in het algemeen het meest specifieke type argument afleiden dat het aanroeptype correct maakt.
Soms, hoewel zelden, kan het nodig zijn om deze type inferentie te negeren met expliciete type argumenten:
void usage() {
consumeObjects(this.<Object>makeList("Jeff", "Atwood").stream());
}
void consumeObjects(Stream<Object> stream) { ... }
Het is in dit voorbeeld nodig omdat de compiler niet "vooruit kan kijken" om te zien dat Object
gewenst is voor T
na het aanroepen van stream()
en anders String
zou afleiden op basis van de makeList
argumenten. Merk op dat de Java-taal geen ondersteuning biedt voor het weglaten van de klasse of het object waarop de methode wordt aangeroepen ( this
in het bovenstaande voorbeeld) wanneer typargumenten expliciet worden opgegeven.
De diamant
Java 7 introduceerde de Diamond 1 om een aantal ketelplaten rond generieke klasse instantiatie te verwijderen. Met Java 7+ kun je schrijven:
List<String> list = new LinkedList<>();
Waar je in vorige versies moest schrijven, dit:
List<String> list = new LinkedList<String>();
Een beperking is voor anonieme klassen , waarbij u nog steeds de parameter type moet opgeven in de instantie:
// This will compile:
Comparator<String> caseInsensitiveComparator = new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return s1.compareToIgnoreCase(s2);
}
};
// But this will not:
Comparator<String> caseInsensitiveComparator = new Comparator<>() {
@Override
public int compare(String s1, String s2) {
return s1.compareToIgnoreCase(s2);
}
};
Hoewel het gebruik van de diamant met Anonymous Inner Classes niet wordt ondersteund in Java 7 en 8, zal het worden opgenomen als een nieuwe functie in Java 9 .
Voetnoot:
1 - Sommige mensen noemen het <>
gebruik de "diamond operator". Dit is incorrect. De diamant gedraagt zich niet als een operator en wordt nergens in de JLS of de (officiële) Java-tutorials beschreven als een operator. Inderdaad, <>
is niet eens een duidelijk Java-token. Het is eerder een <
token gevolgd door een >
token, en het is legaal (hoewel slechte stijl) om witruimte of opmerkingen tussen de twee te hebben. De JLS en de Tutorials verwijzen consequent naar <>
als "de diamant", en dat is daarom de juiste term ervoor.
Meerdere bovengrenzen vereisen ("breidt A & B uit")
U kunt een generiek type vereisen om meerdere bovengrenzen te verlengen.
Voorbeeld: we willen een lijst met nummers sorteren, maar Number
implementeert Comparable
.
public <T extends Number & Comparable<T>> void sortNumbers( List<T> n ) {
Collections.sort( n );
}
In dit voorbeeld moet T
Number
uitbreiden en Comparable<T>
implementeren, wat moet passen bij alle "normale" ingebouwde BigDecimal
zoals Integer
of BigDecimal
maar niet bij de meer exotische zoals Striped64
.
Aangezien meervoudige overerving niet is toegestaan, kunt u maximaal één klasse als een limiet gebruiken en moet deze als eerste worden vermeld. <T extends Comparable<T> & Number>
is bijvoorbeeld niet toegestaan omdat Vergelijkbaar een interface is en geen klasse.
Een begrensde generieke klasse maken
U kunt de geldige typen beperken die in een generieke klasse worden gebruikt door dat type in de klassedefinitie te beperken. Gegeven de volgende eenvoudige typehiërarchie:
public abstract class Animal {
public abstract String getSound();
}
public class Cat extends Animal {
public String getSound() {
return "Meow";
}
}
public class Dog extends Animal {
public String getSound() {
return "Woof";
}
}
Zonder begrensde generieke geneesmiddelen kunnen we geen containerklasse maken die zowel generiek is als weet dat elk element een dier is:
public class AnimalContainer<T> {
private Collection<T> col;
public AnimalContainer() {
col = new ArrayList<T>();
}
public void add(T t) {
col.add(t);
}
public void printAllSounds() {
for (T t : col) {
// Illegal, type T doesn't have makeSound()
// it is used as an java.lang.Object here
System.out.println(t.makeSound());
}
}
}
Met generieke gebonden in klassedefinitie is dit nu mogelijk.
public class BoundedAnimalContainer<T extends Animal> { // Note bound here.
private Collection<T> col;
public BoundedAnimalContainer() {
col = new ArrayList<T>();
}
public void add(T t) {
col.add(t);
}
public void printAllSounds() {
for (T t : col) {
// Now works because T is extending Animal
System.out.println(t.makeSound());
}
}
}
Dit beperkt ook de geldige instantiaties van het generieke type:
// Legal
AnimalContainer<Cat> a = new AnimalContainer<Cat>();
// Legal
AnimalContainer<String> a = new AnimalContainer<String>();
// Legal because Cat extends Animal
BoundedAnimalContainer<Cat> b = new BoundedAnimalContainer<Cat>();
// Illegal because String doesn't extends Animal
BoundedAnimalContainer<String> b = new BoundedAnimalContainer<String>();
Beslissen tussen `T`,`? super T` en `? verlengt T`
De syntaxis voor Java-generieken begrensd jokertekens, die het onbekende type vertegenwoordigen door ?
is:
? extends T
vertegenwoordigt een wildcard met bovengrens. Het onbekende type vertegenwoordigt een type dat een subtype van T moet zijn, of type T zelf.? super T
staat voor een wildcard met ondergrens. Het onbekende type vertegenwoordigt een type dat een supertype van T moet zijn, of type T zelf.
Als vuistregel moet u gebruiken
-
? extends T
als u alleen "lees" toegang nodig heeft ("invoer") -
? super T
als je "write" toegang nodig hebt ("output") -
T
als je beide nodig hebt ("wijzigen")
Het gebruik van extends
of super
is meestal beter omdat het uw code flexibeler maakt (zoals in: het gebruik van subtypen en supertypes toestaan), zoals u hieronder zult zien.
class Shoe {}
class IPhone {}
interface Fruit {}
class Apple implements Fruit {}
class Banana implements Fruit {}
class GrannySmith extends Apple {}
public class FruitHelper {
public void eatAll(Collection<? extends Fruit> fruits) {}
public void addApple(Collection<? super Apple> apples) {}
}
De compiler kan nu bepaald slecht gebruik detecteren:
public class GenericsTest {
public static void main(String[] args){
FruitHelper fruitHelper = new FruitHelper() ;
List<Fruit> fruits = new ArrayList<Fruit>();
fruits.add(new Apple()); // Allowed, as Apple is a Fruit
fruits.add(new Banana()); // Allowed, as Banana is a Fruit
fruitHelper.addApple(fruits); // Allowed, as "Fruit super Apple"
fruitHelper.eatAll(fruits); // Allowed
Collection<Banana> bananas = new ArrayList<>();
bananas.add(new Banana()); // Allowed
//fruitHelper.addApple(bananas); // Compile error: may only contain Bananas!
fruitHelper.eatAll(bananas); // Allowed, as all Bananas are Fruits
Collection<Apple> apples = new ArrayList<>();
fruitHelper.addApple(apples); // Allowed
apples.add(new GrannySmith()); // Allowed, as this is an Apple
fruitHelper.eatAll(apples); // Allowed, as all Apples are Fruits.
Collection<GrannySmith> grannySmithApples = new ArrayList<>();
fruitHelper.addApple(grannySmithApples); //Compile error: Not allowed.
// GrannySmith is not a supertype of Apple
apples.add(new GrannySmith()); //Still allowed, GrannySmith is an Apple
fruitHelper.eatAll(grannySmithApples);//Still allowed, GrannySmith is a Fruit
Collection<Object> objects = new ArrayList<>();
fruitHelper.addApple(objects); // Allowed, as Object super Apple
objects.add(new Shoe()); // Not a fruit
objects.add(new IPhone()); // Not a fruit
//fruitHelper.eatAll(objects); // Compile error: may contain a Shoe, too!
}
De juiste T
kiezen ? super T
of ? extends T
is noodzakelijk om het gebruik met subtypen mogelijk te maken. De compiler kan dan voor typeveiligheid zorgen; u hoeft niet te casten (dit is niet type-veilig en kan programmeerfouten veroorzaken) als u ze correct gebruikt.
Als het niet gemakkelijk te begrijpen is, onthoud dan de PECS- regel:
P roducer gebruik "E xtends" en CONSUMENT gebruik "S uper".
(Producent heeft alleen schrijftoegang en Consument heeft alleen leestoegang)
Voordelen van generieke klasse en interface
Code die generieke geneesmiddelen gebruikt, heeft veel voordelen ten opzichte van niet-generieke code. Hieronder staan de belangrijkste voordelen
Sterkere typecontroles tijdens het compileren
Een Java-compiler past sterke typecontrole toe op generieke code en geeft fouten uit als de code de typeveiligheid schendt. Het oplossen van compilatie-fouten is eenvoudiger dan het repareren van runtime-fouten, die moeilijk te vinden zijn.
Verwijdering van casts
Het volgende codefragment zonder generieken vereist casten:
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0);
Wanneer opnieuw geschreven om generieke geneesmiddelen te gebruiken , hoeft de code niet te worden gecast:
List<String> list = new ArrayList<>();
list.add("hello");
String s = list.get(0); // no cast
Programmeurs in staat stellen generieke algoritmen te implementeren
Door generieke geneesmiddelen te gebruiken, kunnen programmeurs generieke algoritmen implementeren die werken in verzamelingen van verschillende typen, kunnen worden aangepast en zijn type veilig en gemakkelijker te lezen.
Bindende generieke parameter aan meer dan 1 type
Algemene parameters kunnen ook aan meer dan één type worden gebonden met behulp van de syntaxis T extends Type1 & Type2 & ...
Stel dat u een klasse wilt maken waarvan het generieke type zowel Flushable
als Closeable
moet implementeren, u kunt schrijven
class ExampleClass<T extends Flushable & Closeable> {
}
Nu accepteert de ExampleClass
alleen generieke parameters, types die zowel Flushable
als Closeable
.
ExampleClass<BufferedWriter> arg1; // Works because BufferedWriter implements both Flushable and Closeable
ExampleClass<Console> arg4; // Does NOT work because Console only implements Flushable
ExampleClass<ZipFile> arg5; // Does NOT work because ZipFile only implements Closeable
ExampleClass<Flushable> arg2; // Does NOT work because Closeable bound is not satisfied.
ExampleClass<Closeable> arg3; // Does NOT work because Flushable bound is not satisfied.
De klassemethoden kunnen ervoor kiezen om generieke Closeable
af te leiden als Closeable
of Flushable
.
class ExampleClass<T extends Flushable & Closeable> {
/* Assign it to a valid type as you want. */
public void test (T param) {
Flushable arg1 = param; // Works
Closeable arg2 = param; // Works too.
}
/* You can even invoke the methods of any valid type directly. */
public void test2 (T param) {
param.flush(); // Method of Flushable called on T and works fine.
param.close(); // Method of Closeable called on T and works fine too.
}
}
Notitie:
U kunt de generieke parameter niet binden aan een van beide typen met de clausule OR ( |
). Alleen de clausule AND ( &
) wordt ondersteund. Generiek type kan slechts één klasse en veel interfaces uitbreiden. Klasse moet aan het begin van de lijst worden geplaatst.
Een generiek type instantiëren
Vanwege het wissen van het type werkt het volgende niet:
public <T> void genericMethod() {
T t = new T(); // Can not instantiate the type T.
}
Het type T
is gewist. Omdat de JVM tijdens runtime niet weet wat T
oorspronkelijk was, weet hij niet welke constructor hij moet bellen.
Tijdelijke oplossingen
T
klasse vanT
bij het aanroepen vangenericMethod
:public <T> void genericMethod(Class<T> cls) { try { T t = cls.newInstance(); } catch (InstantiationException | IllegalAccessException e) { System.err.println("Could not instantiate: " + cls.getName()); } }
genericMethod(String.class);
Dat levert uitzonderingen op, omdat er geen manier is om te weten of de doorgegeven klasse een toegankelijke standaardconstructor heeft.
Verwijzing naar de constructor van
T
:public <T> void genericMethod(Supplier<T> cons) { T t = cons.get(); }
genericMethod(String::new);
Verwijzend naar het aangegeven generieke type in zijn eigen verklaring
Hoe gebruik je een instantie van een (mogelijk verder) overgeërfd generiek type binnen een methodeverklaring in het generieke type dat wordt gedeclareerd? Dit is een van de problemen waarmee je te maken krijgt als je wat dieper op generieke geneesmiddelen ingaat, maar nog steeds een vrij veel voorkomend probleem.
Stel dat we een DataSeries<T>
-type hebben (interface hier), dat een generieke gegevensreeks met waarden van type T
definieert. Het is omslachtig om direct met dit type te werken wanneer we veel bewerkingen met bijvoorbeeld dubbele waarden willen uitvoeren, dus definiëren we DoubleSeries extends DataSeries<Double>
. Neem nu aan dat het oorspronkelijke type DataSeries<T>
een methode add(values)
die nog een reeks van dezelfde lengte toevoegt en een nieuwe retourneert. Hoe handhaven we het type values
en het type retour DoubleSeries
plaats van DataSeries<Double>
in onze afgeleide klasse?
Het probleem kan worden opgelost door een generieke typeparameter toe te voegen die verwijst naar en het type dat wordt gedeclareerd uitbreidt (hier toegepast op een interface, maar hetzelfde staat voor klassen):
public interface DataSeries<T, DS extends DataSeries<T, DS>> {
DS add(DS values);
List<T> data();
}
Hier vertegenwoordigt T
het gegevenstype dat de serie bevat, bijvoorbeeld Double
en DS
de serie zelf. Een overgeërfd type (of typen) kan nu eenvoudig worden geïmplementeerd door de hierboven genoemde parameter te vervangen door een bijbehorend afgeleid type, waardoor een concrete Double
gebaseerde definitie van de vorm wordt verkregen:
public interface DoubleSeries extends DataSeries<Double, DoubleSeries> {
static DoubleSeries instance(Collection<Double> data) {
return new DoubleSeriesImpl(data);
}
}
Op dit moment implementeert zelfs een IDE de bovenstaande interface met de juiste typen, die er na een beetje vullen van inhoud als volgt uit kunnen zien:
class DoubleSeriesImpl implements DoubleSeries {
private final List<Double> data;
DoubleSeriesImpl(Collection<Double> data) {
this.data = new ArrayList<>(data);
}
@Override
public DoubleSeries add(DoubleSeries values) {
List<Double> incoming = values != null ? values.data() : null;
if (incoming == null || incoming.size() != data.size()) {
throw new IllegalArgumentException("bad series");
}
List<Double> newdata = new ArrayList<>(data.size());
for (int i = 0; i < data.size(); i++) {
newdata.add(this.data.get(i) + incoming.get(i)); // beware autoboxing
}
return DoubleSeries.instance(newdata);
}
@Override
public List<Double> data() {
return Collections.unmodifiableList(data);
}
}
Zoals u kunt zien, wordt de add
methode gedeclareerd als DoubleSeries add(DoubleSeries values)
en is de compiler tevreden.
Het patroon kan indien nodig verder worden genest.
Gebruik van instanceof met Generics
Generics gebruiken om het type bijvoorbeeld te definiëren
Beschouw de volgende generieke klasse Example
gedeclareerd met de formele parameter <T>
:
class Example<T> {
public boolean isTypeAString(String s) {
return s instanceof T; // Compilation error, cannot use T as class type here
}
}
Dit geeft altijd een compilatiefout, omdat zodra de compiler de Java-bron in Java bytecode compileert, hij een proces toepast dat bekend staat als typewissen , waarbij alle generieke code wordt omgezet in niet-generieke code, waardoor het onmogelijk is om onderscheid te maken tussen T-typen tijdens runtime. Het type dat wordt gebruikt met instanceof
moet opnieuw te verifiëren zijn , wat betekent dat alle informatie over het type beschikbaar moet zijn tijdens runtime, en dit is meestal niet het geval voor generieke typen.
De volgende klasse geeft aan hoe twee verschillende klassen van Example
, Example<String>
en Example<Number>
eruit zien nadat generieke geneesmiddelen zijn gestript per type verwijdering :
class Example { // formal parameter is gone
public boolean isTypeAString(String s) {
return s instanceof Object; // Both <String> and <Number> are now Object
}
}
Omdat typen verdwenen zijn, is het voor de JVM niet mogelijk om te weten welk type T
.
Uitzondering op de vorige regel
U kunt altijd een onbegrensd jokerteken (?) Gebruiken om als volgt een type in het instanceof
op te geven:
public boolean isAList(Object obj) {
return obj instanceof List<?>;
}
Dit kan handig zijn om te evalueren of een instantie obj
een List
of niet:
System.out.println(isAList("foo")); // prints false
System.out.println(isAList(new ArrayList<String>()); // prints true
System.out.println(isAList(new ArrayList<Float>()); // prints true
In feite wordt een onbegrensd jokerteken beschouwd als een herbruikbaar type.
Een generieke instantie gebruiken met instanceof
De keerzijde van de medaille is dat het gebruik van een instantie t
van T
met instanceof
legaal is, zoals in het volgende voorbeeld wordt getoond:
class Example<T> {
public boolean isTypeAString(T t) {
return t instanceof String; // No compilation error this time
}
}
omdat na het wissen van het type de klasse er als volgt zal uitzien:
class Example { // formal parameter is gone
public boolean isTypeAString(Object t) {
return t instanceof String; // No compilation error this time
}
}
Omdat, zelfs als het wissen van het type toch gebeurt, de JVM nu onderscheid kan maken tussen verschillende typen in het geheugen, zelfs als ze hetzelfde referentietype ( Object
) gebruiken, zoals het volgende fragment laat zien:
Object obj1 = new String("foo"); // reference type Object, object type String
Object obj2 = new Integer(11); // reference type Object, object type Integer
System.out.println(obj1 instanceof String); // true
System.out.println(obj2 instanceof String); // false, it's an Integer, not a String
Verschillende manieren om een generieke interface te implementeren (of een generieke klasse uit te breiden)
Stel dat de volgende generieke interface is verklaard:
public interface MyGenericInterface<T> {
public void foo(T t);
}
Hieronder staan de mogelijke manieren om het te implementeren.
Niet-generieke klasse-implementatie met een specifiek type
Kies een specifiek type om de formele MyGenericClass
<T>
van MyGenericClass
en implementeer het, zoals in het volgende voorbeeld:
public class NonGenericClass implements MyGenericInterface<String> {
public void foo(String t) { } // type T has been replaced by String
}
Deze klasse behandelt alleen String
, en dit betekent dat het gebruik van MyGenericInterface
met verschillende parameters (bijv. Integer
, Object
etc.) niet zal compileren, zoals het volgende fragment laat zien:
NonGenericClass myClass = new NonGenericClass();
myClass.foo("foo_string"); // OK, legal
myClass.foo(11); // NOT OK, does not compile
myClass.foo(new Object()); // NOT OK, does not compile
Generieke klasse-implementatie
Verklaar een andere generieke interface met de formele MyGenericInterface
<T>
die MyGenericInterface
als volgt implementeert:
public class MyGenericSubclass<T> implements MyGenericInterface<T> {
public void foo(T t) { } // type T is still the same
// other methods...
}
Merk op dat een ander formeel type parameter als volgt is gebruikt:
public class MyGenericSubclass<U> implements MyGenericInterface<U> { // equivalent to the previous declaration
public void foo(U t) { }
// other methods...
}
Implementatie van onbewerkte typeklasse
Verklaar een niet-generieke klasse die MyGenericInteface
implementeert als een ruw type (helemaal geen generiek), als volgt:
public class MyGenericSubclass implements MyGenericInterface {
public void foo(Object t) { } // type T has been replaced by Object
// other possible methods
}
Op deze manier wordt niet aanbevolen, omdat het niet 100% veilig is tijdens runtime omdat het onbewerkte type (van de subklasse) wordt gecombineerd met generieke (van de interface) en het is ook verwarrend. Moderne Java-compilers zullen een waarschuwing geven met dit soort implementatie, maar de code zal - vanwege compatibiliteitsredenen met oudere JVM (1.4 of eerder) - compileren.
Alle bovenstaande manieren zijn ook toegestaan bij gebruik van een generieke klasse als een supertype in plaats van een generieke interface.
Generics gebruiken om automatisch te casten
Met generieken is het mogelijk om terug te keren wat de beller verwacht:
private Map<String, Object> data;
public <T> T get(String key) {
return (T) data.get(key);
}
De methode compileert met een waarschuwing. De code is eigenlijk veiliger dan het lijkt, omdat de Java-runtime een cast uitvoert wanneer u deze gebruikt:
Bar bar = foo.get("bar");
Het is minder veilig wanneer u generieke typen gebruikt:
List<Bar> bars = foo.get("bars");
Hier werkt de cast wanneer het geretourneerde type een willekeurige List
(dat wil zeggen dat terugkerende List<String>
geen ClassCastException
zou activeren; je zou het uiteindelijk krijgen als je elementen uit de lijst haalt).
Om dit probleem te omzeilen, kunt u een API maken die getypte sleutels gebruikt:
public final static Key<List<Bar>> BARS = new Key<>("BARS");
samen met deze put()
-methode:
public <T> T put(Key<T> key, T value);
Met deze aanpak kunt u het verkeerde type niet in de kaart plaatsen, dus het resultaat is altijd correct (tenzij u per ongeluk twee sleutels met dezelfde naam maar verschillende typen maakt).
Verwant:
Verkrijg klasse die voldoet aan generieke parameter tijdens runtime
Veel ongebonden generieke parameters, zoals die gebruikt in een statische methode, kunnen tijdens runtime niet worden hersteld (zie Andere threads bij wissen ). Er is echter een gemeenschappelijke strategie voor toegang tot het type dat tijdens runtime voldoet aan een generieke parameter voor een klasse. Dit maakt generieke code mogelijk die afhankelijk is van de toegang tot het type zonder dat type-informatie door elke oproep hoeft te worden gevoerd.
Achtergrond
Algemene parameterinstellingen voor een klasse kunnen worden geïnspecteerd door een anonieme binnenklasse te maken. Deze klasse legt de type-informatie vast. Over het algemeen worden dit mechanisme supertype tokens genoemd , die worden beschreven in de blogpost van Neal Gafter .
implementaties
Drie veel voorkomende implementaties in Java zijn:
Voorbeeld gebruik
public class DataService<MODEL_TYPE> {
private final DataDao dataDao = new DataDao();
private final Class<MODEL_TYPE> type = (Class<MODEL_TYPE>) new TypeToken<MODEL_TYPE>
(getClass()){}.getRawType();
public List<MODEL_TYPE> getAll() {
return dataDao.getAllOfType(type);
}
}
// the subclass definitively binds the parameterization to User
// for all instances of this class, so that information can be
// recovered at runtime
public class UserService extends DataService<User> {}
public class Main {
public static void main(String[] args) {
UserService service = new UserService();
List<User> users = service.getAll();
}
}