4

I am writing a generic collection based on a binary tree model.

class MyTree <T extends Comparable<T>> extends AbstractCollection<T>{...}

The underlying Node<T> class (among others) contains the following methods:

public Node<T> getLeft()  // left Node
public Node<T> getRight() // right Node
public T getValue()       // value stored in the Node

I want to override the method boolean contains(Object o) of the interface AbstractCollection<T> to have the possibility to check for Object's of a type other than T.

For the tree traversal in O(log n) the generic type T must implement Comparable<T>, so it has the method compareTo(T t).

My code:

@Override
public boolean contains(Object o){
    T t = (T) o; // produces warning (see below)
    BSNode<T> currentNode = this.root;
    while(currentNode != null){
        if(currentNode.getValue().equals(o)) {return true;}
        if(currentNode.getValue().compareTo(t) < 0)  {currentNode = currentNode.getRight();}
        if(currentNode.getValue().compareTo(t) > 0)  {currentNode = currentNode.getLeft();}
    }
    return false;
}

The problem is that I can not just cast Object o to T t for using compareTo(T t). Technically the Object's are castable to T, but as T is a generic type, I get this warning:

warning: [unchecked] unchecked cast
          T t = (T) o;
                    ^
required: T
found:    Object
where T is a type-variable:
  T extends Comparable<T> declared in class MyTree

Can someone either

  1. confirm that I can safely ignore the warning using @SuppressWarnings("unchecked"),
  2. suggest how I can safely cast Object to T,
  3. explain why neither of the points above can be satisfied so I can stop thinking about how to make this work?

Thanks a lot!

Niklas Mertsch
  • 1,399
  • 12
  • 24
  • I don't think Object can be safely cast to T since it might no be an object of type T but something else. Also, why passing an Object to your `contains()` if you are defining a collection of T type? – LMC May 16 '18 at 23:22
  • 1
    *Unrelated:* Instead of calling `equals` once and `compareTo` twice, just call `compareTo` once: `int cmp = currentNode.getValue().compareTo(t); if (cmp == 0) { return true; } currentNode = (cmp < 0 ? currentNode.getRight() : currentNode.getLeft());` --- Remember, `equals` and `compareTo` might be relatively expensive calls. – Andreas May 16 '18 at 23:24
  • @Andreas I actually planned to do this once everything is working fine. Did not do it here to show the different parameter types of `equals(Object o)` and `compareTo(T)` and also to present how the method should work without adding that small layer of abstraction. Thanks anyways for the suggestion. :) – Niklas Mertsch May 17 '18 at 06:28
  • @LuisMuñoz It is generally possible that `Object o` can be cast into `T t`, as `Object.equals(Object o)` does not strictly forbid equality of `Object`'s of different types. That's why `AbstractCollection` implements `contains(Object o)`. Also see [here](https://stackoverflow.com/questions/2994123/why-do-we-have-containsobject-o-instead-of-containse-e) – Niklas Mertsch May 17 '18 at 06:46

1 Answers1

4

If you would like to have unrestricted searches, you need to do a cast. You can add instanceof to guard the cast from exceptions, but that is not ideal either.

Consider changing the bounds of T as follows:

class MyTree <T extends Comparable<? super T>> extends AbstractCollection<T>{...}

Since you do an override, suppressing the warning is pretty much required. The cast should look as follows:

@SuppressWarnings("unchecked")
Comparable<? super T> t = (Comparable<? super T>) o;

See the source of getEntry method of java.util.TreeMap for an example of how it is done in Java source (they do it for the same reason - the need to override a method with a signature taking Object).

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • Thanks a lot for your answer, it motivated me to take a closer look at generics in java and now everything works fine and I even understand, why. Actually `instanceof` is not just not ideal, it does not work with generics (due to type erasure). Also thanks for the referral to the source code of `java.util.TreeMap`, I did not even know a thing like the OpenJDK existed or think about the possibility to take a look at some source code. – Niklas Mertsch May 24 '18 at 10:34
  • 1
    @NiklasMertsch You can do `instanceof` with generics using the so-called "type token" trick described [here](https://docs.oracle.com/javase/tutorial/extra/generics/literals.html). Essentially, the caller would need to supply an instance of `Class` for type `T`, giving your code access to [`isInstance(obj)`](https://docs.oracle.com/javase/7/docs/api/java/lang/Class.html#isInstance(java.lang.Object)) method. It's unrelated to this question, just a nice thing to keep in mind when type erasure seems to get in your way with Java generics :-) – Sergey Kalinichenko May 24 '18 at 10:49