Java泛型:返回有界通用类型

3
我遵循了下面的代码片段:
public <T extends ParentException> T managedException(Exception cause) {        
    if(ExceptionA.class.isInstance(cause)) {
        return ExceptionA.class.cast(cause);
    } else if(ExceptionB.class.isInstance(cause)) {
        return ExceptionB.class.cast(cause);
    } else if(ExceptionC.class.isInstance(cause)){
        return ExceptionC.class.cast(cause);
    } else {
        return new ExceptionD(cause.getMessage(), cause);
    }
}

这里的 ExceptionAExceptionBExceptionCExceptionD 都是 ParentException 的子类。
编译时,我收到了以下错误:
incompatible types: ExceptionA cannot be converted to T
incompatible types: ExceptionB cannot be converted to T
incompatible types: ExceptionC cannot be converted to T
incompatible types: ExceptionD cannot be converted to T

但是,如果我将代码更改为:
@SuppressWarnings("unchecked")
public <T extends ParentException> T managedException(Exception cause) {        
    if(ExceptionA.class.isInstance(cause)) {
        return (T) ExceptionA.class.cast(cause);
    } else if(ExceptionB.class.isInstance(cause)) {
        return (T) ExceptionB.class.cast(cause);
    } else if(ExceptionC.class.isInstance(cause)){
        return (T) ExceptionC.class.cast(cause);
    } else {
        return (T) new ExceptionD(cause.getMessage(), cause);
    }
}

它可以正常工作,没有编译错误。

如SO线程的这个答案中所提到的:如何使方法返回类型通用?,使用T进行强制转换是允许的,另一个指针在这个线程中给出:Java泛型:仅将通用类型定义为返回类型。但我的问题是:当T被限定并且所有返回对象都落在指定的范围内时,为什么需要使用类型转换?

3个回答

4
因为
ExceptionA a = managedException(new ExceptionB());

如果使用 ClassCastException,代码将会崩溃。Java 推断 TExceptionA,而你的代码将进入 B 分支,导致错误的类型转换。

Java 的判断是正确的,因为 ExceptionC 不是 T,因为 T 可以是 任何 其他的 ParentException 子类。


4

您所做的是错误的。这就是为什么会出现错误的原因。您可以使用 ExceptionC exceptionC=managedException(ExceptionD d) 调用您的方法,最终你将得到一个强制转换 (ExceptionC) exceptionD; 强制转换掩盖了错误,但在运行时会出现错误。

请将您的方法更改为:

public ParentException managedException(Exception cause) {        
    if(ExceptionA.class.isInstance(cause)) {
        return ExceptionA.class.cast(cause);
    } else if(ExceptionB.class.isInstance(cause)) {
        return ExceptionB.class.cast(cause);
    } else if(ExceptionC.class.isInstance(cause)){
        return ExceptionC.class.cast(cause);
    } else {
        return new ExceptionD(cause.getMessage(), cause);
    }
}

您不需要在此使用泛型。所有这些异常也都是ParentExceptions,因此您可以返回它们。当您考虑时,您试图使方法返回不同的类型。这样做是行不通的,因为如果您有一个从此方法初始化的变量,您需要知道结果会是什么。您知道结果将是ParentException,但您无法知道哪种类型的父异常。

原因是,如果像这样编写您的方法,则其不会返回ParentException-它将返回T(子类)。您可以返回不同类型的子类而不是您想要获取的子类。

在一个更简单的示例中,如果我们有:

class A {}

class B extends A{  };

class C extends A{  };

public  <T extends A> T test() {        
        return (T) new B();
}   

我们可以使用 C c=test(); 调用它,实际上我们尝试将 (C) new B(); 强制转换为 C 类型,这是不兼容的,但我们已经掩盖了它,并在运行时获得了异常。

2
你在 managedException() 中提出的代码可以编译,但是无济于事:你将其转换为特定类型,但该方法返回父类型。 - davidxxx
是的,但这就是继承的作用。你不能有一个返回不同类型的方法,对吧? :) 通过从 Exception 更改为 ParentException,它变得更加通用。想象一下,如果该方法返回不同类型的异常(实际上确实如此),你只能将结果分配给 ParentException 类的变量。你不能将其分配给子类,因为你可能会得到一个不同的子类。 - Veselin Davidov
1
您可以使用以下代码替换所有分支:return cause instanceof ParentException ? (ParentException) cause : new ExceptionD(cause.getMessage(), cause); - Lii
同意。我只是想让它尽可能接近原始问题,这样问题就更容易理解了。 - Veselin Davidov

2

在你的方法上下文中,T 是一个动态类型参数,由编译器从调用上下文中推断出来。很容易破坏你的方法:

ExceptionC c = managedException(new ExceptionA());

在两个版本中,这都会在运行时失败。这就是为什么编译器不接受你的任何一个版本(除了第二个版本,带有泛型警告,正是我上面概述的原因)。通常,为了绑定参数,您需要添加一个Class参数。请保留HTML标签。
public <T extends ParentException> T managedException(Exception cause, Class<T> targetClass);

但我认为这很明显会使该方法无效(从一个不相关的类到另一个类的野蛮转换肯定会在运行时失败)。
该方法本身看起来有点可怕。与无操作转换唯一不同的是最后的ExceptionD构造。也许你应该重新考虑你真正想要实现什么——这种方法似乎不是实现它的正确方式。
另一种解决方案:有时使用泛型并不是正确的选择;-)
为什么不直接写
public ParentException managedException(Exception cause);

这应该正是你需要的,对于我能想象到的用例。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接