Question 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 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 peut voir quelque chose qui me manque:

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

EDIT: Il s'avère que Jetons Super Type pourrait être utilisé pour résoudre mon problème, mais il 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 a quelque chose de dramatiquement différent de celui de Ian Robertson Artima Article.


491
2017-09-16 18:04


origine


Réponses:


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

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

C'est une douleur. Mais ça fonctionne. En l'emballant dans le modèle d'usine le rend un peu plus tolérable.


285
2017-09-16 18:10



Ne sais pas si cela aide, mais lorsque vous sous-classe (y compris anonymement) un type générique, les informations de type sont disponibles par 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-classez 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 pratique cependant.


111
2017-09-16 18:18



Vous aurez besoin d’une sorte d’usine abstraite d’une sorte ou d’une autre pour passer le

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

81
2017-09-16 18:36



En Java 8, vous pouvez utiliser le Supplier interface fonctionnelle pour réaliser ceci 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 un 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));

73
2018-03-30 16:51



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

}

21
2017-09-03 12:41



Si vous avez besoin d'une nouvelle instance d'un argument de type dans une classe générique, faites en sorte que vos constructeurs exigent 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 consomme votre téléphone portable pour le 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 réglage, mais vous devez vous rappeler de lui appeler ultérieurement.
  • L'objection de Robertson ... Plus de barres qu'un mouton noir (bien que spécifier la classe d'argument de type une fois de plus ne vous tuera pas exactement). Et contrairement aux affirmations de Robertson, cela ne viole en aucun cas le principe DRY car le compilateur assurera l'exactitude du type.
  • Pas entièrement Foo<L>preuve. Pour commencer... newInstance() lancera un wobbler si la classe d'argument de type n'a pas de constructeur par défaut. Cela s'applique cependant à toutes les solutions connues.
  • Manque l'encapsulation totale de l'approche STT. Pas un gros problème cependant (compte tenu des coûts de production exorbitants de STT).

18
2018-01-07 07:11



Vous pouvez le faire maintenant et cela ne nécessite 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 qui nécessite une réflexion, mais qui est très bien documenté, cette astuce ne l'est pas!

Voici la JavaDoc pour TypeToken.


17
2017-08-08 02:03



Pensez à une approche plus fonctionnelle: au lieu de créer un E hors de rien (qui est clairement une odeur de code), passez une fonction qui sait en créer une, à savoir:

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

12
2018-04-16 16:20



De Tutoriel Java - 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);
}

En guise de solution de contournement, 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 de la manière suivante:

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

8
2017-09-13 13:12



Voici une option que j'ai trouvée, ça 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 également utiliser ce constructeur (mais il nécessite une instance de E):

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

6
2017-09-16 18:14