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();
}
}
Whenever you want to restrict the type parameter to subtypes of a particular class you can use the bounded type parameter. If you just specify a type (class) as bounded parameter, only sub types of that particular class are accepted by the current generic class. These are known as bounded-types in generics in Java.
An unbounded wildcard is the one which enables the usage of all the subtypes of an unknown type i.e. any type (Object) is accepted as typed-parameter. For example, if want to accept an ArrayList of object type as a parameter, you just need to declare an unbounded wildcard.
To declare a bounded type parameter, list the type parameter's name, followed by the extends keyword, followed by its upper bound, which in this example is Number .
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With