4

I am trying to create a hybrid list that uses Binary Search Trees for various functions. The class tries to implement List< E >. To index, these trees use comparison to decide whether data should be placed in the left or right child of the parent. This is done using a Comparator variable set by the Constructor:

private Comparator<E> comparator; // method for ordering the tree

This ensures that type E will be comparable in some way. The problem however is in how the List Interface declared its abstract functions. For example:

public boolean contains(Object o);

Normally, I would assume that the parameter is comparable and then use a recursive lookup method on the tree, but because the parameter is Object, I cannot make that assumption.

I have tried to ensure that it is of type E by doing the following:

if (o instanceof E) {}

but it does not work and gives me this suggestion:

Cannot perform instanceof check against type parameter E. 
Use its erasure Object instead since further generic type information will be erased at runtime

Any ideas on how to solve this?

Edit:

This is how the class is instantiated:

TreeCollection<Integer> c = new TreeCollection<>(
            new Comparator<Integer>() {

                @Override
                public int compare(Integer o1, Integer o2) {
                    return o1.compareTo(o2);
                }

            });
jeanluc
  • 1,608
  • 1
  • 14
  • 28
  • If you can't use `instanceof` you could use a `try{ E myE = (E)o;//Do something with your E}catch(Exception e){//Your object it's not of class E}`. If the code (the cast) inside the try succeds then your object it's of class E, else, you get a `ClassCastException` and enter on the catch. – sergioFC Aug 02 '14 at 00:52
  • @sergioFC That wouldn't cause a casting exception. This is consequence of how generics are implemented in Java -- it maintains compatibility with non-generic Java classes by using type erasure. That is, generic types only exist at compile time and reduced to lowest possible superclass on compilation (in this case it's Object). So `E e = (E)o;` becomes `Object e = (Object)o;` – Dunes Aug 02 '14 at 19:48
  • You are incorrect. A ClassCastException is raised. I just tested it. – jeanluc Aug 02 '14 at 19:52
  • @jeanluc (@Dunes) Thanks for that useful info. I just tested an example and it didn't raised any Exception (maybe it depends on the case?). My example is here http://www.heypasteit.com/clip/1H6C . – sergioFC Aug 02 '14 at 20:08

3 Answers3

4

There's no perfect answer for your situation. There's a kind of conflict between the Comparator, where correct use is enforced by compile-time generics, and the .contains() method, whose contract depends on .equals(), which takes any type of object (there is no compile-time constraint), and whose logic must be based on runtime information. This problem is also faced by TreeSet.

The whole point of your data structure is lookup is O(log n), so you cannot go through every element and check; instead, you must go down the binary search tree, by comparing the given value to the elements using the comparator. But since the given element is any type of object, we don't know the comparator can take it. I think the best solution is just to cast it to E and assume that your comparator can take it. If the object is not an instance of E, the comparator will probably throw ClassCastException somehow, and you can just let that exception pass up to the user. TreeSet takes this approach -- the documentation for TreeSet.contains() says that it throws ClassCastException when the specified object cannot be compared with the elements in the set.

Although this makes .contains() less general, it avoids breaking the contract of Collection.contains(), because in the case where someone gives us an object to .contains() that our comparator cannot compare, the correct answer could be true or false according to the contract, but we can't determine that efficiently; so instead of returning something that might be wrong, we throw an exception, which is allowed in the contract.

newacct
  • 119,665
  • 29
  • 163
  • 224
  • Comparators are an integral part because I am using a Binary Search Tree to look up items. It seems that you are not familiar with that data structure. Basically it compares the item you are looking up with the current node. If its equal to the not, it returns true. However if its node, then it travels down the left or right child if the data is less or greater respectively. – jeanluc Aug 02 '14 at 05:22
  • @jeanluc: I am very familiar with those data structures. Thank you for your condescending tone. It does not change the fact that the contract of `contains` has nothing to do with comparators or comparables. – newacct Aug 02 '14 at 05:32
  • Yes, but within my data structure, the whole idea of the tree is to accelerate look up and sorting, which inherently must take comparable data. Do you see what I mean? – jeanluc Aug 02 '14 at 05:34
  • @jeanluc: No. When two objects should be treated as "equal", their `.equals()` should be true, regardless of whether they have an ordering or not. And every ordering should preserve this property. – newacct Aug 02 '14 at 05:38
  • Alright lets back up a step. You are saying that each object, no matter what it is, can be tested against another object for equality right? I am not disagreeing with that. Now how do you propose I test the objects in my Tree List with the object I am looking up? – jeanluc Aug 02 '14 at 05:42
  • @jeanluc: `Object` has a `.equals()` method. Thus every object in Java has an equality test already. If your class has a more specific notion of equality, you should have overrode the `.equals()` method to customize what it does; but keep in mind that `.equals()` can be used to test against objects of any class. – newacct Aug 02 '14 at 05:44
  • I understand. It does not have a more specific notion of equality. I am not arguing that, and the class currently uses the equals() method to test for equality, but that is not the problem. You said you are familiar with Binary Search Trees. Are you then familiar with how they look up data? – jeanluc Aug 02 '14 at 05:47
  • @jeanluc: Yes, I am familiar with how they look up data. By the way, `TreeSet` and `TreeMap` in the Java library are self-balancing binary search trees. – newacct Aug 02 '14 at 05:55
  • Yeah but that would be cheating. This is a homework assignment. Anyways, in order for me to look up something in a tree I have to be able to test three conditions against the current node: less than, greater than, and equal to. If I only have .equals() then how do I do that? – jeanluc Aug 02 '14 at 05:57
  • @jeanluc: I see your problem. I've updated the answer. – newacct Aug 02 '14 at 09:13
2

There's a design pattern in Java called "Heterogeneous Container" that might be useful here.

Basically, erasure and generics was kind of a crappy idea, as you've discovered, so the Heterogeneous Container is a way you can fix it up.

Basically you just pass in a class object of the type you want.

public class MyListTreeSet<E> extends AbstractList<E>
{

   private Class<E> type;
   private TreeSet<E> set;

   public MyListTreeSet( Comparator<? super E> comp, Class<E> type )
   {
      this.type = type;
      set = new TreeSet<>( comp );
   }

Now when you do the contains() method you can test directly whether the parameter is of the correct type.

   @Override
   public boolean contains( Object o )
   {
      if( !type.isInstance( o ) ) return false;
      E e = type.cast( o );
      // e is now the same type as your set and therefore 
      // implements Comparable
      return set.contains( e );  // quickie example
   }

There's ways you can fancy this up, but this is the most basic example.

EDIT:

To instantiate this as you show, you must create a new type (class). However it can be a subtype of your existing class, so no other code besides the creation of the set has to change. If TreeCollection is code you control, then it would be a lot easier to just add the changes to the TreeCollection class directly.

TreeCollection<Integer> c = new NewTreeCollection<>(
        new Comparator<Integer>() {

            @Override
            public int compare(Integer o1, Integer o2) {
                return o1.compareTo(o2);
            }

        }, Integer.class );

The NewTreeCollection follows the same basic pattern as the List I made above, although if TreeCollection inherits from TreeSet then you can remove some of the variables.

public class NewTreeCollection<E> extends TreeCollection<E>
{

   private Class<E> type;
   private TreeSet<E> set;

   public NewTreeCollection( Comparator<? super E> comp, Class<E> type )
   {
      this.type = type;
      set = new TreeSet<>( comp );
   }

   @Override
   public boolean contains( Object o )
   {
      if( !type.isInstance( o ) ) return false;
      E e = type.cast( o );
      // e is now the same type as your set and therefore 
      // implements Comparable
      return set.contains( e );  // quickie example
   }
}
markspace
  • 10,621
  • 3
  • 25
  • 39
  • That is a great idea. My only problem with that is the way you would have to make a new List: `TreeList list = new TreeList<>(..., Integer);` That seems rather redundant. Is there a way to set `type` equal to E? – jeanluc Aug 02 '14 at 18:18
  • In your OP, you say you're trying to create a "hybrid list" that uses binary search trees. When you create the trees, can you store the Class variable then? Because of erasure in generics, it really isn't feasible to retrieve the type parameter later. – markspace Aug 02 '14 at 19:29
  • I could, but the problem is that the user currently never specifies what type of object will be placed in the data structure except in the "<>" brackets. Thus, there is noting to set the Class variable too. The only way to initialize it would be to get direct user input as a parameter reflecting what type `E` is. It seems that there is no way of avoiding that. – jeanluc Aug 02 '14 at 19:34
  • I'm really not seeing what you are trying to do. How do you collect this "E" variable now? Could you edit your OP with code to show EXACTLY what you are talking about? Is this user a programmer or someone else? – markspace Aug 02 '14 at 19:36
  • I shall add an example on how this class is instantiated. I should also note that to solve the problem currently, I just use type casting as newacct suggested. So, if you have a less fragile and more proper method, do tell. – jeanluc Aug 02 '14 at 19:40
0

There is a way of obtaining the generic types used in the creation of concrete classes of a given generic class/interface.

eg.

List<Integer> list = new ArrayList<>(); 

Cannot retrieve generic type of list at runtime as it is an instance of ArrayList which is generic and contains no reference to its generic type.

class MyList extends ArrayList<Integer> {}
List<Integer> list1 = new MyList();

MyList is a non-generic and concrete implementation of a generic ArrayList. As such you will be able to use reflections to inspect what that generic type actually is.

Comparator<Integer> cmp = new Comparator<Integer>() {...};

cmp is an anonymous, but non-generic and concrete implementation of the Comparator interface. As such we can determine what generic type it is using.

How to determine the generic type of a concrete sub-class

Comparator<Integer> cmp = new Comparator<Integer>() {
    @Override
    public int compare(Integer x, Integer y) {
        return x.compareTo(y);
    }
};
Type genericType = cmp.getClass().getGenericInterfaces()[0];
// use getGenericSuperclass() if subclassing a class rather than implementing an 
// interface
if (genericType instanceof Class) {
    throw new RuntimeException("Missing type parameter.");
}
Type type = ((ParameterizedType) genericType).getActualTypeArguments()[0];  

Class<?> cls = (Class<?>) type;
boolean is_true = cls.isInstance(1);
boolean is_false = cls.isInstance(1.);
Dunes
  • 37,291
  • 7
  • 81
  • 97