提问人:Joost den Boer 提问时间:11/16/2023 最后编辑:VGRJoost den Boer 更新时间:11/18/2023 访问量:95
Java 21 中泛型的不兼容更改
Incompatible changes in generics in Java 21
问:
有没有人也注意到 Java 21 中 Java 泛型的变化?
我们使用 Jdbi 进行数据库访问,当更新到 Java 21 时,由于无法再确定泛型类型,因此会出现问题。
在调试时,我注意到一个条件
java.lang.reflect.Type type = ...
if (type instanceof Class) {..}
其中 是接口在 Java 17 中计算的,但在 Java 21 中。type
false
true
Jdbi 依赖于 Geantyref 库来获取其某些泛型。静态方法 GenericTypeReflector.getTypeParameter(..) 在 Java 17 中返回泛型类型,但在 Java 21 中返回。null
在发行说明或其他公告中,我找不到任何关于泛型在 Java 21 中发生更改的信息。也没有向后兼容的中断性变更。
我假设由于记录反射 (JEP-440) 或开关的模式匹配 (JEP-441) 而可能已更改某些内部结构,但这些功能我已经用作 17 预览功能,没有任何问题。
有人知道是什么导致了这些变化,为什么?
更新 17-Nov-2023
我想我已经想通了。我将问题追溯到记录类的构造函数参数。 似乎在 Java 21 中,对于记录类,默认构造函数的参数不知何故没有泛型类型。 所以对于像这样的记录
record Person(Optional<String> name, Optional<Integer> age) {}
泛型类型,并在 Person 的默认构造函数中丢失。
现在 Java 生成的默认构造函数无法更改,但可以添加一个额外的构造函数:String
Integer
record Person(Optional<String> name, Optional<Integer> age) {
@JdbiConstructor
public Person(Optional<Integer> anAge, Optional<String> aName) {
this(aName, anAge);
}
}
请注意参数的交换以创建不同的构造函数,因为无法替换 Record 的默认构造函数。
通过添加注解(Jdbi 始终需要在 Record 上),Jdbi 被指示使用哪个构造函数。
因为这个构造函数现在显式定义了包括泛型类型在内的参数类型,所以反射实用程序能够确定类型,我让它与 Java 21 一起使用。@JdbiConstructor
在过去的 2 个小时里,我一直在尝试创建一个最小的测试用例,以验证泛型类型是否在 Record 的默认构造函数参数中丢失,但我无法在测试中重现它。即使它在我们的应用程序中始终失败。 我一直在比较类型参数,同时调试应用程序和测试。参数、值和类型看起来相同,但在 Java 21 中测试仍然成功。
我现在离开测试,因为我必须继续我的工作,我现在确实有一个可行的解决方法,即添加一个额外的构造函数。 我稍后会尝试回到这个问题。它必须在简单的测试中是可重复的。
答:
在应用一些更正后,我可以重现您的问题:
使用
时不会出现问题 但是既然你说,JDBI总是需要记录上的注释,我假设你提供了一个带注释的紧凑构造函数。然后,问题发生。record Person(Optional<String> name, Optional<Integer> age) {}
@JdbiConstructor
您的修复根本不起作用。当交换两个参数时,这两个参数的类型都是 ,你会得到一个与规范构造函数具有相同擦除的构造函数。正确的编译器不接受这一点,即使编译器接受了它,它也会在运行时中断。
Optional
无法修复规范构造函数是不正确的。您可以创建显式规范构造函数,而不是紧凑构造函数。
使用以下测试程序:
import java.util.Optional;
public class Reproducer {
interface NoConstructorDeclarations {
record Person(Optional<String> name, Optional<Integer> age) {}
}
interface AnnotatedCompactConstructor {
record Person(Optional<String> name, Optional<Integer> age) {
@Deprecated public Person {}
}
}
interface AnotatedExplicitCanonicalConstructor {
record Person(Optional<String> name, Optional<Integer> age) {
@Deprecated
public Person(Optional<String> name, Optional<Integer> age) {
this.name = name;
this.age = age;
}
}
}
public static void main(String args[]) {
for(var approach: Reproducer.class.getDeclaredClasses()) {
Class<?> recordClass = approach.getClasses()[0];
System.out.println(approach.getSimpleName());
var constructor = recordClass.getConstructors()[0];
System.out.println(constructor.isAnnotationPresent(Deprecated.class));
for(var p: constructor.getParameters()) {
System.out.println(p);
}
System.out.println();
}
}
}
从 JDK 21 中,我得到:javac
AnotatedExplicitCanonicalConstructor
true
java.util.Optional<java.lang.String> name
java.util.Optional<java.lang.Integer> age
AnnotatedCompactConstructor
true
java.util.Optional name
java.util.Optional age
NoConstructorDeclarations
false
java.util.Optional<java.lang.String> name
java.util.Optional<java.lang.Integer> age
它确认了问题并演示了解决方法。以前的 JDK 和 Eclipse 的编译器都不会出现此问题。javac
我用 代替 ,使示例独立于第三方库。@Deprecated
@JdbiConstructor
评论
@JdbiConstructor
record Person(Optional<String> name, Optional<Integer> age) {}