我正在尝试做某事,但这可能不是解决问题的最佳方法,或者也许我只是在语言设计中缺少了一些东西。基本上,我有一些类似的服务:

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中完成此操作?

最佳答案

这将永远无法工作:

new MyService<clazz>()


其中clazz是变量。

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

类型参数是只对编译时间有影响的实体,其引入是为了限制参数的类型并在通用类和方法中返回值,如果代码可能在运行时传递错误类型的值,则它们将在编译时失败,时间。

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

事实是,限制返回的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);

关于java - 创建新实例时,将变量用于通用类型,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/42772701/

10-11 23:05
查看更多