简体   繁体   中英

How does a method with a varargs of Bounded Wildcard type compile?

I'm scratching my head over how this example works and seems to print appropriately .

public class Test {

    static class Shape {
        public String toString() {
            return "Shape";
        }
    }

    static class Circle extends Shape {
        public String toString() {
            return "Circle";
        }
    }

    static class Square extends Shape {
        public String toString() {
            return "Square";
        }
    }


    public static void wildCardVarArgs(ThreadLocal<? extends Shape>... list) {
        for (ThreadLocal<? extends Shape> s : list) {
            System.out.println(s.get().toString());
        }
}

    public static void test() {
        ThreadLocal<Shape> shape = new ThreadLocal<>();
        shape.set(new Shape());
        ThreadLocal<Square> square = new ThreadLocal<>();
        square.set(new Square());
        ThreadLocal<Circle> circle = new ThreadLocal<>();
        circle.set(new Circle());
        wildCardVarArgs(shape, square, circle);
    }
}

Calling tests will print:

"Shape"
"Square"
"Circle"

Intuitively this makes sense, as the method signature is described as accepting any amount of arguments as long as they are of type ThreadLocal with a type of any extension of Shape. So passing in a ThreadLocal<Square> along with ThreadLocal<Circle> fits the bill.

But how does this compile in such a way that runtime can determine the proper overload of string? My hazy understanding of generic type erasure makes it seem like this sort of method signature should not be possible to even compile. I would've thought that the signature for wildCardVarArgs becomes something along the lines of wildCardVarArgs(ThreadLocal[]) in byte code, which seems like execution should run into a ClassCastException. But the more I think about it, the more confused I get.

Anyone able to make sense of this and what does compiling do to a Bounded Wildcard type of ThreadLocal?

Basically, even if you remove the types, here's what you get:

....
public static void wildCardVarArgs(ThreadLocal... list) {
    for (ThreadLocal s : list) {
        Object o = s.get(); //Compiler knows there's "at least" Object inside
        System.out.println(o.toString()); //And all Objects have "toString"
    }
}
....

So, whatever you push in it, the base class of everything, that is Object, has a method toString, which in your case is overriden. Therefore, calling it on Object type variable never fails, and in your case - calls the overriden method.


Added:

Now, in case you add some new method into the Shape class, here is what happens:

public static void wildCardVarArgs(ThreadLocal<? extends Shape>... list) {
    for (ThreadLocal<? extends Shape> s : list) {
        Shape o = s.get(); //Compiler knows there's "at least" Shape inside
        System.out.println(o.someCustomMethod()); //And all Shapes have "someCustomMethod", does not matter if it is overriden or not
    }
}

Given this code, compiler knows for sure, that whichever ThreadLocal you give to this method, it contains some kind of a Shape implementation. It does not actually care which one it is, however, it can guarantee that you didn't give it anything but some kind of a Shape . Therefore, it can check at compile time, that Shape indeed has someCustomMethod on it. Again, it does not care at this point if someone Overriden it or not, it just calls it. If it was overriden - the overriden one is called. If it was not overriden - the original, from Shape class is called.


More added:

I would've thought that the signature for wildCardVarArgs becomes something along the lines of wildCardVarArgs(ThreadLocal[]) in byte code, which seems like execution should run into a ClassCastException.

That's exactly what happens (not exactly the array part, but the erasure). That is, after the compilation all that remains is, basically:

public static void wildCardVarArgs(ThreadLocal... list) {
    for (ThreadLocal s : list) {
        Object o = s.get();
        Shape p = (Shape)o; //Java hopes this won't fail, because compiler checked that it should not
        System.out.println(p.someCustomMethod());
    }
}

However, Java does not really care. It hopes that during the compile time it found all the errors correctly, therefore such errors during runtime should not happen (spoiler: they do happen anyway, time from time, if one is not careful).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM