2015-10-16 3 views
12

Я занимаюсь программированием на C++ в школе уже 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()); 
    } 
} 
+5

Какова часть примера сортировки слияний, которую вы считаете странной? – azurefrog

+0

Также см. Http://stackoverflow.com/questions/529085/how-to-create-a-generic-array-in-java – azurefrog

+3

Oof, я не заметил его заранее, но 'import java.lang. * 'совершенно бессмысленно; вы не хотите, чтобы вас поймали, делая это во что угодно. – Makoto

ответ

19

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

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

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

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

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

+1

ArrayList ** не использует ** общие массивы. [Он использует 'Object []'.] (Http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8u40-b25/java/util/ArrayList.java#134) Кроме того, 'E [] foo = (E []) новый объект [10];' не создает общий массив; это ошибочный приказ, который будет обнаружен, как только вы попытаетесь присвоить массив не общей переменной, например 'Integer [] a = Thing .methodReturningEArray()'. Единственный безопасный способ создания общего массива - использовать другой массив или объект класса для получения информации о типе во время выполнения, например 'ArrayList.toArray (T [])'. – user2357112

+0

Вы правы в бит 'ArrayList' - я с удовольствием исправлю это, но я не соглашусь с вами во второй части, особенно учитывая, что в этом методе есть приведение к' T []'. И если вы спуститесь [*, это * кроличья дыра] (http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8u40-b25/java/util/Arrays.java#Arrays .copyOf% 28java.lang.Object% 5B% 5D% 2Cint% 2Cjava.lang.Class% 29), вы бросаете 'Object []' в общий 'T []' в * lot * мест.Таким образом, эти методы могут быть безопасными, но соглашение, которое я описываю, также используется в библиотеках. – Makoto

+1

Есть листинг в 'T []', но это потому, что массив известен как 'T []', поскольку он был создан с использованием 'T []' в качестве шаблона. Создание нового объекта 'Object []' и отбрасывание его на 'T []' полностью отличается. [Здесь, смотри, что это не работает!] (Http://ideone.com/sslO80). Что касается примера «вниз по кроличьей дыре», он бросает 'Object []' в 'T []', потому что он только что определил что 'T', фактически,' Object'. – user2357112

1

Добавление к ответ Макото, я бы сказал, что 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 

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

4

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

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

E[] array = new E[10]; 

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

Обходной в ответ Макото:

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.

Смежные вопросы