【问题标题】:Instantiate generic class from a String variable (Java) [duplicate]从字符串变量(Java)实例化泛型类
【发布时间】:2016-06-01 13:55:20
【问题描述】:

我正在尝试用 Java 实例化一个给定字符串的类。像这样的:

// Pseudocode
String name = "MyClass";
SomeClass<name> sc = new SomeClass<name>(parameter);

在我的例子中,构造函数采用Class&lt;E&gt; 类型。所以我会通过MyClass.class。理想情况下,获取字符串并附加.class。结果是这样的:

SomeClass&lt;name&gt; sc = new SomeClass&lt;name&gt;(name.class);

我在这里查看答案:Create new class from a Variable in Java 但无法使其正常工作。使用该链接中的信息,我尝试了这个:

String name = "MyClass";
Class className = Class.forName(name);
className param1;
Constructor con = className.getConstructor(className.class);
SomeClass<className> sc = con.newInstance(param1);

当然,这是行不通的。我将如何实现这一目标?

【问题讨论】:

  • 你的目标构造函数的签名是什么?
  • 方法签名为:public SomeClass(Class&lt;E&gt; type) {}
  • 好吧,说真的:您学习了 Oracle 提供的关于如何进行反射的优秀教程。然后你继续努力。问题是:反射非常复杂。但是当你只是拿起一些别人为你写的代码时,你将无法掌握它。含义:所有这些都记录在案;通过研究这些资源,您将了解更多信息;而不是让其他人向你解释。
  • 你能解释一下为什么你认为你需要这个吗?
  • @SotiriosDelimanolis 我正在使用一些 XML 文件进行一些自动化测试。有很多用于解组的类,所以我想要一种方法让开发人员只传递他们想要解组 XML 的类的名称。它节省了大量的编码和修改。

标签: java string class generics


【解决方案1】:

泛型主要是一种编译时工具,可帮助编译器发现转换错误。由于类型擦除,大多数通用信息在运行时都会丢失——除了反射数据中的一些基本信息(这在这里对你没有帮助)。

假设您的构造函数如下所示:

public SomeClass( Class<T> param ) { ... }

由于在运行时类型擦除,它看起来像这样(当被调用时):

public SomeClass( Class param ) { ... }

如您所见,没有通用信息,因此您可以使用 SomeClass.getConstructor( Class.class )

然后您可以使用newInstance( Class.forName("java.lang.String") ) 调用它。 您再次看到,您基本上可以将 any 类作为参数传递,这是由于泛型是编译时工具,即 compiler 在应用这些检查时运行时信任编译后的代码。

