it-swarm-fr.com

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

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

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

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

Je vais laisser la porte ouverte pour voir si quelqu'un propose quelque chose de radicalement différent de celui d'Ian Robertson Article d'Artima .

525
David Citron

Vous avez raison. Vous ne pouvez 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 pénible. Mais ça marche. Envelopper le modèle d'usine le rend un peu plus tolérable.

305
Justin Rudd

Je ne sais pas si cela aide, mais lorsque vous sous-classez (y compris de manière anonyme) un type générique, les informations de type sont disponibles via une 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 cela demande beaucoup de travail et ne fonctionne que pour les sous-classes. Peut être pratique si.

121
noah

Vous aurez besoin d’une sorte d’usine abstraite pour passer de l’argent à:

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

En Java 8, vous pouvez utiliser l'interface fonctionnelle Supplier 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 construiriez cette classe comme ceci:

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

La syntaxe String::new sur cette ligne est une référence de constructeur .

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

SomeContainer<BigInteger> bigIntegerContainer
    = new SomeContainer<>(() -> new BigInteger(1));
86
Daniel Pryden
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());
    }

}
23
Lars Bohl

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 au petit-déjeuner).

Les inconvénients:

  • Impossible de passer 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 alors 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 tuera pas exactement). Et contrairement à ce que prétend Robertson, cela ne viole pas le principe DRY de toute façon, car le compilateur assurera l'exactitude du type.
  • Pas entièrement Foo<L>proof. Pour commencer ... newInstance() lancera un wobbler si la classe d'argument de type n'a pas de constructeur par défaut. Ceci s’applique de toute façon à toutes les solutions connues.
  • Manque l'encapsulation totale de l'approche STT. Pas un gros problème cependant (compte tenu de la surcharge de performance scandaleuse de STT).
20
R2D2M2

Vous pouvez le faire maintenant et cela n'exige pas beaucoup 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 avez besoin d'appeler le constructeur, il faudra réfléchir, mais c'est très bien documenté, cette astuce ne l'est pas!

Voici le JavaDoc pour TypeToken .

19
user177800

Pensez à une approche plus fonctionnelle: au lieu de créer du E hors de rien (ce qui est clairement une odeur de code), transmettez une fonction qui sait comment en créer une, c'est-à-dire.

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

From Java Tutorial - Restrictions on Generics :

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

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

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

Pour contourner le 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 invoquer la méthode append comme suit:

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

Voici une option que j'ai proposée, elle 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: Vous pouvez aussi utiliser ce constructeur (mais cela nécessite une instance de E):

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

Si vous ne souhaitez pas saisir le nom de la classe deux fois lors de l'instanciation, procédez comme suit:

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
jb.

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 lors de la compilation:

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

Pour contourner le 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 invoquer la méthode append comme suit:

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

Lorsque vous travaillez avec E au moment de la compilation, le type générique réel "E" ne vous intéresse pas vraiment (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 l'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
Ira

Utilisez la classe TypeToken<T> :

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

Vous pouvez utiliser:

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

Mais vous devez fournir le nom exact de la classe, y compris les packages. Java.io.FileInputStream. J'ai utilisé cela pour créer un analyseur d'expressions mathématiques.

3
Jaroslav Smid

Un importation 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 modifié l'ordre.

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 au moment de l'exécution.

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 à une ligne

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

Je pensais que je pouvais le faire, mais très déçu: cela ne fonctionne pas, mais je pense que cela vaut encore 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
Luigi R. Viggiano

Plusieurs bibliothèques peuvent résoudre E en utilisant des techniques similaires à celles décrites dans l'article de Robertson. Voici une implémentation de createContents qui utilise TypeTools pour résoudre la classe brute représentée par E:

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

Ceci 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 à l'exécution si elle n'est pas capturée dans une sous-classe.

0
Jonathan

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

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

class IntegerFactory implements Factory<Integer>{    
  private static int i = 0; 
  Integer create() {        
    return i++;    
  }
}
0
Pavel Feldman
return   (E)((Class)((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()[0]).newInstance();
0
Rachid

Vous pouvez y parvenir avec l'extrait 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 );
   }
}
0
bogdan

Voici une implémentation de createContents qui 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 SomeContainer est sous-classé afin que la valeur réelle de E soit capturée dans une définition de type:

class SomeStringContainer extends SomeContainer<String>

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

0
Jonathan

ce que tu peux faire c'est -

  1. Commencez par déclarer la variable de cette classe générique

    2.Alors en faire un constructeur et instancier cet objet

  2. Ensuite, utilisez-le partout 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
Sudhanshu Jain

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

0
Adam Rosenfield

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

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

Dans mon cas, je ne peux pas transmettre de paramètres à la méthode createContents. Ma solution est d’étendre 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();
    }
}

Ceci est mon exemple de cas pour lequel je ne peux pas transmettre de paramètres.

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

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

L'utilisation de 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 à un objet, convertissez cette erreur en erreur de compilation. 

0
Se Song

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 dans la mise en œuvre. Factory ne devrait pas renvoyer d'instance, mais un type. Dès que vous renvoyez une instance en utilisant Class.newInstance(), vous réduisez le champ d’utilisation. Parce que seuls les constructeurs sans argument peuvent être invoqués comme ceci. Un meilleur moyen consiste à renvoyer un type et à permettre à un client de choisir le constructeur à 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 un exemple d'utilisation. @Lars Bohl n'a montré qu'un moyen efficace d'obtenir une extension générique réifiée. @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 TypeReference à toujours utiliser {} lorsque l'instance est créée en rendant cette classe abstraite: public abstract class TypeReference<T>. Je ne l'ai pas fait, seulement pour montrer le cas de test effacé. 

0
Alexandr