user5182503
user5182503

Reputation:

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?

Upvotes: 2

Views: 2722

Answers (3)

mittah
mittah

Reputation: 41

I would like to post here the test demonstrating builder pattern with deep inheritance.

class TypeParamTest {

@Test
void test() {
    Dd dd = Dd.builder()
        .setIntAa(0)
        .setIntBb(1)
        .setIntCc(2)
        .setIntDd(3)
        .build();

    assertEquals(0, dd.intAa);
    assertEquals(1, dd.intBb);
    assertEquals(2, dd.intCc);
    assertEquals(3, dd.intDd);
}

abstract static class Aa {
    int intAa;

    static class AaBuilder<B extends AaBuilder> {
        int intAa;

        Aa build(Aa aa) {
            aa.intAa = intAa;
            return aa;
        }

        B setIntAa(int i) {
            this.intAa = i;
            return (B) this;
        }
    }
}

abstract static class Bb extends Aa {
    int intBb;

    static class BbBuilder<B extends BbBuilder<B>>
        extends AaBuilder<B>
    {
        int intBb;

        Bb build(Bb bb) {
            bb = (Bb) super.build(bb);
            bb.intBb = intBb;
            return bb;
        }

        B setIntBb(int i) {
            this.intBb = i;
            return (B) this;
        }
    }
}

static class Cc extends Bb {
    int intCc;

    static CcBuilder<?> builder() {
        return new CcBuilder<>();
    }

    static class CcBuilder<B extends CcBuilder<B>>
        extends BbBuilder<B>
    {
        int intCc;

        Cc build() {
            return build(new Cc());
        }

        Cc build(Cc cc) {
            cc = (Cc) super.build(cc);
            cc.intCc = intCc;
            return cc;
        }

        B setIntCc(int i) {
            this.intCc = i;
            return (B) this;
        }
    }
}

static class Dd extends Cc {
    int intDd;

    static DdBuilder<?> builder() {
        return new DdBuilder<>();
    }

    static class DdBuilder<B extends DdBuilder<B>>
        extends CcBuilder<B>
    {
        int intDd;

        Dd build() {
            return build(new Dd());
        }

        Dd build(Dd dd) {
            dd = (Dd) super.build(dd);
            dd.intDd = intDd;
            return dd;
        }

        B setIntDd(int i) {
            this.intDd = i;
            return (B) this;
        }
    }
}
}

Upvotes: 1

John Bollinger
John Bollinger

Reputation: 180058

As @AndyTurner already observed, the problem is that you use raw versions of your builder class types as type parameters. He did not go into detail, but the upshot is this:

ClassC C=ClassC.newBuilder() // yields a ClassCBuilder<ClassCBuilder>
        .setaString(null)    // yields a raw ClassCBuilder (type parameter)
        .setbString(null)    // yields a raw ClassBBuilder (type parameter bound)
        .setcString(null)    // ERROR: no such method on ClassBBuilder
        .build();

To fix this with minimal change to your class structure and strategy, you must not only correct the type parameter bounds for your builder classes, as Andy advised ...

ClassABuilder<T extends ClassABuilder<T>>

... etc., but also make a change to ClassC.newBuilder(), such as to make it generic:

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

With that combination of changes, your code compiles for me.

Upvotes: 0

Thomas
Thomas

Reputation: 88707

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

Upvotes: 0

Related Questions