Question

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();
     }
}
Was it helpful?

Solution

What was the additional information passed to the compiler using the additional <?>?

The additional information by using a wildcard <?> was, that the returned Rectangle.Builder<?> is the super class of all possible generic Rectangle.Builder<T> classes (see Wildcards). And since Rectangle.Builder<T> is guaranteed to have a type argument T, that is itself a subclass of Rectangle.Builder, as long as its generic typing is not ignored, Rectangle.Builder<?> is also guaranteed to be at least of type Rectangle.Builder<? extends Rectangle.Builder<?>>. If you completely ignore the generics by removing the wildcard, this information is lost and the code will be compiled as ordinary pre-Java5.0 code (where generics did not exist). This is necessary for backward compatibility.

To see the difference, consider a subclass of Rectangle.Builder that ignores the generic typing:

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

Note, that this class overwrites Shape.Builder#opacity without returning a subclass of itself. The compiler will not generate errors for this class (but it may warn you, that the class ignores the generic typing). So without the generic information, it is legal, to return the type Shape.Builder from the opacity method. As soon as you add a type argument to BadBuilder, this code will no longer compile:

public static class BadBuilder extends Rectangle.Builder<BadBuilder> // -> compile time error

So the reason you get the compiler error cannot find symbol is, because the class Shape.Builder does not itself declare the method/symbol T Shape.Builder#heigth(), and the declared method T Shape.Builder#opacity() does only guarantee, that the returned Object is of type Shape.Builder, as declared in the type argument of class Shape.Builder<T extends Shape.Builder<T>>. Therefore calling the method chain Rectangle.builder().opacity(0.5).height(250) will only work, if Rectangle.builder() is actually guaranteed to return a Builder, that is typed with a subclass of Rectangle.Builder. And this guarantee can only be given, if the generic typing is not ignored (as seen in the BadBuilder example).

When you add the method Shape.Builder#heigth, by removing the comment in your code, this error obviously goes away, because then the Shape.Builder object returned by Shape.Builder#opacity will also have the corresponding method. You could also remove this error, by re-declaring Shape.Builder#opacity in Rectangle.Builder like so:

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

If you do this, then it is guaranteed, that the returned Object of T Rectangle.Builder#opacity() is of type Rectangle.Builder, as declared in the type arguments to class Rectangle.Builder<T extends Rectangle.Builder<T>> extends Shape.Builder<T>.

Hope this helps.

OTHER TIPS

This difference is because when you use a raw type in a method, it turns generics for ALL the things you do with that type.

For example, suppose Builder had a method foo() that returns a List<String>. If you call foo() on an expression of type Builder<?>, it will be type List<String>. On the other hand, if call foo() on an expression of the raw type Builder, the type of that expression is List, not List<String>, even though the type List<String> is not related to T at all. It gets treated as if the method foo()'s return type was the erasure of what it actually is.

So in your case, suppose Rectangle.builder() returned type Rectangle.Builder<?>. For convenience let's give a name to this ?, say X. So you have Rectangle.Builder<X> (which inherits from Shape.Builder<X>) and you call opacity() on it, which results in X. We know because X is the type parameter of Rectangle.Builder, X must be a subtype of Rectangle.Builder<X>. So we can call height() on it.

However, if Rectangle.builder() returned the raw type Rectangle.Builder, and you call opacity() on it, it turns off generics on the method opacity(), so it returns the erasure of its return type, which is Shape.Builder. And you cannot call height() on that.

I am one who asks similar questions. Thanks to the answers by Balder and newacct. I tried to summarize it in layman's word which I can memorize.

  • Without <?>, Compiler only knows the returned type is Rectangle.Builder and Rectangle.Builder is a subclass of Shape.Builder without any other information. Since by definition T Shape.Builder#Opacity() in class Shape.Builder<T extends Shape.Builder<T>, the best knowledge for Compiler is that the returned T is a subclass of Shape.Builder, therefore, method opacity() returns a type of Shape.Builder and this type cannot access method height().
  • With <?>, Compiler knows

    1. the returned type is Rectangle.Builder<Something>;
    2. this Type Something by definition is a subclass of Rectangle.Builder, because T extends Rectangle.Builder<T>;
    3. the returned type is also a subclass of Shape.Builder<Something>, because in definition Rectangle.Builder<T ...> extends Shape.Builder<T>.

By Point 3, Compiler knows that T Shape.Builder#opacity() returns a T that is Type Something; By Point 2, Compiler knows that the Type Something is a subclass of Rectangle.Builder, so after calling method opacity(), the returned type can access Rectangle.Builder 's method height().

I hope the compiler really thinks like above.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top