我正在阅读this文章,关于子类化构建器类。我理解这篇文章,但有一点困扰我。有这种方法,
public static Builder<?> builder() {
return new Builder2();
}
当我将Builder<?>
更改为Builder
(原始类型)时,编译器不会编译代码。错误是,
Rectangle.java:33: error: cannot find symbol
System.out.println(Rectangle.builder().opacity(0.5).height(250);
使用附加<?>
传递给编译器的附加信息是什么?我怀疑是编译器在编译期间无法找到正确的实例。如果我删除(A)中的注释标记编译并运行正常。它总是引用Rectangle实例。所以,我的猜测是编译器失败了。
如果有人能指出我解释这个问题的文章或者找到更多信息,那就太好了。感谢。
我在这里粘贴了代码:
public class Shape {
private final double opacity;
public static class Builder<T extends Builder<T>> {
private double opacity;
public T opacity(double opacity) {
this.opacity = opacity;
return self();
}
/* Remove comment markers to make compilation works (A)
public T height(double height) {
System.out.println("height not set");
return self();
}
*/
protected T self() {
System.out.println("shape.self -> " + this);
return (T) this;
}
public Shape build() {
return new Shape(this);
}
}
public static Builder<?> builder() {
return new Builder();
}
protected Shape(Builder builder) {
this.opacity = builder.opacity;
}
}
public class Rectangle extends Shape {
private final double height;
public static class Builder<T extends Builder<T>> extends Shape.Builder<T> {
private double height;
public T height(double height) {
System.out.println("height is set");
this.height = height;
return self();
}
public Rectangle build() {
return new Rectangle(this);
}
}
public static Builder<?> builder() {
return new Builder();
}
protected Rectangle(Builder builder) {
super(builder);
this.height = builder.height;
}
public static void main(String[] args) {
Rectangle r = Rectangle.builder().opacity(0.5).height(250).build();
}
}
答案 0 :(得分:5)
使用。传递给编译器的附加信息是什么 额外的
<?>
?
使用通配符<?>
的附加信息是,返回的Rectangle.Builder<?>
是所有可能的泛型 Rectangle.Builder<T>
类的超类(请参阅{{ 3}})。并且由于Rectangle.Builder<T>
保证有一个类型参数T,它本身就是Rectangle.Builder
的子类,只要它的泛型类型不被忽略,Rectangle.Builder<?>
也保证至少是Rectangle.Builder<? extends Rectangle.Builder<?>>
类型。如果通过删除通配符完全忽略泛型,则此信息将丢失,代码将编译为普通的Java5.0之前的代码(其中泛型不存在)。这对于向后兼容性是必要的。
要查看差异,请考虑忽略泛型类型的Rectangle.Builder的子类:
public static class BadBuilder extends Rectangle.Builder {
private double height;
public BadBuilder height(double height) {
System.out.println("height is set");
this.height = height;
return (BadBuilder) self();
}
@Override
public Shape.Builder opacity(double opacity) {
return new Shape.Builder();
}
public Rectangle build() {
return new Rectangle(this);
}
}
请注意,此类会覆盖Shape.Builder#opacity
,而不会返回自身的子类。编译器不会为此类生成错误(但它可能会警告您,该类忽略了泛型类型)。因此,如果没有通用信息,则从 legal 返回不透明度方法中的类型Shape.Builder
。只要向BadBuilder添加类型参数,此代码就不再编译:
public static class BadBuilder extends Rectangle.Builder<BadBuilder> // -> compile time error
因此,您得到编译器错误cannot find symbol
的原因是,因为类Shape.Builder
本身不声明方法/符号T Shape.Builder#heigth()
,并且声明的方法T Shape.Builder#opacity()
会只保证返回的Object的类型为Shape.Builder
,如class Shape.Builder<T extends Shape.Builder<T>>
的类型参数中所声明的那样。因此,只有Rectangle.builder().opacity(0.5).height(250)
实际上保证返回一个使用Rectangle.Builder的子类键入的Builder时,调用方法链Rectangle.builder()
才有效。如果不忽略泛型类型(如BadBuilder示例中所示),则只能给出此保证。
当您添加方法Shape.Builder#heigth
时,通过删除代码中的注释,此错误显然会消失,因为Shape.Builder
返回的Shape.Builder#opacity
对象也将具有相应的方法。您也可以通过在Rectangle.Builder中重新声明Shape.Builder#opacity
来删除此错误:
@Override
public T opacity(double opacity) {
return super.opacity(opacity);
}
如果这样做,那么可以保证,T Rectangle.Builder#opacity()
的返回对象的类型为Rectangle.Builder
,如class Rectangle.Builder<T extends Rectangle.Builder<T>> extends Shape.Builder<T>
的类型参数中所声明的那样。
希望这有帮助。
答案 1 :(得分:2)
这种差异是因为当你在方法中使用原始类型时,它会为你使用该类型做的所有事情变成泛型。
例如,假设Builder
有一个返回foo()
的方法List<String>
。如果您在foo()
类型的表达式上调用Builder<?>
,则其类型为List<String>
。另一方面,如果对原始类型foo()
的表达式调用Builder
,则该表达式的类型为List
,而不是List<String>
,,即使类型List<String>
与T
完全无关。它被视为方法foo()
的返回类型是它实际上的擦除。
因此,在您的情况下,假设Rectangle.builder()
返回了类型Rectangle.Builder<?>
。为方便起见,我们为此?
命名,例如X
。因此,您Rectangle.Builder<X>
(继承自Shape.Builder<X>
)并在其上调用opacity()
,结果为X
。我们知道因为X
是Rectangle.Builder
的类型参数,X
必须是Rectangle.Builder<X>
的子类型。所以我们可以在上面调用height()
。
但是,如果Rectangle.builder()
返回原始类型Rectangle.Builder
,并且您在其上调用opacity()
,则会关闭方法opacity()
上的泛型,因此它会返回删除其返回类型,Shape.Builder
。你不能在那上面打height()
。
答案 2 :(得分:0)
我是一个提出类似问题的人。感谢Balder和newacct的答案。我试着用外行的话来概括它,我可以记住它。
<?>
,编译器只知道返回的类型为Rectangle.Builder
,而Rectangle.Builder
是Shape.Builder
的子类,没有任何其他信息。根据类T Shape.Builder#Opacity()
中的定义Shape.Builder<T extends Shape.Builder<T>
,编译器的最佳知识是返回的T
是Shape.Builder
的子类,因此,方法opacity()
返回类型Shape.Builder
,此类型无法访问方法height()
。使用<?>
,编译器知道
Rectangle.Builder<Something>
; Rectangle.Builder
,因为T extends Rectangle.Builder<T>
; Shape.Builder<Something>
的子类,
因为在定义Rectangle.Builder<T ...> extends
Shape.Builder<T>
。通过第3点,编译器知道T Shape.Builder#opacity()
返回{{1>}类型 Something ;通过 Point 2 ,编译器知道Type Something 是T
的子类,因此在调用方法Rectangle.Builder
之后,返回的类型可以访问{ {1}}方法opacity()
。
我希望编译器真的像上面那样思考。