无法推断类型变量 - 通配符与泛型

cannot infer type-variable(s) - Wildcard vs Generic

提问人:Paul Marcelin Bejan 提问时间:11/16/2023 最后编辑:Paul Marcelin Bejan 更新时间:11/17/2023 访问量:51

问:

我创建了一个 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);
    }
    
}

但是为什么使用通配符不起作用?

Java 泛型无 界通配符

评论

0赞 Sweeper 11/16/2023
“为什么”并不是一个可以回答的问题。本来可以有这样的转变,但就是没有。你在寻找什么样的答案?顺便说一句,有多种方法可以解决这个问题。
0赞 Paul Marcelin Bejan 11/16/2023
在问题的最后,我已经提供了问题的解决方案,我只是问为什么不使用通配符,而只使用泛型。
0赞 Sweeper 11/16/2023
正如我所说,“为什么”是一个很难回答的问题,特别是因为我不知道什么样的答案会让你满意。“Java 不是这样设计的”是对“为什么”的回答。“Java 语言规范没有说这是可能的”也是一个答案。对于我给出的任何答案,你可以继续问“但为什么会这样?这就是为什么你要清楚你在寻找什么样的答案是很重要的,这样我就知道什么样的答案会让你满意。
0赞 Sweeper 11/17/2023
好的,Java 语言规范确实指定了这一点。你想从中得到一些引述作为答案吗?

答:

1赞 Sweeper 11/17/2023 #1

我将稍微简化您的代码,并考虑:

static <U> void f(Collection<U> c) {}

static <T extends Collection<?>> void g(T t) {
    f(t); // error
}

f基本上是你的,并且是.ValidatorUtils.validateAllgCollectionValidator.validate

调用 f(t) 时,类型参数 U 是什么类型?

这个问题没有答案,如果是 类型 。这就是您收到错误“无法推断类型变量”的原因。tT

如果你想知道这在规范中的哪个位置被指定,你可以从调用适用性推理开始,其中的适用性是确定的。在此步骤中,将生成约束公式 ‹ → ›,其中 α 是一个推理变量,表示我们尝试推断的类型参数。ftCollection<α>U

‹t → α› 然后减少到 ‹ → ›, ‹ <: ›,最后 ‹ <= α›。您可以按照“减少”部分中的减少步骤进行操作。此时,‹ <= α› 被简化为“false”,导致推理失败。TCollection<α>TCollection<α>??

形式为 ‹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);
}

那就没问题了,因为类型推断可以推断出应该是 。您可以按照上面的推理步骤操作,并看到这次得到一个 ‹ <= α› 约束,该约束被简化为 ‹ = α›,并成为绑定 = α。UEEEE


奖励:为什么要编译这些?

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#1UTCAP#1CAP#1

两者都经过捕获转换。请参阅强制转换表达式和简单表达式名称temp(Collection<?>)t

如果表达式名称出现在赋值上下文、调用上下文或强制转换上下文中,则表达式名称的类型是捕获转换后字段、局部变量或参数的声明类型。

强制转换表达式的类型是将捕获转换应用于此目标类型的结果。

所以表达式 和 实际上是 和 类型。没有通配符! 并且都是真实的类型! 在这两种情况下,可以分别推断为 和,其推断方式与推断为 是 的方式相同。temp(Collection<?>)tCollection<CAP#1>Collection<CAP#2>CAP#1CAP#2UCAP#1CAP#2E

t在原始代码中也要进行捕获转换,因为它是一个简单的表达式名称。但是,捕获转换仅转换参数化类型(如 ),而不转换类型变量(如 )。事实上,如果类型变量也被转换为 - 您将无法执行以下简单操作:Collection<?>TCollection<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);
    }
}

请注意,我刻意避免编写 和 的类型,因为它们的类型包含由捕获转换创建的类型变量。toValidateviolations