如果您需要使用任何边界,您需要使用不同的方法或检查构造函数内部传递的正确类(例如,使用方法 Class#isAssignableFrom())。

将创建的实例分配给变量时,您需要使用通配符,即SomeClass&lt;?&gt; sc,或原始类型,即SomeClass sc(我建议使用通配符)。当然,编译器不允许您调用仅存在于某些类中的任何方法(例如在MyClass 中),因为它根本不知道(并且它不能仅从名称中知道类定义) .

编辑:

希望能够澄清最后一部分,我将尝试添加另一个示例。

假设MyClass 有一个方法foo() 并且SomeClass 有一个方法T getTypeInstance()。如果你的变量类型是SomeClass&lt;MyClass&gt; sc,你可以调用sc.getTypeInstance().foo(),因为编译器知道T绑定到MyClass

但是,当使用反射时,信息会丢失,也就是说,由于您必须使用 SomeClass&lt;?&gt; sc,编译器将不知道 getTypeInstance() 的返回类型,因此不允许调用 foo()

如果您知道可以作为参数传递的所有类都实现了相同的接口,那么您可以执行类似SomeClass&lt;CommonInterface&gt; sc 这样的操作,因为Constructor#newInstance() 返回原始类型,因此编译器检查在此处被禁用。在这种情况下,您可以将 foo() 添加到 CommonInterface 并能够调用它 - 至少除非您破坏代码。

上面的问题是您也可以将String.class 传递给构造函数,编译器和运行时会允许这样做,但由于String 没有实现CommonInterface,这仍然是错误的。因此,您在尝试创建实例时会遇到异常,将其强制转换为 CommonInterface 或在其上调用 foo() - 除非您在构造函数中添加一个检查,该检查拒绝任何不是可分配给 @987654349 的类的参数@(例如,通过检查 CommonInterface.class.isAssignableFrom( param ))。

【讨论】:

  • 谢谢,这真的很有帮助。因此,从您的意思来看,如果我继续使用反射,编译器将不知道这些方法?对不起,如果我误解了。
  • @Flow 我不确定我是否正确理解了您的问题,因此我将添加有关如何解释您的问题的更新。我希望这能让它更清楚。
  • 我不确定您所说的 Class#isAssignableFrom()Constructor#newInstance() 是什么意思。我以前从未见过这种符号。无论如何,我确实知道所有可以通过的类。但是,我需要在界面中放入太多方法。我用它来解组 XML 文件,所以会有很多类和方法。不过谢谢你,我只需要想一个新的方法来实现我的目标。
  • @Flow Class#isAssignableFrom(...) 是类Class 中的JavaDoc 表示法(或至少类似)含义方法isAssignableFrom(...)。我通常使用它来不将其与静态方法混淆,例如String.valueOf(...) 表示String 类中的静态方法,而String#length() 将是length() 方法,不知道它是否是静态的。
【解决方案2】:

假设你的泛型类看起来像这样:

public GenericClass(Class<T> param) {
    System.out.println(param.getName());
}

您的代码可能如下所示:

@SuppressWarnings("unchecked")
public static void main(String[] args) throws Exception {
    String type = "java.lang.String";
    Class g = GenericClass.class;
    Constructor c = g.getConstructor(Class.class);
    GenericClass<?> genericClass = (GenericClass<?>) c.newInstance(Class.forName(type));
}

您会在控制台中看到它打印“java.lang.String”。

这是可行的,因为在反射中,一切都必须有类型,并且由于泛型在编译中没有类型,它显示为java.lang.Class,而不是java.lang.Class&lt;T&gt;。因此,要获取构造函数,您必须告诉它找到带有类对象的构造函数。然后,只需使用Class.forName 找到type 中的类,并调用构造函数,传入该类。

【讨论】:

  • 当我这样做时我得到ClassNotFoundException。我假设这是因为托马斯的上述解释。尝试从GenericClass 调用方法无法完成,因为它在编译时不知道类。
【解决方案3】:

我在您的代码中发现了一些错误:

什么是:className param1;?您不能将 className 用作类 Class 的对象作为类型。

调用:

Constructor con = className.getConstructor(className.class);

返回类“className”的构造函数,其参数类型为“className”。这是你的要求吗? (例如,如果您的类是ArrayList,则构造函数采用ArrayList 类型的1 个参数)

代码:

SomeClass<className> sc = con.newInstance(param1);

语法不正确。方法newInstance 返回Object 类型为T。尝试将其分配给Object 类型的变量。


你的代码应该是这样的:

String name = "MyClass";
Class className = Class.forName(name);
String param1 = "something";
// Using a constructor that use a String as parameter
Constructor con = className.getConstructor(String.class); 
Object sc = con.newInstance(param1);

【讨论】:

  • 我正在关注链接帖子中的代码。我认为Param1Type 是您希望参数成为的类,因为在我的代码中,Param1Type 无法解析。我基本上想这样做:SomeClass&lt;AnotherClass&gt; sc = new SomeClass&lt;AnotherClass&gt;(AnotherClass.class)AnotherClass 将是给定的字符串。
  • 是的....你是对的。谢谢 Sotirios。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-18
  • 1970-01-01
  • 1970-01-01
  • 2012-11-30
相关资源
最近更新 更多