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:
fis basically yourValidatorUtils.validateAll, andgisCollectionValidator.validate.What type is the type parameter
U, when you callf(t)?There is no answer to that question, if
tis 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
fis determined. At this step, a constraint formula ‹t→Collection<α>› is generated, where α is an inference variable representing the type parameterUthat 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
glike this:Then it is fine, because type inference can infer that
Ushould 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#1is a fresh type variable (UandTare also type variables, butCAP#1is a brand new type). Note that you cannot actually write this new type variable in Java code. I'm usingCAP#1here because that is what javac usually calls them.Both
tempand(Collection<?>)tundergo capture conversion. See Cast Expressions and Simple Expression Names.So the expressions
tempand(Collection<?>)tare actually of typeCollection<CAP#1>andCollection<CAP#2>. There is no wildcards!CAP#1andCAP#2are both real types!Ucan be inferred to beCAP#1andCAP#2in the two cases respectively, in the same way that it is inferred to beE.tin 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
validateAlldoesn't actually need to be generic.Note that I deliberately avoided writing the types of
toValidateandviolations, because their types contain type variables created by capture conversion.