Java 方法无限制类型或类返回

9
我将为您翻译以下内容:

我正在阅读有关子类化生成器类的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();
     }
}

强制转换 (T) this 不安全。 - newacct
3个回答

6
使用通配符<?>时传递给编译器的附加信息是:返回的Rectangle.Builder<?>是所有可能的泛型Rectangle.Builder<T>类的超类(请参阅通配符)。由于Rectangle.Builder<T>保证具有类型参数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方法。编译器不会针对此类生成错误(但它可能会警告您,该类忽略了泛型类型)。因此,在没有泛型信息的情况下,从opacity方法返回类型Shape.Builder是合法的。一旦为BadBuilder添加类型参数,这段代码将无法编译:
public static class BadBuilder extends Rectangle.Builder<BadBuilder> // -> compile time error

因为类Shape.Builder本身没有声明方法/符号T Shape.Builder#heigth(),所以你会得到编译错误cannot find symbol。而已声明的方法T Shape.Builder#opacity()仅保证返回对象的类型是Shape.Builder,如在class Shape.Builder<T extends Shape.Builder<T>>的类型参数中声明。因此,只有当Rectangle.builder()确实保证返回的Builder是类型为Rectangle.Builder子类的Builder时,才能调用方法链Rectangle.builder().opacity(0.5).height(250)。这种保证只有在不忽略泛型类型的情况下才能给出(如在BadBuilder示例中所见)。
通过注释代码中的代码Shape.Builder#heigth添加该方法,显然可以消除此错误,因为此时由Shape.Builder#opacity返回的Shape.Builder对象也将具有相应的方法。您还可以通过在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> 的类型参数中已经声明。
希望这能帮助您。

2

这是因为当你在方法中使用原始类型时,它会将所有使用该类型的泛型都转换成相同的类型。

例如,假设 Builder 有一个返回类型为 List<String>foo() 方法。如果你在类型为 Builder<?> 的表达式上调用 foo(),它将是类型为 List<String>。另一方面,如果你在原始类型 Builder 的表达式上调用 foo(),那么该表达式的类型就是 List,而不是 List<String>尽管类型 List<String>T 没有任何关系。它会被处理成似乎方法 foo() 的返回类型实际上是其擦除形式。

所以在你的情况下,假设 Rectangle.builder() 返回类型为 Rectangle.Builder<?>。为了方便起见,我们给这个 ? 赋一个名称,比如叫做 X。所以你拥有的是 Rectangle.Builder<X>(继承自 Shape.Builder<X>),并在其上调用 opacity(),结果是 X。我们知道因为 XRectangle.Builder 的类型参数,X 必须是 Rectangle.Builder<X> 的子类型。所以我们可以在其上调用 height()

然而,如果 Rectangle.builder() 返回原始类型 Rectangle.Builder,并在其上调用 opacity(),它会关闭方法 opacity() 上的泛型,从而返回其擦除形式的返回类型,即 Shape.Builder。你无法对其调用 height()


0

我也是一个提出类似问题的人。感谢 Baldernewacct 的答案。我试图用通俗易懂的话语总结一下,以便我能够记住。

  • 没有<?>,编译器只知道返回类型是Rectangle.Builder,而Rectangle.BuilderShape.Builder的子类,但没有其他信息。根据定义T Shape.Builder#Opacity()在类Shape.Builder<T extends Shape.Builder<T>中,编译器最好的理解是返回的TShape.Builder的子类,因此opacity()方法返回的类型是Shape.Builder,而这个类型无法访问height()方法。
  • 有了<?>,编译器知道:

    1. 返回类型是Rectangle.Builder<Something>
    2. 根据定义,SomethingRectangle.Builder的子类,因为T extends Rectangle.Builder<T>
    3. 返回类型也是Shape.Builder<Something>的子类,因为在定义中Rectangle.Builder<T ...> extends Shape.Builder<T>
通过Point 3,编译器知道T Shape.Builder#opacity()返回一个类型为SomethingT;通过Point 2,编译器知道SomethingRectangle.Builder的子类,所以在调用方法opacity()后,返回的类型可以访问Rectangle.Builder的方法height()
我希望编译器真的像上面那样思考。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接