0

I'm trying to create an array of the nested class Key which uses type variables/parameters from the main class BTree, but I can't get rid of the ClassCastException at runtime. I'm not very good with generics in Java, I'd appreciate if someone let me know what the issue is and how to fix it.

public class BTree<T extends Comparable<T>, V> {
   //...
   private class Node {
        public int n;
        public boolean isLeaf = false;
        public Key[] keys = (Key[]) new Comparable[2 * MIN_DEGREE - 1]; //ClassCastException
        public Node[] children = (Node[]) new Object[2 * MIN_DEGREE];
    }

    private class Key implements Comparable<Key> {
        public T key;
        public V val;

        public Key(T key, V val) {
            this.key = key;
            this.val = val;
        }

        public boolean lessThan(Key that) {
            return this.key.compareTo(that.key) < 0;
        }

        public boolean greaterThan(Key that) {
            return this.key.compareTo(that.key) > 0;
        }

        @Override
        public int compareTo(Key that) {
            if (this.lessThan(that)) return -1;
            if (this.greaterThan(that)) return 1;
            return 0;
        }
    }
    //....
}

Edit:

I also tried casting Object array to Key array and it throws ClassCastException as well:

public Key[] keys = (Key[]) new Object[2 * MIN_DEGREE - 1]; 

And when I create Key array without casting it gives Generic array creation error when compiling:

public Key[] keys = new Key[2 * MIN_DEGREE - 1]; 
razz
  • 8,452
  • 6
  • 45
  • 61
  • You are trying to convert an array of Comparables into an array of Keys. That's like converting an array of mammals (Camels, horses and cats) into an array of dogs. Why do that anyway? Create an array of Key and an array of Node. – RealSkeptic May 12 '19 at 15:13
  • @RealSkeptic I get generic array creation error when i create an array of Key `new Key[size]` – razz May 12 '19 at 15:17
  • Yeah, because generics and arrays don't mix. I'm not sure what you are trying to do here, actually, but why aren't you using lists? – RealSkeptic May 12 '19 at 15:26
  • Generic array creation errors can be solved with creating an Array of Object and cast that to the requested Generic type. e.g. T[] myArray = (T[]) new Object[10]; Also check other discussions about this e.g. https://stackoverflow.com/questions/17831896/creating-generic-array-in-java-via-unchecked-type-cast – Konrad Neitzel May 12 '19 at 15:29
  • @RealSkeptic I am implementing B-trees from CLRS book and it uses arrays, also I think using lists might affect the running time (although not sure) of the operations like search and insert. – razz May 12 '19 at 15:29

3 Answers3

2

And when I create Key array without casting it gives Generic array creation error when compiling:

public Key[] keys = new Key[2 * MIN_DEGREE - 1];

The reason for this is that Key is an inner class inside the generic class BTree<T, V>, so when you write Key by itself inside BTree, it implicitly means BTree<T, V>.Key, and this is a parameterized type, and you are not allowed to create an array of a parameterized type.

When people want to create an array of a parameterized type, one solution is to create an array of the raw type, and then cast it to the "array of parameterized type" type. The tricky question here is how to write the raw type. It is not Key by itself, as it's a parameterized type (since it's implicitly qualified by the parameterized outer type). Instead, you must explicitly qualify it with the raw outer type in order to write the raw type of the inner class:

public Key[] keys = (Key[])new BTree.Key[2 * MIN_DEGREE - 1];
newacct
  • 110,405
  • 27
  • 152
  • 217
0

EDIT: complete rewrite

What about extracting the casting of the inner class into a wrapper type, having the inner class as its generic type parameter? The other answer suggests collections, which have backing objects anyways. If we define such an array wrapper, then the only drawback is, that when the array is being read or being written, it is through methods and not directly:

public class Outer<A,B> {
    static final int SIZE = 10;

    public Outer() {
        Inner1 innerInstance = new Inner1();
    }

    private class Inner1 {
        //Inner2[] inner2array = new Inner2[SIZE];
        Array<Inner2> inner2array = new Array<>(SIZE);
    }

    private class Inner2 {
        A a;
        B b;
    }

    public static void main(String[] test) {
        Outer outerInstance = new Outer();
    }

    private static class Array<T> {
        private Object[] values;

        public Array(int size) {
            values = new Object[size];
        }

        @SuppressWarnings("unchecked")
        public T get(int index) {
            return (T) values[index];
        }

        public void set(int index, T value) {
            values[index] = value;
        }

        public int length() {
            return values.length;
        }
    }

}

So this:

public Key[] keys = new Key[2 * MIN_DEGREE - 1];

Becomes:

public Array<Key> keys = new Array<>(2 * MIN_DEGREE - 1); 

Of course it can be improved, like made iterable, so foreach loops will work too:

@Override // implements Iterable<T>
public Iterator<T> iterator() {
    return new Iterator<T>() {
        int index = 0;

        @Override
        public boolean hasNext() {
            return index < values.length;
        }

        @Override @SuppressWarnings("unchecked")
        public T next() {
            T current = (T) values[index];
            ++index;
            return current;
        }
    };
}
  • This is not an ideal solution and it's kind of a hack but it works. I think it's a smart solution though and I like it. Thanks. – razz May 13 '19 at 21:04
0

Arrays and generics do not mix. Just use a collection like list:

public List<Node> children = new ArrayList<Node>();

Performance difference is going to be negligible.

For supporting legacy code. Java offers "raw" types to circumvent generic type system when dealing with arrays. Your problem is that Node is inner class of a generic class and contains implicit generic parameters. The type is really BTree<T, V>.Node. You cannot create an array of such generic type. To create an array you need "raw" type without any generic parameters. In your case that type is BTree.Node:

class BTree<T> {
    static final int MIN_DEGREE = 1;
    private class Node {
        public Node[] children = (Node[]) new BTree.Node[2 * MIN_DEGREE];
    }
    Node node = new Node();
    public static void main(String[] args) {
        new BTree();
    }
}

Note that this requires an unsafe cast.

Piotr Praszmo
  • 16,785
  • 1
  • 51
  • 60
  • Thanks for your answer but ArrayLists are dynamic arrays in java and the amortized constant time for insertion would complicate things for me. I need actual constant time insert and index. – razz May 13 '19 at 21:02