【问题标题】:ClassCastException when trying to create an array of a class with type variables尝试创建具有类型变量的类的数组时发生 ClassCastException
【发布时间】:2019-05-12 15:11:42
【问题描述】:

我正在尝试创建嵌套类Key 的数组,它使用主类BTree 中的类型变量/参数,但我无法在运行时摆脱ClassCastException。我不太擅长 Java 中的泛型,如果有人告诉我问题是什么以及如何解决它,我将不胜感激。

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;
        }
    }
    //....
}

编辑:

我还尝试将Object 数组转换为Key 数组,它也会抛出ClassCastException

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

当我创建 Key 数组而不进行强制转换时,它会在编译时出现 Generic array creation 错误:

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

【问题讨论】:

  • 您正在尝试将 Comparables 数组转换为 Keys 数组。这就像将一系列哺乳动物(骆驼、马和猫)转换成一系列狗。为什么要这样做?创建一个 Key 数组和一个 Node 数组。
  • @RealSkeptic 我在创建 Key new Key[size] 的数组时出现通用数组创建错误
  • 是的,因为泛型和数组不能混用。实际上,我不确定您要在这里做什么,但是您为什么不使用列表?
  • 通用数组创建错误可以通过创建对象数组并将其转换为请求的通用类型来解决。例如T[] myArray = (T[]) new Object[10];还要检查有关此的其他讨论,例如stackoverflow.com/questions/17831896/…
  • @RealSkeptic 我正在实现 CLRS 书中的 B-trees,它使用数组,而且我认为使用列表可能会影响搜索和插入等操作的运行时间(尽管不确定)。

标签: java arrays generics


【解决方案1】:

当我创建 Key 数组而不进行强制转换时,它会在编译时出现 Generic array creation 错误:

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

原因是Key是泛型类BTree&lt;T, V&gt;内部的一个内部类,所以当你在BTree内部单独写Key时,它隐含的意思是BTree&lt;T, V&gt;.Key,这是一个参数化的类型,并且不允许创建参数化类型的数组。

当人们想要创建一个参数化类型的数组时,一种解决方案是创建一个原始类型的数组,然后将其转换为“参数化类型的数组”类型。这里棘手的问题是如何编写原始类型。它本身不是Key,因为它是一个参数化类型(因为它被参数化外部类型隐式限定)。相反,您必须使用原始外部类型显式限定它,以便编写内部类的原始类型:

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

【讨论】:

    【解决方案2】:

    数组和泛型不能混用。只需使用列表之类的集合:

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

    性能差异可以忽略不计。

    用于支持遗留代码。 Java 在处理数组时提供了“原始”类型来规避泛型类型系统。您的问题是 Node 是泛型类的内部类并包含隐式泛型参数。类型真的是BTree&lt;T, V&gt;.Node。您不能创建此类泛型类型的数组。要创建一个数组,您需要没有任何泛型参数的“原始”类型。在您的情况下,该类型是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();
        }
    }
    

    请注意,这需要不安全的强制转换。

    【讨论】:

    • 感谢您的回答,但 ArrayLists 是 java 中的动态数组,插入的摊销常数时间会使我的事情变得复杂。我需要实际的恒定时间插入和索引。
    【解决方案3】:

    编辑:完全重写

    如果将内部类的转换提取到包装器类型中,将内部类作为其泛型类型参数呢?另一个答案建议集合,无论如何都有支持对象。如果我们定义这样一个数组包装器,那么唯一的缺点是,当数组被读取或被写入时,它是通过方法而不是直接的:

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

    所以这个:

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

    变成:

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

    当然它可以改进,比如可以迭代,所以 foreach 循环也可以工作:

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

    【讨论】:

    • 这不是一个理想的解决方案,它有点像黑客,但它确实有效。我认为这是一个聪明的解决方案,我喜欢它。谢谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-06-11
    • 1970-01-01
    • 1970-01-01
    • 2018-08-03
    • 2018-11-12
    • 2020-02-27
    • 1970-01-01
    相关资源
    最近更新 更多