通用数组转换异常

时间:2017-05-27 16:45:02

标签: java arrays generics

我有一个Generic类,返回一个通用数组的方法:

public class ZTagField<T> extends JTextPane {
    public ZTagField(StringBasedFactory<T> factory) {
        assert (factory != null);
        this.factory = factory;
        init();
    }

    public T[] getItems() {
        ...
        T[] arrItems = (T[]) currentItems.toArray((T[])new Object[0]);
        return arrItems;
    }

另一个使用它:

public class Xxx {
    ZTagField<clTag> txtTags = null;
    public Xxx() {
        txtTags = new ZTagField<clTag>(createFactory());
    }

    public clTag[] getSelectedTags() {
        return txtTags.getItems(); 
    }
}

后者txtTags.getItems()给了我一个例外:==&gt; Exception [Object cannot be cast to [clTag ????

任何人都可以解释我为什么吗?

我试图尽可能多地申请How to create a generic array,但无济于事。 我有一个丑陋的解决方法:

return Arrays.asList(txtTags.getItems()).toArray(new clTag[0])

但我想在ZTagFieldClass中使用它。

3 个答案:

答案 0 :(得分:4)

阵列具体化。这意味着它们持有对其组件类型的引用,并且在插入元素时,它使用该引用来检查inserted元素是否实际上是组件类型的子类型。

因此,要创建T[],您需要对组件类型T进行具体引用,并且由于删除了泛型,泛型 {{1}不算数。 (这也是为什么你不能直接创建像T这样的T[]的原因。)

集合的T[] arr = new T[]方法通过让用户传递组件类型的数组来解决这个问题。但是你试图通过将你的toArray强制转换为Object[]来尝试作弊,而T[]实际上并没有创建T的数组(在哪里会引用具体T来自?)。如果没有取消选中,这样的演员会失败,除非T实际上是Object

这也是ClassCastException来自的地方。您创建了Object[],因此组件类型为Object,无论您将其转换为T[],组件类型都会保留Object。但是稍后,您知道所需的实际组件类型(clTag):

public clTag[] getSelectedTags() {
    return txtTags.getItems(); 
}

因此编译器会在此处向clTag[]插入一个隐式转换:

public clTag[] getSelectedTags() {
    return (clTag[]) txtTags.getItems();
}

但是你无法将Object[]投射到clTag[],就像你无法将Object投射到clTag一样。

您的解决方法有效,因为您实际上是在提供对组件类型的引用:

Arrays.asList(txtTags.getItems()).toArray(new clTag[0]) // <-- 'clTag' here

比传递组件类型数组更现代的解决方案是将包含数组构造函数的IntFuntion<T[]>传递给方法:

public T[] getItems(IntFunction<T[]> arrCons) {
    ...
    T[] arrItems = currentItems.toArray(arrCons.apply(0));
    return arrItems;
}

...

txtTags.getItems(clTag[]::new);

但是你无法以某种方式传递组件类型,除非你切换到返回List<T>(如GhostCat也建议的那样)。由于泛型具体化,您可以创建List<T>而不引用组件类型:

public List<T> getItems() {
    ...
    return new ArrayList<>(currentItems);
}

答案 1 :(得分:3)

按设计工作:在运行时没有泛型类型。

它被删除,并创建一个Object数组。 Object数组不能转换为另一种数组。

这是Java泛型的限制之一,基于它们的实现方式。

建议您小心使用数组和泛型。您可能更喜欢使用通用列表。

只是要清楚这一点:是的,你可以让数组与泛型一起工作,正如其他答案所显示的那样。但是:你花时间与症状作斗争。数组和泛型在Java中不能很好地结合在一起。接受,使用通用列表,因此:解决问题而不是解决它。

答案 2 :(得分:2)

编译后,类型将被删除 由于T不受特定类型限制,T将被Object替换。

所以,这个:

T[] arrItems = (T[]) currentItems.toArray((T[])...);
return arrItems;

不会在运行时创建并返回类实例使用的特定类型的数组,但只会创建Object的数组。

此外,在Collection.toArray()中,您无法传递数组(new T[]),因为无法创建通用数组

因此,如果您想使用toArray()方法,最终只能以这种方式传递Object数组:

 Object[] arrayObject = values.toArray(new Object[currentItems.size()]);

但是数组不能用作List类型。
特定类型的数组不能转换为其他类型的数组,即使它包含的元素属于转换目标的类型。
因此,即使数组包含具有此特定类型的元素,也不能将Object数组转换为特定类型的数组,例如。

所以这会产生一个ClassCastException:

clTag[] values = (clTag[]) arrayObject;

解决您的问题:


如果您可以使用Java 8,那么使用功能界面确实是一个干净的解决方案。 Jorn Vernee给出了一个非常好的答案来说明它。

否则,在Java 8之前,创建与泛型集合中使用的参数化类型相同类型的数组的单一方法是:

1)创建具有指定类型的新数组 java.lang.reflect.Array.newInstance(Class clazz, int length)方法 允许创建指定类和长度的数组。

2)在通用类的实例中存储声明类型的类。您可以通过在其构造函数中添加类参数来实现。

3)从通用集合的元素中填充它 一种简单的方法是使用<Object, Object> Object[] java.util.Arrays.copyOf(Object[] original, int newLength, Class<? extends Object[]> newType)方法,但它无效,因为首先必须将集合转换为toArray()的数组,以便能够将其传递给copyOf()方法。

例如,使用通用List,您可以写:

public class ZTagField<T> {

    private class<T> clazz;
    private List<T> list = new ArrayList<>();

    public ZTagField (class<T> clazz){
         this.clazz = clazz;
    }

    public T[] get() {    
       T[] array = (T[]) Array.newInstance(clazz, list.size());            
       Class<? extends Object[]> clazzArray = array.getClass();
       array  = (T[]) Arrays.copyOf(values.toArray(), values.size(), clazzArray);
       return array;
    }
}

虽然有效,但据说没有效果 一个更有效的解决方案是迭代列表并在新数组中添加元素而不是使用Arrays.copyOf()

    public T[] get() {    
       T[] array = (T[]) Array.newInstance(clazz, list.size());            
        for (int i = 0; i < values.size(); i++) {
             array[i] = values.get(i);
        }
       return array;
    }