Créer une instance de type générique en Java?


576

Est-il possible de créer une instance de type générique en Java? Je pense en fonction de ce que j'ai vu que la réponse est no(en raison de l'effacement du type ), mais je serais intéressé si quelqu'un pouvait voir quelque chose qui me manquait:

class SomeContainer<E>
{
    E createContents()
    {
        return what???
    }
}

EDIT: Il s'avère que les jetons Super Type pourraient être utilisés pour résoudre mon problème, mais cela nécessite beaucoup de code basé sur la réflexion, comme l'ont indiqué certaines des réponses ci-dessous.

Je vais laisser cela ouvert pendant un petit moment pour voir si quelqu'un propose quelque chose de radicalement différent de l' article Artima d'Ian Robertson .


2
Performances récemment testées sur un appareil Android. 10000 opérations et: 8-9 ms prend une nouvelle SomeClass (), 9-11 ms prend Factory <SomeClass> .createInstance () et 64-71 ms prend la réflexion la plus courte: SomeClass z = SomeClass.class.newInstance (). Et tous les tests étaient dans un seul bloc try-catch. La réflexion newInstance () lève 4 exceptions différentes, vous vous en souvenez? J'ai donc décidé d'utiliser le modèle d'usine
Deepscorn


4
Avec Java 8, vous pouvez maintenant passer une référence de constructeur ou un lambda, ce qui rend ce problème assez simple à contourner. Voir ma réponse ci-dessous pour plus de détails.
Daniel Pryden

Je pense que c'est une mauvaise idée d'écrire un tel code, ce sont des façons plus élégantes et lisibles de résoudre le problème sous-jacent.
Krzysztof Cichocki

1
@DavidCitron "pendant un petit moment", a-t-il dit ... Cela fait onze ans depuis ...
MC Emperor

Réponses:


332

Vous avez raison. Tu ne peux pas faire new E(). Mais vous pouvez le changer en

private static class SomeContainer<E> {
    E createContents(Class<E> clazz) {
        return clazz.newInstance();
    }
}

C'est une douleur. Mais ça marche. L'envelopper dans le modèle d'usine le rend un peu plus tolérable.


11
Oui, j'ai vu cette solution, mais cela ne fonctionne que si vous avez déjà une référence à un objet Class du type que vous souhaitez instancier.
David Citron,

11
Ouais je sais. Ce serait bien si vous pouviez faire E.class mais cela vous donne simplement Object.class à cause de l'effacement :)
Justin Rudd

6
C'est l'approche correcte pour ce problème. Ce n'est généralement pas ce que vous souhaitez, mais c'est ce que vous obtenez.
Joachim Sauer

38
Et comment appelez-vous la méthode createContents ()?
Alexis Dufrenoy

8
Ce n'est plus le seul moyen de le faire, il existe maintenant un meilleur moyen qui ne nécessite pas de passer une Class<?>référence en utilisant Guava et TypeToken, voir cette réponse pour le code et les liens!

129

Ne sais pas si cela aide, mais lorsque vous sous-classe (y compris de manière anonyme) un type générique, les informations de type sont disponibles via la réflexion. par exemple,

public abstract class Foo<E> {

  public E instance;  

  public Foo() throws Exception {
    instance = ((Class)((ParameterizedType)this.getClass().
       getGenericSuperclass()).getActualTypeArguments()[0]).newInstance();
    ...
  }

}

Ainsi, lorsque vous sous-classe Foo, vous obtenez une instance de Bar, par exemple,

// notice that this in anonymous subclass of Foo
assert( new Foo<Bar>() {}.instance instanceof Bar );

Mais c'est beaucoup de travail, et ne fonctionne que pour les sous-classes. Peut être utile cependant.


2
Oui, c'est bien surtout si la classe générique est abstraite, vous pouvez le faire dans les sous-classes concrètes :)
Pierre Henry

Cette méthode fonctionne également si la classe Foon'est pas abstraite. Mais pourquoi ne fonctionne-t-il que sur les sous-classes anonymes de Foo? Supposons que nous fabriquons du Foobéton (nous omettons abstract), pourquoi new Foo<Bar>();entraînera une erreur, alors new Foo<Bar>(){};que non? (Exception: "La classe ne peut pas être convertie en ParameterizedType")
Tim Kuipers

