How do I get an inner class to inherit enclosing c

2019-02-02 11:53发布

问题:

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?

回答1:

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



回答2:

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.



回答3:

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);
        }
    }
}


回答4:

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.



回答5:

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
    }
}