27

I'm using Java 6.

I'm having trouble getting my inner class to use the same generic class as its enclosing class. Currently I have

public class TernarySearchTree < T > {
    ...
    protected class TSTNode < T > {
        // index values for accessing relatives array
        protected static final int PARENT = 0, LOKID = 1, EQKID = 2, HIKID = 3; 
        protected char splitchar;
        protected TSTNode < T > [] relatives;
        private T data;

        protected TSTNode(char splitchar, TSTNode < T > parent) {
            this.splitchar = splitchar;
            relatives = new TSTNode[4];
            relatives[PARENT] = parent;
        }
    }
}

Right now I get the warning

The type parameter T is hiding the type T

If I remove the type parameter from the inner class (i.e. remove the <T> from teh protected class TSTNode<T> line), then I get a compile error on the line relatives = new TSTNode[4].

How can I make everything right?

nbro
  • 12,226
  • 19
  • 85
  • 163
Dave
  • 17,420
  • 96
  • 300
  • 582
  • 4
    If you get a compile error, you should **include** the compile error message in your question! But it works just fine for me. Just make sure you get rid of the `` on your *declaration* of `relatives` as well. That said, you can always make `TSTNode` static and just use a different parameter, like `E`. – Mark Peters Jan 06 '12 at 22:24
  • Why it is not possible to create generic arrays: http://stackoverflow.com/questions/2927391/whats-the-reason-i-cant-create-generic-array-types-in-java – Rogel Garcia Jan 06 '12 at 22:46
  • Does TSTNode need to access instance fields or methods from TernarySearchTree? – Rogel Garcia Jan 06 '12 at 23:06
  • Most of the issues can be solved by using a List (instead of an array), or - even better - an EnumMap. See my updated answer. – Itay Maman Jan 07 '12 at 09:25

5 Answers5

17

You can either:

  • remove the <T> type parameter from TSTNode (i.e., make it non-generic) - it will still have access to the outer <T>.

  • rename the <T> type parameter in class TSTNode to (say) U.

[UPDATE]

Below are four different ways to rewrite your code. All of them compile. I think you should consider the use of an EnumMap (see Version 4, below).

Version 1: use a differenly named type parameter in the inner class. you need to use a List instead of an array.

  public class TernarySearchTree<T> {

    protected class TSTNode<U> {
      // index values for accessing relatives array:
      protected static final int PARENT = 0, LOKID = 1, EQKID = 2, HIKID = 3;

      protected char splitchar;
      protected List<TSTNode<U>> relatives;
      private U data;

      protected TSTNode(char splitchar, TSTNode<U> parent) {
        this.splitchar = splitchar;
        relatives = new ArrayList<TSTNode<U>>();
        for (int i = 0; i < HIKID; ++i) {  // Allocate 4 slots in relatives
          relatives.add(null);
        }
        relatives.set(PARENT, parent);
      }          
    }

    private TSTNode<T> node; // When you use it, pass T as U

    public TernarySearchTree() {
      node = new TSTNode<T>(',', null);  // When you use it, pass T as U 
    }
  }

Version 2: inherit T from enclosing class

  public class TernarySearchTree<T> {

    protected class TSTNode {
      // index values for accessing relatives array:
      protected static final int PARENT = 0, LOKID = 1, EQKID = 2, HIKID = 3;

      protected char splitchar;
      protected List<TSTNode> relatives;
      private T data;

      protected TSTNode(char splitchar, TSTNode parent) {
        this.splitchar = splitchar;
        relatives = new ArrayList<TSTNode>();
        for (int i = 0; i < HIKID; ++i) {  // Allocate 4 slots in relatives
          relatives.add(null);
        }
        relatives.set(PARENT, parent);
      }
    }

    private TSTNode node; 

    public TernarySearchTree() {
      node = new TSTNode(',', null);  
    }
  }

Version 3: use a Map (instead of a List)

  public class TernarySearchTree<T> {

    protected class TSTNode {
      // index values for accessing relatives array:
      protected static final int PARENT = 0, LOKID = 1, EQKID = 2, HIKID = 3;

      protected char splitchar;
      protected Map<Integer, TSTNode> relatives;
      private T data;

      protected TSTNode(char splitchar, TSTNode parent) {
        this.splitchar = splitchar;
        // Create a hash map. No need to pre-allocate!
        relatives = new HashMap<Integer, TSTNode>(); 
        relatives.put(PARENT, parent); // set -> put
      }
    }

    private TSTNode node; 

    public TernarySearchTree() {
      node = new TSTNode(',', null);  
    }
  }
}

