Create instance of generic type in Java?

2018-12-31 01:13发布

Is it possible to create an instance of a generic type in Java? I'm thinking based on what I've seen that the answer is no (due to type erasure), but I'd be interested if anyone can see something I'm missing:

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

EDIT: It turns out that Super Type Tokens could be used to resolve my issue, but it requires a lot of reflection-based code, as some of the answers below have indicated.

I'll leave this open for a little while to see if anyone comes up with anything dramatically different than Ian Robertson's Artima Article.

标签: java generics
24条回答
孤独寂梦人
2楼-- · 2018-12-31 02:11

You are correct. You can't do new E(). But you can change it to

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

It's a pain. But it works. Wrapping it in the factory pattern makes it a little more tolerable.

查看更多
泪湿衣
3楼-- · 2018-12-31 02:11

Dunno if this helps, but when you subclass (including anonymously) a generic type, the type information is available via reflection. e.g.,

public abstract class Foo<E> {

  public E instance;  

  public Foo() throws Exception {
    instance = ((Class)((ParameterizedType)this.getClass().
       getGenericSuperclass()).getActualTypeArguments()[0]).newInstance();
    ...
  }

}

So, when you subclass Foo, you get an instance of Bar e.g.,

// notice that this in anonymous subclass of Foo
assert( new Foo<Bar>() {}.instance instanceof Bar );

But it's a lot of work, and only works for subclasses. Can be handy though.

查看更多
余生无你
4楼-- · 2018-12-31 02:11

You'll need some kind of abstract factory of one sort or another to pass the buck to:

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();
    }
}
查看更多
查无此人
5楼-- · 2018-12-31 02:13

Think about a more functional approach: instead of creating some E out of nothing (which is clearly a code smell), pass a function that knows how to create one, i.e.

E createContents(Callable<E> makeone) {
     return makeone.call(); // most simple case clearly not that useful
}
查看更多
余生请多指教
6楼-- · 2018-12-31 02:14

If you need a new instance of a type argument inside a generic class then make your constructors demand its class...

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

Pros:

  • Much simpler (and less problematic) than Robertson's Super Type Token (STT) approach.
  • Much more efficient than the STT approach (which will eat your cellphone for breakfast).

Cons:

  • Can't pass Class to a default constructor (which is why Foo is final). If you really do need a default constructor you can always add a setter method but then you must remember to give her a call later.
  • Robertson's objection... More Bars than a black sheep (although specifying the type argument class one more time won't exactly kill you). And contrary to Robertson's claims this does not violate the DRY principal anyway because the compiler will ensure type correctness.
  • Not entirely Foo<L>proof. For starters... newInstance() will throw a wobbler if the type argument class does not have a default constructor. This does apply to all known solutions though anyway.
  • Lacks the total encapsulation of the STT approach. Not a big deal though (considering the outrageous performance overhead of STT).
查看更多
步步皆殇っ
7楼-- · 2018-12-31 02:14

I thought I could do that, but quite disappointed: it doesn't work, but I think it still worths sharing.

Maybe someone can correct:

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()+"]");

    }
}

It produces:

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)

Line 26 is the one with the [*].

The only viable solution is the one by @JustinRudd

查看更多
登录 后发表回答