简体   繁体   中英

Generics code fails to compile without intermediate variable

I am surprised that the first 2 lines of code in main below are allowed by the compiler. It seems to be trusting me when I say that getIt() will return a Dog even though the signature also allows a Cat to be returned.

public class GenericsAreWeird {
    public static void main(String... args){
        // This is fine
        Dog d = getIt();
        d.woof();

        // This fails to compile
        // getIt().woof();

        // This compiles but gives a runtime ClassCastException
        Cat c = getIt();
        c.meow();
    }

    private static <T extends Animal> T getIt(){
        return (T) new Dog();
    }

    public static class Animal {}

    public static class Cat extends Animal{
        public void meow(){}
    }

    public static class Dog extends Animal{
        public void woof(){}
    }
}

I generally trust Java generics to protect me from ClassCastExceptions by giving compiler errors, rather than telling me at runtime that something won't work, so allowing this seems broken.

It also seems counter-intuitive that:

Dog d = getIt();
d.woof();

is not exactly equivalent to:

getIt().woof();

the compiler needs the intermediate variable as a 'hint'.

Have generics always worked this way? Can someone point me to (or provide) a decent explanation of any other scenarios where the compiler fails to pick up a class-related error like this?

return (T) new Dog(); for the most part tells the compiler to
"shut up with your type checking, I know better than you that Dog will always be assignable to T "
to which the compiler replies
"okay, go ahead then, then you better take care of not ever calling this method expecting it to return a anything else / a Cat "

Then you do call it expecting it to return a Cat and get a deserved ClassCastException . You get a runtime exception because you tell the compiler that you know it better (which you do not).

After type erasure your getIt() method will look very much like this:

  private static Animal getIt(){
    return new Dog();
  }

But before the type erasure happens, the compiler sees, that you use a generic return type T extends Animal for the method! And thus, once it sees the line

Dog d = getIt();

it says: Ok, so T will be Dog in this particular case, so I need to insert a cast to Dog after type erasure like this:

Dog d = (Dog) getIt();

In the same way, for the call Cat c = getIt() it will resolve the type T to be Cat and add an according cast to Cat:

Cat c = (Cat) getIt();

But inside of your method the compiler warns you: Hey, are you sure, that the Dog you create will always be of type T ? Remember, this is not guaranteed generally! So if you ignore this warning, you should know, what you are doing, taht's the point.

And finally, of course the call

getIt().woof() 

will not compile, because in this case the compiler has no idea how to resolve the type T , so it will be just Animal , the upper limitation of T . It is not able to guess that it is a Dog only based on the call to the woof() method.

The following line:

 getIt().woof();

fails because woof() is defined on Dog not Animal and getIt() returns a sub-class of Animal . Hence the compiler is unaware of woof() .

You could try:

 abstract class Animal { abstract void speak(); }
 class Dog extends Animal { void speak() { // Dog specific implementation } }
 class Cat extends Animal { void speak() { // Cat specific implementation } }

Then you could use:

 getIt().speak();

You get a ClassCastException because the only information about the type returned by getIt() is that it is a sub-class of Animal . Assigning it to a variable of type Cat or Dog is unsafe. This would be true of any similar use of generics.

A common workaround is to define getIt() something like this:

<T extends Animal> T getIt(Class<T> clazz) {
    // return an instance of the appropriate type based on clazz
}

You can use it thus:

Dog dog = getIt(Dog.class);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM