在Java中实例化泛型类型

74
我想在Java中创建一个泛型类型的对象。请建议如何实现。
注意:这可能看起来是一个微不足道的泛型问题。但我敢打赌…它并不是。 :)
假设我有以下类声明:
public class Abc<T> {
    public T getInstanceOfT() {
       // I want to create an instance of T and return the same.
    }
}

你是如何知道“T”是什么的?程序的哪一部分会“告诉”你使用哪个T? - seh
1
我还建议查看泛型常见问题解答:http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html - Hosam Aly
12个回答

81
public class Abc<T> {
    public T getInstanceOfT(Class<T> aClass) {
       return aClass.newInstance();
    }
}

您需要添加异常处理。

在运行时,您必须传递实际类型,因为它不是编译后的字节码的一部分,所以没有办法在不显式提供的情况下知道它。


8
你如何使用这个方法?换句话说,调用会是什么样子?T newT = this.getInstance(T.class) 无法编译。 - SMBiggs
aClass.newInstance(); - Valeria Khusnulina
5
自Java 9起,clazz.newInstance()已经被弃用,可以使用clazz.getDeclaredConstructor().newInstance()代替。 - Gerold Broser

28
在您发布的代码中,由于不知道T是什么类型,因此无法创建T的实例:
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
       }
} 

当然,如果您有对`Class<T>`的引用并且`T`具有默认构造函数,则可以调用`Class`对象上的`newInstance()`方法。如果您子类化了`Abc<T>`,甚至可以解决类型擦除问题,并且不必传递任何`Class<T>`引用:
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>
{
}

你的理由是错误的,因为同样的理由也可以适用于 Class<T>,但在这里我们可以创建一个实例。 - Konrad Rudolph
不同意你的观点。我已经能够达到相同的结果了..请检查我的答案。 - Mohd Farid
当然,如果您知道T的类型(通过Class<T>)并且该类型具有默认构造函数,则可以实例化T。但是,在最初发布的代码中并非如此。 - Martin
1
非常棒!这是一个获取泛型实例的最佳解决方案,而不需要使用new关键字。这里发布的大多数答案都会抛出空指针异常,否则就需要传递.class并使用new关键字。你的方法真是太棒了,谢谢! - TechDog
1
clazz.newInstance()已在Java 9中被弃用:可以使用clazz.getDeclaredConstructor().newInstance()来替代。 - Gerold Broser

15
你写的内容没有任何意义,Java中的泛型是为了给对象添加参数多态的功能。
这是什么意思呢?这意味着您希望保持一些类的类型变量未定,以便能够使用许多不同类型的类。
但是你的类型变量T是一个在运行时解析的属性,Java编译器将编译您的类以证明类型安全性,而不试图知道T是什么样的对象,因此它不可能让您在静态方法中使用类型变量。类型与运行时对象实例相关联,而public void static main(..)与类定义相关联,在该范围内,T没有任何意义。
如果您想在静态方法中使用类型变量,必须将方法声明为通用方法(因为,如前所述,模板类的类型变量与其运行时实例相关),而不是类:
class SandBox
{
  public static <T> void myMethod()
  {
     T foobar;
  }
}

这个可以工作,但是当没有通用方法调用时,就不能使用main方法了。
编辑:问题在于由于类型擦除,只编译并传递了一个通用类到JVM中。类型检查器只检查代码是否安全,然后因为它证明了每种通用信息都被丢弃了。
要实例化T,你需要知道T的类型,但它可以同时是许多类型,所以一种仅需要最少量反射的解决方案是使用Class<T>来实例化新对象:
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());
    }
}

当然,这意味着您想要实例化的类型:
- 不是原始类型 - 它有一个默认构造函数
要使用不同类型的构造函数,您需要深入了解反射。

3
在.NET中,这是完全合理的,因为泛型的概念与Java非常相似。唯一的真正区别在于,.NET没有类型擦除,这就是技术上的区别,使这样的代码适用于.NET而不适用于Java。 - Konrad Rudolph
请忽略它从一个主方法中调用的问题。我已经更新了代码。现在,我们有一个 getInstanceOfT 方法。 - Mohd Farid
2
Konrad,.NET具有类型推断功能,即使您没有明确说明,它也知道您在谈论什么。例如,IEnumerable上的许多函数都是通用类型函数,但通常不必指定钻石运算符,因为.NET会推断其通用类型。 我真的希望Java也能这么聪明。此外,在.NET中,您可以简单地调用default(T);Activator.CreateInstance(typeof(T)); - Felype
那么像ArrayList和HashMap这样的东西是如何工作的 - 它们实例化您的T类的实例,而无需了解更多信息呢? - Mikepote
4
他们不会实例化对象,而是提供功能来存储对已创建的对象的引用。你不让ArrayList创建T实例,只需存储先前分配的T引用即可。 - Jack
实际上,.Net 在编译时也不知道 T 到底代表什么,但在运行时会得到解决,因此在 .net 中,Type genericType = typeof(T) 是完全有意义的,这将在运行时解析,并且编译器不会对该行代码产生任何影响。 现在根据我在这里调试的内容,似乎 Java 甚至在运行时也不知道 T 代表什么,就是完全一无所知。 - Felype

8

您需要静态获取类型信息。请尝试以下方法:

