具有 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并不在意。它希望在编译期间它正确地发现了所有的错误,因此在运行时不应该发生这样的错误(剧透:它们无论如何都会发生,不时发生,如果一个人不小心的话) .
我绞尽脑汁想知道这个例子是如何工作的,而且似乎打印得很好。
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并不在意。它希望在编译期间它正确地发现了所有的错误,因此在运行时不应该发生这样的错误(剧透:它们无论如何都会发生,不时发生,如果一个人不小心的话) .