2
Le @TimKuipers <E>en class Foo<E>est pas lié à un type particulier. Vous verrez le comportement exceptionnel à chaque fois Eest pas statiquement lié, comme dans: new Foo<Bar>(), new Foo<T>() {...}ou class Fizz <E> extends Foo<E>. Le premier cas n'est pas lié statiquement, il est effacé au moment de la compilation. Le second cas remplace une autre variable de type (T) à la place de Emais n'est toujours pas lié. Et dans le dernier cas, il devrait être évident qu'il En'est toujours pas lié.
William Price

4
Un exemple de liaison statique du paramètre type serait class Fizz extends Foo<Bar>- dans ce cas, les utilisateurs de Fizzget quelque chose qui est un Foo<Bar>et ne peut être rien d'autre qu'un a Foo<Bar>. Donc, dans ce cas, le compilateur est heureux d'encoder ces informations dans les métadonnées de classe pour Fizzet de les rendre disponibles en tant que ParameterizedTypecode de réflexion. Lorsque vous créez une classe interne anonyme comme new Foo<Bar>() {...}s'il faisait la même chose, sauf que Fizzle compilateur génère un nom de classe "anonyme" que vous ne connaîtrez pas tant que la classe externe ne sera pas compilée.
William Price

4
Il convient de noter que cela ne fonctionnera pas si les arguments de type sont également un ParameterizedType. Par exemple Foo<Bar<Baz>>,. Vous allez créer une instance ParameterizedTypeImplqui ne peut pas être explicitement créée. Par conséquent, c'est une bonne idée de vérifier si getActualTypeArguments()[0]renvoie un ParameterizedType. Si tel est le cas, vous souhaitez obtenir le type brut et créer une instance de celui-ci à la place.
écraser

106

Dans Java 8, vous pouvez utiliser l' Supplierinterface fonctionnelle pour y parvenir assez facilement:

class SomeContainer<E> {
  private Supplier<E> supplier;

  SomeContainer(Supplier<E> supplier) {
    this.supplier = supplier;
  }

  E createContents() {
    return supplier.get();
  }
}

Vous construirez cette classe comme ceci:

SomeContainer<String> stringContainer = new SomeContainer<>(String::new);

La syntaxe String::newsur cette ligne est une référence constructeur .

Si votre constructeur prend des arguments, vous pouvez utiliser une expression lambda à la place:

SomeContainer<BigInteger> bigIntegerContainer
    = new SomeContainer<>(() -> new BigInteger(1));

6
Bon. Il évite la réflexion et le traitement des exceptions.
Bruno Leite

2
Vraiment gentil. Malheureusement pour les utilisateurs d'Android, cela nécessite un niveau d'API 24 ou supérieur.
Michael Updike

1
Selon moins d'approche et d'approche fonctionnelle, cette réponse sera acceptée à mon avis
Tomas

2
… Et ce n'est pas différent de cette réponse encore plus ancienne montrant que le modèle technique derrière elle est encore plus ancien que le support de Java pour les expressions lambda et les références de méthode alors que vous pouvez même utiliser cet ancien code avec eux une fois que vous avez mis à niveau votre compilateur…
Holger

Serait-il possible de mettre juste SomeContainer stringContainer = new SomeContainer(String::new);?
Aaron Franke

87

Vous aurez besoin d'une sorte d'usine abstraite d'une sorte ou d'une autre pour passer la balle à:

interface Factory<E> {
    E create();
}

class SomeContainer<E> {
    private final Factory<E> factory;
    SomeContainer(Factory<E> factory) {
        this.factory = factory;
    }
    E createContents() {
        return factory.create();
    }
}

..et à quoi ressemble Factory.create ()?
OhadR

6
@OhadR Factory<>est une interface et il n'y a donc pas de corps. Le fait est que vous avez besoin d'une couche d'indirections pour passer la balle aux méthodes qui "connaissent" le code requis pour construire une instance. Il est préférable de le faire avec du code normal plutôt que métalinguistique Classou Constructorcar la réflexion apporte tout un monde de souffrance.
Tom Hawtin - tackline

2
De nos jours, vous pouvez créer une instance d'usine avec une expression de référence de méthode comme celle-ci:SomeContainer<SomeElement> cont = new SomeContainer<>(SomeElement::new);
Lii

