使用泛型数组的ArrayList调用ArrayList.toArray()方法如何转换?

10

这是一个让我几乎失去所有希望的难题。我正在尝试编写一个函数,但是在获取 ArrayList.toArray() 返回我所需类型方面遇到了问题。

下面是一个最简示例,展示了我的问题:

public static <T> T[] test(T one, T two) {
    java.util.List<T> list = new ArrayList<T>();
    list.add(one);
    list.add(two);
    return (T[]) list.toArray();
}

通常情况下,我可以使用这个表单 (T[]) list.toArray(new T[0]),但是出现了两个额外的困难:

  1. 由于协变规则,我不能类型转换数组,(T[]) myObjectArray 会抛出 ClassCastException
  2. 您无法创建泛型类型的新实例,这意味着我不能实例化 new T[]。我也不能在 ArrayList 的一个元素上使用 clone 或尝试获取其类。

我一直在尝试使用以下调用获取一些信息:

public static void main(String[] args) {
   System.out.println(test("onestring", "twostrings"));
}

结果的形式为 [Ljava.lang.Object; @ 3e25a5 ,表明返回值的强制类型转换无效。奇怪的是下面的代码:

public static void main(String[] args) {
    System.out.println(test("onestring", "twostrings").getClass());
}

返回结果如下:
Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String;
at patchLinker.Utilities.main(Utilities.java:286)

所以我的最佳猜测是它认为它是一个字符串数组标签,但在内部是一个对象数组,任何尝试访问都会引发这种不一致性。如果有人能找到任何解决方法(因为两个常规解决方法被拒绝了),我将非常感激。
K.Barad JDK1.6
编辑
感谢Peter Lawrey解决了最初的问题。现在的问题是如何使解决方案适用于我更不平凡的例子:
public static <T> T[] unTreeArray(T[][] input) {
    java.util.List<T> outList = new ArrayList<T>();
    java.util.List<T> tempList;
    T[] typeVar;
    for (T[] subArray : input) {
        tempList = java.util.Arrays.asList(subArray);
        outList.addAll(tempList);
    }
    if (input.length > 0) {
        typeVar = input[0];
    } else {
        return null;
    }
    return (T[]) outList.toArray((T[]) java.lang.reflect.Array.newInstance(typeVar.getClass(),outList.size()));
}

public static void main(String[] args) {
    String[][] lines = { { "111", "122", "133" }, { "211", "222", "233" } };
    unTreeArray(lines);
}

目前的结果是:
Exception in thread "main" java.lang.ArrayStoreException
at java.lang.System.arraycopy(Native Method)
at java.util.ArrayList.toArray(Unknown Source)
at patchLinker.Utilities.unTreeArray(Utilities.java:159)
at patchLinker.Utilities.main(Utilities.java:301)

我正在进行一些测试,以查看是否可以获得比这更有用的信息,但目前我不确定从哪里开始。 我会在获取更多信息时添加任何更多的信息。
K.Barad
编辑2
现在有更多信息了。 我尝试手动构建数组并逐个元素地传输项目,因此我将其强制转换为T,而不是T []。 我发现了一个有趣的结果:
public static <T> T[] unTreeArray(T[][] input) {
    java.util.List<T> outList = new ArrayList<T>();
    java.util.List<T> tempList;
    T[] outArray;
    for (T[] subArray : input) {
        tempList = java.util.Arrays.asList(subArray);
        outList.addAll(tempList);
    }
    if (outList.size() == 0) {
        return null;
    } else {
        outArray = input[0];// use as a class sampler
        outArray =
                (T[]) java.lang.reflect.Array.newInstance(outArray.getClass(), outList.size());
        for (int i = 0; i < outList.size(); i++) {
            System.out.println(i + "  " + outArray.length + "   " + outList.size() + "   "  + outList.get(i));
            outArray[i] = (T) outList.get(i);
        }
        System.out.println(outArray.length);
        return outArray;
    }
}

我仍然得到以下输出:
0  6   6   111
Exception in thread "main" java.lang.ArrayStoreException: java.lang.String
at patchLinker.Utilities.unTreeArray(Utilities.java:291)
at patchLinker.Utilities.main(Utilities.java:307)

这是否意味着即使您间接创建了一个T[],也无法对其进行写入操作?


我建议坚持使用集合(collections)并放弃老派的引用数组(reference arrays)。 - Tom Hawtin - tackline
2
很想这样做,但有时您在这件事上没有选择。向后兼容性始终意味着某些东西是向后的。 - K.Barad
3个回答

4

toArray()总是返回一个对象数组。

要返回特定类型的数组,您需要使用toArray(Object[])。

为了为数组参数提供正确的类型,您需要使用特定的类或在这种情况下使用反射。

尝试

 return (T[]) list.toArray(Array.newInstance(one.getClass(), list.size()));

感谢您的快速回复。我尝试了那个方法,但是出现了一个相当模糊的错误: 类型List<T>中的toArray(T[])方法对参数(Object)不适用。 我进行了一些手动检查,我可以在实例上调用getClass()方法,所以我怀疑这是因为newInstance()返回了一个对象。 - K.Barad
你应该在Array.newInstance方法的结果上添加一个强制类型转换。例如:list.toArray((T[])Array.newInstance(one.getClass(), 0)) - Tomas Narros
它需要另一个强制转换。如果两个对象的类型不同,它也会出现问题。或者如果返回的数组被调用者修改。 - Tom Hawtin - tackline
谢谢,我忘记了将 Object => T[] 转换是有效的,只有 Object[] => T[] 不行。好的,现在测试一下在我的(稍微不太简单)真实方法中是否可行。感谢您的帮助,希望问题已经解决了。 - K.Barad
没有运气。可能是我犯了一些愚蠢的错误,我已经看了这段代码太久了,没有休息。 - K.Barad

2

您第二个代码示例中的问题是由于您的T[] typeVar引起的:输入是一个二维数组,因此input[0]将返回一个一维数组(一个String[]),而不是一个预期的字符串。

如果您要将List<T>转换为T[],则需要一个T typeVar

修复方法:

public static <T> T[] unTreeArray(T[][] input) {
    java.util.List<T> outList = new ArrayList<T>();
    java.util.List<T> tempList;

    if (input.length == 0) {
        return null;
    }


    T typeVar=null;
    for (T[] subArray : input) {
        if(typeVar==null && subArray.length>0) {
            typeVar=subArray[0];
        }
        tempList = java.util.Arrays.asList(subArray);
        outList.addAll(tempList);
    }

    return outList.toArray((T[]) Array.newInstance(typeVar.getClass(),0));
}

public static void main(String[] args) {
    String[][] lines = { { "111", "122", "133" }, { "211", "222", "233" } };
    String[] result=unTreeArray(lines);

    System.out.println(result);

    System.out.println(result.getClass());

        //added for completion:
    System.out.println( Arrays.toString(result));

}

生成的输出结果为:

[Ljava.lang.String;@5a405a4 类
[Ljava.lang.String;
[111, 122, 133, 211, 222, 233]

注意:本文中的HTML标签已保留。

1
好的,肯定是我疯了。当然你传递的是元素而不是数组类型,否则有什么意义呢?谢谢你,看起来最终一切都正常工作了。 - K.Barad
很常见的情况。有时候我们从太近的距离看自己的代码,会发现隐藏的 bug :) - Tomas Narros
向@TomasNarros提问,如果输入数组为空,则typeVar将保留为NULL,因此您将得到一个NullPointerException。在这种情况下如何返回一个空列表? - Dmitry
如果输入可能为空,你应该在最开始检查,并将 if (input.length == 0) 改为 if (input !=null && input.length == 0) - Tomas Narros

1

你不能创建一个泛型类型参数的数组。简单的原因是Java通常会在运行时擦除泛型类型信息,因此运行时不知道T是什么(当T是泛型类型参数时)。

因此,解决方案是实际上在运行时获取类型信息。通常,这是通过传递一个Class<T> type参数来完成的,但在这种情况下,您可以避免这样做:

@peter的答案看起来很好,我会使用它:)。

return (T[]) list.toArray(Array.newInstance(one.getClass(), list.size()));

是的,我花了不少时间搜索阅读所有(诚然相当好的)理由,说明为什么泛型无法让我获得简单的解决方案。 - K.Barad

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接