Version 4: define the indices as an enum + use an EnunMap (instead of a hash map)

  public class TernarySearchTree<T> {

    protected static enum Index {
      PARENT, LOKID, EQKID, HIKID;
    }

    protected class TSTNode {    
      protected char splitchar;
      protected EnumMap<Index, TSTNode> relatives;
      private T data;

      protected TSTNode(char splitchar, TSTNode parent) {
        this.splitchar = splitchar;
        // Create an EnumMap. 
        relatives = new EnumMap<Index, TSTNode>(Index.class);
        relatives.put(Index.PARENT, parent); 
      }
    }

    private TSTNode node; 

    public TernarySearchTree() {
      node = new TSTNode(',', null);  
    }
  }

[Update 2] One thing to keep in mind: Use EnumMap instead of ordinal indexing

Itay Maman
  • 28,289
  • 9
  • 76
  • 114
5

As to the compile error for generic array creation when you remove the T from the inner class:

Because it's a non-static inner class, it's within the scope of the outer class's type parameter. Which means that it is implicitly also parameterized by its outer class's type parameter

So when you write TSTNode it basically means TernarySearchTree<T>.TSTNode (the T here is the outer T). So TSTNode is still a generic type (even though you don't see any brackets explicitly), and creating an array of a generic type fails.

You can refer to the raw type of TSTNode by manually qualifying the name: TernarySearchTree.TSTNode.

So new TernarySearchTree.TSTNode[4] is the answer.

You will get an unchecked warning, which you can ignore (it is something you have to live with with arrays of generic types)

P.S. removing the type parameter from the inner class is almost certainly the right choice, as non-static inner classes in Java implicitly have a reference to an instance of the outer class. So it is already parameterized with the outer T. If you simply want to use the same T, don't declare another one.

newacct
  • 110,405
  • 27
  • 152
  • 217
2

I don't know what are you trying to do but, there's this sollution:

public class TernarySearchTree<T> {

protected class TSTNode<E extends T> {
    protected static final int PARENT = 0, LOKID = 1, EQKID = 2, HIKID = 3; 
    protected char splitchar;
    protected TSTNode<E>[] relatives;
    private E data;

    protected TSTNode(char splitchar, TSTNode<E> parent) {
        this.splitchar = splitchar;
        relatives = new TSTNode[4];
        relatives[PARENT] = parent;
    }
}
}

With this you get a warn instead of a error at the same line.

Using a List is possible a better solution (no warnings)

public class TernarySearchTree<T> {

    protected class TSTNode<E extends T> {
        protected static final int PARENT = 0, LOKID = 1, EQKID = 2, HIKID = 3; 
        protected char splitchar;
        protected List<TSTNode<E>> relatives;
        private E data;

        protected TSTNode(char splitchar, TSTNode<E> parent) {
            this.splitchar = splitchar;
            relatives = new ArrayList<TSTNode<E>>();
            relatives.set(PARENT, parent);
        }
    }
}
Rogel Garcia
  • 1,835
  • 14
  • 16
0

A variation on Itay Maman's solution.

This is an answer to a broader question than the OP is asking: How do I create an array of generics to be used only internally in Java? (This solution is NOT intended to be used to create a generic array to be returned to the user -- that would be unsafe as is well recognized.)

Edit: Version 5: Use enums with an array. (I think V4 is better for the OP, but if you need an array with generics, here is how -- Josiah Yoder)

public class TernarySearchTreeWithArray<T> {

    protected static enum Index {
        PARENT, LOKID, EQKID, HIKID, ARRAY_SIZE;
    }

    protected class TSTNode<U> {
        protected char splitchar;

        @SuppressWarnings("unchecked")
        protected TSTNode<U>[] relatives = (TSTNode<U>[]) new TSTNode[Index.ARRAY_SIZE.ordinal()];

        private U data;

        protected TSTNode(char splitchar, TSTNode<U> parent) {
            this.splitchar = splitchar;
            relatives[Index.PARENT.ordinal()] = parent;
        }
    }

    private TSTNode<T> root; // When you use it, pass T as U

    public TernarySearchTreeWithArray() {
        root = new TSTNode<>(',', null);  // When you use it, pass T as U
    }
}
Community
  • 1
  • 1
Josiah Yoder
  • 2,380
  • 4
  • 29
  • 46
0

I suspect what you want is something like:

class Tree<T> {
   Node<T> head;

   static class Node<T> {
      List<Node<T>> relatives = new ArrayList<Node<T>>();
      T value;
   }
}

Here, a tree's head node has the same T as the tree itself, and every relative node has the same T as the parent node, so all the nodes in the tree will have the same value type as the tree itself.

I used an ArrayList here because arrays cannot have generic types.

Russell Zahniser
  • 15,480
  • 35
  • 29
  • 1
    This changes the program semantics as Node cannot access Tree members anymore (in the original problem it can). But it is a valid alternative. – Rogel Garcia Jan 06 '12 at 23:08
  • The node can always have an explicit Tree field if it really needs to refer back to its owner, but my guess was that Dave wan't actually using it as an inner class. – Russell Zahniser Jan 07 '12 at 00:20
  • I agree with you, the inner class is probably not necessary. – Rogel Garcia Jan 07 '12 at 00:34