24
package org.foo.com;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

/**
 * Basically the same answer as noah's.
 */
public class Home<E>
{

    @SuppressWarnings ("unchecked")
    public Class<E> getTypeParameterClass()
    {
        Type type = getClass().getGenericSuperclass();
        ParameterizedType paramType = (ParameterizedType) type;
        return (Class<E>) paramType.getActualTypeArguments()[0];
    }

    private static class StringHome extends Home<String>
    {
    }

    private static class StringBuilderHome extends Home<StringBuilder>
    {
    }

    private static class StringBufferHome extends Home<StringBuffer>
    {
    }   

    /**
     * This prints "String", "StringBuilder" and "StringBuffer"
     */
    public static void main(String[] args) throws InstantiationException, IllegalAccessException
    {
        Object object0 = new StringHome().getTypeParameterClass().newInstance();
        Object object1 = new StringBuilderHome().getTypeParameterClass().newInstance();
        Object object2 = new StringBufferHome().getTypeParameterClass().newInstance();
        System.out.println(object0.getClass().getSimpleName());
        System.out.println(object1.getClass().getSimpleName());
        System.out.println(object2.getClass().getSimpleName());
    }

}

3
Une bonne approche par ce code peut provoquer une exception ClassCastException si vous utilisez dans le générique un type générique. Ensuite, vous récupérez l'argument actualType. Vous devez vérifier qu'il s'agit également de ParamterizedType et, si tel est le cas, renvoyer son RawType (ou quelque chose de mieux que cela). Un autre problème avec cela est lorsque nous étendons plus d'une fois que ce code lancera également ClassCastExeption.
Damian Leszczyński - Vash

3
Causé par: java.lang.ClassCastException: sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl ne peut pas être converti en java.lang.Class
juan

@ DamianLeszczyński-Vash échouera également avec, par exempleclass GenericHome<T> extends Home<T>{}
Holger

22

Si vous avez besoin d'une nouvelle instance d'un argument de type dans une classe générique, demandez à vos constructeurs d'exiger sa classe ...

public final class Foo<T> {

    private Class<T> typeArgumentClass;

    public Foo(Class<T> typeArgumentClass) {

        this.typeArgumentClass = typeArgumentClass;
    }

    public void doSomethingThatRequiresNewT() throws Exception {

        T myNewT = typeArgumentClass.newInstance();
        ...
    }
}

Usage:

Foo<Bar> barFoo = new Foo<Bar>(Bar.class);
Foo<Etc> etcFoo = new Foo<Etc>(Etc.class);

Avantages:

  • Beaucoup plus simple (et moins problématique) que l'approche Super Type Token (STT) de Robertson.
  • Beaucoup plus efficace que l'approche STT (qui mangera votre téléphone portable pour le petit déjeuner).

