Неужели плохая практика состоит в создании общих массивов, и какова будет альтернатива?
Я занимаюсь программированием на С++ в школе уже 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());
}
}
Ответы
Ответ 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 ограничены защитой типа компиляции, а массивы имеют доступную информацию о реальном типе даже во время выполнения