Hani Hani - 1 month ago 14
Java Question

Method call chaining with generic types (Curiously Recurring Generic Pattern)

I'm getting a compile error that I don't understand the reason for.

I'm trying to create a builder that extends another builder, all using generic types.

The problem is that the return type of some generic methods is the parent class, not the child, which then prevents me from chaining any of the child methods.

Here is simple example:

public class BuilderParent {
public static class BuilderParentStatic<B extends BuilderParentStatic<B>> {
public BuilderParentStatic() {}
public B withParentId(int rank) { return self(); }
protected B self() { return (B)this; }
}
}

public class BuilderChild extends BuilderParent {
public static class BuilderChildStatic<B extends BuilderChildStatic<B>>
extends BuilderParent.BuilderParentStatic<B> {
public BuilderChildStatic() {}
public B withChildStuff(String s) { return (B)this.self(); }
protected B self() { return (B)this; }
}
}

public class Test {
public static void main(String args[]) {
BuilderChild.BuilderChildStatic builder = new BuilderChild.BuilderChildStatic();

// OK (uses child first, then parent):
builder.withChildStuff("childStuff").withParentId(1);

// compile error (uses parent first, then child):
builder.withParentId(1).withChildStuff("childStuff");
}
}


Why do I get the compilation error? How can I make it work as expected ?

Answer

Philip Voronov's explanation is correct (and I've upvoted it), but since you also asked for an explanation of how to fix it . . . the easiest fix is to split each builder class in two:

  • a generic parent, defined exactly like how you've defined BuilderParentStatic and BuilderChildStatic (but perhaps renamed), to implement the chaining/inheritance/etc.
  • a non-generic child class that, by specifying its parent's type argument, ensures that clients don't have to.

For example, if you rename BuilderParentStatic and BuilderChildStatic to BuilderParentGeneric and BuilderChildGeneric (respectively), then you can write:

public static final class BuilderParentStatic
    extends BuilderParentGeneric<BuilderParentStatic> {
    // empty class definition -- everything we need is in BuilderParentGeneric
}

and

public static final class BuilderChilderStatic
    extends BuilderChildGeneric<BuilderChilderStatic> {
    // empty class definition -- everything we need is in BuilderChildGeneric
}

and then declare and initialize your builder exactly like you're doing now.

This way, you avoid raw types (and all the problems they bring), but without needing to specify type arguments everywhere.

Comments