Java 泛型、类型擦除、通配符和函数 <?...> 产生不兼容的类型
Java Generics, Type Erasure, wildcard and Function<?...> produce incompatible types
又一个菜鸟问题,不好意思。
让我们考虑以下代码:
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
表示怀疑,因为这意味着(仍然根据我的理解)类型擦除对整个代码部分都无效。
有人能指出我代码中的错误吗(可能是一个文档,我可以找到一些关于编译器内部行为的泛型、通配符和类型擦除) ?
注意:代码还抱怨类型不兼容。
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
抱歉这个问题太长了,感谢阅读和回答!
此致
当你声明一个像 Function<? extends Exception, …>
这样的函数时,你是说参数的类型是未知的,因此你不能 apply
这个函数,因为你不知道实际参数与未知参数类型兼容。这同样适用于 ExceptionManager<? extends Exception>
,它接收一个未知的异常类型作为参数。
这与不知道 return 类型不同,因为当函数 returns ? 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);
}
}
有一个地方,未经检查的操作是不可避免的,当 return 通过 instanceof
检查发现合适的特定处理程序时。此处必须小心,因为异常可能是子类型 TypeMismatchException
。也有可能该实例在运行时是 TypeMismatchException
,但调用者已将其超类型替换为 E
。后者是更危险的情况,因为通用签名将承诺能够处理比实际更广泛的类型。只要该方法是 private
,您就可以很容易地看出调用者只传递了用于检查的相同实例,所以它会起作用。
又一个菜鸟问题,不好意思。
让我们考虑以下代码:
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
表示怀疑,因为这意味着(仍然根据我的理解)类型擦除对整个代码部分都无效。
有人能指出我代码中的错误吗(可能是一个文档,我可以找到一些关于编译器内部行为的泛型、通配符和类型擦除) ?
注意:代码还抱怨类型不兼容。
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
抱歉这个问题太长了,感谢阅读和回答! 此致
当你声明一个像 Function<? extends Exception, …>
这样的函数时,你是说参数的类型是未知的,因此你不能 apply
这个函数,因为你不知道实际参数与未知参数类型兼容。这同样适用于 ExceptionManager<? extends Exception>
,它接收一个未知的异常类型作为参数。
这与不知道 return 类型不同,因为当函数 returns ? 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);
}
}
有一个地方,未经检查的操作是不可避免的,当 return 通过 instanceof
检查发现合适的特定处理程序时。此处必须小心,因为异常可能是子类型 TypeMismatchException
。也有可能该实例在运行时是 TypeMismatchException
,但调用者已将其超类型替换为 E
。后者是更危险的情况,因为通用签名将承诺能够处理比实际更广泛的类型。只要该方法是 private
,您就可以很容易地看出调用者只传递了用于检查的相同实例,所以它会起作用。