Sök…


Introduktion

Generics är en anläggning för generisk programmering som utökar Javas typsystem så att en typ eller metod kan fungera på objekt av olika slag samtidigt som det ger säkerhet för kompileringstiden. Speciellt stöder ramverket för Java-samlingar generiska för att ange vilken typ av objekt som är lagrade i en samlingsinstans.

Syntax

  • klass ArrayList <E> {} // en generisk klass med parametern E
  • klass HashMap <K, V> {} // en generisk klass med två typparametrar K och V
  • <E> void print (E-element) {} // en generisk metod med typparameter E
  • ArrayList <String> -namn; // förklaring av en generisk klass
  • ArrayList <?> -Objekt; // deklaration av en generisk klass med en okänd typparameter
  • ny ArrayList <String> () // inställning av en generisk klass
  • ny ArrayList <> () // inställning med typinferens "diamant" (Java 7 eller senare)

Anmärkningar

Generik implementeras i Java genom typ-radering, vilket innebär att under körning är den typinformation som anges i inställningen av en generisk klass inte tillgänglig. Till exempel uttalandet List<String> names = new ArrayList<>(); producerar ett listobjekt från vilket elementtypen String inte kan återvinnas vid körning. Men om listan lagras i ett fält av typen List<String> eller skickas till en metod / konstruktör parameter av samma typ, eller återvänt från en metod för att returtyp, då hela typinformation kan återvinnas vid körning genom Java Reflection API.

Detta betyder också att när casting till en generisk typ (t.ex.: (List<String>) list , är rollspelaren en okontrollerad roll . Eftersom parametern <String> raderas kan JVM inte kontrollera om en roll från en List<?> Till en List<String> är korrekt; JVM ser bara en roll för List to List vid körning.

Skapa en generisk klass

Generics gör det möjligt för klasser, gränssnitt och metoder att ta andra klasser och gränssnitt som typparametrar.

Detta exempel använder generisk klass Param att ta en parameter av en typ T , avgränsad av vinkelfästen ( <> ):

public class Param<T> {
    private T value;

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }
}

För att instansera denna klass, ange ett typargument istället för T Till exempel, Integer :

Param<Integer> integerParam = new Param<Integer>();

Typargumentet kan vara vilken referens som helst, inklusive matriser och andra generiska typer:

Param<String[]> stringArrayParam;
Param<int[][]> int2dArrayParam;
Param<Param<Object>> objectNestedParam;

I Java SE 7 och senare kan typargumentet ersättas med en tom uppsättning typargument ( <> ) som kallas diamanten :

Java SE 7
Param<Integer> integerParam = new Param<>();

Till skillnad från andra identifierare har typparametrar inga namnbegränsningar. Men deras namn är vanligtvis den första bokstaven i deras syfte med versaler. (Detta gäller även i de officiella JavaDocs.)
Exempel inkluderar T för "typ" , E för "element" och K / V för "nyckel" / "värde" .


Utöka en generisk klass

public abstract class AbstractParam<T> {
    private T value;

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }
}

AbstractParam är en abstrakt klass som deklareras med en typparameter av T Vid utökning av denna klass kan den här parametern ersättas med ett typargument skrivet inuti <> , eller typparametern kan förbli oförändrad. I det första och andra exemplet nedan ersätter String och Integer typparametern. I det tredje exemplet förblir typparametern oförändrad. Det fjärde exemplet använder inte generiska alls, så det liknar om klassen hade en Object parameter. Kompilatorn varnar för att AbstractParam är en rå typ, men den kommer att kompilera klassen ObjectParam . Det femte exemplet har två typparametrar (se "parametrar för flera typer" nedan) och väljer den andra parametern som typparameter som skickas till superklassen.

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> {
    // ...
}

Följande är användningen:

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);

Observera att i klassen Email fungerar T getValue() -metoden som om den hade en signatur av String getValue() , och metoden void setValue(T) fungerar som om den förklarades void setValue(String) .

Det är också möjligt att initiera med anonym innerklass med tomma lockiga hängslen ( {} ):

AbstractParam<Double> height = new AbstractParam<Double>(){};
height.setValue(198.6);

