6

Why is the unsafe cast (T) needed in this generic interface? If T is comparable to itself, i.e. implements ExtendedComparable<super of T> which means also ExtendedComparable<T>, then why does type erasure require ExtendedComparable<T> to be cast to T?

/* @param <T> T must be comparable to itself or any of its superclass
 * (comparables are consumers, thus acc. to the PECS principle 
 * = producer-extends,consumer-super we use the bounded wildcard type "super")
 */   
public interface ExtendedComparable<T extends ExtendedComparable<? super T>> {
    Comparator<? super T> getComparator();
    default boolean greaterThen(T toCompare) {
        return getComparator().compare((T) this, toCompare) > 0;
    }
}
khelwood
  • 55,782
  • 14
  • 81
  • 108
ms34449
  • 61
  • 2

2 Answers2

5

Because there is no guarantee that this is actually an instance of class T or even extend it.

For example consider this:

public class T0 implements ExtendComparable<T0> {...}
public class T1 implements ExtendComparable<T0> {...}

In T0 complies fine as it complies with the bound: T0 extends ExtendComparable<T0> and T0 is super of T0. In this case this is an instance of T0 here so you are fine; the cast (T)this (thus (T0)this) makes sense.

With T1 the declaration is correct also because the bound is applied to T0 no T1, T is substituted T0. However this is T1 and T1 is not super nor a child of T0. Yes, both implement ExtendedCompatible<T0>, but you cannot cast between siblings. For example Integer and Double extend Number but (Integer) new Double(0.0) fails. So too does the cast (T) translated to (T0) fail.

The assumption you are making is that T is going to be set to the same as the class that is been declared and currently there is no way to force those semantics. I hope this will change at some point in future releases of the Java language but perhaps there is actual reason why the Java language "task force" are avoiding to do so.

There is a way to avoid the cast altogether but is better when you make ExtendedCompatible an abstract class rather than an interface.

You can declare a final field of type T which value would be set by a protected constructor by extending class which in turn must pass this as its value:

public abstract class ExtendedCompatible<T extends ExtendedCompatible<? super T>> {
  private final T thiz;

  protected ExtendedCompatible(final T thiz) {
     if (this != thiz) throw new IllegalArgumentException("you must pass yourself");
     this.thiz = thiz;
  }
  ...

  public class MyExtendedCompatible extends ExtendedCompatible<MyExtendedCompatible> {
     public MyExtendedCompatible() {
           super(this);
     }
  }

The price you pay is the extra memory consumption of having a silly reference to itself and the added code/CPU burden of passing this to the parent constructor.

Another would be to declare an abstract method to get the T (this):

// Parent abstract class:
   protected abstract T getThiz();
// Child class... for each class:
   protected MyChildClass getThiz() { return this; }
Eugene
  • 117,005
  • 15
  • 201
  • 306
Valentin Ruano
  • 2,726
  • 19
  • 29
  • 1
    that'a s very detailed and good answer IMO. stack overflow has a reputation of awarding only top rated users sometimes, but this is a really good one. +1 – Eugene Sep 19 '17 at 20:33
  • Thanks. That is right. We intended the interface to be used for many enums. But, unfortunately, the proposed work around is not suitable for enums, because they do not support inheritace and thus could not be derived from an abstract class. The 2nd work around is also not quite feasible, as each method in the interface would need to check getThiz() != this (in Java 9, at least all these identical checks could be refactored into a private interface method) and each enum would need to implement getThiz(). – ms34449 Sep 20 '17 at 07:02
  • @ms34449 What about enums? I think you can override methods per constant in you include a body in their declaration. EDIT ah! you mean across enums not across constants. – Valentin Ruano Sep 20 '17 at 07:28
0

Thanks. Valentin is right. Even if both types implement the same interface, this does not and should not make the cast between them to work. And yes, there is no mechanism in Java to enforce passing in T the same class as the class being declared.

ms34449
  • 61
  • 2
  • 2
    this should be a comment in response to Valentin answer - and you should accept his probably – Eugene Sep 19 '17 at 11:50