public class Abc<T> {
    private Class<T> clazz;

    public Abc(Class<T> clazz) {
        this.clazz = clazz;
    }

    public T getInstanceOfT()
            throws throws InstantiationException,
                 IllegalAccessException,
                 IllegalArgumentException,
                 InvocationTargetException,
                 NoSuchMethodException,
                 SecurityException {
        return clazz.getDeclaredConstructor().newInstance();
    }
}

使用方法如下:

        Abc<String> abc = new Abc<String>(String.class);
        abc.getInstanceOfT();

根据您的需求,您可能希望使用 Class<? extends T>


2
clazz.newInstance()已在Java 9中被弃用:可以使用clazz.getDeclaredConstructor().newInstance()来替代。 - Gerold Broser
1
谢谢您提醒,@GeroldBroserreinstatesMonica。我已经更新了示例。 - Hosam Aly

5
唯一的解决方法是使用具体化泛型。然而,Java目前还不支持这一功能(或许在Java 7中有计划支持,但已经被推迟)。例如,在C#中,只要T有一个默认构造函数,就支持此功能。你甚至可以通过typeof(T)获取运行时类型,并通过Type.GetConstructor()获取构造函数。我不熟悉C#语法,但它大致如下:
public class Foo<T> where T:new() {
    public void foo() {
        T t = new T();
    }
}

在Java中,最好的解决方法是将Class<T>作为方法参数传递,而不是像其他答案已经指出的那样。

Reified Generics添加到Java是否已关闭?https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8061418 - user7294900

4

首先,在静态的main方法中无法访问类型参数T,只能在非静态类成员中访问(在这种情况下)。

其次,你无法实例化T,因为Java使用类型擦除实现泛型。几乎所有的泛型信息都在编译时被擦除了。

基本上,你不能这样做:

T member = new T();

这是一个关于泛型的好教程。


首先,在静态main方法中无法访问类型参数T,只能在非静态类成员中访问(在这种情况下)。 ---同意--- 因此,我已更新代码以具有非静态方法。但其他原因并不太相关。 - Mohd Farid

1

当你真的必须这样做时,有一些变通的方法。

以下是一个我发现非常有用的转换方法的示例;它提供了一种确定泛型具体类的方法。

此方法接受对象集合作为输入,并返回一个数组,其中每个元素是在输入集合中调用字段getter的结果。例如,假设您有一个 List<People> ,并且您想要一个包含所有人姓氏的 String[]

getter返回的字段值类型由泛型 E 指定,我需要实例化类型为 E[] 的数组以存储返回值。

该方法本身有点丑陋,但使用它的代码可以更加简洁。

请注意,此技术仅在输入参数中的某个对象的类型与返回类型匹配,并且您可以确定地找出它时才起作用。如果您的输入参数(或其子对象)的具体类无法告诉您有关泛型的信息,则此技术将无法正常工作。

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

完整的代码在这里: https://github.com/cobbzilla/cobbzilla-utils/blob/master/src/main/java/org/cobbzilla/util/collection/FieldTransformer.java#L28


1
你似乎不理解泛型的工作原理。 你可以查看http://java.sun.com/j2se/1.5.0/docs/guide/language/generics.html 基本上,你可能会做类似这样的事情。
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();
  }

} 

11
你已经完美地叙述了问题。唯一缺少的是...我们在哪里初始化someGenericThing? - Mohd Farid
同意Mohd的观点,stringAbc.getSomeGenericThing()实际上是null。 - eric2323223
是的,我忘记添加那一部分了。你当然需要设置/初始化一些你想要的T,例如在Abc构造函数中。或者通过Setter方法。但Mohd的问题与我发布的内容无关(至少在他发布了一些评论之后)。 - Tedil

1

我是用以下方法来实现相同的功能的。

public class Abc<T>
{
   T myvar;

    public T getInstance(Class<T> clazz) throws InstantiationException, IllegalAccessException
    {
        return clazz.newInstance();
    }
}

我试图找到更好的方法来实现同样的目标。

这不可能吗?


2
最好在构造函数中接收clazz(请查看我的答案)。如果您在方法中执行此操作,则几乎没有意义,因为它只是调用someObject.getClass().newInstance()的快捷方式,似乎并没有什么作用。即使您决定以这种方式执行此操作,也不需要泛型类;静态泛型方法就足够了。 - Hosam Aly
clazz.newInstance()已在Java 9中被弃用:可以使用clazz.getDeclaredConstructor().newInstance()来替代。 - Gerold Broser

1

类型擦除解决方案

受到@martin的回答的启发,我编写了一个帮助类,使我能够解决类型擦除问题。使用这个类(和一些不太优美的技巧),我能够创建一个模板类型的新实例:

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

这里的巧妙技巧是将类声明为 abstract,以便类的使用者被迫对其进行子类型化。在这里,我通过在调用构造函数后附加 {} 来对它进行了子类化。这定义了一个新的匿名类并创建了它的实例。
一旦使用具体模板类型对泛型类进行了子类型化,我就能够检索模板类型。
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 );
        }
    }
}

clazz.newInstance()已在Java 9中被弃用:可以使用clazz.getDeclaredConstructor().newInstance()来替代。 - Gerold Broser

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接