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 Element
s 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' bySplittable
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.