type - java<>




在Java中創建泛型類型的實例? (16)

是否有可能在Java中創建一個泛型類型的實例? 我正在考慮基於我所看到的答案是no由於類型擦除 ),但是如果有人能看到我失踪的東西,我會感興趣:

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

編輯:事實證明, 超類型令牌可以用來解決我的問題,但它需要很多基於反射的代碼,因為下面的答案已經表明。

我將暫時擱置一會兒,看看是否有人提出與伊恩羅伯遜的Artima Article截然不同的東西


@諾亞答案的不足之處。

變化的原因

a]如果您更改了訂單,則使用多於1個泛型類型會更安全。

b]類的泛型類型簽名會隨時發生變化,這樣您就不會在運行時出現無法解釋的異常而感到驚訝。

健壯的代碼

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

或者使用一個班輪

一行代碼

model = ((Class<M>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[1]).newInstance();

Java不幸地不允許你想要做什麼。 請參閱http://docs.oracle.com/javase/tutorial/java/generics/restrictions.html#createObjects

您不能創建類型參數的實例。 例如,下面的代碼會導致編譯時錯誤:

public static <E> void append(List<E> list) {
    E elem = new E();  // compile-time error
    list.add(elem);
}

作為一種解決方法,您可以通過反射來創建類型參數的對象:

public static <E> void append(List<E> list, Class<E> cls) throws Exception {
    E elem = cls.newInstance();   // OK
    list.add(elem);
}

您可以按如下方式調用append方法:

List<String> ls = new ArrayList<>();
append(ls, String.class);

不知道,如果這有幫助,但是當你繼承(包括匿名)泛型時,類型信息可以通過反射獲得。 例如,

public abstract class Foo<E> {

  public E instance;  

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

}

所以,當你將Foo子類化時,你會得到一個Bar的實例,例如,

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

但是這是很多工作,只適用於子類。 可以很方便。


你是對的。 你不能做“新E”。 但你可以改變它

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

這是一個痛苦。 但它的工作。 用工廠模式包裝它使它更容易一些。


你需要某種形式的抽象工廠來傳遞:

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

在Java 8中,您可以使用Supplier功能界面輕鬆實現:

class SomeContainer<E> {
  private Supplier<E> supplier;

  SomeContainer(Supplier<E> supplier) {
    this.supplier = supplier;
  }

  E createContents() {
    return supplier.get();
  }
}

你會像這樣構造這個類:

SomeContainer<String> stringContainer = new SomeContainer<>(String::new);

該行上的語法String::new是一個構造函數引用

如果你的構造函數帶有參數,你可以使用lambda表達式代替:

SomeContainer<BigInteger> bigIntegerContainer
    = new SomeContainer<>(() -> new BigInteger(1));

如果你的意思是new E()那麼這是不可能的。 我想補充一點,它並不總是正確的 - 你怎麼知道E是否具有公共無參數構造函數? 但是你總是可以將創建委託給其他知道如何創建實例的Class<E> - 它可以是Class<E>或者你的自定義代碼

interface Factory<E>{
    E create();
}    

class IntegerFactory implements Factory<Integer>{    
  private static int i = 0; 
  Integer create() {        
    return i++;    
  }
}

如果你需要一個泛型類中的類型參數的新實例,然後讓你的構造函數要求它的類...

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();
        ...
    }
}

用法:

Foo<Bar> barFoo = new Foo<Bar>(Bar.class);
Foo<Etc> etcFoo = new Foo<Etc>(Etc.class);

優點:

  • 比Robertson的超級類型令牌(STT)方法更簡單(也更少問題)。
  • 比STT方法更有效(早餐會吃掉你的手機)。

缺點:

  • 無法將Class傳遞給默認的構造函數(這就是為什麼Foo是final)。 如果你確實需要一個默認構造函數,你總是可以添加一個setter方法,但是之後你必須記得給她打電話。
  • 羅伯遜的反對意見...更多酒吧比黑羊(儘管再次指定類型參數類不會完全殺死你)。 與羅伯遜的說法相反,這並不違反DRY主體,因為編譯器將確保類型的正確性。
  • 不完全是Foo<L>證明。 對於初學者來說...如果類型參數類沒有默認構造函數, newInstance()將會拋出一個晃動器。 儘管如此,這確實適用於所有已知的解決方案。
  • 缺乏STT方法的全部封裝。 沒有什麼大不了的(考慮到STT的巨大性能開銷)。

您可以使用類加載器和類名稱,最終還有一些參數。

final ClassLoader classLoader = ...
final Class<?> aClass = classLoader.loadClass("java.lang.Integer");
final Constructor<?> constructor = aClass.getConstructor(int.class);
final Object o = constructor.newInstance(123);
System.out.println("o = " + o);

您可以使用:

Class.forName(String).getConstructor(arguments types).newInstance(arguments)

但是你需要提供確切的類名,包括包,例如。 java.io.FileInputStream 。 我用它來創建一個數學表達式解析器。


想想更具功能性的方法:不要從無中創建E(這顯然是代碼味道),而是傳遞知道如何創建一個的函數,即

E createContents(Callable<E> makeone) {
     return makeone.call(); // most simple case clearly not that useful
}

我認為我可以這樣做,但相當失望:它不起作用,但我認為它仍然值得分享。

也許有人可以糾正:

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

    }
}

它產生:

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)

第26行是帶有[*]

唯一可行的解​​決方案是@JustinRudd


正如你所說,由於類型擦除你不能真正做到這一點。 你可以使用反射進行排序,但它需要大量的代碼和大量的錯誤處理。


當你在編譯時使用E時,你並不關心實際泛型類型“E”(無論是使用反射還是使用泛型類型的基類),所以讓子類提供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();
    }
}

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

}

return   (E)((Class)((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()[0]).newInstance();




generics