Ist es möglich, eine Instanz eines generischen Typs in Java zu erstellen? Ich denke, basierend auf dem, was ich gesehen habe, dass die Antwort no
ist (aufgrund von Typlöschung), aber es würde mich interessieren, wenn jemand etwas sieht, was mir fehlt:
class SomeContainer<E>
{
E createContents()
{
return what???
}
}
BEARBEITEN: Es stellt sich heraus, dass Super Type Tokens verwendet werden könnte, um mein Problem zu lösen, aber es erfordert eine Menge reflexionsbasierten Codes, wie einige der folgenden Antworten gezeigt haben.
Ich lasse das für eine Weile offen, um zu sehen, ob jemand etwas anderes als Ian Robertsons Artima-Artikel herausfindet.
Du hast Recht. Sie können new E()
nicht tun. Sie können es aber ändern
private static class SomeContainer<E> {
E createContents(Class<E> clazz) {
return clazz.newInstance();
}
}
Es ist ätzend. Aber es funktioniert. Das Einwickeln in das Fabrikmuster macht es ein wenig erträglicher.
Keine Ahnung, ob dies hilft, aber wenn Sie einen generischen Typ (einschließlich anonym) in eine Unterklasse unterteilen, sind die Typinformationen über Reflection verfügbar. z.B.,
public abstract class Foo<E> {
public E instance;
public Foo() throws Exception {
instance = ((Class)((ParameterizedType)this.getClass().
getGenericSuperclass()).getActualTypeArguments()[0]).newInstance();
...
}
}
Wenn Sie also die Unterklasse Foo wählen, erhalten Sie eine Instanz von Bar, z.
// notice that this in anonymous subclass of Foo
assert( new Foo<Bar>() {}.instance instanceof Bar );
Aber es ist viel Arbeit und funktioniert nur für Unterklassen. Kann aber praktisch sein.
Sie brauchen eine Art abstrakte Fabrik der einen oder anderen Art, um den Dollar weiterzugeben:
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();
}
}
In Java 8 können Sie die Funktion Supplier
verwenden, um dies ganz leicht zu erreichen:
class SomeContainer<E> {
private Supplier<E> supplier;
SomeContainer(Supplier<E> supplier) {
this.supplier = supplier;
}
E createContents() {
return supplier.get();
}
}
Sie würden diese Klasse folgendermaßen konstruieren:
SomeContainer<String> stringContainer = new SomeContainer<>(String::new);
Die Syntax String::new
in dieser Zeile ist eine Konstruktorreferenz .
Wenn Ihr Konstruktor Argumente akzeptiert, können Sie stattdessen einen Lambda-Ausdruck verwenden:
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());
}
}
Wenn Sie eine neue Instanz eines Typarguments in einer generischen Klasse benötigen, fordern Sie Ihre Konstruktoren dazu auf, ihre Klasse zu fordern.
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();
...
}
}
Verwendungszweck:
Foo<Bar> barFoo = new Foo<Bar>(Bar.class);
Foo<Etc> etcFoo = new Foo<Etc>(Etc.class);
Pros:
Nachteile:
Foo<L>
proof. Für den Anfang ... wirft newInstance()
einen Wobbler, wenn die Typargumentklasse keinen Standardkonstruktor hat. Dies gilt jedoch für alle bekannten Lösungen.Sie können dies jetzt tun, und es ist kein Haufen Reflexionscode erforderlich.
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()) {};
}
}
Wenn Sie den Konstruktor aufrufen müssen, der einige Überlegungen erfordert, aber das ist sehr gut dokumentiert, ist dieser Trick natürlich nicht!
Hier ist das JavaDoc für TypeToken .
Stellen Sie sich einen funktionaleren Ansatz vor: Anstatt etwas E aus dem Nichts zu erzeugen (was eindeutig ein Codegeruch ist), übergeben Sie eine Funktion, die weiß, wie man ein solches erstellt, d. H.
E createContents(Callable<E> makeone) {
return makeone.call(); // most simple case clearly not that useful
}
Aus Java-Tutorial - Einschränkungen für Generics :
Instanzen von Typparametern können nicht erstellt werden
Sie können keine Instanz eines Typparameters erstellen. Der folgende Code verursacht beispielsweise einen Fehler bei der Kompilierung:
public static <E> void append(List<E> list) {
E elem = new E(); // compile-time error
list.add(elem);
}
Als Problemumgehung können Sie ein Objekt mit einem Typparameter durch Reflektion erstellen:
public static <E> void append(List<E> list, Class<E> cls) throws Exception {
E elem = cls.newInstance(); // OK
list.add(elem);
}
Sie können die Append-Methode wie folgt aufrufen:
List<String> ls = new ArrayList<>();
append(ls, String.class);
Hier ist eine Option, die ich mir ausgedacht habe, es kann helfen:
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();
}
}
BEARBEITEN: Alternativ können Sie diesen Konstruktor verwenden (er erfordert jedoch eine Instanz von E):
@SuppressWarnings("unchecked")
public Container(E instance) {
this.clazz = (Class<E>) instance.getClass();
}
Wenn Sie den Klassennamen während der Instantiierung nicht zweimal eingeben möchten, wie in:
new SomeContainer<SomeType>(SomeType.class);
Sie können die Factory-Methode verwenden:
<E> SomeContainer<E> createContainer(Class<E> class);
Wie in:
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();
}
}
Java lässt leider nicht zu, was Sie tun möchten. Siehe den offiziellen Workaround :
Sie können keine Instanz eines Typparameters erstellen. Der folgende Code verursacht beispielsweise einen Fehler bei der Kompilierung:
public static <E> void append(List<E> list) {
E elem = new E(); // compile-time error
list.add(elem);
}
Als Problemumgehung können Sie ein Objekt mit einem Typparameter durch Reflektion erstellen:
public static <E> void append(List<E> list, Class<E> cls) throws Exception {
E elem = cls.newInstance(); // OK
list.add(elem);
}
Sie können die Append-Methode wie folgt aufrufen:
List<String> ls = new ArrayList<>();
append(ls, String.class);
Wenn Sie zur Kompilierzeit mit E arbeiten, kümmert es Sie nicht wirklich um den generischen Typ "E" (entweder verwenden Sie Reflection oder arbeiten mit der Basisklasse des generischen Typs).
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();
}
}
Verwenden Sie die Klasse TypeToken<T>
:
_public class MyClass<T> {
public T doSomething() {
return (T) new TypeToken<T>(){}.getRawType().newInstance();
}
}
_
Sie können verwenden:
Class.forName(String).getConstructor(arguments types).newInstance(arguments)
Sie müssen jedoch den genauen Klassennamen angeben, einschließlich der Pakete, z. Java.io.FileInputStream
. Ich habe damit einen Parser für mathematische Ausdrücke erstellt.
Eine Verbesserung von @ Noahs Antwort.
Änderungsgrund
a] Ist sicherer, wenn mehr als 1 generischer Typ verwendet wird, falls Sie die Reihenfolge geändert haben.
b] Eine generische Typensignatur der Klasse ändert sich von Zeit zu Zeit, so dass Sie nicht von unerklärlichen Ausnahmen in der Laufzeit überrascht werden.
Robuster Code
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);
}
}
}
}
Oder verwenden Sie den einen Liner
Einleitungscode
model = ((Class<M>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[1]).newInstance();
Ich dachte, ich könnte das, aber ziemlich enttäuscht: Es funktioniert nicht, aber ich denke, es lohnt sich trotzdem, es zu teilen.
Vielleicht kann jemand korrigieren:
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()+"]");
}
}
Es produziert:
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)
Zeile 26 ist die mit dem [*]
.
Die einzig gangbare Lösung ist die von @JustinRudd
Es gibt verschiedene Bibliotheken, die E
für Sie auflösen können, indem Sie Techniken verwenden, die denen des Robertson-Artikels ähneln. Hier ist eine Implementierung von createContents
, die TypeTools verwendet, um die durch E dargestellte rohe Klasse aufzulösen:
E createContents() throws Exception {
return TypeTools.resolveRawArgument(SomeContainer.class, getClass()).newInstance();
}
Dies setzt voraus, dass getClass () in eine Unterklasse von SomeContainer aufgelöst wird und andernfalls fehlschlägt, da der tatsächlich parametrierte Wert von E zur Laufzeit gelöscht wird, wenn er nicht in einer Unterklasse erfasst wird.
Wenn Sie "new E()
.__" meinen, ist dies unmöglich. Und ich würde hinzufügen, dass es nicht immer richtig ist - woher wissen Sie, ob E über einen öffentlichen No-Args-Konstruktor verfügt?. __ Sie können die Erstellung jedoch jederzeit an andere Klassen delegieren, die wissen, wie eine Instanz erstellt wird - Class<E>
oder Ihr benutzerdefinierter Code wie dieser
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();
Sie können dies mit dem folgenden Snippet erreichen:
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 );
}
}
Hier ist eine Implementierung von createContents
, die TypeTools verwendet, um die durch E
dargestellte Rohklasse aufzulösen:
E createContents() throws Exception {
return TypeTools.resolveRawArgument(SomeContainer.class, getClass()).newInstance();
}
Dieser Ansatz funktioniert nur, wenn SomeContainer
eine Unterklasse enthält, sodass der tatsächliche Wert von E
in einer Typdefinition erfasst wird:
class SomeStringContainer extends SomeContainer<String>
Andernfalls wird der Wert von E zur Laufzeit gelöscht und kann nicht wiederhergestellt werden.
was Sie tun können, ist -
Deklarieren Sie zuerst die Variable dieser generischen Klasse
2. Erstellen Sie dann einen Konstruktor und instanziieren Sie das Objekt
Verwenden Sie es dann überall dort, wo Sie es verwenden möchten
beispiel-
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ät);
Wie Sie gesagt haben, können Sie es wegen der Löschung von Typen nicht wirklich tun. Sie können dies mit Reflection tun, aber es erfordert viel Code und eine Menge Fehlerbehandlung.
Ich hoffe, es ist nicht zu spät um zu helfen !!!
Java ist typensicher, nur Object kann eine Instanz erstellen.
In meinem Fall kann ich keine Parameter an die createContents
-Methode übergeben. Meine Lösung verwendet Erweiterungen statt aller untenstehenden Antworten.
private static class SomeContainer<E extends Object> {
E e;
E createContents() throws Exception{
return (E) e.getClass().getDeclaredConstructor().newInstance();
}
}
Dies ist mein Beispiel, den ich nicht übergeben kann.
public class SomeContainer<E extends Object> {
E object;
void resetObject throws Exception{
object = (E) object.getClass().getDeclaredConstructor().newInstance();
}
}
Verwenden Sie Reflection-Laufzeitfehler, wenn Sie Ihre generische Klasse ohne Objekttyp erweitern. Um Ihren generischen Typ zu erweitern, konvertieren Sie diesen Fehler in einen Kompilierzeitfehler.
Hier ist eine verbesserte Lösung, basierend auf ParameterizedType.getActualTypeArguments
, die bereits von @noah, @Lars Bohl und einigen anderen erwähnt wurde.
Erste kleine Verbesserung in der Implementierung. Factory sollte keine Instanz, sondern einen Typ zurückgeben. Sobald Sie mit Class.newInstance()
eine Instanz zurückgeben, reduzieren Sie den Nutzungsumfang. Weil nur Konstruktoren ohne Argumente so aufgerufen werden können. Besser ist es, einen Typ zurückzugeben und einem Client die Auswahl zu erlauben, welchen Konstruktor er aufrufen möchte:
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);
}
}
}
Hier ist ein Anwendungsbeispiel. @Lars Bohl hat nur einen wichtigen Weg gezeigt, um durch die Erweiterung genitalisiert zu werden. @noah nur durch Erstellen einer Instanz mit {}
. Hier sind Tests, um beide Fälle zu demonstrieren:
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);
}
}
Hinweis: Sie können die Clients von TypeReference
zwingen, immer {}
zu verwenden, wenn die Instanz erstellt wird, indem Sie diese Klasse abstrakt machen: public abstract class TypeReference<T>
. Ich habe es nicht getan, nur um gelöschte Testfälle anzuzeigen.