Подтвердить что ты не робот

Неужели плохая практика состоит в создании общих массивов, и какова будет альтернатива?

Я занимаюсь программированием на С++ в школе уже 3 года. Я начал кодирование на Java всего 2 дня назад; мой вопрос:

Неправильная ли практика создания общих массивов? Какая альтернатива?

Я в тупике, и я не могу создать общий массив, помимо того, что делаю что-то странное, например, этот пример:

//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());
    }
}
4b9b3361

Ответ 1

Это не то, что это плохая идея сама по себе; это просто, что дженерики и массивы не очень хорошо смешиваются.

Причина связана с ковариацией и инвариантностью. Массивы являются ковариантными (Integer[] является Object[], потому что Integer является Object, но общие классы являются инвариантными (List<Integer> не является List<Object>, хотя Integer является Object).

Вам также нужно иметь дело с неконтролируемыми бросками, которые преследуют цель создания дженериков. Наиболее распространенный способ создания общего массива - E[] foo = (E[]) new Object[10]; - не является безопасным для типов и не может быть применен во время компиляции. Это можно рассуждать во время выполнения, но в этот момент теряются проверки времени компиляции, которые генерируют дженерики в таблице.

Чтобы ответить на вопрос напрямую, где и когда это возможно, вы хотите использовать "Коллекции Java" , поскольку они очень хорошо играют с генериками.

Просто взглянув на предоставленный вами код, я полагаю, что использование List<T> вместо T[] приведет вас к большинству ваших проблем (и я надеюсь, что вы передадите ArrayList, поскольку эти операции могут стать дорогой со связанным списком).

Ответ 2

Неплохая практика создания общего массива, но делать это правильно, так громоздкие люди обычно избегают этого.

Причина громоздкости в том, что дженерики стираются, а массивы - заново. То есть, параметры типа стираются во время компиляции, тогда как тип компонентов массивов сохраняется. Поэтому среда выполнения знает тип компонента каждого массива, но забыла аргументы типа всех объектов, то есть строку

E[] array = new E[10];

не компилируется, потому что среда выполнения должна знать тип компонента для нового массива, но забыл, что был E is.

Обходной путь в Макото:

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[], если только кто-то не солгал нам о типе этого массива с использованием метода Макото.

Как вы можете видеть, можно создать общий массив, но он настолько громоздкий, что люди обычно идут на большие расстояния, чтобы избежать этого. Обычная альтернатива - использование массива некоторого супер-типа (в вашей сортировке слияния Comparable[] может работать даже лучше, чем Object[], потому что вам не нужно было бы делать бросок) или вместо ArrayList.

Ответ 3

Добавляя к ответ 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

По этой причине общие массивы не идут хорошо, так как Generics ограничены защитой типа компиляции, а массивы имеют доступную информацию о реальном типе даже во время выполнения