Pavel Pavel -4 years ago 237
Java Question

Java: builder pattern, inheritance and generics

I want to implement Builder Pattern with inheritance. So I have 4 following classes: one abstract class (ClassA), ClassB, ClassC. TestTest class is used to see how all this works:

public abstract class ClassA {

private String aString;

public String getaString() {
return aString;
}

public abstract class ClassABuilder<T extends ClassABuilder>{

public T setaString(String str) {
ClassA.this.aString = str;
return (T)this;
}

public abstract ClassA build();

}
}

public class ClassB extends ClassA{

private String bString;

public String getbString() {
return bString;
}

public class ClassBBuilder<T extends ClassBBuilder> extends ClassA.ClassABuilder<T>{

public T setbString(String str) {
ClassB.this.bString = str;
return (T)this;
}

@Override
public ClassB build(){
return ClassB.this;
}
}
}

public class ClassC extends ClassB{

private String cString;

public String getcString() {
return cString;
}

public static ClassCBuilder<ClassCBuilder> newBuilder(){
return new ClassC().new ClassCBuilder();
}

public class ClassCBuilder<T extends ClassCBuilder> extends ClassB.ClassBBuilder<T>{

public T setcString(String str) {
ClassC.this.cString = str;
return (T)this;
}

@Override
public ClassC build(){
return ClassC.this;
}
}
}

public class TestTest {

public static void main(String[] args) {
// TODO code application logic here
ClassC C=ClassC.newBuilder()
.setaString(null)
.setbString(null)
.setcString(null) //LINE XXX
.build();
}
}


The problem is that at TestTest at LINE XXX I get can't find symbol "setcString". What do I do wrong?

Answer Source

Let's track it down along the hierarchy:

First consider this signature:

class ClassABuilder<T extends ClassABuilder>

When you call setaString(null) the returned T will be an object that extends ClassABuilder. The compiler knows that this is a ClassBBuilder and thus will allow you to call setbString(null).

However, since the definition states T is required to extend a raw ClassBBuilder only any information on ClassBBuilder's generic types will be lost. Thus the compiler only knows that T is a ClassBBuilder but not that it's actually a ClassCBuilder which extends ClassBBuilder<ClassCBuilder> and hence doesn't know about setcString() on the returned type.

As has already been mentioned, using T extends ClassABuilder<T> will fix that since now the compiler knows there's another generic type to be passed down the hierarchy.

newBuilder() would then have to look like this:

public static ClassCBuilder<?> newBuilder(){
    //you have too create a raw type here so you'll have to ignore/suppress/live with the warning
    return (new ClassC().new ClassCBuilder());
}
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download