Observera att det inte är tillåtet att använda diamanten med anonyma innerklasser.


Parametrar för flera typer

Java ger möjlighet att använda mer än en typ av parametrar i en generisk klass eller gränssnitt. Parametrar av flera typer kan användas i en klass eller gränssnitt genom att placera en kommaseparerad lista med typer mellan vinkelfästena. Exempel:

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;
    }
}

Användningen kan göras enligt nedan:

MultiGenericParam<String, String> aParam = new MultiGenericParam<String, String>("value1", "value2");
MultiGenericParam<Integer, Double> dayOfWeekDegrees = new MultiGenericParam<Integer, Double>(1, 2.6);

Förklara en generisk metod

Metoder kan också ha generiska parametrar.

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);
    }
}

Observera att vi inte behöver överföra ett faktiskt argument till en generisk metod. Kompilatorn anger typargumentet för oss, baserat på måltypen (t.ex. variabeln som vi tilldelar resultatet) eller på typen av faktiska argument. Det kommer i allmänhet att sluta det mest specifika typargumentet som gör samtalstypen korrekt.

Ibland, om än sällan, kan det vara nödvändigt att åsidosätta den här typen av slutsatser med uttryckliga typargument:

void usage() {
    consumeObjects(this.<Object>makeList("Jeff", "Atwood").stream());
}

void consumeObjects(Stream<Object> stream) { ... }

Det är nödvändigt i det här exemplet eftersom kompilatorn inte kan "se framåt" för att se att Object är önskvärd för T efter kräver stream() och det skulle annars Infer String baserat på makeList argument. Observera att Java-språket inte stöder att utelämna klassen eller objektet som metoden kallas på ( this i exemplet ovan) när typargument uttryckligen tillhandahålls.

Diamanten

Java SE 7

Java 7 introducerade Diamond 1 för att ta bort någon pannplatta kring generisk klassinstitution. Med Java 7+ kan du skriva:

List<String> list = new LinkedList<>();

Där du var tvungen att skriva i tidigare versioner, detta:

List<String> list = new LinkedList<String>();

En begränsning är för Anonyma klasser , där du fortfarande måste ange typparametern i inställningen:

// 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);
    }
};
Java SE 8

Även om användning av diamanten med anonyma inre klasser inte stöds i Java 7 och 8 kommer den att inkluderas som en ny funktion i Java 9 .


Fotnot:

1 - Vissa kallar det <> användning av "diamant operator". Det här är felaktigt. Diamanten uppför sig inte som en operatör och beskrivs eller listas inte någonstans i JLS eller (officiella) Java Tutorials som operatör. Faktum är att <> inte ens en distinkt Java-token. Det är snarare ett < token följt av ett > token, och det är lagligt (men dålig stil) att ha mellanrum eller kommentarer mellan de två. JLS och tutorials hänvisar konsekvent till <> som "diamanten", och det är därför den rätta termen för det.

Kräver flera övre gränser ("förlänger A & B")

Du kan kräva en generisk typ för att förlänga flera övre gränser.

Exempel: vi vill sortera en lista med siffror men Number implementerar inte Comparable .

public <T extends Number & Comparable<T>> void sortNumbers( List<T> n ) {
  Collections.sort( n );
}

I det här exemplet måste T utöka Number and implement Comparable<T> som ska passa alla "normala" inbyggda antal implementationer som Integer eller BigDecimal men inte passar de mer exotiska som Striped64 .

Eftersom flera arv inte är tillåtna, kan du använda högst en klass som en bunden och det måste vara den första listade. Till exempel är <T extends Comparable<T> & Number> inte tillåtet eftersom Jämförbart är ett gränssnitt och inte en klass.

Skapa en begränsad generisk klass

Du kan begränsa de giltiga typerna som används i en generisk klass genom att begränsa den typen i klassdefinitionen. Med tanke på följande enkla typhierarki:

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";
    }
}

Utan avgränsade generika kan vi inte skapa en behållarklass som är både generisk och vet att varje element är ett djur:

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()); 
        }
    }
}

