Unlimited Java Method Type or Class Return

I read this article about a subclass of a builder class. I understood this article, but it bothered me a little. There was this method,

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

When I changed Builder<?> To Builder , a raw type, the compiler will 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 extra <?> ? I suspected it was a compiler that could not determine the correct instance at compile time. If I remove the comment markers in (A), the code compiled and works fine. All the while, he referred to an instance of Rectangle. So, I think it was a compiler crash.

It would be great if someone could point me to an article that explains this, or leads to know more about it. Thanks.

I 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(); } } 
+7
java generics design-patterns unbounded-wildcard
source share
3 answers

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

Additional information using the wildcard <?> Was that the returned Rectangle.Builder<?> Is a superclass of all possible common classes of Rectangle.Builder<T> (see Wildcards ). And since Rectangle.Builder<T> guaranteed to have an argument of type T, which itself is a subclass of Rectangle.Builder , until its typical typing is ignored , it is also guaranteed that Rectangle.Builder<?> smallest type of Rectangle.Builder<? extends Rectangle.Builder<?>> Rectangle.Builder<? extends Rectangle.Builder<?>> . If you completely ignore generics by removing the wildcard, this information will be lost and the code will be compiled as regular pre-Java5.0 code (where generics do not exist). This is necessary for backward compatibility.

To see the difference, consider a subclass of Rectangle.Builder that ignores typical 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 can warn you that the class ignores typical typing). Thus, without general information, it is legal to return a Shape.Builder type from an opacity method. Once 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 a compiler error cannot find symbol is because the Shape.Builder class itself does not declare the T Shape.Builder#heigth() method / symbol, and the declared T Shape.Builder#opacity() method guarantees just that the returned object is of type Shape.Builder , as indicated in the argument of type class Shape.Builder<T extends Shape.Builder<T>> . Therefore, calling the Rectangle.builder().opacity(0.5).height(250) method chain will only work if Rectangle.builder() really guaranteed to return the Builder that is typed by a subclass of Rectangle.Builder. And this guarantee can only be provided if typical typing is not ignored (as shown in the BadBuilder example).

When you add the Shape.Builder#heigth method by deleting the comment in the code, this error will obviously disappear, because then the Shape.Builder object returned by Shape.Builder#opacity will also have the corresponding method. You can also remove this error by re-declaring Shape.Builder#opacity in Rectangle.Builder as follows:

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

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

Hope this helps.

+5
source share

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

For example, suppose Builder has a foo() method that returns a List<String> . If you call foo() on an expression of type Builder<?> , It will be of type List<String> . On the other hand, if foo() called in an expression of raw Builder type, the type of this expression is List , not List<String> , although the type of List<String> not associated with T at all. It is treated as if the return type of the foo() method was an erasure of what it actually is.

So, in your case, suppose Rectangle.builder() returns the type Rectangle.Builder<?> . For convenience, 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 that X is a parameter of type Rectangle.Builder , X must be a subtype of Rectangle.Builder<X> . Therefore, we can call height() on it.

However, if Rectangle.builder() returns the raw type Rectangle.Builder , and you call opacity() on it, it disables generics for the opacity() method, so it returns erasing its return type, which is equal to Shape.Builder . And you cannot call height() on this.

+2
source share

I am the one who asks such questions. Thanks to the answers of Balder and newacct . I tried to summarize it in the word layman, which I can remember.

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

    • return type Rectangle.Builder<Something> ;
    • This type Something is by definition a subclass of Rectangle.Builder , because T extends Rectangle.Builder<T> ;
    • the return type is also a subclass of Shape.Builder<Something> , because in the definition of Rectangle.Builder<T ...> extends Shape.Builder<T> .

In step 3, the compiler knows that T Shape.Builder#Opacity() returns T , which is Type Something; In step 2, the compiler knows that Type Something is a subclass of Rectangle.Builder , so after calling the opacity() method, the return type can access the Rectangle.Builder method of height() .

I hope the compiler really thinks as described above.

0
source share

All Articles