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 .
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.
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.
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();
}
}
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));
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());
}
}
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:
Les inconvénients:
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.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 .
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
}
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);
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();
}
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();
}
}
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);
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();
}
}
Utilisez la classe TypeToken<T>
:
_public class MyClass<T> {
public T doSomething() {
return (T) new TypeToken<T>(){}.getRawType().newInstance();
}
}
_
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.
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();
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
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.
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++;
}
}
return (E)((Class)((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()[0]).newInstance();
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 );
}
}
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.
ce que tu peux faire c'est -
Commencez par déclarer la variable de cette classe générique
2.Alors en faire un constructeur et instancier cet objet
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é);
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.
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.
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é.