9

I was reading this article, , about subclassing a builder class. I understood the article but there was one small bit that bothered me. There was this method,

public static Builder<?> builder() {
        return new Builder2();
}

When I changed Builder<?> to Builder, a raw type, the compiler would not compile the code. The error was,

Rectangle.java:33: error: cannot find symbol
System.out.println(Rectangle.builder().opacity(0.5).height(250);

What was the additional information passed to the compiler using the additional <?>? I suspected it was the compiler which could not figure the right instance during compilation. If I remove the comment markers in (A) the code compiled and ran fine. All the time it was referring to the Rectangle instance. So, my guess is it was the compiler which failed.

It would be great if someone can point me to an article that explains this or leads to find out more information to this. Thanks.

I have pasted the code here:

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

additional を使用してコンパイラに渡された追加情報は何でした<?>か?

ワイルドカードを使用することによる追加情報<?>は、返される がすべての可能なジェネリックRectangle.Builder<?>クラスのスーパー クラスであるということでした(ワイルドカードを参照)。また、は型引数 T を持つことが保証されているため、それ自体が のサブクラスであり、そのジェネリック型付けが無視されない限り、も少なくとも型 であることが保証されます。ワイルドカードを削除してジェネリックを完全に無視すると、この情報が失われ、コードは通常の Java5.0 以前のコード(ジェネリックが存在しないコード) としてコンパイルされます。これは下位互換性のために必要です。 Rectangle.Builder<T>Rectangle.Builder<T>Rectangle.BuilderRectangle.Builder<?>Rectangle.Builder<? extends Rectangle.Builder<?>>

違いを確認するには、一般的な型付けを無視する 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自体が method/symbolT Shape.Builder#heigth()を宣言しておらず、宣言されたメソッドT Shape.Builder#opacity()が、返された Object がShape.Builder型引数で宣言されているように型であることを保証するだけであるためですclass Shape.Builder<T extends Shape.Builder<T>>。したがって、メソッド チェーンの呼び出しは、Rectangle.Builder のサブクラスで型指定された Builder を実際に返すことが保証されているRectangle.builder().opacity(0.5).height(250)場合にのみ機能します。Rectangle.builder()そして、この保証は、一般的な型付けが無視されない場合にのみ与えられます (BadBuilder の例に見られるように)。

method を追加するShape.Builder#heigthと、コード内のコメントを削除して、このエラーは明らかになくなります。これは、Shape.Builderによって返されるオブジェクトにShape.Builder#opacityも対応するメソッドがあるためです。Shape.Builder#opacity次のように Rectangle.Builder で再宣言することにより、このエラーを削除することもできます。

@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>

お役に立てれば。

于 2014-03-20T11:04:22.610 に答える
0

私も同じような質問をする者です。Baldernewacctによる回答に感謝します。覚えられる素人の言葉でまとめてみました。

  • がない<?>場合、コンパイラは、返された型がRectangle.BuilderRectangle.Builderあり、 のサブクラスであることのみを認識し、Shape.Builder他の情報はありません。T Shape.Builder#Opacity()classの定義によるShape.Builder<T extends Shape.Builder<T>と、コンパイラにとって最善の知識は、返さTれる が のサブクラスでShape.Builderあることです。したがって、 methodopacity()は の型を返し、Shape.Builderこの型は method にアクセスできませんheight()
  • を使用<?>すると、コンパイラは知っています

    1. 返される型はRectangle.Builder<Something>;
    2. このタイプの何かは、定義により のサブクラス Rectangle.BuilderですT extends Rectangle.Builder<T>
    3. 返される型も のサブクラスです。Shape.Builder<Something>なぜなら、定義ではRectangle.Builder<T ...> extends Shape.Builder<T>.

ポイント 3までに、コンパイラはそれが Type SomethingT Shape.Builder#opacity()である a を返すことを認識しています。ポイント 2までに、Compiler は Type Somethingが のサブクラスであることを認識するため、method を呼び出した後、返された型はの methodにアクセスできます。TRectangle.Builderopacity()Rectangle.Builderheight()

コンパイラが本当に上記のように考えていることを願っています。

于 2014-03-21T08:48:46.673 に答える