提问人:Paul Marcelin Bejan 提问时间:11/16/2023 最后编辑:Paul Marcelin Bejan 更新时间:11/17/2023 访问量:51
无法推断类型变量 - 通配符与泛型
cannot infer type-variable(s) - Wildcard vs Generic
问:
我创建了一个 ValidatorUtils 来根据应用于要验证的类型的 jakarta 验证来验证集合:
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ValidatorUtils {
private static final Validator VALIDATOR = Validation.buildDefaultValidatorFactory().getValidator();
/**
* Validate all the elements of the collection, then if at least one was not valid, a ConstraintViolationException is thrown with all the violations encountered.
*/
public static <TO_VALIDATE> void validateAll(Collection<TO_VALIDATE> collectionToValidate) {
Set<ConstraintViolation<TO_VALIDATE>> allViolations = new HashSet<>();
for (TO_VALIDATE toValidate : collectionToValidate) {
Set<ConstraintViolation<TO_VALIDATE>> violations = VALIDATOR.validate(toValidate);
allViolations.addAll(violations);
}
if (!allViolations.isEmpty()) {
throw new ConstraintViolationException(allViolations);
}
}
}
我有一个带有 validate 方法的接口 CollectionValidator,默认情况下使用 ValidatorUtils.validateAll 方法验证集合:
public interface CollectionValidator<REQUEST extends Collection<?>> {
public default void validate(REQUEST request) {
ValidatorUtils.validateAll(request);
}
}
构建项目时,我收到以下错误:
[ERROR] Compilation failure
[ERROR] CollectionValidator.java:[11,31] method validateAll in class ValidatorUtils cannot be applied to given types;
[ERROR] required: java.util.Collection<TO_VALIDATE>
[ERROR] found: REQUEST
[ERROR] reason: cannot infer type-variable(s) TO_VALIDATE
[ERROR] (argument mismatch; REQUEST cannot be converted to java.util.Collection<TO_VALIDATE>)
为了解决这个问题,我在 CollectionValidator 上添加了TYPE_TO_VALIDATE作为参数化类型:
public interface CollectionValidator<TYPE_TO_VALIDATE, REQUEST extends Collection<TYPE_TO_VALIDATE>> extends Validator<REQUEST> {
@Override
public default void validate(REQUEST request) {
ValidatorUtils.validateAll(request);
}
}
但是为什么使用通配符不起作用?
答:
我将稍微简化您的代码,并考虑:
static <U> void f(Collection<U> c) {}
static <T extends Collection<?>> void g(T t) {
f(t); // error
}
f
基本上是你的,并且是.ValidatorUtils.validateAll
g
CollectionValidator.validate
调用 f(t)
时,类型参数 U
是什么类型?
这个问题没有答案,如果是 类型 。这就是您收到错误“无法推断类型变量”的原因。t
T
如果你想知道这在规范中的哪个位置被指定,你可以从调用适用性推理开始,其中的适用性是确定的。在此步骤中,将生成约束公式 ‹ → ›,其中 α 是一个推理变量,表示我们尝试推断的类型参数。f
t
Collection<α>
U
‹t → α› 然后减少到 ‹ → ›, ‹ <: ›,最后 ‹ <= α›。您可以按照“减少”部分中的减少步骤进行操作。此时,‹ <= α› 被简化为“false”,导致推理失败。T
Collection<α>
T
Collection<α>
?
?
形式为 ‹S <= T› 的约束公式,其中 S 和 T 是类型 参数 (§4.5.1) 简化如下:
如果 T 是类型:
如果 S 是类型,则约束减少为 ‹S = T›。
如果 S 是通配符,则约束将减少为 false。
(在本例中,S 是通配符,T 是推理变量 α。?
如果你这样写:g
static <E, T extends Collection<E>> void g(T t) {
f(t);
}
那就没问题了,因为类型推断可以推断出应该是 。您可以按照上面的推理步骤操作,并看到这次得到一个 ‹ <= α› 约束,该约束被简化为 ‹ = α›,并成为绑定 = α。U
E
E
E
E
奖励:为什么要编译这些?
static <T extends Collection<?>> void g(T t) {
Collection<?> temp = t;
f(temp); // OK
f((Collection<?>)t); // OK
}
这是因为捕获转换。捕获转换将参数化类型中的通配符替换为新的类型变量。例如,它可以转换为 ,其中 是一个新类型变量(也是类型变量,但是一个全新的类型)。请注意,您实际上无法在 Java 代码中编写此新类型变量。我在这里使用是因为这是 javac 通常对它们的称呼。Collection<?>
Collection<CAP#1>
CAP#1
U
T
CAP#1
CAP#1
两者都经过捕获转换。请参阅强制转换表达式和简单表达式名称。temp
(Collection<?>)t
如果表达式名称出现在赋值上下文、调用上下文或强制转换上下文中,则表达式名称的类型是捕获转换后字段、局部变量或参数的声明类型。
强制转换表达式的类型是将捕获转换应用于此目标类型的结果。
所以表达式 和 实际上是 和 类型。没有通配符! 并且都是真实的类型! 在这两种情况下,可以分别推断为 和,其推断方式与推断为 是 的方式相同。temp
(Collection<?>)t
Collection<CAP#1>
Collection<CAP#2>
CAP#1
CAP#2
U
CAP#1
CAP#2
E
t
在原始代码中也要进行捕获转换,因为它是一个简单的表达式名称。但是,捕获转换仅转换参数化类型(如 ),而不转换类型变量(如 )。事实上,如果类型变量也被转换为 - 您将无法执行以下简单操作:Collection<?>
T
Collection<CAP#1>
class C<T extends Collection<?>> {
void one(T a) { }
void two(T b) {
// b would be type Collection<CAP#1> here, and you would not be able to call one
one(b);
}
}
最后,你实际上不需要是通用的。validateAll
static void validateAll(Collection<?> collectionToValidate) {
Set<ConstraintViolation<?>> allViolations = new HashSet<>();
for (var toValidate : collectionToValidate) {
var violations = VALIDATOR.validate(toValidate);
allViolations.addAll(violations);
}
if (!allViolations.isEmpty()) {
throw new ConstraintViolationException(allViolations);
}
}
请注意,我刻意避免编写 和 的类型,因为它们的类型包含由捕获转换创建的类型变量。toValidate
violations
评论