Med generisk bunden i klassdefinition är detta nu möjligt.

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()); 
        }
    }
}

Detta begränsar också giltiga inställningar av den generiska typen:

// 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>();

Bestämma mellan `T`,`? super T`, och `? sträcker sig T`

Syntaxen för Java-generiska avgränsade jokertecken, som representerar den okända typen med ? är:

  • ? extends T representerar ett övre gränsat jokertecken. Den okända typen representerar en typ som måste vara en subtyp av T eller typ T själv.

  • ? super T representerar ett nedre avgränsat jokertecken. Den okända typen representerar en typ som måste vara en supertyp av T, eller typ T själv.

Som tumregel bör du använda

  • ? extends T om du bara behöver "läs" -åtkomst ("input")
  • ? super T om du behöver "skriva" -åtkomst ("output")
  • T om du behöver båda ("modifiera")

Att använda extends eller super är vanligtvis bättre eftersom det gör din kod mer flexibel (som i: tillåter användning av subtyper och supertyper), som du ser nedan.

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) {}
}

Kompilatorn kan nu upptäcka dålig användning:

 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!
}

Att välja rätt T ? super T eller ? extends T är nödvändigt för att tillåta användning med subtyper. Kompilatorn kan då säkerställa typsäkerhet; Du bör inte behöva casta (som inte är typsäker och kan orsaka programmeringsfel) om du använder dem ordentligt.

Om det inte är lätt att förstå, kom ihåg PECS- regeln:

P roducer använder " E xtends" och C- användare använder " S uper".

(Producenten har bara skrivåtkomst, och konsumenten har bara läsåtkomst)

Fördelarna med generisk klass och gränssnitt

Kod som använder generik har många fördelar jämfört med icke-generisk kod. Nedan är de viktigaste fördelarna


Starkare typkontroller vid sammanställningstiden

En Java-kompilator tillämpar stark typkontroll för generisk kod och ger fel om koden bryter mot typsäkerhet. Att fixa kompileringstidsfel är enklare än att fixa runtime-fel, vilket kan vara svårt att hitta.


Avskaffande av gjutningar

Följande kodavsnitt utan generik kräver casting:

List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0);

Vid omskrivning för att använda generik kräver inte koden casting:

List<String> list = new ArrayList<>();
list.add("hello");
String s = list.get(0);   // no cast

Aktivera programmerare att implementera generiska algoritmer

Genom att använda generika kan programmerare implementera generiska algoritmer som arbetar med samlingar av olika typer, kan anpassas och är typsäkra och lättare att läsa.

Binder generisk parameter till mer än 1 typ

Generiska parametrar kan också bindas till mer än en typ med hjälp av syntaxen T extends Type1 & Type2 & ...

Låt oss säga att du vill skapa en klass vars generiska typ ska implementera både Flushable och Closeable , du kan skriva

class ExampleClass<T extends Flushable & Closeable> {
}

Nu accepterar ExampleClass endast som generiska parametrar, typer som implementerar både Flushable och 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.

Klassmetoderna kan välja att dra slutsatser om generiska typer som antingen Closeable eller 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.
    }
}

Notera:

Du kan inte binda den generiska parametern till någon av typen med ELLER ( | ) -bestämmelsen. Endast AND ( & ) -klausulen stöds. Generisk typ kan bara utvidga en klass och många gränssnitt. Klass måste placeras i början av listan.

Instantiera en generisk typ

På grund av radering av typ fungerar inte följande:

public <T> void genericMethod() {
    T t = new T(); // Can not instantiate the type T.
}

Typen T raderas. Eftersom JVM vid körning inte vet vad T ursprungligen var, vet den inte vilken konstruktör han ska ringa.


lösningar

  1. Passerar T klass när du ringer genericMethod :

    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);
    

    Vilket kastar undantag, eftersom det inte finns något sätt att veta om den godkända klassen har en tillgänglig standardkonstruktör.

Java SE 8
  1. Skickar en referens till T 's konstruktör:

    public <T> void genericMethod(Supplier<T> cons) {
        T t = cons.get();
    }
    
    genericMethod(String::new);
    

Med hänvisning till den deklarerade generiska typen inom sin egen deklaration

