24

As I know, one of the main purposes of generics in Java is providing compile-time type safety. If it gets compiled, the code will run without issues.

Then why is the following code being compiled?

public static void main(String[] args) {
    String s = getList();
}

private static <T extends List> T getList() {
    return (T)new ArrayList();
}

It compiles fine. Where is my type-safe compilation? The getList() method has nothing in common with the String class.

Andrew Tobilko
  • 44,067
  • 12
  • 74
  • 128
  • 1
    No, this compiles even when using non-raw types. – Jorn Vernee Sep 03 '16 at 10:25
  • @MarkusFischer, I also thought about that, but the compiler realizes that String is a final class and can't have any subclasses – Andrew Tobilko Sep 03 '16 at 14:40
  • 1
    @MarkusFischer, you should transform this comment to an answer. I think that's exactly the issue op observed here. `MyClass extends String implements List` could be possible at compile time as the compiler doesn't consider String being `final`, as you said, for those kind of generic-checks. – Zabuzard Sep 03 '16 at 14:42
  • 6
    @AndrewTobilko the compiler only looks at the *type*, not whether the type is final. Final prevents subclasses from being declared, but does not prevent the compiler from allowing the possibility of subclasses (google Liskov). Put another way, adding or removing final from (non subclassed) classes should not cause compilation failure at their usages. The type hasn't changed, only the restriction on subclassing, which is an implementation thing, not a type thing. – Bohemian Sep 03 '16 at 15:40
  • "If it gets compiled, the code will run without issues." No. You get a warning when you compile this telling you it may not work. – Louis Wasserman Sep 03 '16 at 20:00

1 Answers1

15

This is not a type erasure problem per se, but almost the opposite: You get a problem at runtime, when the system knows the actual types, but not at compile time. The reason why this compiles is that List is an interface. As far as the compiler is concerned, a subclass of String might actually implement that interface, so the compiler reasons that there could be valid runtime situations where the actual object returned is a String that is also a List. The compiler does not consider that String is final, and thus that it's impossible to actually create a List-implementing String class.

As to why final is not considered during compilation, Bohemian's comment to the question gives a good explanation.

Andrew Tobilko
  • 44,067
  • 12
  • 74
  • 128
Markus Fischer
  • 1,271
  • 7
  • 12
  • Should there not at least be a compiler warning? I haven't checked javac itself, but my IDE does not warn about anything but an unchecked cast and raw types. I can work up examples where there are no raw types and where there are no warnings, but where the type is obviously not correct. – RudolphEst Jan 04 '17 at 14:55
  • I agree that a compiler warning would be nice and would make sense for this situation. I don't see one from javac nor Eclipse either. Again, this boils down to how many edge cases the compiler investigates, and apparently a decision was made to not check for this constellation. – Markus Fischer Jan 05 '17 at 15:56