【问题标题】:Java Generics: Returning Bounded Generic TypeJava 泛型:返回有界泛型类型
【发布时间】:2018-08-17 20:26:51
【问题描述】:

我有以下代码:

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

这里ExceptionAExceptionBExceptionCExceptionDParentException 的子级。

编译时出现错误:

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 线程的这个答案中提到的:How do I make the method return type generic?,允许使用T 进行强制转换,并且在该线程中给出了另一个指针:Java Generics: Generic type defined as return type only。但我的问题是:当T 有界并且所有返回的对象都落入指定的范围时,为什么我需要使用类型转换?

【问题讨论】:

    标签: java generics


    【解决方案1】:

    你做错了。这就是你得到错误的原因。你可以用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();,但我们已经屏蔽了它,我们在运行时得到了异常

    【讨论】:

    • 您在managedException() 中提出的代码编译但无能为力:您强制转换为特定类型,但该方法返回父类型。
    • 是的,但这就是继承的目的。你不能有一个返回不同类型的方法吗? :) 它通过从 Exception 更改为 ParentException 使其更通用。想象一下该方法返回不同类型的异常(就像它实际上所做的那样),您只能将结果分配给 ParentException 类的变量。您不能将其分配给子类,因为您可能会得到不同的子类
    • 您可以将所有分支替换为以下内容:return cause instanceof ParentException ? (ParentException) cause : new ExceptionD(cause.getMessage(), cause);
    • 同意我只是想让它与原始问题一样接近,这样问题更容易理解
    【解决方案2】:

    因为

    ExceptionA a = managedException(new ExceptionB());
    

    会因ClassCastException 而崩溃。 Java 将 T 推断为 ExceptionA 并且您的代码将进入 B 情况,这将导致错误的转换。

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

    【讨论】:

      【解决方案3】:

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

      ExceptionC c = managedException(new ExceptionA());
      

      这在两个版本中都会在运行时失败。这就是为什么编译器不接受您的任何版本的原因(除了第二个版本,带有泛型警告,这正是我上面概述的原因)。

      要绑定参数,通常添加Class&lt;T&gt;参数:

      public <T extends ParentException> T managedException(Exception cause, Class<T> targetClass);
      

      但我认为很明显为什么这会导致方法无效(从一个类到不相关的另一个类的狂野转换肯定会在运行时失败)。

      这个方法本身看起来有点诡异。与无操作演员表唯一不同的是最后的ExceptionD 构造。或许你应该重新考虑一下你真正想要实现的目标——这种方法似乎不是实现它的正确方法。

      另一种解决方案:有时使用泛型是不对的 ;-)

      为什么不直接写

      public ParentException managedException(Exception cause);
      

      对于我可以想象的用例,这应该正是您所需要的。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-12-24
        • 2022-10-14
        • 2013-08-04
        • 1970-01-01
        相关资源
        最近更新 更多