Java方法无界类型或类返回

时间:2014-03-20 06:28:59

标签: java generics design-patterns unbounded-wildcard

我正在阅读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();
     }
}

3 个答案:

答案 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。我们知道因为XRectangle.Builder的类型参数,X必须是Rectangle.Builder<X>的子类型。所以我们可以在上面调用height()

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

答案 2 :(得分: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. 此类型 Something 按定义是其子类 Rectangle.Builder,因为T extends Rectangle.Builder<T>;
    3. 返回的类型也是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()

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