Vasile Turcu Vasile Turcu - 1 month ago 10
Java Question

Java-How do I compare generic types?

Here's the code for a generic binary search tree. Now, it runs and compiles perfectly, but with one problem that I only noticed now, after I have finished this class. The problem is that the insert method uses compareTo()(when it comes to comparing the nodes' elements) to decide where to place the next node in the tree according to its' value. And for instance for an input of:


1, 112, 2


instead of getting this tree:

1
\
2
\
112


What I end up getting is:

1
\
2
/
112


Because probably the comparison is done in a lexicographically way and so it see 2 > 112.

Here's the code for the Tree class:

import java.util.*;
import java.io.*;
import java.lang.*;

public class Tree<T extends Comparable<T>>
{ private Node<T> root = null;
static String S="";

public Tree()
{File file=new File("date.in.txt");

try
{ Scanner input = new Scanner( file );
while(input.hasNext())
insert((T)input.next());}

catch (FileNotFoundException ex)
{ System.out.printf("ERROR: %s!\n", ex); } }


public void show(){ printLevelOrder(maxDepth()); }

public void printLevelOrder(int depth)
{ for (int i = 1; i <= depth; i++)
{ System.out.print("Level " + (i-1) + ": ");
String levelNodes = printLevel(root, i);
System.out.print(levelNodes + "\n"); } }

public String printLevel(Node<T> t, int level)
{ if (t == null)
return "";
if (level == 1)
return t.element + " ";
else if (level > 1)
{ String leftStr = printLevel(t.left, level - 1);
String rightStr = printLevel(t.right, level - 1);
return leftStr + rightStr; }
else
return ""; }


int maxDepth(){ return maxDepth2(root); }

int maxDepth2(Node<T> node)
{ if (node == null)
return (0);
else
{ int leftDepth = maxDepth2(node.left);
int rightDepth = maxDepth2(node.right);

if (leftDepth > rightDepth )
return (leftDepth + 1);
else
return (rightDepth + 1); } }


public String toString(){ return this.InOrder(); }


public String InOrder(){ inOrder2(root); return S; }

public void inOrder2(Node<T> root)
{ if(root != null)
{ inOrder2(root.left);
S=S+root.element+" ";
inOrder2(root.right); } }


public boolean insert(T element) // I N S E R T M E T H O D
{ if (isEmpty())
{ root = new Node<T>(element);
return true; }

Node<T> current = root;
Node<T> parent;

do
{ parent = current;

if (element.compareTo(current.element)<0)
current = current.left;
else if (element.compareTo(current.element)>0)
current = current.right;
else
return false; }
while (current != null);

Node<T> node = new Node<T>(element);


if ( element.compareTo(parent.element)>0 )
parent.right = node;
else
parent.left = node;

return true; }


public boolean isEmpty() { return root == null; }


private static class Node<T extends Comparable<T>>
{ Node<T> left = null;
Node<T> right = null;
final T element;

Node(T element) { this.element = element; } } }


And here's the main:

import java.util.*;
import java.io.*;

public class Main
{public static void main(String[]args)
{Tree <Double> T=new Tree<>(); } }


Now I did a little bit of research and asked here and there, and I got wind of something called comparator but I haven't quite used one before and I'm not sure how to implement it. Now if you got any solution to how to fix this, or what to add/do, I'm all eyes and ears homies.

Answer

Your line insert((T)input.next()); doesn't make sense - input.next() is a String, but you're casting it to a T even though T isn't associated with a real type at this point. When constructing a Tree a caller could, for instance, say new Tree<Integer> making that line, in essence, insert((Integer)input.next());, which is broken.

If your goal is for Tree to always contain Strings, just remove the generic T type from Tree and just use String. If you actually want to support arbitrary types you need to move the file-reading behavior out of the Tree constructor (e.g. into a static method that returns a Tree<String>).

For example:

public static Tree<String> readFromFile(Path file) throws FileNotFoundException {
  try (Scanner input = new Scanner(file)) {
    Tree<String> tree = new Tree<>();
    while(input.hasNext()) {
      tree.insert(input.next());
    }
    return tree;
  }
}

Notice that I used Path instead of File, the try-with-resources pattern, and I don't suppress the exception, instead the caller (likely the main method) should handle the exception properly (likely by reporting the file doesn't exist and exiting). Alternatively adding a catch block like so:

catch (IOException e) {
  throw new RuntimeException("Could not open " + file, e);
}

would avoid forcing callers to handle the exception.


Now that we've cleaned up the Tree type, your ordering question is likely simpler to answer. If you intend to order the elements as integers, convert the inputs to Integers, e.g.

public static Tree<Integer> readIntsFromFile(Path file) throws FileNotFoundException {
  ...
  tree.insert(Integer.valueOf(input.next()));
  ...
}

This will order the tree based on the integer ordering, rather than the strings' lexicographic ordering. This is what I'd suggest you do. It's straightforward and does what you want.


If you really want a Tree<String> but you want to order them as if they were integers you need a custom Comparator, as you mention. You would then need to pass the Comparator into the Tree's constructor, and call comparator.compare(element, current.element) where you currently call element.compareTo(current.element).