web-dev-qa-db-fra.com

Instanciation d’une classe générique dans Java

Je sais que les génériques de Java sont un peu inférieurs à ceux de .Net.

J'ai une classe générique Foo<T>, et j’ai vraiment besoin d’instancier un T dans Foo à l’aide d’un constructeur sans paramètre. Comment peut-on contourner la limitation de Java?

145
ripper234

Une option consiste à passer Bar.class (Ou le type de votre choix - n'importe quel moyen de spécifier la référence Class<T> Appropriée] et à conserver cette valeur sous forme de champ:

public class Test
{   
    public static void main(String [] args)
        throws Exception // Just for simplicity!
    {
        Generic<Bar> x = new Generic<Bar>(Bar.class);
        Bar y = x.buildOne();
    }
}

public class Generic<T>
{
    private Class<T> clazz;

    public Generic(Class<T> clazz)
    {
        this.clazz = clazz;
    }

    public T buildOne() throws InstantiationException,
        IllegalAccessException
    {
        return clazz.newInstance();
    }
}

public class Bar
{
    public Bar()
    {
        System.out.println("Constructing");
    }
}

Une autre option consiste à avoir une interface "factory" et à transmettre une fabrique au constructeur de la classe générique. C'est plus flexible et vous n'avez pas à vous soucier des exceptions de réflexion.

165
Jon Skeet

Et voici l’implémentation de Factory, comme Jon Skeet a suggéré :

interface Factory<T> {
    T factory();
}

class Araba {
    //static inner class for Factory<T> implementation
    public static class ArabaFactory implements Factory<Araba> {
        public Araba factory() {
            return new Araba();
        }
    }
    public String toString() { return "Abubeee"; }
}

class Generic<T> {
    private T var;

    Generic(Factory<T> fact) {
        System.out.println("Constructor with Factory<T> parameter");
        var = fact.factory();
    }
    Generic(T var) {
        System.out.println("Constructor with T parameter");
        this.var = var;
    }
    T get() { return var; }
}

public class Main {
    public static void main(String[] string) {
        Generic<Araba> gen = new Generic<Araba>(new Araba.ArabaFactory());
        System.out.print(gen.get());
    }
}

Sortie:

Constructor with Factory<T> parameter
Abubeee
47
Özgür

Voici une manière assez artificielle de le faire sans utiliser explicitement un argument de constructeur. Vous devez étendre une classe abstraite paramétrée.

public class Test {   
    public static void main(String [] args) throws Exception {
        Generic g = new Generic();
        g.initParameter();
    }
}

import Java.lang.reflect.ParameterizedType;
public abstract class GenericAbstract<T extends Foo> {
    protected T parameter;

    @SuppressWarnings("unchecked")
    void initParameter() throws Exception, ClassNotFoundException, 
        InstantiationException {
        // Get the class name of this instance's type.
        ParameterizedType pt
            = (ParameterizedType) getClass().getGenericSuperclass();
        // You may need this split or not, use logging to check
        String parameterClassName
            = pt.getActualTypeArguments()[0].toString().split("\\s")[1];
        // Instantiate the Parameter and initialize it.
        parameter = (T) Class.forName(parameterClassName).newInstance();
    }
}

public class Generic extends GenericAbstract<Foo> {
}

public class Foo {
    public Foo() {
        System.out.println("Foo constructor...");
    }
}
7
Glenn

J'ai vraiment besoin d'instancier un T dans Foo en utilisant un constructeur sans paramètre

La réponse simple est "vous ne pouvez pas faire cela" Java utilise un type d'effacement pour implémenter des génériques qui vous empêcherait de le faire.

Comment peut-on contourner la limitation de Java?

Une façon (il pourrait y en avoir d’autres) est de transmettre l’objet de passer l’instance de T au constructeur de Foo<T>. Ou vous pourriez avoir une méthode setBar(T theInstanceofT); pour obtenir votre T au lieu d'instancier lui-même dans la classe.

4
hhafez

Les génériques en Java sont généralement plus puissants qu'en C #.

Si vous voulez construire un objet sans configurer une méthode constructeur/statique, utilisez une fabrique abstraite. Vous devriez pouvoir trouver des informations détaillées et des didacticiels sur le motif Abstract Factory dans n’importe quel livre de motifs de conception de base, avec introduction à OOP ou sur l’ensemble des sites Web. Cela ne vaut pas la peine de dupliquer le code ici, sauf pour mentionner que la syntaxe de fermeture de Java est nulle.

IIRC, C # a un cas particulier pour spécifier qu'un type générique a un constructeur no-args. Cette irrégularité, par définition, présuppose que le code client souhaite utiliser cette forme de construction particulière et encourage la mutabilité.

Utiliser la réflexion pour cela est juste une erreur. Les génériques dans Java sont une fonctionnalité de typage statique au moment de la compilation. Toute tentative de les utiliser au moment de l'exécution indique clairement que quelque chose ne va pas. La réflexion provoque du code détaillé, des défaillances de l'exécution, des dépendances non vérifiées et la sécurité vulnérabilités. (Class.forName est particulièrement pervers.

2

Je pourrais le faire dans une configuration de test JUnit.

Je voulais tester une façade Hibernate, donc je cherchais un moyen générique de le faire. Notez que la façade implémente également une interface générique. Ici, T est la classe de base de données et U la clé primaire. Ifacade<T,U> est une façade permettant d'accéder à l'objet de base de données T avec la clé primaire U.

public abstract class GenericJPAController<T, U, C extends IFacade<T,U>>

{
    protected static EntityManagerFactory emf;

    /* The properties definition is straightforward*/
    protected T testObject;
    protected C facadeManager;

    @BeforeClass
    public static void setUpClass() {


        try {
            emf = Persistence.createEntityManagerFactory("my entity manager factory");

        } catch (Throwable ex) {
            System.err.println("Failed to create sessionFactory object." + ex);
            throw new ExceptionInInitializerError(ex);
        }

    }

    @AfterClass
    public static void tearDownClass() {
    }

    @Before
    public void setUp() {
    /* Get the class name*/
        String className = ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[2].getTypeName();

        /* Create the instance */
        try {
            facadeManager = (C) Class.forName(className).newInstance();
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException ex) {
            Logger.getLogger(GenericJPAController.class.getName()).log(Level.SEVERE, null, ex);
        }
        createTestObject();
    }

    @After
    public void tearDown() {
    }

    /**
     * Test of testFindTEntities_0args method, of class
     * GenericJPAController<T, U, C extends IFacade<T,U>>.
     * @throws Java.lang.ClassNotFoundException
     * @throws Java.lang.NoSuchMethodException
     * @throws Java.lang.InstantiationException
     * @throws Java.lang.IllegalAccessException
     */
    @Test
    public void  testFindTEntities_0args() throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException {

        /* Example of instance usage. Even intellisense (NetBeans) works here!*/
        try {
            List<T> lista = (List<T>) facadeManager.findAllEntities();
            lista.stream().forEach((ct) -> {
                System.out.println("Find all: " + stringReport());
            });
        } catch (Throwable ex) {
            System.err.println("Failed to access object." + ex);
            throw new ExceptionInInitializerError(ex);
        }
    }


    /**
     *
     * @return
     */
    public abstract String stringReport();

    protected abstract T createTestObject();
    protected abstract T editTestObject();
    protected abstract U getTextObjectIndex();
}
1
Krauss

De https://stackoverflow.com/a/2434094/848072 . Vous avez besoin d'un constructeur par défaut pour la classe T.


import Java.lang.reflect.ParameterizedType;

class Foo {

  public bar() {
    ParameterizedType superClass = (ParameterizedType) getClass().getGenericSuperclass();
    Class type = (Class) superClass.getActualTypeArguments()[0];
    try {
      T t = type.newInstance();
      //Do whatever with t
    } catch (Exception e) {
      // Oops, no default constructor
      throw new RuntimeException(e);
    } 
  }
}
1
albfan

Utilisez le Constructor.newInstance méthode. Le Class.newInstance Cette méthode est obsolète depuis Java 9 pour améliorer la reconnaissance par le compilateur des exceptions d'instanciation.

public class Foo<T> {   
    public Foo()
    {
        Class<T> newT = null; 
        instantiateNew(newT);
    }

    T instantiateNew(Class<?> clsT)
    {
        T newT;
        try {
            newT = (T) clsT.getDeclaredConstructor().newInstance();
        } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
            | InvocationTargetException | NoSuchMethodException | SecurityException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            return null;
        }
        return newT;
    }
}
0
Erik Ward Eeshwar Das

Pour Java 8 ....

Il existe une bonne solution à https://stackoverflow.com/a/36315051/2648077 post.

Ceci utilise Java 8 Supplier interface fonctionnelle

0
Alireza Fattahi

Solution rapide qui a fonctionné pour moi. Je vois qu’il existe déjà une solution à ce problème et que cela n’est peut-être même pas la meilleure façon de procéder. En outre, pour ma solution, vous aurez besoin de Gson .

Cependant, j'ai rencontré une situation où je devais créer une instance d'une classe générique de type Java.lang.reflect.Type.

Le code suivant créera une instance de la classe souhaitée avec des variables d'instance null.

T object = new Gson().fromJson("{}", myKnownType);

myKnownType est connu d'avance et est obtenu via TypeToken.getType().

Vous pouvez maintenant définir les propriétés appropriées sur cet objet. Encore une fois, ce n’est peut-être pas la meilleure façon de le faire mais cela fonctionne comme une solution rapide si c’est ce dont vous avez besoin.

0
Markymark