2

I have a builder class that I want to extend, here is the simple version:

class A {
    public A withSomeAStuff() {
        return this;
    }
}
A a = new A().withSomeAStuff();

When I extend it, I know that I can do this without any problem:

class AA<T extends AA> {
    public T withSomeAStuff() {
        return (T) this;
    }
}
class BB extends AA<BB> {
    public BB withSomeBStuff() {
        return this;
    }
}
AA aa = new AA().withSomeAStuff();
BB bb = new BB().withSomeAStuff().withSomeBStuff();

But now I want to extend it further with another class, so I try this:

class AAA<T extends AAA> {
    public T withSomeAStuff() {
        return (T) this;
    }
}
class BBB<T extends BBB> extends AAA<T> {
    public T withSomeBStuff() {
        return (T) this;
    }
}
class CCC extends BBB<CCC> {
    public CCC withSomeCStuff() {
        return this;
    }
}
AAA aaa = new AAA().withSomeAStuff();
BBB bbb = new BBB().withSomeAStuff().withSomeBStuff(); //breaks here!
CCC ccc = new CCC().withSomeAStuff().withSomeBStuff().withSomeCStuff();

My new CCC class works fine, but my BBB class is broken and I cannot figure out why.

What do I need to do to fix it?

james.cookie
  • 357
  • 5
  • 14
  • 1
    Note that when you compile your code (up to the second block; ignore the third for now), your compiler will warn you about "unchecked or unsafe operations". This should be your first hint that "without any problem" isn't really true. – Andy Turner Jul 15 '16 at 07:42

2 Answers2

2

When you introduce generics in the type declaration, then use it while creating objects of the type too.

    AAA<AAA> aaa = new AAA<>().withSomeAStuff();
    BBB<BBB> bbb = new BBB<>().withSomeAStuff().withSomeBStuff(); //Does not break anymore.
    CCC ccc = new CCC().withSomeAStuff().withSomeBStuff().withSomeCStuff();

Note: While this will solve your compiler error, this is not a foolproof way and guaranteed to work in every case. You will get compiler warnings to confirm that.

For example you could do,

BBB<CCC> bbb = new BBB<CCC>().withSomeAStuff().withSomeBStuff();

and get a shock during runtime.

Codebender
  • 14,221
  • 7
  • 48
  • 85
  • This is only part of the issue. The definition of `class AAA` is broken, because the `extends AAA` is raw; this erases the generics on the class' methods (and its subclasses too). – Andy Turner Jul 15 '16 at 07:38
  • @AndyTurner, True. Edited to iterate the loopholes. – Codebender Jul 15 '16 at 07:50
  • Whilst that stops the initial problem, I can still easily break it with: `BBB bbb = new BBB<>().withSomeAStuff().withSomeAStuff().withSomeBStuff(); //still breaks here` – james.cookie Jul 15 '16 at 07:53
  • @Codebender the problem is in the class definitions, not how they are instantiated. – Andy Turner Jul 15 '16 at 07:55
1

Never ignore raw type warnings: What is a raw type and why shouldn't we use it?

I added a method self() so you only have a single unchecked cast in your code.

class AAA<T extends AAA<T>> {
    public T withSomeAStuff() {
        return self();
    }

    @SuppressWarnings("unchecked")
    protected T self() {
        return (T) this;
    }
}

class BBB<T extends BBB<T>> extends AAA<T> {
    public T withSomeBStuff() {
        return self();
    }
}

class CCC extends BBB<CCC> {
    public CCC withSomeCStuff() {
        return this;
    }
}


public static void main(String[] args) {
    AAA<?> aaa = new AAA<>().withSomeAStuff();
    BBB<?> bbb = new BBB<>().withSomeAStuff().withSomeBStuff(); 
    CCC ccc = new CCC().withSomeAStuff().withSomeBStuff().withSomeCStuff();
}
Community
  • 1
  • 1
k5_
  • 5,450
  • 2
  • 19
  • 27
  • The key part of the answer is that BBB needs to be defined as `BBB>` and should be constructed as `new BBB<>()`. Then it all works, thanks! – james.cookie Jul 15 '16 at 10:27
  • 1
    But this is still unsafe; you are just ignoring the warning. – newacct Jul 15 '16 at 22:38
  • @newacct can you give me an example where this cast actually produces an poluted heap? (Without using another unsafe cast or raw type) – k5_ Jul 16 '16 at 06:37
  • 1
    @k5_: `class Foo extends AAA{} class Bar extends AAA{} Foo x = new Bar().withSomeAStuff();` – newacct Jul 16 '16 at 23:37