Java generics, type removal, wildcards, and Function?… produce incompatible types

Java generics, type removal, wildcards, and Function produce incompatible types … here is a solution to the problem.

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.

Related Problems and Solutions