I have created a ValidatorUtils to validate collections based on the jakarta validation applied on the type to validate:
@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);
}
}
}
I have an interface CollectionValidator with a validate method, that by default validate a collection using the ValidatorUtils.validateAll method:
public interface CollectionValidator<REQUEST extends Collection<?>> {
public default void validate(REQUEST request) {
ValidatorUtils.validateAll(request);
}
}
when building the project, I have got the following error:
[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>)
to solve it, I have added TYPE_TO_VALIDATE as parameterized type on CollectionValidator:
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);
}
}
But why with wildcard is not working?
I will simplify your code a bit and just consider:
f
is basically yourValidatorUtils.validateAll
, andg
isCollectionValidator.validate
.What type is the type parameter
U
, when you callf(t)
?There is no answer to that question, if
t
is of typeT
. That is why you get the error "cannot infer type-variable(s)".If you want to know where in the spec this is specified, you can start with Invocation Applicability Inference, where the applicability of
f
is determined. At this step, a constraint formula ‹t
→Collection<α>
› is generated, where α is an inference variable representing the type parameterU
that we are trying to infer.‹t → α› then gets reduced to ‹
T
→Collection<α>
›, ‹T
<:Collection<α>
›, and finally ‹?
<= α›. You can follow the reduction steps in the Reduction section. At this point, ‹?
<= α› gets reduced to "false", causing the inference to fail.(S is the wildcard
?
and T is the inference variable α, in this case.)If you write
g
like this:Then it is fine, because type inference can infer that
U
should beE
. You can follow the inference steps above, and see that you get a ‹E
<= α› constraint this time, which gets reduced to ‹E
= α›, and becomes the boundE
= α.Bonus: why do these compile?
This is because of capture conversion. Capture conversion replaces the wildcards in a parameterised type with fresh type variables. For example, it can convert
Collection<?>
toCollection<CAP#1>
, whereCAP#1
is a fresh type variable (U
andT
are also type variables, butCAP#1
is a brand new type). Note that you cannot actually write this new type variable in Java code. I'm usingCAP#1
here because that is what javac usually calls them.Both
temp
and(Collection<?>)t
undergo capture conversion. See Cast Expressions and Simple Expression Names.So the expressions
temp
and(Collection<?>)t
are actually of typeCollection<CAP#1>
andCollection<CAP#2>
. There is no wildcards!CAP#1
andCAP#2
are both real types!U
can be inferred to beCAP#1
andCAP#2
in the two cases respectively, in the same way that it is inferred to beE
.t
in the original code is also subject to capture conversion, because it is a simple expression name. However, capture conversion only converts parameterised types likeCollection<?>
, not a type variable likeT
. In fact, it would be disastrous if type variables are also converted toCollection<CAP#1>
- you wouldn't be able to do something as simple as:Finally, your
validateAll
doesn't actually need to be generic.Note that I deliberately avoided writing the types of
toValidate
andviolations
, because their types contain type variables created by capture conversion.