Java Generics: Returning Bounded Generic Type

Tapas Bose :

I have following the piece of code:

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

Here ExceptionA, ExceptionB, ExceptionC, ExceptionD are children of ParentException.

While compiling, I got the errors:

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

But, if I change the code to:

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

It works without compilation error.

As mentioned in this answer of the SO thread: How do I make the method return type generic?, casting with T is allowed and another pointer is given in this thread: Java Generics: Generic type defined as return type only. But my question is: why do I need to use typecasting when the T is bounded and all returning objects fall into the specified bound?

Veselin Davidov :

What you are doing is wrong. That's why you get the error. You can call your method with ExceptionC exceptionC=managedException(ExceptionD d) and you will end up with a cast (ExceptionC) exceptionD; And casting it masks the error but you get it at runtime.

Change your method to:

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

You don't need generics here. All these exceptions are also ParentExceptions so you can juse return them. When you think about it you are trying to make the method return different types. Which cannot be done like that because if you have a variable that is initialized from this method you need to know what would be the result. And you know that the result will be ParentException but you can't know which kind of parent exception is that.

The reason behind it is that your method if written like is not returning ParentException - it is returning T (a subclass). And you can return a different type of subclass and not the one you are trying to get.

In a simpler example if we have:

class A {}

class B extends A{  };

class C extends A{  };

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

we can call it with C c=test(); we actually try to cast (C) new B(); which is incompatible but we have masked it and we get the exception at runtime

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=85865&siteId=1
Recommended