0

I'm trying to subclass a Builder class, as described in Subclassing a Java Builder class.

The solution given there is pretty simple. Here is the base class:

public class NutritionFacts {

    private final int calories;

    public static class Builder<T extends Builder> {

        private int calories = 0;

        public Builder() {}

        public T calories(int val) {
            calories = val;
            return (T) this;
        }

        public NutritionFacts build() { return new NutritionFacts(this); }
    }

    protected NutritionFacts(Builder builder) {
        calories = builder.calories;
    }
}

And the subclass:

public class GMOFacts extends NutritionFacts {

    private final boolean hasGMO;

    public static class Builder extends NutritionFacts.Builder<Builder> {

        private boolean hasGMO = false;

        public Builder() {}

        public Builder GMO(boolean val) {
            hasGMO = val;
            return this;
        }

        public GMOFacts build() { return new GMOFacts(this); }
    }

    protected GMOFacts(Builder builder) {
        super(builder);
        hasGMO = builder.hasGMO;
    }
}

However, the return (T) this; in NutritionFacts.Builder#calories(int) results in a

NutritionFacts.java uses unchecked or unsafe operations

warning.

Given the generics, why is this cast unsafe? I know that T extends Builder and that this is a Builder, so why is the cast from this (Builder) to T (extends Builder) unsafe? The cast should never fail, right?

Community
  • 1
  • 1
Dmitry Minkovsky
  • 36,185
  • 26
  • 116
  • 160

4 Answers4

2

It can very well fail. In

public T calories(int val) {
    calories = val;
    return (T) this;
}

you assume that T in

public static class Builder<T extends Builder> {

will always be bound to the type of the subclass. For example

public static class Real extends Builder<Real>{}

There is no way in Java to enforce that the type argument is the same as the declared type itself. Someone could very well have

public static class Fake extends Builder<Real> {}

in which case

Builder<Real> fake = new Fake();
Real real = fake.calories(4);

would fail with a ClassCastException.

If you know this will never happen, just ignore the warning. A cast is an explicit assertion that you know what you're doing.

Sotirios Delimanolis
  • 274,122
  • 60
  • 696
  • 724
  • Thank you: this was the pivotal sentence: "There is no way in Java to enforce that the type argument is the same as the declared type itself.", and then of course the examples made it clear. – Dmitry Minkovsky Jan 30 '15 at 16:54
  • Any way to remediate this situation design-wise? – Dmitry Minkovsky Jan 30 '15 at 16:55
  • @dimadima That's a good question. And it seems you found your current solution [here](http://stackoverflow.com/questions/17164375/subclassing-a-java-builder-class). What you were looking for, I assume, is [method chaining in inheritance hierarchies](http://stackoverflow.com/questions/1069528/method-chaining-inheritance-don-t-play-well-together-java). Java does not have a clean solution afaik. – Sotirios Delimanolis Jan 30 '15 at 17:01
  • Brilliant. The link over there to http://www.angelikalanger.com/GenericsFAQ/FAQSections/ProgrammingIdioms.html#FAQ206 is great. – Dmitry Minkovsky Jan 30 '15 at 17:29
0

Say you have

Builder<SubClassOfBuilder> b = ...
SubClassOfBuilder scob = b.calories(1); // compiles fine.

The problem is that b might not be a SubClassOfBuilder and so you will get a ClassCastException at runtime.

Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
0

Because NutritionFacts.Builder is a raw type. References to the generic type NutritionFacts.Builder should be parameterized if you want to avoid the compiler warning.

You should really do this:

public static class Builder<T extends Builder<?>>
Gaz
  • 328
  • 3
  • 15
0

Just because this is a Builder, and T is a subtype of Builder, doesn't mean that this is a T.

Socrates is a human. A woman is a type of human. That doesn't mean Socrates is a woman.

khelwood
  • 55,782
  • 14
  • 81
  • 108