25

If I have a class:

public class GenericClass<TBlah extends Number> {
    public List<String> getList() {
        return null;
    }
}

When I attempt to use that method from another class:

public class OtherClass {
    public void test() {
        GenericClass a = null;
        for (String s : a.getList()) {

        }
    }
}

Why does a.getList() return a List<Object> until I change the line above the for loop to:

GenericClass<Number> a = null;

At which point a.getList() returns a List<String> like it should do?

Edit: I don't understand why the contract specified by getList() should be affected whatsoever by how I declare my variable 'a'. getList() always returns a List<String>, it doesn't matter what TBlah is.

Xenoprimate
  • 7,691
  • 15
  • 58
  • 95
  • 1
    Uhm, why initialize it to `null`? – fge Jul 19 '13 at 10:05
  • @fge Simplicity. It's irrelevant to the problem I think. – Xenoprimate Jul 19 '13 at 10:07
  • 7
    Because that's the choice made by the writers of the JLS. AFAIK, the reasoning is that if you use raw types, you don't care about generic types. The solution is to avoid using raw types. – JB Nizet Jul 19 '13 at 10:08
  • @Motig, you're right. It is irrelebvant to the question. Same thing happens even if `a` is initialized to a non-null value. – yair Jul 19 '13 at 10:17
  • 2
    @JBNizet is right. [And I found that a similar question was actually already answered with reference to the JLS](http://stackoverflow.com/questions/11007723/combining-raw-types-and-generic-methods). – yair Jul 19 '13 at 10:22
  • 2
    @JBNizet is spot on (and yair posted a link explaining the situation). What you have to keep in mind is that raw types exist **exclusively** for backwards compatibility (i.e. to allow pre-generics code to compile with generics-enabled collections classes, for example). You should *never* use them for anything else (the wildcard solves most other places where you *would* want to use them). – Joachim Sauer Jul 19 '13 at 10:49
  • @EJP It could return a `List`, because that `getString()` isn't affected by the 'value' of **TBlah**. – Xenoprimate Jul 19 '13 at 10:56
  • No, it can't do that, because there is no such thing as a List if you're not using Generics. – user207421 Jul 19 '13 at 11:11
  • @EJP this is not precise. If a class type was not declared then the code would be valid and it would return `List`. The fact that a type is declared for the class but it is not used by the raw type, causes type erasure for all types in that class. I believe the most appropriate answer is JBNizet's. This is after all a design decision (maybe it has to do with compiler's implementation who knows) – c.s. Jul 19 '13 at 11:49

1 Answers1

17

Because this is how generics work. Do not forget that before generics when you declared a List it was a list of Object. You were expected to put/get Object and you were forced to cast to get your object with the correct type. Actually it still is a list of Object in runtime.

Generics is a way for the compiler to guarentee you type safety at compile time assuming you have no warnings. In runtime there are no List<String>. There is just List. The compiler puts the automatic cast for you, so you are able in your code to write String s = list.get(i) without casting.

When you declare GenericClass a you are declaring a raw type (you should get a warning on this) thus the compiler does not have a way to know what type the a.getList() is supposed to return. So it uses Object. When you declare GenericClass<Number> a = null; now the compiler knows what type to expect for the a.getList() and uses the desired one.

Edit: It should be clarified that the compiler can know what to expect only if you respect the contract of the signature (i.e. as is the case with GenericClass<Number>). If you don't respect the contract (i.e. you are using a raw type that does not extends Number) then the contract does not apply anymore. The compiler behaves as if no type information was present. Do not forget that the compiler needs to also maintain backwards compatibility with code that was created in the pre-generics era

c.s.
  • 4,786
  • 18
  • 32
  • That's sort of sensible. But why does the compiler not know what a.getList() should return? It's right there in the method signature. And that doesn't change no matter what I declare TBlah to be... So why does it matter that I use the raw type? – Xenoprimate Jul 19 '13 at 10:32
  • Thats is not correct. It is not in the signature. The signature says a `blah that extends Number`. You have used a raw type (i.e. a simple `Object`) so the signature does not apply anymore because `Object` does not extend `Number` – c.s. Jul 19 '13 at 10:35
  • 4
    But the return type of List in getList() is unaffected by whatever I choose TBlah to be. So why does it matter? – Xenoprimate Jul 19 '13 at 10:41
  • @Motig very interesting question! But I don't think you will get a sensible answer as the phrasing doesn't point out explicitly that the return value of `getList()` has nothing to do with type `T`. – Tim Bender Jul 19 '13 at 10:44
  • @TimBender Thanks, I'll update the question to make that more obvious. – Xenoprimate Jul 19 '13 at 10:53