Java generics, type removal, wildcards, and Function produce incompatible types
Another rookie question, sorry.
Let’s consider the following code:
public class ExceptionHandler {
simple internal manager
@FunctionalInterface
private interface ExceptionManager<D extends Exception> {
int getErrorCode(D e, WebRequest request,
HttpServletRequest servletRequest);
}
One field, just for the illustration
(TypeMismatchException came from spring framework)
private ExceptionManager<TypeMismatchException> tmeManager =
(ex, req, servletRequest) -> {
int errorCode = 0;
// ...
return errorCode;
};
A simple "factory" for an ExceptionManager
private Function<? extends Exception,
Optional<ExceptionManager<? extends Exception>>> factory = (ex) -> {
if(ex instanceof TypeMismatchException) {
return Optional.of(tmeManager);
}
/* ... */
return Optional.empty();
};
global exception manager
private ExceptionManager<? extends Exception> defaultExceptionManager =
(exception, request, servletRequest) -> {
final Optional<ExceptionManager<? extends Exception>> manager =
factory.apply(exception);
if(manager.isPresent()) {
return manager.get()
.getErrorCode(exception, request, servletRequest);
}
return 1;
};
}
The following code fails to compile. It is actually a hint on type incompatibility issues.
Error:(...) java: incompatible types: java.lang.Exception
cannot be converted to capture#1 of ? extends java.lang.Exception
Error:(...) java: incompatible types: java.lang.Exception
cannot be converted to capture#2 of ? extends java.lang.Exception
After thinking and reading the question, it seems that Java did type removal (for JVM backwards compatibility), hence the code:
private ExceptionManager<? extends Exception> defaultExceptionManager =
(exception, request, servletRequest) -> { /* ... */ }
Become
private ExceptionManager<Exception> defaultExceptionManager =
(exception, request, servletRequest) -> { /* ... */ }
In fact, the first parameter (that
is, exception) of getErrorCode
is fixed to Exception
.
As I understand
(not sure if I really understand), the process for generics should be the same. Therefore
private interface ExceptionManager<D extends Exception> { /* ... */ }
Should become
private interface ExceptionManager<Exception> { /* ... */ }
Therefore, the parameter e
in the getErrorCode
method is also changed to Exception
.
The type incompatibility issue becomes clearer later (if I’m right). However, I’m still interested in capture#xx of ? extends Exception
because it means (still according to my understanding) that type deletion is not valid for the entire section of code.
Can someone point out the error in my code (probably a documentation where I can find some explanation of the compiler’s internal behavior for generics, wildcards, and type removal)?
Note: The code also hints that the type is incompatible.
protected ResponseEntity<Object> handleTypeMismatch(final TypeMismatchException ex,
final HttpHeaders headers, final HttpStatus status,
final WebRequest request) {
/* ... */
int errorCode = defaultExceptionManager.getErrorCode(ex, request, servletRequest);
}
The result of this call is
:
Error:(154, 63) java: incompatible types:
org.springframework.beans.TypeMismatchException
cannot be converted to capture#3 of ? extends java.lang.Exception
We apologize for the length of this question, thank you for reading and answering!
Greetings
Solution
When you declare a < like Function? extends Exception, ...
When you > such a function, you are saying that the type of the argument is unknown, so you cannot apply
the function because you do not know whether the actual parameter is compatible with the unknown parameter type. The same applies to ExceptionManager<? extends Exception>
, which receives an unknown exception type as a parameter.
This is not the same as not knowing the return type, just as a function returns −extends R
. , you still know that the result can be assigned to R
or R's
parent (super class) type
There is some relationship between the incoming parameter and the result type that would make the code usable if it were generic, but you cannot make a variable (which holds a reference to Function
) generic. You can solve this problem using a normal method that can declare type parameters. This is almost straightforward, since you’re overusing functions here anyway:
public class ExceptionHandler {
simple internal manager
@FunctionalInterface
private interface ExceptionManager<D extends Exception> {
int getErrorCode(D e, WebRequest request, HttpServletRequest servletRequest);
}
One field, just for the illustration
private static ExceptionManager<TypeMismatchException> tmeManager =
(ex, req, servletRequest) -> {
int errorCode = 0;
// ...
return errorCode;
};
A simple "factory" for an ExceptionManager
private static <E extends Exception> Optional<ExceptionManager<E>> factory(E ex) {
if(ex instanceof TypeMismatchException) {
unavoidable unchecked operation
@SuppressWarnings("unchecked") ExceptionManager<E> em
= (ExceptionManager<E>)tmeManager;
return Optional.of(em);
}
/* ... */
return Optional.empty();
}
global exception manager
private ExceptionManager<Exception> defaultExceptionManager
= ExceptionHandler::handleDefault;
static <E extends Exception> int handleDefault(E exception, WebRequest request,
HttpServletRequest servletRequest) {
final Optional<ExceptionManager<E>> manager = factory(exception);
return manager.map(em -> em.getErrorCode(exception, request, servletRequest))
.orElse(1);
}
}
In one place, unchecked operations are inevitable when a suitable specific handler is returned via instanceof
. View. The instance may also be TypeMismatchException
at runtime, but the caller has replaced its parent (super class) type with E
. The latter is a more dangerous case because the universal signature will promise Ability to handle a wider range of types than they actually are. As long as the method
is private
, you can easily tell that the caller is only passing the same instance for the check, so it will work.