2

I have a class with generics. I know that generic type information is stripped at runtime, but this is with a bound type. I thought that at compilation java.lang.Object is replaced with the bound type. If I know that everything will always be at least an Animal, then why does the compiler leave it as Object? Is there something I'm missing that would make this work like I want? Specifically, that last for loop in the main method has a compile-time problem.

Thanks!

public static void main( String[] args ) throws Exception {

    Litter<Cat> catLitter = new Litter<>();
    for( Cat cat : catLitter ) {}

    Litter<Animal> animalLitter = new Litter<>();
    for( Animal animal : animalLitter ) {}

    Litter litter = new Litter();
    for( Animal animal : litter ) {} // Type mismatch: cannot convert from element type Object to Animal
}

public class Litter<T extends Animal> implements Iterable<T>{
    @Override public java.util.Iterator<T> iterator() {
        return new LitterIterator();
    }

    class LitterIterator implements java.util.Iterator<T> {
        @Override public boolean hasNext() { return false; }
        @Override public T next() { return null; }
    }
}

public class Animal {}
public class Dog extends Animal{}
public class Cat extends Animal{}
Sotirios Delimanolis
  • 274,122
  • 60
  • 696
  • 724
bmauter
  • 2,953
  • 4
  • 21
  • 24
  • 3
    If you turn on all compiler warnings, you’ll see that you’re using a raw type for `litter`, which essentially makes the compiler ignore the existence of generics entirely for that statement. – VGR Feb 22 '17 at 17:27
  • When no type is specified, all generic info is removed - you're left with just Object. – Bohemian Feb 22 '17 at 17:28
  • It's not that simple @Bohemian. A bounded type changes the rules a bit. As Sotirios demonstrates, an instance method that normally returns T when T extends Animal, will still return Animal on a raw type. My confusion is why the Iterator isn't parameterized to Animal. – bmauter Feb 23 '17 at 19:10

1 Answers1

4

Mandatory redirection:

The behavior you're expecting would apply to something like

public class Litter<T extends Animal> implements Iterable<T>{
    public T get() {return null; /* or whatever */}
    ...
}

and

Litter litter = ...; // raw type 
Animal animal = litter.get(); // compiles fine

Since Litter is a raw type and

The type of a constructor (§8.8), instance method (§8.4, §9.4), or non-static field (§8.3) of a raw type C that is not inherited from its superclasses or superinterfaces is the raw type that corresponds to the erasure of its type in the generic declaration corresponding to C

and since

The erasure of a type variable (§4.4) is the erasure of its leftmost bound.

then the method get appears as

public Animal get() {...}

to code using the raw Litter type.

As for Litter#iterator() however, its return type is Iterator<T> and since

The erasure of a parameterized type (§4.5) G<T1,...,Tn> is |G|.

its erasure is just Iterator. The next() method of Iterator is then erased to

public Object next() {...}

so obviously its return value cannot be assigned to a variable of type Animal.

Is there something I'm missing that would make this work like I want?

Not with raw types, no.

Community
  • 1
  • 1
Sotirios Delimanolis
  • 274,122
  • 60
  • 696
  • 724
  • Thanks, this answer is very helpful. I'm still struggling with how this makes sense. Your first quote of the JLS would make me think that the get() method would return Object, not Animal as in your example. (I know you're right, I double-checked with a compiler.) The raw Litter object's get() method returns Animal, but its iterator() method returns a raw Iterator (instead of Iterator). How does this even make sense? When I'm looking at the list of methods available to me in Eclipse, this is confusing. – bmauter Feb 22 '17 at 21:15
  • Raw type should be removed in Java altogether, as it breaks common code assumption. It was probably added for the backward compatibility, but in my opinion it was a missed decision. – MaxZoom Mar 07 '17 at 15:46
  • @bmauter Sorry, I hadn't seen your comment then. It's the difference between the erasure of `T` (which is `Animal`) and the erasure of `Iterator` which is `Iterator`. Then, `Iterator`'s `next` also must be erased since you're accessing it on a raw value. The erasure of its type parameter `E` is `Object`, because it's unbounded. – Sotirios Delimanolis Mar 07 '17 at 15:53