具有 Bounded Wildcard 类型的可变参数的方法如何编译?

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

我绞尽脑汁想知道这个例子是如何工作的,而且似乎打印得很好。

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);
    }
}

调用测试将打印:

"Shape"
"Square"
"Circle"

直觉上这是有道理的,因为方法签名被描述为接受任意数量的参数,只要它们是 ThreadLocal 类型和 Shape 的任何扩展类型即可。因此,传入 ThreadLocal<Square>ThreadLocal<Circle> 符合要求。

但是如何以 运行time 可以确定字符串的正确重载的方式进行编译?我对泛型类型擦除的模糊理解使得这种方法签名似乎甚至无法编译。我原以为 wildCardVarArgs 的签名在字节代码中变成了类似于 wildCardVarArgs(ThreadLocal[]) 的东西,这似乎执行应该 运行 到 ClassCastException 中。但是越想越糊涂

任何人都能够理解这一点以及编译对 ThreadLocal 的有界通配符类型做了什么?

基本上,即使您删除了类型,您也会得到以下结果:

....
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"
    }
}
....

因此,无论您向其中推送什么,所有内容的基础 class,即 Object,都有一个 toString 方法,在您的情况下,该方法已被覆盖。因此,在 Object 类型变量上调用它永远不会失败,并且在您的情况下 - 调用覆盖方法。


已添加:

现在,如果您向 Shape class 添加一些新方法,会发生以下情况:

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
    }
}

鉴于此代码,编译器肯定知道,无论您为该方法提供哪个 ThreadLocal,它都包含 某种 Shape 实现。它实际上并不关心它是哪一个,但是,它可以保证您除了 某种 Shape 之外没有给它任何东西。因此,它可以在编译时检查,Shape 上确实有 someCustomMethod。同样,此时它不关心是否有人覆盖它,它只是调用它。如果它被覆盖 - 被覆盖的那个被调用。如果它没有被覆盖 - 原始的,来自 Shape class 被调用。


更多补充:

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.

这正是发生的事情(不完全是数组部分,而是擦除)。也就是说,编译之后剩下的基本上就是:

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());
    }
}

不过,Java并不在意。它希望在编译期间它正确地发现了所有的错误,因此在运行时不应该发生这样的错误(剧透:它们无论如何都会发生,不时发生,如果一个人不小心的话) .