Java 21 中泛型的不兼容更改

Incompatible changes in generics in Java 21

提问人:Joost den Boer 提问时间:11/16/2023 最后编辑:VGRJoost den Boer 更新时间:11/18/2023 访问量:95

问:

有没有人也注意到 Java 21 中 Java 泛型的变化?

我们使用 Jdbi 进行数据库访问,当更新到 Java 21 时,由于无法再确定泛型类型,因此会出现问题。

在调试时,我注意到一个条件

java.lang.reflect.Type type = ...
if (type instanceof Class) {..}

其中 是接口在 Java 17 中计算的,但在 Java 21 中。typefalsetrue

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 生成的默认构造函数无法更改,但可以添加一个额外的构造函数:StringInteger

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 中测试仍然成功。

我现在离开测试,因为我必须继续我的工作,我现在确实有一个可行的解决方法,即添加一个额外的构造函数。 我稍后会尝试回到这个问题。它必须在简单的测试中是可重复的。

Java 泛型 JDBI JAVA-21

评论

1赞 Gimby 11/16/2023
根据 JDBI github 页面,他们自己对 Java 21 运行 CI 测试,因此不会真正备份您立即将您看到的问题视为 JDK 问题。github.com/jdbi/jdbi .您在这里跳过了几个步骤。就像创建错误报告一样。
0赞 Sören 11/17/2023
在创建错误报告之前,您可能希望创建一个最小的可重现示例
0赞 dan1st 11/17/2023
你能在没有任何库的情况下创建一个最小的可重现示例吗?如果没有,请尝试使用最少的库创建它,并在问题中包含完整的最小示例。
3赞 VGR 11/17/2023
该构造函数不应编译。方法签名在擦除后必须是唯一的,这意味着一个类只能有一个 (Optional, Optional) 构造函数。事实上,它不会为我编译。
1赞 Holger 11/18/2023
如果 Jdbi 始终要求在记录上添加注释,则发布的声明与要求不匹配。正如其他人所说,您应该发布一个最小的可重现示例@JdbiConstructorrecord Person(Optional<String> name, Optional<Integer> age) {}

答:

6赞 Holger 11/18/2023 #1

在应用一些更正后,我可以重现您的问题:

  • 使用

    时不会出现问题 但是既然你说,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

评论

0赞 Steven Schlansker 11/18/2023
感谢@Holger找到复制者。有人可能应该将其作为错误提交给 JDK 人员 - 他们通常相当认真地对待不正确的编译。由于这是您的代码,因此您首先会得到 dibs,否则其他人应该:)
0赞 Steven Schlansker 11/18/2023
PS,我们仍然使用您在 2016 年分享的记忆供应商实施,所以谢谢你!
0赞 Joost den Boer 11/20/2023
感谢您通过一个简单的测试用例来解决这个问题。