9

我正在阅读这篇关于子类化构建器类的文章。我理解这篇文章,但有一点让我感到困扰。有这个方法,

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

3 回答 3

6

使用附加传递给编译器的附加信息是什么<?>

使用通配符的附加信息<?>是,返回Rectangle.Builder<?>的是所有可能的泛型 Rectangle.Builder<T>类的超类(请参阅通配符)。并且由于Rectangle.Builder<T>保证有一个类型参数 T,它本身就是 的子类Rectangle.Builder只要它的泛型类型不被忽略Rectangle.Builder<?>也保证至少是 type 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 而不返回其自身的子类。编译器不会为此类生成错误(但它可能会警告您,该类忽略泛型类型)。因此,如果没有通用信息,从 opacity 方法返回类型是合法的。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 是 type Shape.Builder,正如在 type 参数中声明的那样class Shape.Builder<T extends Shape.Builder<T>>。因此,调用方法链Rectangle.builder().opacity(0.5).height(250)只有在Rectangle.builder()实际保证返回一个 Builder 时才有效,该 Builder 是使用 Rectangle.Builder 的子类键入的。并且只有在不忽略泛型类型的情况下才能提供这种保证(如 BadBuilder 示例所示)。

当您添加方法Shape.Builder#heigth时,通过删除代码中的注释,此错误显然消失了,因为Shape.Builder返回的对象Shape.Builder#opacity也将具有相应的方法。Shape.Builder#opacity您也可以通过在 Rectangle.Builder 中重新声明来消除此错误,如下所示:

@Override
public T opacity(double opacity) {
    return super.opacity(opacity);
}

如果您这样做,则可以保证返回的 Object 的T Rectangle.Builder#opacity()类型 Rectangle.Builder为 ,正如在 的类型参数中声明的那样class Rectangle.Builder<T extends Rectangle.Builder<T>> extends Shape.Builder<T>

希望这可以帮助。

于 2014-03-20T11:04:22.610 回答
2

这种差异是因为当您在方法中使用原始类型时,它会为您使用该类型所做的所有事情变成泛型。

例如,假设Builder有一个foo()返回List<String>. 如果调用foo()type 的表达式Builder<?>,它将是 type List<String>。另一方面,如果调用foo()原始类型的表达式,则该表达式Builder的类型是List,不是List<String>即使该类型根本List<String>不相关T。它被视为方法foo()的返回类型是对其实际内容的擦除。

因此,在您的情况下,假设Rectangle.builder()返回 type Rectangle.Builder<?>。为方便起见,我们给它起个名字?,比如说X。所以你有Rectangle.Builder<X>(继承自Shape.Builder<X>)并且你调用opacity()它,结果是X. 我们知道因为X是 的类型参数Rectangle.BuilderX必须是 的子类型Rectangle.Builder<X>。所以我们可以调用height()它。

但是,如果Rectangle.builder()返回原始类型Rectangle.Builder,并且您调用opacity()它,它会关闭方法上的泛型opacity(),因此它返回其返回类型的擦除,即Shape.Builder. 你不能要求height()那个。

于 2014-03-20T22:20:42.100 回答
0

我是一个问类似问题的人。感谢Baldernewacct的回答。我试图用我能记住的外行的话来概括它。

  • 没有<?>,编译器只知道返回的类型是Rectangle.Builder并且Rectangle.Builder是的子类,Shape.Builder没有任何其他信息。由于根据T Shape.Builder#Opacity()Shape.Builder<T extends Shape.Builder<T>中的定义,Compiler 的最佳知识是返回T的是 的子类Shape.Builder,因此,方法opacity()返回一个类型,Shape.Builder并且该类型不能访问方法height()
  • <?>编译器知道

    1. 返回的类型是Rectangle.Builder<Something>
    2. 根据定义,这个 Type Something是 的子类 Rectangle.Builder,因为T extends Rectangle.Builder<T>;
    3. 返回的类型也是 的子类Shape.Builder<Something>,因为在定义中Rectangle.Builder<T ...> extends Shape.Builder<T>

通过第 3 点,编译器知道T Shape.Builder#opacity()返回的T是 Type Something;通过第 2 点,Compiler 知道 Type Something是的子类Rectangle.Builder,因此在调用方法之后opacity(),返回的类型可以访问Rectangle.Builder's 方法height()

我希望编译器真的像上面那样思考。

于 2014-03-21T08:48:46.673 回答