Les inconvénients:

  • Impossible de transmettre Class à un constructeur par défaut (c'est pourquoi Foo est final). Si vous avez vraiment besoin d'un constructeur par défaut, vous pouvez toujours ajouter une méthode de définition, mais vous devez vous rappeler de lui donner un appel plus tard.
  • L'objection de Robertson ... Plus de barres qu'un mouton noir (bien que spécifier une fois de plus la classe d'argument type ne vous tue pas exactement). Et contrairement aux affirmations de Robertson, cela ne viole pas de toute façon le principal DRY car le compilateur garantira l'exactitude du type.
  • Pas entièrement Foo<L> preuve. Pour commencer...newInstance() lancera un wobbler si la classe d'argument type n'a pas de constructeur par défaut. Cependant, cela s'applique à toutes les solutions connues.
  • Manque l'encapsulation totale de l'approche STT. Pas un gros problème cependant (compte tenu des frais généraux scandaleux de STT).

22

Vous pouvez le faire maintenant et cela ne nécessite pas un tas de code de réflexion.

import com.google.common.reflect.TypeToken;

public class Q26289147
{
    public static void main(final String[] args) throws IllegalAccessException, InstantiationException
    {
        final StrawManParameterizedClass<String> smpc = new StrawManParameterizedClass<String>() {};
        final String string = (String) smpc.type.getRawType().newInstance();
        System.out.format("string = \"%s\"",string);
    }

    static abstract class StrawManParameterizedClass<T>
    {
        final TypeToken<T> type = new TypeToken<T>(getClass()) {};
    }
}

Bien sûr, si vous devez appeler le constructeur qui nécessitera une réflexion, mais qui est très bien documenté, cette astuce ne l'est pas!

Voici le JavaDoc pour TypeToken .


6
Cette solution fonctionne pour un ensemble limité de cas, tout comme la réponse de @ noah avec réflexion. Je les ai tous essayés aujourd'hui ... Et j'ai fini par passer une instance de la classe de paramètres à la classe paramétrée (afin de pouvoir appeler .newInstance ()). Très grosse carence en "génériques" ... nouveau Foo <Bar> (Bar.class); ... classe Foo <T> {classe finale privée <T> mTFactory; Foo (classe <T> tClass) {mTFactory = tClass; ...} T instance = tFactory.newInstance (); }
yvolk

cela fonctionne dans tous les cas, même les méthodes d'usine statiques qui prennent des paramètres génériques

13

Pensez à une approche plus fonctionnelle: au lieu de créer du E à partir de rien (ce qui est clairement une odeur de code), passez une fonction qui sait en créer un, c'est à dire

E createContents(Callable<E> makeone) {
     return makeone.call(); // most simple case clearly not that useful
}

6
Techniquement, vous ne passez pas une fonction, vous passez un objet fonction (également connu sous le nom de foncteur ).
Lorne Laliberte

3
Ou peut-être pour surmonter l' Exceptionutilisation de la capture Supplier<E>.
Martin D

10

De Java Tutorial - Restrictions sur les génériques :

Impossible de créer des instances de paramètres de type

Vous ne pouvez pas créer une instance d'un paramètre de type. Par exemple, le code suivant provoque une erreur de compilation:

public static <E> void append(List<E> list) {
    E elem = new E();  // compile-time error
    list.add(elem);
}

Pour contourner ce problème, vous pouvez créer un objet d'un paramètre de type par réflexion:

public static <E> void append(List<E> list, Class<E> cls) throws Exception {
    E elem = cls.newInstance();   // OK
    list.add(elem);
}

Vous pouvez appeler la méthode append comme suit:

List<String> ls = new ArrayList<>();
append(ls, String.class);

6

Voici une option que j'ai trouvée, cela peut aider:

public static class Container<E> {
    private Class<E> clazz;

    public Container(Class<E> clazz) {
        this.clazz = clazz;
    }

    public E createContents() throws Exception {
        return clazz.newInstance();
    }
}

EDIT: Alternativement, vous pouvez utiliser ce constructeur (mais il nécessite une instance de E):

@SuppressWarnings("unchecked")
public Container(E instance) {
    this.clazz = (Class<E>) instance.getClass();
}

Oui, cela fonctionne de la même manière sans génériques - avec les génériques, l'instanciation de ce conteneur devient un peu redondante (vous devez spécifier ce que "E" est deux fois).
David Citron

Eh bien, c'est ce qui se passe lorsque vous utilisez Java et des génériques ... ils ne sont pas jolis, et il y a de sérieuses limitations ...
Mike Stone

6

Si vous ne voulez pas taper deux fois le nom de la classe pendant l'instanciation comme dans:

new SomeContainer<SomeType>(SomeType.class);

Vous pouvez utiliser la méthode d'usine:

<E> SomeContainer<E> createContainer(Class<E> class); 

Comme dans:

public class Container<E> {

    public static <E> Container<E> create(Class<E> c) {
        return new Container<E>(c);
    }

    Class<E> c;

    public Container(Class<E> c) {
        super();
        this.c = c;
    }

    public E createInstance()
            throws InstantiationException,
            IllegalAccessException {
        return c.newInstance();
    }

}

6

Malheureusement, Java ne permet pas ce que vous voulez faire. Voir la solution de contournement officielle :

Vous ne pouvez pas créer une instance d'un paramètre de type. Par exemple, le code suivant provoque une erreur de compilation:

public static <E> void append(List<E> list) {
    E elem = new E();  // compile-time error
    list.add(elem);
}

Pour contourner ce problème, vous pouvez créer un objet d'un paramètre de type par réflexion:

public static <E> void append(List<E> list, Class<E> cls) throws Exception {
    E elem = cls.newInstance();   // OK
    list.add(elem);
}

Vous pouvez appeler la méthode append comme suit:

List<String> ls = new ArrayList<>();
append(ls, String.class);

Pourriez-vous s'il vous plaît me dire pourquoi vous votez en bas lorsque vous le faites? Je ne vois pas pourquoi la solution de contournement officielle est une mauvaise solution. Merci.
Neepsnikeep

Je suppose que vous obtenez des votes bas, car votre réponse est essentiellement la même que celle de Justin Rudd: stackoverflow.com/a/75254/103412
Torsten

5

Lorsque vous travaillez avec E au moment de la compilation, vous ne vous souciez pas vraiment du type générique réel "E" (soit vous utilisez la réflexion, soit vous travaillez avec une classe de base de type générique), alors laissez la sous-classe fournir une instance de E.

Abstract class SomeContainer<E>
{

    abstract protected  E createContents();
    public doWork(){
        E obj = createContents();
        // Do the work with E 

     }
}


BlackContainer extends SomeContainer<Black>{
    Black createContents() {
        return new  Black();
    }
}

4

Vous pouvez utiliser:

Class.forName(String).getConstructor(arguments types).newInstance(arguments)

Mais vous devez fournir le nom de classe exact, y compris les packages, par exemple. java.io.FileInputStream. Je l'ai utilisé pour créer un analyseur d'expressions mathématiques.


15
Et comment obtenir le nom de classe exact du type générique lors de l'exécution?
David Citron

Vous devez l'enregistrer en utilisant une instance de cette classe. Faisable, mais peu pratique. Si votre générique avait un membre de type E (ou T ou autre), obtenir son nom binaire est juste foo.getClass().getName(). D'où vient cette instance? J'en passe actuellement un à un constructeur dans le projet sur lequel je travaille actuellement.
Mark Storer

3

J'espère que ce n'est pas trop tard pour aider !!!

Java est de type sécurisé, seul Object est capable de créer une instance.

Dans mon cas, je ne peux pas passer de paramètres à la createContentsméthode. Ma solution utilise étend au lieu de toutes les réponses ci-dessous.

private static class SomeContainer<E extends Object> {
    E e;
    E createContents() throws Exception{
        return (E) e.getClass().getDeclaredConstructor().newInstance();
    }
}

C'est mon cas d'exemple dont je ne peux pas passer les paramètres.

public class SomeContainer<E extends Object> {
    E object;

    void resetObject throws Exception{
        object = (E) object.getClass().getDeclaredConstructor().newInstance();
    }
}

L'utilisation de la réflexion crée une erreur d'exécution, si vous étendez votre classe générique avec aucun type d'objet. Pour étendre votre type générique en objet, convertissez cette erreur en erreur de compilation.


3

Utilisez la TypeToken<T>classe:

public class MyClass<T> {
    public T doSomething() {
        return (T) new TypeToken<T>(){}.getRawType().newInstance();
    }
}

Si vous utilisez Guava au lieu de GSON, c'est un peu différent:(T) new TypeToken<T>(getClass()){}.getRawType().newInstance();
Jacob van Lingen

2

Je pensais pouvoir le faire, mais assez déçu: ça ne marche pas, mais je pense que ça vaut quand même la peine d'être partagé.

Peut-être que quelqu'un peut corriger:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

interface SomeContainer<E> {
    E createContents();
}

public class Main {

    @SuppressWarnings("unchecked")
    public static <E> SomeContainer<E> createSomeContainer() {
        return (SomeContainer<E>) Proxy.newProxyInstance(Main.class.getClassLoader(),
                new Class[]{ SomeContainer.class }, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Class<?> returnType = method.getReturnType();
                return returnType.newInstance();
            }
        });
    }

    public static void main(String[] args) {
        SomeContainer<String> container = createSomeContainer();

    [*] System.out.println("String created: [" +container.createContents()+"]");

    }
}

