制作通用数组是不好的做法,还有什么选择呢?

时间:2015-10-16 19:32:59

标签: java sorting generics

我已经在学校用C ++编写了3年的编码。我刚开始在2天前用Java编写代码;我的问题是:

制作通用数组是不好的做法吗?会有什么替代方案?

我很难过,除了做一些奇怪的事情之外,我似乎无法创建一个通用数组,例如:

//Class implementing the MergeSort algorithm with generic types
// Revised by Doina January 2014

package Sorting;

import java.lang.*;

public class MergeSort {

    // Wrapper method for the real algorithm
    // T is the generic type which will be instantiated at runtime
    //  elementas are required to be comparable
    public static <T extends Comparable<T>> void sort(T[] a) {
        mergesort(a, 0, a.length - 1);
    }

    // Recursive mergesort method, following the pseudocode
    private static <T extends Comparable<T>> void mergesort(T[] a, int i, int j) {
        if (j - i < 1) return;
        int mid = (i + j) / 2;
        mergesort(a, i, mid);
        mergesort(a, mid + 1, j);
        merge(a, i, mid, j);
    }

    // Merge method
    // Here we need to allocate a new array, but Java does not allow allocating arrays of a generic type
    // As a work-around we allocate an array of type Object[] the use type casting
    // This would usually generate a warning, which is suppressed
    @SuppressWarnings("unchecked")
    private static <T extends Comparable<T>> void merge(T[] a, int p, int mid, int q) {

        Object[] tmp = new Object[q - p + 1];
        int i = p;
        int j = mid + 1;
        int k = 0;
        while (i <= mid && j <= q) {
            if (a[i].compareTo(a[j]) <= 0)
                tmp[k] = a[i++];
            else
                tmp[k] = a[j++];
            k++;
        }
        if (i <= mid && j > q) {
            while (i <= mid)
                tmp[k++] = a[i++];
        } else {
            while (j <= q)
                tmp[k++] = a[j++];
        }
        for (k = 0; k < tmp.length; k++) {
            a[k + p] = (T) (tmp[k]); // this is the line that woudl generate the warning
        }
    }

    // Main methos to test the code, using Integer Objects
    public static void main(String[] args) {
        Integer[] a = new Integer[5];
        a[0] = new Integer(2);
        a[1] = new Integer(1);
        a[2] = new Integer(4);
        a[3] = new Integer(3);
        a[4] = new Integer(-1);

        // T will be instantiated to Integer as a resutl of this call
        MergeSort.sort(a);

        // Print the result after the sorting
        for (int i = 0; i < a.length; i++)
            System.out.println(a[i].toString());
    }
}

3 个答案:

答案 0 :(得分:21)

这本身并不是一个坏主意;只是泛型和数组不能很好地混合。

原因是协方差和不变性。数组是协变的(Integer[]Object[],因为IntegerObject,但是泛型类不变List<Integer>是<即使List<Object>Integer,也不会<{1}}。

你还必须处理未经检查的强制转换,这会破坏泛型的整个目的。创建通用数组的最常用方法 - Object - 不是类型安全的,无法在编译时强制执行。可以在运行时对其进行推理,但是那时泛型带来的编译时检查会丢失。

要直接回答这个问题,您可以在何时何地使用Java Collections代替,因为他们非常适合使用泛型

只要看一下你提供的代码,我想使用E[] foo = (E[]) new Object[10];代替List<T>可以解决你的大多数问题(我希望你传递的是T[]因为链接列表会使这些操作变得昂贵。

答案 1 :(得分:4)

创建一个通用数组并不是一种坏习惯,但正确地这样做是非常麻烦的,人们通常会避免它。

它很麻烦的原因是擦除了泛型,而阵列则被强化了。也就是说,类型参数在编译期间被擦除,而数组的组件类型被保留。因此,运行时知道每个数组的组件类型,但忘记了所有对象的类型参数,即行

E[] array = new E[10];

无法编译,因为运行时需要知道新数组的组件类型,但遗忘的是E

Makoto回答的解决方法:

E[] array = (E[]) new Object[10];

不是一个好主意,因为它实际上创建了一个Object[],但随后伪装成E[]的编译器。由于运行时遗忘的是E,因此即使它的类型不正确,此转换也会在运行时成功。但是,运行时仍然通过一旦能够执行附加检查来强制执行存储器安全,即当对象存储在其类型不是通用的变量中时。例如:

static <E> E[] createArray(int size) {
    return (E[]) new Object[size];
}

public static void main(String[] args) {
    String[] array = createArray(size); // throws ClassCastException
    for (String s : array) {
        // whatever
    }
}

也就是说,这种解决方法是只在某些情况下才能工作的黑客攻击,否则会导致非常令人费解的行为(在一行代码中不包含强制转换的ClassCastException ...)。

创建E[]的唯一方法是通过反射提供我们所需组件类型的类对象:

Class<E> eClass = ...;
E[] array = Arrays.newInstance(eClass, 10);

但我们怎样才能得到这个类对象?如果我们的调用者知道,他们可以传递给我们一个类文字(如Integer.class),或者我们可以在其他对象上使用反射。在您的情况下,您手头有另一个E[],因此您可以询问该数组E是什么:

E[] originalArray = ...;
Class<E> eClass = (Class<E>) originalArray.getClass().getComponentType();
E[] newArray = (E[]) Array.newInstance(eClass, size);

这将确保新数组与旧数组的类型相同,即E[],除非有人使用Makoto的解决方法向我们说明该数组的类型。

正如您所看到的,可以创建一个通用数组,但它非常麻烦,人们通常会竭尽全力避免它。通常的替代方案是使用一些超类型的数组(在合并排序中,Comparable[]可能比Object[]更好,因为您不必强制转换),或使用ArrayList代替。

答案 2 :(得分:1)

添加到 Makoto的回答,我会说Arrays是协变的,因为它们的类型信息在运行时可用,而泛型类是不变的,因为类型信息不可用到期在编译时键入擦除。

协变数组: -

Object[] covariantArrays = new String[5];
covariantArrays[0] = new Dog(); // will give java.lang.ArrayStoreException

不变数组: -

List invariantArrays   = new List<String>();
invariantArrays.add(new Dog());   // Works fine as type information is not available

由于这个原因,通用数组不顺利,因为泛型仅限于编译类型安全性,并且数组甚至在运行时也有可用的实际类型信息