Search code examples
javacompilationjava-17

Cast List<List<String>> to List<String>


How this code is working in Java (I tested it with java 17)?

import java.util.List;

class Scratch {
    public static void main(String[] args) {

        List<String> test = (List<String>) getLists();
        System.out.print(test);

    }

    private static Object getLists() {
        return List.of(List.of("123", "456"));
    }
}

the output is:

[[123, 456]]

shouldn't the types mismatch and have an exception of some sort ?


Solution

  • Now try adding at the end of your main method:

    String x = test.get(0);
    

    and watch as you get a ClassCastException. Which is weird, as that line.. has zero casts on it.

    Generics are a figment of the compiler's imagination. The runtime has no idea. Hence, at runtime, that's just:

    List list = (List) getlists();
    

    which is, of course, fine. The reference returned by the getLists() method is pointing at an object of type List. Not List<String> or even List<List<String>> - at runtime generics do not exist at all.

    Hence, the compiler simulates it all. Which means this:

    List<String> list = new ArrayList<String>();
    list.add("Hi!");
    String y = list.get(0);
    

    Is translated to:

    List list = new ArrayList();
    list.add("Hi!");
    String y = (String) list.get(0);
    

    and this explains why your code snippet seems to work (though, you do get that warning: That's the compiler saying: Um, that whole generics stuff? I'm the only one that does that, and with these casts I cannot do my job so, well, if you say so, I guess I shall continue assuming that you are right. If you're wrong, weird things are going to happen. Like ClassCastException occurs on lines with zero casts in them!)

    toString() of List doesn't use its own generics to do its job, hence, you never run into one of those invisible casts that will asplode once you get to one of those.