0

I didn't understand very well Generics and maybe the Type Erasure concepts and restrictions. So if upon the compilation process, the type information is dropped, why this code gives me an Exception at Runtime and not at compile time? I know that generics was made carefully to have compatibility to the older versions, but I didn't understand why it returns me an Exception if at Runtime the type turns into Object? Should not be at compile time? I'm sorry if you couldn't understand well, I'm not very good at speaking English.

    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        add(list, 1l);
        for(String n : lista) {
            System.out.println(n);
        }
    }

    public static void add(List l, Long lNumber ) {
        l.add(lNumber);
    }
  • [Effects of Type Erasure and Bridge Methods](https://docs.oracle.com/javase/tutorial/java/generics/bridgeMethods.html) should clear up your confusion. – Elliott Frisch Sep 02 '19 at 19:55
  • 1
    You don't get a compile time error, because your raw type `List l` parameter of the `add` method kind of disables the generic check of the compiler for that method. Thus it can't tell you anymore that you're adding a type that isn't expected for the list. Runtime doesn't and can't ignore the type differences, that is why you get a ClassCastException. Please check https://stackoverflow.com/questions/2770321/what-is-a-raw-type-and-why-shouldnt-we-use-it for more information. – Tom Sep 02 '19 at 19:56
  • And if you read the stack trace, you'll notice that you don't get a ClassCastException when storing a Log in the List. You get it when you cast (implicitely) the element that you got out of the list to a String, in `for(String n : lista) {` – JB Nizet Sep 02 '19 at 19:59
  • Thank you for the answers ^^!! @JBNizet That is one of the things that got me confused. But what I still can't understand is: In compile time, it knows that I'm trying to put a Number into a String, so it shouldn't compile, or not? And at runtime, it knows that I'm trying to put a Long into a Object, so it should run. Am I wrong? Why? – Luiz Fernando Sep 02 '19 at 20:47
  • No, it doesn't know. You're adding a Long into a list declared as `List`. How could it know that this list is only supposed to contain Strings? That's why you're supposed to not use raw types, and declare your variable as `List`: so that the compiler knows that the list is only supposed to contain Strings. – JB Nizet Sep 02 '19 at 20:53
  • Ohh, yes! I finally understood, I already tried to put #List# instead of just #List# in the parameter, and I've got a compile time error. Thanks – Luiz Fernando Sep 02 '19 at 20:58

1 Answers1

1
public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    add(list, 1l);

    // On compile time: list contains only String, so no error
    // ON runtime: list has a Number that you added before, and Number -> String throws ClassCastException
    for(String n : list) {
        System.out.println(n);
    }
}

// List here has elements with type Object. you add a Number
// At compile time: you get a warning only
public static void add(List l, Long lNumber ) {
    l.add(lNumber);
}
Oleg Cherednik
  • 17,377
  • 4
  • 21
  • 35
  • 1
    Thank you for the answer. But it is still unclear for me. In compile time, it knows that I'm trying to put a Number into a String, so it shouldn't compile, or not? And at runtime, it knows that I'm trying to put a Long into a Object, so it should run*. That is what I'm trying to understand. – Luiz Fernando Sep 02 '19 at 20:44
  • 1
    No, at compile-time JVM knows only that `List` contains `String`, but in the method, you use `List`, therefore JVM does not check the type at compile time. *P.S.* At runtime, `List` becomes `List`, because JVM erases type information. – Oleg Cherednik Sep 03 '19 at 05:14