Frage

This has probably been asked before in some form or other but I still can't solve it so I thought I'd ask the collective wisdom here. I have an interface like so - I left in some of the comments as they may be of help.

/** Technical note: this generic binding is known as F-bound where class
 * T is 'the worked-on class' by Splittable i.e. the implementing class.
 */
interface Splittable <T extends Element & Splittable<T>> {


/** Returns an object of the same class which has been split off at
 * the point given.
 */ 
public T splitOff(double at);


/** Appends the object provided, which must be of the same class, to
 * this object.
 */
public void append(T el);
}

So then I'm working on an Element which I don't know at compile time thus:

if (el instanceof Splittable) {
    if (nextEl.getClass == el.getClass) {
        // same class so join them
        ((Splittable)el).append(nextEl);
    }
}

and that's where I get a compiler warning - unchecked call to append(T) as a member of the raw type Splittable.

In the Element subclass I have tried both

SubClassEl extends Element implements Splittable

and

SubClassEl extends Element implements Splittable<SubClassEl>

with no difference in warning.

Also, at the point of calling append(), I tried

((Splittable)el).splitOff(x).append(nextElement)

but the compiler doesn't recognise that splitOff should return a Splittable, only an Element.

Any ideas?

War es hilfreich?

Lösung

What's happening here is that you're using raw types, which "opt out" of generic type checking. When that happens, T is erased to Element. From The Java Tutorials:

When a multiple bound is used, the first type mentioned in the bound is used as the erasure of the type variable.

As far as how to get your code compiling without warnings, it may not be possible with your current design. Working on plain Elements with instanceof and casts is not going to mix well with the recursively-bound type parameter declared on Splittable.

Avoiding raw types, the closest I could get was this:

static <T extends Element & Splittable<T>> void maybeAppend(T el1, Element el2) {
    if (el1.getClass() == el2.getClass()) {
        @SuppressWarnings("unchecked") // arguments' runtime types are equal
        final T el2WithNarrowedType = (T)el2;
        el1.append(el2WithNarrowedType);
    }
}

...

if (el instanceof Splittable<?>) {
    maybeAppend(el, nextEl); //compiler error
}

This still doesn't compile because without a type parameter there's no way to express that el is both an Element and a Splittable of its own type.

I answered a similar problem here: Passing a runtime resolved parameter to a method which has multiple bound type, compilation error. The closest solution not using raw types that I could come up with was a major hack and only worked on certain compilers. The OP ended up using a raw type.

My recommendation to you is to avoid the self-typing and consider implementing something like this instead:

interface ElementSplitter<T extends Element> {

    T splitOff(T element, double at);

    void append(T element, T appendedElement);
}

If an implementation's splitOff and append functionality require access to private members of a given Element derivation, a workaround would be to make it a nested class, for example:

class SubClassEl extends Element {

    ...

    static class Splitter implements ElementSplitter<SubClassEl> {
        ...
    }
}

I notice in the Splittable javadoc you wrote:

Technical note: this generic binding is known as F-bound where class T is 'the worked-on class' by Splittable i.e. the implementing class.

You should just be aware that true self-typing is not supported by Java - what you have is a recursively bound type parameter, which is supposedly, but not necessarily, the self-type. See my answer here for more details on this pattern and its pitfalls.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top