1

I'm having an issue with a self-bounding generic type which has a self-bounding generic subtype.

I'm trying to implement some kind of builder pattern and i'd like to have my statements more or less like in the main method.

Can anyone help me out in finding a better why to declare the generics so I no longer need the cast and I don't get compilation errors in the statements. Or can anyone explain in clear text why this can't work?

import java.util.Date;

public class SelfBoundingGenericTypeTest {
    public static void main(String[] args) {
        ConcreteType type = new ConcreteType().pageSize(1).id(12);

        SubType type2 = (SubType) new SubType().id(10).pageSize(0); // Why do i need the cast?

        SubType type3 = new SubType().pageSize(0).id(10); // Compile error
    }
}

abstract class SuperType<E extends SuperType<E>> {
    private int _pageSize = Integer.MIN_VALUE;
    private int _startIndex = Integer.MIN_VALUE;

    @SuppressWarnings("unchecked")
    public E pageSize(int value) {
        this._pageSize = value;
        return (E) this;
    }

    @SuppressWarnings("unchecked")
    public E startIndex(int value) {
        this._startIndex = value;
        return (E) this;
    }

    public int getPageSize() {
        return _pageSize;
    }

    public int getStartIndex() {
        return _startIndex;
    }
}

class SubType<E extends SubType<E>> extends SuperType<E> {
    private long _id = Long.MIN_VALUE;

    @SuppressWarnings("unchecked")
    public E id(long value) {
        this._id = value;
        return (E) this;
    }

    public long getId() {
        return _id;
    }
}

class ConcreteType extends SubType<ConcreteType> {
    private Date _startDate;

    public Date getStartDate() {
        return _startDate;
    }

    public ConcreteType startDate(Date value) {
        this._startDate = value;
        return this;
    }
}
BartCr
  • 65
  • 1
  • 5

2 Answers2

1

You need the cast because SubType is a raw type. As such all its members are raw, including those inherited from SuperType. The raw signature of SuperType.pageSize is its erasure SuperType pageSize(int). So the "fix" is to not use raw types. This will all magically work for ConcreteType.

Edit: Don't use raw types. Ever. You should use your ConcreteType, but before you use the stupid, moronic, idiotic "solution" of redeclaring every method, use ((SubType<?>) new SubType()) instead.

Ben Schulz
  • 6,101
  • 1
  • 19
  • 15
0

I am not really sure about the failing reason but that's my understanding: abstract class SuperType> { public E pageSize(int value) { ...} }

For method pageSize, as you have E declared as extends SuperType<E>, after type-erasure, the method signature is in fact SuperType pageSize(int) , which caused the problem in new SubType().pageSize(0).id(10) because pageSize is returning a SuperType.

Though it doesn't looks as magical as you expect, with covariant return type you can simply "overload" those methods in inherited class:

import java.util.Date;

public class SelfBoundingGenericTypeTest {
    public static void main(String[] args) {
        ConcreteType type = new ConcreteType().pageSize(1).id(12);

        SubType type2 = new SubType().id(10).pageSize(0); // works fine now

        SubType type3 = new SubType().pageSize(0).id(10); // works fine too
    }
}

abstract class SuperType {
    private int _pageSize = Integer.MIN_VALUE;
    private int _startIndex = Integer.MIN_VALUE;

    public SuperType pageSize(int value) {
        this._pageSize = value;
        return this;
    }

    public SuperType startIndex(int value) {
        this._startIndex = value;
        return this;
    }

    public int getPageSize() {
        return _pageSize;
    }

    public int getStartIndex() {
        return _startIndex;
    }
}

class SubType extends SuperType {
    private long _id = Long.MIN_VALUE;

    public SubType id(long value) {
        this._id = value;
        return this;
    }

    public SubType pageSize(int value) {
        return (SubType) super.pageSize(value);
    }

    public SuperType startIndex(int value) {
        return (SubType) super.pageSize(value);
    }

    public long getId() {
        return _id;
    }
}

class ConcreteType extends SubType {
    private Date _startDate;

    public Date getStartDate() {
        return _startDate;
    }

    public ConcreteType startDate(Date value) {
        this._startDate = value;
        return this;
    }

    public ConcreteType id(long value) {
        return (ConcreteType) super.id(value);
    }

    public ConcreteType pageSize(int value) {
        return (ConcreteType) super.pageSize(value);
    }

    public ConcreteType startIndex(int value) {
        return (ConcreteType) super.pageSize(value);
    }

}
Adrian Shum
  • 38,812
  • 10
  • 83
  • 131
  • We were trying to avoid the override by using generics, but maybe there's no better way to write it. – BartCr Aug 28 '12 at 09:14