Hur går man till med att använda en instans av en (eventuellt ytterligare) ärvad generisk typ i en metoddeklaration i själva generiska typen som deklareras? Detta är ett av problemen du kommer att möta när du gräver lite djupare i generiska, men fortfarande ett ganska vanligt problem.

Antag att vi har en DataSeries<T> -typ (gränssnitt här), som definierar en generisk dataserie som innehåller värden av typ T Det är besvärligt att arbeta med den här typen direkt när vi vill utföra en hel del operationer med t.ex. dubbla värden, så vi definierar DoubleSeries extends DataSeries<Double> . DataSeries<T> nu att den ursprungliga DataSeries<T> har en add(values) som lägger till en annan serie av samma längd och returnerar en ny. Hur säkerställer vi typen av values och typen av återgång till att vara DoubleSeries snarare än DataSeries<Double> i vår härledda klass?

Problemet kan lösas genom att lägga till en parameter av generisk typ som refererar till och utvidga typen som deklareras (tillämpas på ett gränssnitt här, men samma gäller klasser):

public interface DataSeries<T, DS extends DataSeries<T, DS>> {
    DS add(DS values);
    List<T> data();
}

Här representerar T den datatyp serien innehar, t.ex. Double och DS själva serien. En ärvd typ (eller typer) kan nu enkelt implementeras genom att ersätta ovannämnda parameter med en motsvarande härledd typ, vilket ger en konkret Double definition av formen:

public interface DoubleSeries extends DataSeries<Double, DoubleSeries> {
    static DoubleSeries instance(Collection<Double> data) {
        return new DoubleSeriesImpl(data);
    }
}

För tillfället kommer till och med en IDE att implementera ovanstående gränssnitt med korrekta typer på plats, som efter lite innehållsfyllning kan se ut så här:

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);
    }
}

Som du kan se add deklareras som DoubleSeries add(DoubleSeries values) och kompilatorn är glad.

Mönstret kan kapslas ytterligare om det behövs.

Användning av instans med Generics

Använda generiska för att definiera typen i förekomst

Tänk på följande generiska klass Example deklarerats med den formella parametern <T> :

class Example<T> {
    public boolean isTypeAString(String s) {
        return s instanceof T; // Compilation error, cannot use T as class type here
    }
}

Detta ger alltid ett kompileringsfel eftersom så snart kompilatorn sammanställer Java-källan till Java-bytkod tillämpar den en process som kallas typ radering , som omvandlar all generisk kod till icke-generisk kod, vilket gör det omöjligt att skilja mellan T-typer vid körning. Den typ som används med instanceof måste vara återförsäljbar , vilket innebär att all information om typen måste vara tillgänglig vid körning, och detta är vanligtvis inte fallet för generiska typer.

Följande klass representerar hur två olika klasser i Example , Example<String> och Example<Number> ser ut efter att generik har tagits bort efter radering av typ :

class Example { // formal parameter is gone
    public boolean isTypeAString(String s) {
        return s instanceof Object; // Both <String> and <Number> are now Object
    }
}

Eftersom typer är borta är det inte möjligt för JVM att veta vilken typ som är T


Undantag från föregående regel

Du kan alltid använda obundat jokertecken (?) För att ange en typ i instanceof enligt följande:

    public boolean isAList(Object obj) {
        return obj instanceof List<?>;
    }

Detta kan vara användbart för att bedöma om en instans obj är en List eller inte:

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

I själva verket anses obegränsat jokertecken vara en återförsäljbar typ.


Använda en generisk instans med instans av

Den andra sidan av myntet är att det är lagligt att använda en instans t av T med instanceof , vilket visas i följande exempel:

class Example<T> {
    public boolean isTypeAString(T t) {
        return t instanceof String; // No compilation error this time
    }
}

eftersom efter typen av radering kommer klassen att se ut enligt följande:

class Example { // formal parameter is gone
    public boolean isTypeAString(Object t) {
        return t instanceof String; // No compilation error this time
    }
}

Eftersom JVM, även om typen av radering inträffar ändå, kan skilja mellan olika typer i minnet, även om de använder samma referenstyp ( Object ), som följande utdrag visar:

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

