views:

386

answers:

2

Hi all,

I'm attempting to create a class with many parameters, using a Builder pattern rather than telescoping constructors. I'm doing this in the way described by Joshua Bloch's Effective Java, having private constructor on the enclosing class, and a public static Builder class. The Builder class ensures the object is in a consistent state before calling build(), at which point it delegates the construction of the enclosing object to the private constructor. Thus

public class Foo {

    // Many variables

    private Foo(Builder b) {
        // Use all of b's variables to initialize self
    }

    public static final class Builder {

        public Builder(/* required variables */) {

        }

        public Builder var1(Var var) {
            // set it
            return this;
        }

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

    }

}

I then want to add type bounds to some of the variables, and thus need to parametrize the class definition. I want the bounds of the Foo class to be the same as that of the Builder class.

public class Foo<Q extends Quantity> {

    private final Unit<Q> units;
    // Many variables

    private Foo(Builder<Q> b) {
        // Use all of b's variables to initialize self
    }

    public static final class Builder<Q extends Quantity> {
        private Unit<Q> units;

        public Builder(/* required variables */) {

        }

        public Builder units(Unit<Q> units) {
            this.units = units;
            return this;
        }

        public Foo build() {
            return new Foo<Q>(this);
        }

    }

}

This compiles fine, but the compiler is allowing me to do things I feel should be compiler errors. E.g.

public static final Foo.Builder<Acceleration> x_Body_AccelField =
        new Foo.Builder<Acceleration>()
        .units(SI.METER)
        .build();

Here the units argument is not Unit<Acceleration> but Unit<Length>, but it is still accepted by the compiler.

What am I doing wrong here? I want to ensure at compile time that the unit types match up correctly.

+2  A: 

units should return Builder<Q>, not an ungenerified Builder.

Daniel Martin
Thank you, I was not thinking very clearly in that regard.
I82Much
A: 

Although @Daniel's point is valid, still the error in your code is spotted by Eclipse at least. Of course, your definition of Quantity, Unit and METER is probably different from the simplistic hack I put together:

interface Quantity {
}
class Acceleration implements Quantity {
}
class Length implements Quantity {
}
public class Unit<Q extends Quantity> {
    public static final Unit<Length> METER = new Unit<Length>();
}

public static final Foo.Builder<Acceleration> x_Body_AccelField =
    new Foo.Builder<Acceleration>()
    .units(Unit.METER) // here the compiler complains
    .build();

The error message is:

The method units(Unit<Acceleration>) in the type Foo.Builder<Acceleration> is
not applicable for the arguments (Unit<Length>)
Péter Török
Interesting. I was using NetBeans, which did not complain. The unit etc hierarchy I'm using come from JSR 275 (http://download.java.net/maven/2/net/java/dev/jsr-275/jsr-275/1.0-beta-2/) and JScience (http://jscience.org/)
I82Much
You should be getting that error even in NetBeans... if not, that's a very bad bug. The difference in your original code is if you have an intermediate property: new Foo.Builder<Acceleration>().cheese(GOUDA).units(Unit.METER), where the 'cheese' method returns Builder not Builder<Q>.
Cowan