Dupliquer: les génériques Java pourquoi cela ne fonctionnera pas
Je souhaite créer un objet de type générique en Java. S'il vous plaît suggérer comment puis-je obtenir la même chose.
Note: Cela peut sembler un problème trivial de génériques. Mais je parie… ça ne l'est pas. :)
supposons que j'ai la déclaration de classe en tant que:
public class Abc<T> {
public T getInstanceOfT() {
// I want to create an instance of T and return the same.
}
}
public class Abc<T> {
public T getInstanceOfT(Class<T> aClass) {
return aClass.newInstance();
}
}
Vous devrez ajouter la gestion des exceptions.
Vous devez transmettre le type réel au moment de l'exécution, car il ne fait pas partie du code d'octet après la compilation. Il est donc impossible de le savoir sans l'indiquer explicitement.
Dans le code que vous avez publié, il est impossible de créer une instance de T
car vous ne savez pas quel type est:
public class Abc<T>
{
public T getInstanceOfT()
{
// There is no way to create an instance of T here
// since we don't know its type
}
}
Bien sûr, il est possible que si vous avez une référence à Class<T>
et que T
ait un constructeur par défaut, appelez simplement newInstance()
sur l'objet Class
.
Si vous sous-classe Abc<T>
, vous pouvez même contourner le problème d'effacement de type et ne pas avoir à transmettre de références Class<T>
autour de:
import Java.lang.reflect.ParameterizedType;
public class Abc<T>
{
T getInstanceOfT()
{
ParameterizedType superClass = (ParameterizedType) getClass().getGenericSuperclass();
Class<T> type = (Class<T>) superClass.getActualTypeArguments()[0];
try
{
return type.newInstance();
}
catch (Exception e)
{
// Oops, no default constructor
throw new RuntimeException(e);
}
}
public static void main(String[] args)
{
String instance = new SubClass().getInstanceOfT();
System.out.println(instance.getClass());
}
}
class SubClass
extends Abc<String>
{
}
Ce que vous avez écrit n’a aucun sens, generics en Java sont censés ajouter la fonctionnalité de parametric polymorphism aux objets.
Qu'est-ce que ça veut dire? Cela signifie que vous voulez laisser certaines variables type de vos classes indécises, afin de pouvoir utiliser vos classes avec de nombreux types différents.
Mais votre variable typeT
est un attribut résolu au moment de l’exécution, le compilateur Java compilera votre classe en prouvant la sécurité du type sans essayer de savoir quel type d’objet est T
. Il vous sera donc impossible de laisser type variable dans une méthode statique. Le type est associé à une instance d'exécution de l'objet, alors que public void static main(..)
est associé à la définition de classe et qu'à cette étendue T
ne veut rien dire.
Si vous souhaitez utiliser une variable type dans une méthode statique, vous devez déclarer la méthode générique (car, comme expliqué précédemment, les variables type d'une classe de modèle sont liées à son instance d'exécution. ), pas la classe:
class SandBox
{
public static <T> void myMethod()
{
T foobar;
}
}
cela fonctionne, mais bien sûr pas avec la méthode main
puisqu'il n'y a aucun moyen de l'appeler de manière générique.
EDIT: Le problème est qu’en raison de type erasure, une seule classe générique est compilée et transmise à la machine virtuelle Java. Le vérificateur de types vérifie simplement si le code est sûr, puisqu’il en a fait la preuve, tous les types d’informations génériques sont ignorés.
Pour instancier T
, vous devez connaître le type de T
, mais il peut s'agir de plusieurs types à la fois. Une solution nécessitant un minimum de réflexion consiste donc à utiliser Class<T>
pour instancier de nouveaux objets:
public class SandBox<T>
{
Class<T> reference;
SandBox(Class<T> classRef)
{
reference = classRef;
}
public T getNewInstance()
{
try
{
return reference.newInstance();
}
catch (Exception e)
{
e.printStackTrace();
}
return null;
}
public static void main(String[] args)
{
SandBox<String> t = new SandBox<String>(String.class);
System.out.println(t.getNewInstance().getClass().getName());
}
}
Bien sûr, cela implique que le type que vous voulez instancier:
Pour utiliser différents types de constructeurs, vous devez approfondir votre réflexion.
Vous devez obtenir les informations de type de manière statique. Essaye ça:
public class Abc<T> {
private Class<T> clazz;
public Abc(Class<T> clazz) {
this.clazz = clazz;
}
public T getInstanceOfT()
throws InstantiationException, IllegalAccessException {
return clazz.newInstance();
}
}
Utilisez-le comme tel:
Abc<String> abc = new Abc<String>(String.class);
abc.getInstanceOfT();
Selon vos besoins, vous voudrez peut-être utiliser Class<? extends T>
à la place.
Le seul moyen de le faire fonctionner est d'utiliser Reified Generics . Et cela n’est pas supporté en Java (c’était encore prévu pour Java 7, mais il a été reporté). En C # par exemple, il est pris en charge en supposant que T
a un constructeur default. Vous pouvez même obtenir le type d'exécution par typeof(T)
et obtenir les constructeurs par Type.GetConstructor()
. Je ne fais pas C # donc la syntaxe est peut-être invalide, mais ça ressemble à ça:
public class Foo<T> where T:new() {
public void foo() {
T t = new T();
}
}
La meilleure solution de contournement pour cela en Java consiste à passer un Class<T>
comme argument de méthode, comme plusieurs réponses l’ont déjà indiqué.
Tout d'abord, vous ne pouvez pas accéder au paramètre de type T
dans la méthode principale statique, uniquement sur des membres de classe non statiques (dans ce cas).
Deuxièmement, vous ne pouvez pas instancier T
car Java implémente les génériques avec Type Erasure. Presque toutes les informations génériques sont effacées au moment de la compilation.
En gros, vous ne pouvez pas faire ceci:
T member = new T();
Voici un tutoriel de Nice sur génériques .
Vous ne semblez pas comprendre le fonctionnement des génériques . Vous pouvez aussi regarder http://Java.Sun.com/j2se/1.5.0/docs/guide/language/generics.html Fondamentalement, ce que vous pouvez faire est comme
public class Abc<T>
{
T someGenericThing;
public Abc(){}
public T getSomeGenericThing()
{
return someGenericThing;
}
public static void main(String[] args)
{
// create an instance of "Abc of String"
Abc<String> stringAbc = new Abc<String>();
String test = stringAbc.getSomeGenericThing();
}
}
Je mettais en œuvre la même chose en utilisant l'approche suivante.
public class Abc<T>
{
T myvar;
public T getInstance(Class<T> clazz) throws InstantiationException, IllegalAccessException
{
return clazz.newInstance();
}
}
J'essayais de trouver un meilleur moyen d'atteindre le même objectif.
N'est-ce pas possible?
Inspiré par la réponse de @ martin , j'ai écrit une classe d'assistance qui me permet de contourner le problème de l'effacement des types. En utilisant cette classe (et un truc moche), je peux créer une nouvelle instance à partir d'un type de modèle:
public abstract class C_TestClass<T > {
T createTemplateInstance() {
return C_GenericsHelper.createTemplateInstance( this, 0 );
}
public static void main( String[] args ) {
ArrayList<String > list =
new C_TestClass<ArrayList<String > >(){}.createTemplateInstance();
}
}
Le truc moche est de rendre la classe abstract
afin que l’utilisateur de la classe soit obligé de la sous-taper. Ici, je le sous-classe en ajoutant {}
après l'appel au constructeur. Cela définit une nouvelle classe anonyme et en crée une instance.
Une fois que la classe générique est sous-typée avec des types de modèles concrets, je suis en mesure de récupérer les types de modèles.
public class C_GenericsHelper {
/**
* @param object instance of a class that is a subclass of a generic class
* @param index index of the generic type that should be instantiated
* @return new instance of T (created by calling the default constructor)
* @throws RuntimeException if T has no accessible default constructor
*/
@SuppressWarnings( "unchecked" )
public static <T> T createTemplateInstance( Object object, int index ) {
ParameterizedType superClass =
(ParameterizedType )object.getClass().getGenericSuperclass();
Type type = superClass.getActualTypeArguments()[ index ];
Class<T > instanceType;
if( type instanceof ParameterizedType ) {
instanceType = (Class<T > )( (ParameterizedType )type ).getRawType();
}
else {
instanceType = (Class<T > )type;
}
try {
return instanceType.newInstance();
}
catch( Exception e ) {
throw new RuntimeException( e );
}
}
}
On dirait que vous essayez de créer la classe qui sert de point d’entrée générique à votre application et que cela ne fonctionnera pas ... La JVM ne saura pas quel type elle est supposée utiliser lorsqu'elle est instanciée en tant que vous démarrez l'application.
Cependant, s'il s'agissait d'un cas plus général, vous rechercheriez ce que vous cherchez:
public MyGeneric<MyChoiceOfType> getMeAGenericObject(){
return new MyGeneric<MyChoiceOfType>();
}
ou peut-être:
MyGeneric<String> objMyObject = new MyGeneric<String>();
Il y a de bonnes manières de contourner cela lorsque vous devez vraiment le faire.
Voici un exemple de méthode de transformation que je trouve très utile. et fournit un moyen de déterminer la classe concrète d’un générique.
Cette méthode accepte une collection d'objets en tant qu'entrée et renvoie un tableau dans lequel chaque élément est le résultat de l'appel d'un getter de champ sur chaque objet de la collection d'entrée. Par exemple, supposons que vous avez un List<People>
et que vous voulez un String[]
contenant le nom de famille de tout le monde.
Le type de la valeur de champ renvoyée par le getter est spécifié par le générique E
et je dois instancier un tableau de type E[]
pour stocker la valeur de retour.
La méthode elle-même est un peu moche, mais le code que vous écrivez qui l'utilise peut être beaucoup plus propre.
Notez que cette technique ne fonctionne que lorsque quelque part dans les arguments d'entrée, il existe un objet dont le type correspond au type de retour, et vous pouvez le déterminer de manière déterministe. Si les classes concrètes de vos paramètres d'entrée (ou leurs sous-objets) ne peuvent rien vous dire sur les génériques, cette technique ne fonctionnera pas.
public <E> E[] array (Collection c) {
if (c == null) return null;
if (c.isEmpty()) return (E[]) EMPTY_OBJECT_ARRAY;
final List<E> collect = (List<E>) CollectionUtils.collect(c, this);
final Class<E> elementType = (Class<E>) ReflectionUtil.getterType(c.iterator().next(), field);
return collect.toArray((E[]) Array.newInstance(elementType, collect.size()));
}
Le code complet est disponible ici: https://github.com/cobbzilla/cobbzilla-utils/blob/master/src/main/Java/org/cobbzilla/util/collection/FieldTransformer.Java#L28