Olika sätt att implementera ett generiskt gränssnitt (eller utöka en generisk klass)

Anta att följande generiska gränssnitt har deklarerats:

public interface MyGenericInterface<T> {
    public void foo(T t);
}

Nedan listas de möjliga sätten att implementera det.


Icke-generisk klassimplementering med en viss typ

Välj en specifik typ för att ersätta den formella parametern <T> för MyGenericClass och implementera den, som följande exempel gör:

public class NonGenericClass implements MyGenericInterface<String> {
    public void foo(String t) { } // type T has been replaced by String
}

Den här klassen behandlar endast String , och det innebär att användning av MyGenericInterface med olika parametrar (t.ex. Integer , Object etc.) inte kommer att kompilera, som följande utdrag visar:

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

Generisk klassimplementering

Förklara ett annat generiskt gränssnitt med den formella parametern <T> som implementerar MyGenericInterface enligt följande:

public class MyGenericSubclass<T> implements MyGenericInterface<T> {
    public void foo(T t) { } // type T is still the same
    // other methods...
}

Observera att en annan formell parametertyp kan ha använts enligt följande:

public class MyGenericSubclass<U> implements MyGenericInterface<U> { // equivalent to the previous declaration
    public void foo(U t) { }
    // other methods...
}

Implementering av rå typ klass

Förklara en icke-generisk klass som implementerar MyGenericInteface som en rå typ (inte använder generiskt alls) enligt följande:

public class MyGenericSubclass implements MyGenericInterface {
    public void foo(Object t) { } // type T has been replaced by Object
    // other possible methods
}

Detta sätt rekommenderas inte, eftersom det inte är 100% säker vid körning eftersom det blandar upp rå typ (av underklassen) med generiska (av gränssnittet) och det är också förvirrande. Moderna Java-kompilatorer kommer att ge en varning med den här typen av implementering, ändå kommer koden - av kompatibilitetsskäl med äldre JVM (1.4 eller tidigare) - att kompilera.


Alla sätt som anges ovan är också tillåtna när man använder en generisk klass som en supertyp istället för ett generiskt gränssnitt.

Använda Generics för att automatiskt casta

Med generik är det möjligt att returnera vad den som ringer förväntar sig:

private Map<String, Object> data;
public <T> T get(String key) {
    return (T) data.get(key);
}

Metoden kommer att sammanställas med en varning. Koden är faktiskt mer säker än den ser ut eftersom Java-runtime kommer att göra en roll när du använder den:

Bar bar = foo.get("bar");

Det är mindre säkert när du använder generiska typer:

List<Bar> bars = foo.get("bars");

Här kommer rollisten att fungera när den returnerade typen är någon slags List (dvs att returnera List<String> skulle utlösa en ClassCastException ; du skulle så småningom få den när du tar element ur listan).

För att lösa problemet kan du skapa ett API som använder typade nycklar:

public final static Key<List<Bar>> BARS = new Key<>("BARS");

tillsammans med denna put() -metod:

public <T> T put(Key<T> key, T value);

Med det här tillvägagångssättet kan du inte sätta fel typ på kartan, så resultatet kommer alltid att vara korrekt (såvida du inte av misstag skapar två nycklar med samma namn men olika typer).

Relaterad:

Få klass som uppfyller generisk parameter vid körning

Många obundna generiska parametrar, som de som används i en statisk metod, kan inte återställas vid körning (se Andra trådar om radering ). Det finns emellertid en gemensam strategi för att få åtkomst till den typ som uppfyller en generisk parameter på en klass vid körning. Detta möjliggör generisk kod som beror på tillgång till typ utan att behöva tränga typinformation genom varje samtal.

Bakgrund

Generisk parametrering på en klass kan inspekteras genom att skapa en anonym innerklass. Denna klass kommer att fånga typinformationen. I allmänhet hänvisas till denna mekanism som super-typ-tokens , som beskrivs i Neal Gafter's blogginlägg .

implementeringar

Tre vanliga implementationer i Java är:

Exempel på användning

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();
    }
}


Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow