【问题标题】:Java Generics, Type Erasure, wildcard and Function<?...> produce incompatible typesJava 泛型、类型擦除、通配符和 Function<?...> 产生不兼容的类型
【发布时间】:2018-08-19 07:58:38
【问题描述】:

还有一个菜鸟问题,抱歉。

让我们考虑以下代码:

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;
      };
}

以下代码无法编译。这实际上是对类型不兼容问题的抱怨。

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

在思考和阅读了这个问题之后,似乎 java 执行了类型擦除(为了 jvm 向后兼容),因此代码:

private ExceptionManager<? extends Exception> defaultExceptionManager = 
                   (exception, request, servletRequest) -> { /* ... */ }

成为

private ExceptionManager<Exception> defaultExceptionManager = 
                   (exception, request, servletRequest) -> { /* ... */ }

其实就是将getErrorCode(即exception)的第一个参数固定为Exception

据我了解(不确定是否真正了解),泛型类型的过程应该相同。因此

private interface ExceptionManager<D extends Exception> { /* ... */ }

应该变成

private interface ExceptionManager<Exception> { /* ... */ }

因此还将getErrorCode 方法中的参数e 修复为Exception。 之后类型不兼容问题变得更加清晰(如果我是对的)。但是,我仍然对capture#xx of ? extends Exception 持怀疑态度,因为这意味着(仍然根据我的理解)类型擦除对整个代码部分无效。

有人能指出代码中的错误吗(可能是一个文档,我可以找到一些关于泛型、通配符和类型擦除的编译器内部行为的解释)? em>

注意:代码还抱怨类型不兼容。

protected ResponseEntity<Object> handleTypeMismatch(final TypeMismatchException ex,
   final HttpHeaders headers, final HttpStatus status,
   final WebRequest request) {
   /* ... */
   int errorCode = defaultExceptionManager.getErrorCode(ex, request, servletRequest);
}

这个调用的结果是

Error:(154, 63) java: incompatible types:
      org.springframework.beans.TypeMismatchException
      cannot be converted to capture#3 of ? extends java.lang.Exception

抱歉这个问题太长了,感谢您阅读和回答! 问候

【问题讨论】:

  • 我认为这里的第一个答案描述了如何找出问题所在:stackoverflow.com/questions/31227149/…
  • 如果在分配之前显式转换 lambda 会发生什么? ExceptionManager&lt;? extends Exception&gt; defaultExceptionManager = (ExceptionManager&lt;? extends Exception&gt;) ((exception, request, servletRequest) -&gt; { ... ) ?而且,顺便说一句,一个简单的ExceptionManager&lt;Exception&gt; 就不能完成这项工作吗?
  • 感谢@JohnB 的两个回复(您指出的帖子很有趣,之前没有发表过)。对于第二点,演员表不会改变行为。对于第二点,我希望能够在接口中保持通用性,这样我就可以编写管理器(例如:ExceptionManager&lt;TypeMismatchException&gt; tmeManager),而无需在实现中强制转换任何内容。注意代码的结构主要是为了学习如何处理java8的一些特性(函数、lambda和泛型)。再次感谢您的时间和回答。

标签: java generics java-8 wildcard


【解决方案1】:

当你声明一个像Function&lt;? extends Exception, …&gt;这样的函数时,你是说参数的类型是未知的,因此你不能apply这个函数,因为你不知道实际参数是否与未知参数类型。这同样适用于ExceptionManager&lt;? extends Exception&gt;,它接收一个未知的异常类型作为参数。

这和不知道返回类型不同,当一个函数返回? extends R时,你仍然知道结果可以赋值给R或者R的超类型。

传入参数和结果类型之间存在关系,如果它是泛型的,这将使该代码可用,但是,您不能使变量(持有对 Function 的引用)泛型。您可以使用可以声明类型参数的普通方法来解决这个问题。这几乎是直截了当的,因为无论如何你都在过度使用函数:

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);
    }
}

当返回一个通过instanceof 检查发现合适的特定处理程序时,有一个地方不可避免地会出现未经检查的操作。这里必须小心,因为异常可能是子类型TypeMismatchException。也有可能该实例在运行时是 TypeMismatchException,但调用者已经用它的超类型替换了 E。后者是更危险的情况,因为通用签名承诺能够处理比实际更广泛的类型。只要方法是private,您就可以轻松地看到调用者只传递了与检查相同的实例,所以它会起作用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-12-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多