【发布时间】:2019-04-02 01:25:06
【问题描述】:
上下文
我正在从事一个严重依赖泛型类型的项目。它的一个关键组件是所谓的TypeToken,它提供了一种在运行时表示泛型类型并在其上应用一些实用函数的方法。为了避免 Java 的类型擦除,我使用大括号表示法 ({}) 来创建一个自动生成的子类,因为这使得类型可具体化。
TypeToken 的基本作用
这是TypeToken 的一个高度简化的版本,比原来的实现要宽松得多。不过,我使用这种方法是为了确保真正的问题不在于这些实用程序函数之一。
public class TypeToken<T> {
private final Type type;
private final Class<T> rawType;
private final int hashCode;
/* ==== Constructor ==== */
@SuppressWarnings("unchecked")
protected TypeToken() {
ParameterizedType paramType = (ParameterizedType) this.getClass().getGenericSuperclass();
this.type = paramType.getActualTypeArguments()[0];
// ...
}
什么时候起作用
基本上,这种实现几乎适用于所有情况。 处理大多数类型没有问题。以下示例完美运行:
TypeToken<List<String>> token = new TypeToken<List<String>>() {};
TypeToken<List<? extends CharSequence>> token = new TypeToken<List<? extends CharSequence>>() {};
由于它不检查类型,因此上面的实现允许编译器允许的每种类型,包括 TypeVariables。
<T> void test() {
TypeToken<T[]> token = new TypeToken<T[]>() {};
}
在这种情况下,type 是一个 GenericArrayType,将 TypeVariable 作为其组件类型。这完全没问题。
使用 lambda 时的奇怪情况
但是,当您在 lambda 表达式中初始化 TypeToken 时,情况开始发生变化。 (类型变量来源于上面的test函数)
Supplier<TypeToken<T[]>> sup = () -> new TypeToken<T[]>() {};
在这种情况下,type 仍然是 GenericArrayType,但它持有 null 作为其组件类型。
但是,如果您正在创建一个匿名内部类,事情又开始发生变化:
Supplier<TypeToken<T[]>> sup = new Supplier<TypeToken<T[]>>() {
@Override
public TypeToken<T[]> get() {
return new TypeToken<T[]>() {};
}
};
在这种情况下,组件类型再次保持正确的值(TypeVariable)
产生的问题
- lambda 示例中的 TypeVariable 会发生什么情况?为什么类型推断不尊重泛型类型?
- 显式声明的示例和隐式声明的示例有什么区别?类型推断是唯一的区别吗?
- 如何在不使用样板显式声明的情况下解决此问题?这在单元测试中变得尤为重要,因为我想检查构造函数是否抛出异常。
澄清一下:这不是与程序“相关”的问题,因为我根本不允许不可解析的类型,但它仍然是我想了解的有趣现象。
我的研究
更新 1
与此同时,我对这个主题进行了一些研究。在Java Language Specification §15.12.2.2 中,我发现了一个可能与它有关的表达式——“与适用性相关”,提到“隐式类型的 lambda 表达式”是一个例外。很明显,这是错误的章节,但是这个表达式在其他地方使用,包括关于类型推断的章节。
但老实说:我还没有真正弄清楚像 := 或 Fi0 这样的所有这些运算符的含义是什么让我很难详细理解它。如果有人能澄清一下,如果这可能是对奇怪行为的解释,我会很高兴。
更新 2
我再次考虑了这种方法并得出结论,即使编译器会删除该类型,因为它与“适用性无关”,也没有理由将组件类型设置为 null最慷慨的类型,对象。我想不出语言设计者决定这样做的单一原因。
更新 3
我刚刚使用最新版本的 Java 重新测试了相同的代码(我之前使用过 8u191)。令我遗憾的是,这并没有改变任何东西,尽管 Java 的类型推断得到了改进......
更新 4
几天前我在官方的 Java Bug Database/Tracker 中申请了一个条目,它刚刚被接受。由于审查我的报告的开发人员将优先级 P4 分配给了该错误,因此可能需要一段时间才能修复它。您可以找到报告here。
对 Tom Hawtin 大喊大叫 - 提到这可能是 Java SE 本身的一个重要错误。但是,由于 Mike Strobel 令人印象深刻的背景知识,他的报告可能会比我的更详细。然而,当我写报告时,Strobel 的答案还没有出现。
【问题讨论】:
-
您不能泛泛地声明类型标记并期望它们做任何有用的事情。如果可以,就不需要类型标记。如果你想要一个泛型类型标记,你必须传入一个用非泛型代码构造的实例。
-
我完全知道上面的构造函数没有解析类型变量,但这不是
TypeToken的意图。它应该保存不同的通用定义类型信息并对其进行一些操作。显然,不可能知道 T 是哪种类型,我只是想知道为什么在从 lambda 调用时无法访问 TypeVariable 信息,但是当我从显式声明的匿名内部类调用相同的构造函数时可以这样做。 -
TypeVarialbe 信息是指 java.lang.reflect 类的表示,而不是实际的类型信息。
-
这个问题比我预期的要有趣得多,它暴露了编译器和核心反射 API 中的一系列明显的长期存在的错误。运气好的话,我们至少会看到它们在 JDK 11 中得到修复,甚至有望向后移植到 JDK 8。感谢发帖!
标签: java generics lambda java-8