Java 中的未绑定泛型返回类型

ddi*_*rov 5 java generics api-design

我正在考虑一些关于构建 Java API 的想法,今天我花了一些时间思考这段代码:

public <T> T getField1(Class<?> type, Class<T> retvalType, String fieldName) {
  Object retval = ...; // boring reflection code - value could be dog, could be duck
  return retvalType.cast(retval); // cast to expected - exception is raised here
}

// usage - we manually repeat the LHS type as a parameter
int foo = getField(o, Integer.class, "foo");
Run Code Online (Sandbox Code Playgroud)

这就是我通常编写 API 的方式,但他认为指定 可以retvalType给我额外的类型安全性。但确实如此吗?

因为我们涉及到反射,所以 retval 类型是未知的。retvalType仅当is 非泛型时,在第二行上进行强制转换才能可靠地捕获类型不匹配。另一方面,即使我们不执行这种显式转换,Java 运行时在将通用结果设置为变量时也会对通用结果进行转换 - 换句话说,如果我们将上面的代码重写为:

public <T> T getField2(Class<?> type, String fieldName) {
  return ...; // boring reflection code - the value type will be checked when we assign it
}

// usage - exception is raised at the next line if types don't match
int foo = getField(o, "foo");
Run Code Online (Sandbox Code Playgroud)

后一种代码的优点是用法更加紧凑,我们不必重复。缺点是类型转换异常来自不明显的地方(调用站点没有显式转换),但我开始认为这是可以的 - 通过指定类型文字,我们只重复我们已经知道的内容,但最终由于反射,程序的健全性仍然无法保证。

有人能很好地论证为什么第一个片段比第二个片段更好吗?


编辑1:

一个论点是,通过将预期类型作为参数,我们可以在不更改 API 的情况下满足进行重要强制转换的需要。

例如,查找的字段可能是类型Date,但我们可能请求的是long,可以通过执行内部转换date.getTime()。不过,这对我来说似乎有些牵强,而且存在混合责任的风险。

另一方面,不传递类型会限制输入依赖性,这使得 API 对更改更加稳健。(提示“信息隐藏”、“不稳定指标”等)。

ddi*_*rov 1

在与 @shmosel 的对话中,我们发现当 LHS 类型是泛型类型时,第二个版本所依赖的运行时转换不一定会发生。

例如,考虑以下代码:

class Foo<T> {
  void a1(Object o) {
    // forces to be explicit about the chance of heap polution
    @SuppressWarning("unchecked")
    Set<String> a = (Set<String>) getField1(o, Set.class, "foo");
    T c = (T) getField1(o, Set.class, "foo"); // guaranteed not to compile
  }

  void a2(Object o) {
    // implicit chance of heap polution in case of Set<Date>
    Set<String> a = (Set<String>) getField2(o, "foo");
    T c = getField2(o, "foo"); // will succeed even if foo is Date
  }
}
Run Code Online (Sandbox Code Playgroud)

换句话说,通过在getField()方法中进行转换,我们可以硬性保证该值属于指定的类,即使它没有被使用,或者分配给一个对象变量(这在动态语言和反射库中经常发生) 。