创建新实例时,使用变量作为泛型类型

时间:2017-03-13 20:19:39

标签: java generics

我正在尝试做某事,这可能不是最好的方法,或者我只是在语言设计中遗漏了一些东西。基本上,我有一些看起来像这样的服务:

public class MyService<T extends MyAbstractClass>{}

作为我服务器的配置,我想指定我用作环境变量的MyAbstractClass的扩展名。在我的具体用例中,它基本上是为我正在做的事情设置一个特定的数据源,但这在这里并不太重要。

我以为我可能会做这样的事情,但是它给了我语法错误,所以显然不允许这样做。我不确定我是否可以做我想要完成的事情。

@Bean(name="myService")
public MyService<? extends MyAbstractClass> getMyService(){
    String packageName = "com.my.project." + ConfigUtils.get(env, "mypackage") + "";
    Class<? extends MyAbstractClass> clazz = (Class<? extends MyAbstractClass>) Class.forName(packageName);
    return new MyService<clazz>();
}

我一直在寻找答案,但我甚至不确定如何在搜索引擎中正确地说出这一点来找到它。我应该如何在java中尝试实现这一目标?

2 个答案:

答案 0 :(得分:1)

这永远不会奏效:

new MyService<clazz>() 

其中clazz是变量。

new调用中的类型参数只能是封闭方法或类的另一个类型参数(如果封闭方法不是静态的)或具体的类名。

类型参数是仅对编译时有影响的实体,为了限制参数类型和在泛型类和方法中返回值而引入,如果代码可能传递错误类型的值,则在编译时失败在运行时。

clazz的值只能在运行时确定,因此编译器无法确定编译时实际类的内容。这就是为什么使用包含变量的类引用作为类型参数参数是不合法的也没有任何意义。

事实是,很难限制返回的MyService实例的底层类型参数是什么并返回 MyService<? extends MyAbstractClass>和它一样好。

至少你可以仔细检查clazz实际上是否扩展了MyAbstractService而不是简单地相信是这样的:

class ServiceClass {}
class MyService<T extends ServiceClass> {}

class Main {

   public MyService<? extends ServiceClass> getMyService() throws Exception {
      final String className = "com.my.project." + ConfigUtils.get(env, "mypackage");
      @SuppressWarnings("unchecked")
      final Class<? extends ServiceClass> clazz = (Class<? extends ServiceClass>) Class.forName(className);
      if (!ServiceClass.class.isAssignableFrom(clazz))
         throw new RuntimeException("bad configuration blah blah");
      return new MyService<ServiceClass>();
   }
}

请注意new MyService<ServiceClass>()似乎不是很整洁,因为实际上clazz通常会是其他东西; ServiceClass的后代,而不是ServiceClass本身...这个编译没有错误的原因是,到目前为止,给定代码,与返回的类型参数无关,因为在运行时它们都被转换为相同的代码。

但是,在更复杂的上下文中,当您想要对某些对象执行某些操作时,如何引用相同的类型参数,您将开始遇到编译时问题。因此,始终建议使用来自现场的更整洁的基于类型参数的解决方案:

class ServiceClass {}
class MyService<T extends ServiceClass> {}

class Main {

   public <S extends ServiceClass> MyService<S> getMyService() throws Exception {
      final String className = "com.my.project." + ConfigUtils.get(env, "mypackage");
      @SuppressWarnings("unchecked")
      final Class<S> clazz = (Class<S>) Class.forName(className);
      if (!ServiceClass.class.isAssignableFrom(clazz))
         throw new RuntimeException("bad configuration blah blah");
      return new MyService<S>();
   }
}

现在我认为你不需要在MyService中的某个地方保留对服务类的引用是很奇怪的...也许你最终需要编写如下代码:

class ServiceClass {}

class MyService<S extends ServiceClass> {
  private final Class<S> clazz;
  MyService(Class<S> clazz) {
     this.clazz = clazz;
  }

  Class<S> serviceClass() {
    return clazz;
  }
}

class Main {
  public <S extends ServiceClass> MyService<S> getMyService() throws Exception {
    final String className = "com.my.project." + ConfigUtils.get(env, "mypackage");
    @SuppressWarnings("unchecked")
    final Class<S> clazz = (Class<S>) Class.forName(className);
    if (!ServiceClass.class.isAssignableFrom(clazz))
       throw new RuntimeException("bad configuration blah blah");
    return new MyService<S>(clazz);
  }
}

此时S中的类型参数getMyService开始变得必要。

从Java 7开始(我认为)你不需要在新语句中指定S,这将由编译器推断:

return new MyService<>(clazz); 

答案 1 :(得分:-1)

您不能在方法声明中使用通配符。而是使用另一个类型变量并声明它。

public <S extends MyAbstractClass> S getMyService() {
    // implementation omitted
}

请记住,在这种情况下,S与类声明中的T不同。这就是为什么我通常给他们不同的名字。

在方法体中,您可以使用以下内容:

Class<S> clazz = (Class<S>)Class.forName(packageName);
return clazz.newInstance();