Cela produit:

Exception in thread "main" java.lang.ClassCastException: java.lang.Object cannot be cast to java.lang.String
    at Main.main(Main.java:26)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)

La ligne 26 est celle avec le [*].

La seule solution viable est celle de @JustinRudd


2

Une amélioration de la réponse de @ Noah.

Raison du changement

a] Est plus sûr si plus d'un type générique est utilisé au cas où vous auriez changé la commande.

b] Une signature de type générique de classe change de temps en temps afin que vous ne soyez pas surpris par des exceptions inexpliquées dans le runtime.

Code robuste

public abstract class Clazz<P extends Params, M extends Model> {

    protected M model;

    protected void createModel() {
    Type[] typeArguments = ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments();
    for (Type type : typeArguments) {
        if ((type instanceof Class) && (Model.class.isAssignableFrom((Class) type))) {
            try {
                model = ((Class<M>) type).newInstance();
            } catch (InstantiationException | IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

Ou utilisez la doublure

Code d'une ligne

model = ((Class<M>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[1]).newInstance();

2

ce que vous pouvez faire c'est -

  1. Déclarez d'abord la variable de cette classe générique

    Ensuite, faites-en un constructeur et instanciez cet objet

  2. Ensuite, utilisez-le où vous voulez l'utiliser

exemple-

1

private Class<E> entity;

2

public xyzservice(Class<E> entity) {
        this.entity = entity;
    }



public E getEntity(Class<E> entity) throws InstantiationException, IllegalAccessException {
        return entity.newInstance();
    }

3.

E e = getEntity (entité);


0

Comme vous l'avez dit, vous ne pouvez pas vraiment le faire à cause de l'effacement des caractères. Vous pouvez le faire en utilisant la réflexion, mais cela nécessite beaucoup de code et beaucoup de gestion des erreurs.


Comment feriez-vous même en utilisant la réflexion? La seule méthode que je vois est Class.getTypeParameters (), mais qui ne renvoie que les types déclarés, pas les types d'exécution.
David Citron


0

Si vous voulez dire, new E() c'est impossible. Et j'ajouterais que ce n'est pas toujours correct - comment savoir si E a un constructeur public sans argument? Mais vous pouvez toujours déléguer la création à une autre classe qui sait comment créer une instance - cela peut être Class<E>ou votre code personnalisé comme celui-ci

interface Factory<E>{
    E create();
}    

class IntegerFactory implements Factory<Integer>{    
  private static int i = 0; 
  Integer create() {        
    return i++;    
  }
}

0
return   (E)((Class)((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()[0]).newInstance();

1
Cela ne fonctionne pas dans mon exemple dans la question d'origine. La superclasse pour SomeContainerest tout simplement Object. Par conséquent, this.getClass().getGenericSuperclass()renvoie un Class(classe java.lang.Object), pas un ParameterizedType. Cela a déjà été signalé par stackoverflow.com/questions/75175/… .
David Citron

1
Totalement faux: exception dans le thread "principal" java.lang.ClassCastException: java.lang.Class ne peut pas être casté en java.lang.reflect.ParameterizedType
Aubin

0

Vous pouvez y parvenir avec l'extrait de code suivant:

import java.lang.reflect.ParameterizedType;

public class SomeContainer<E> {
   E createContents() throws InstantiationException, IllegalAccessException {
      ParameterizedType genericSuperclass = (ParameterizedType)
         getClass().getGenericSuperclass();
      @SuppressWarnings("unchecked")
      Class<E> clazz = (Class<E>)
         genericSuperclass.getActualTypeArguments()[0];
      return clazz.newInstance();
   }
   public static void main( String[] args ) throws Throwable {
      SomeContainer< Long > scl = new SomeContainer<>();
      Long l = scl.createContents();
      System.out.println( l );
   }
}

4
Totalement faux: exception dans le thread "principal" java.lang.ClassCastException: java.lang.Class ne peut pas être converti en java.lang.reflect.ParameterizedType
Aubin

0

Il existe différentes bibliothèques qui peuvent résoudre Epour vous en utilisant des techniques similaires à ce que l'article Robertson a discuté. Voici une implémentation createContentsqui utilise TypeTools pour résoudre la classe brute représentée par E:

E createContents() throws Exception {
  return TypeTools.resolveRawArgument(SomeContainer.class, getClass()).newInstance();
}

Cela suppose que getClass () se résout en une sous-classe de SomeContainer et échouera sinon car la valeur paramétrée réelle de E aura été effacée au moment de l'exécution si elle n'est pas capturée dans une sous-classe.


0

Voici une implémentation createContentsqui utilise TypeTools pour résoudre la classe brute représentée par E:

E createContents() throws Exception {
  return TypeTools.resolveRawArgument(SomeContainer.class, getClass()).newInstance();
}

Cette approche ne fonctionne que si SomeContainerest sous-classée, de sorte que la valeur réelle de Eest capturée dans une définition de type:

class SomeStringContainer extends SomeContainer<String>

Sinon, la valeur de E est effacée lors de l'exécution et n'est pas récupérable.


-1

Vous pouvez avec un chargeur de classe et le nom de la classe, éventuellement certains paramètres.

final ClassLoader classLoader = ...
final Class<?> aClass = classLoader.loadClass("java.lang.Integer");
final Constructor<?> constructor = aClass.getConstructor(int.class);
final Object o = constructor.newInstance(123);
System.out.println("o = " + o);

c'est pire que de simplement passer l'objet de classe
newacct

Vous n'avez pas du tout besoin de référencer explicitement le chargeur de classe.
Stefan Reich

-1

Voici une solution améliorée, basée sur ParameterizedType.getActualTypeArguments , déjà mentionnée par @noah, @Lars Bohl et quelques autres.

Première petite amélioration de l'implémentation. Factory ne doit pas renvoyer d'instance, mais un type. Dès que vous retournez une instance en utilisant, Class.newInstance()vous réduisez la portée de l'utilisation. Parce que seuls les constructeurs sans arguments peuvent être invoqués comme ceci. Une meilleure façon est de renvoyer un type et de permettre au client de choisir le constructeur qu'il souhaite invoquer:

public class TypeReference<T> {
  public Class<T> type(){
    try {
      ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass();
      if (pt.getActualTypeArguments() == null || pt.getActualTypeArguments().length == 0){
        throw new IllegalStateException("Could not define type");
      }
      if (pt.getActualTypeArguments().length != 1){
        throw new IllegalStateException("More than one type has been found");
      }
      Type type = pt.getActualTypeArguments()[0];
      String typeAsString = type.getTypeName();
      return (Class<T>) Class.forName(typeAsString);

    } catch (Exception e){
      throw new IllegalStateException("Could not identify type", e);
    }

  }
}

Voici quelques exemples d'utilisation. @Lars Bohl a montré seulement un moyen de signe pour obtenir générique réifié via l'extension. @noah uniquement via la création d'une instance avec {}. Voici des tests pour démontrer les deux cas:

import java.lang.reflect.Constructor;

public class TypeReferenceTest {

  private static final String NAME = "Peter";

  private static class Person{
    final String name;

    Person(String name) {
      this.name = name;
    }
  }

  @Test
  public void erased() {
    TypeReference<Person> p = new TypeReference<>();
    Assert.assertNotNull(p);
    try {
      p.type();
      Assert.fail();
    } catch (Exception e){
      Assert.assertEquals("Could not identify type", e.getMessage());
    }
  }

  @Test
  public void reified() throws Exception {
    TypeReference<Person> p = new TypeReference<Person>(){};
    Assert.assertNotNull(p);
    Assert.assertEquals(Person.class.getName(), p.type().getName());
    Constructor ctor = p.type().getDeclaredConstructor(NAME.getClass());
    Assert.assertNotNull(ctor);
    Person person = (Person) ctor.newInstance(NAME);
    Assert.assertEquals(NAME, person.name);
  }

  static class TypeReferencePerson extends TypeReference<Person>{}

  @Test
  public void reifiedExtenension() throws Exception {
    TypeReference<Person> p = new TypeReferencePerson();
    Assert.assertNotNull(p);
    Assert.assertEquals(Person.class.getName(), p.type().getName());
    Constructor ctor = p.type().getDeclaredConstructor(NAME.getClass());
    Assert.assertNotNull(ctor);
    Person person = (Person) ctor.newInstance(NAME);
    Assert.assertEquals(NAME, person.name);
  }
}

Remarque: vous pouvez forcer les clients de TypeReferencetoujours utiliser {}lorsque l' instance est créée en faisant cette classe abstraite: public abstract class TypeReference<T>. Je ne l'ai pas fait, seulement pour montrer le cas de test effacé.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.