2015-10-16 12 views
12

Od 3 lat koduję w C++ w szkole. Zacząłem kodować w Javie zaledwie 2 dni temu; moje pytanie brzmi:Czy złą praktyką jest tworzenie ogólnych tablic i jaka byłaby alternatywa?

Czy tworzenie tablic generycznych jest złym zwyczajem? Jaka byłaby alternatywa?

Jestem zakłopotany i nie wydaje się, aby rodzajowe tablicy oprócz robi coś dziwnego, takie jak ten przykład:

//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

Jaka jest część przykładowego scalania, które uważasz za dziwne? – azurefrog

+0

Zobacz także http://stackoverflow.com/questions/529085/how-to-create-a-generic-array-in-java – azurefrog

+3

Oof, nie zauważyłem tego wcześniej, ale 'import java.lang. * 'jest całkowicie bezcelowe; nie chcesz być przyłapanym na robieniu tego w niczym. – Makoto

Odpowiedz

19

To nie jest tak, że to zły pomysł per se; po prostu generics i array nie mieszają się bardzo dobrze.

Przyczyna jest spowodowana kowariancją i niezmiennością. Tablice są kowariantna (Integer[] jest Object[] ponieważ Integer jest Object, ale klasy generyczne są niezmienna (List<Integer> jest nieList<Object> chociaż Integer jest Object).

Masz również do czynienia z niekontrolowanych odlewów , który pokonuje cały cel generycznych.Najczęstszy sposób tworzenia ogólnej tablicy - E[] foo = (E[]) new Object[10]; - nie jest bezpieczny dla typu i nie można go wymusić w czasie kompilacji.Może się go przekonać w czasie wykonywania, ale podczas kompilacji kontrole, które generics wprowadzają do tabeli, zostają utracone w tym momencie:

Aby odpowiedzieć bezpośrednio na pytanie, gdzie i kiedy jest to możliwe, należy użyć Java Collections, ponieważ bardzo dobrze grają z bardzo.

Tylko patrząc na kodzie dostarczonym, wyobrażam sobie, że za pomocą List<T> zamiast T[] będzie cię przez większość twoich problemów (i mam nadzieję, że jesteś w zdaniu ArrayList ponieważ operacje te mogą stać się droższe z połączonej listy).

+1

ArrayList ** nie używa ** ogólnych tablic. [Używa 'Object []'.] (Http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8u40-b25/java/util/ArrayList.java#134) Również 'E [] foo = (E []) new Object [10];' nie tworzy ogólnej tablicy; jest to błędna obsada, która zostanie wykryta, gdy tylko spróbujesz przypisać tablicę do zmiennej ogólnej, takiej jak 'Integer [] a = Thing .methodReturningErray()'. Jedynym bezpiecznym sposobem utworzenia ogólnej tablicy jest użycie innej tablicy lub obiektu klasy, aby uzyskać informacje o typie w czasie wykonywania, na przykład 'ArrayList.toArray (T [])'. – user2357112

+0

Masz rację co do fragmentu 'ArrayList' - z przyjemnością to poprawię, ale nie zgadzam się z tobą w drugiej części, szczególnie biorąc pod uwagę, że w metodzie tej jest rzut" T [] ". A jeśli zejdziesz na dół [* to * króliczej nory] (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), rzucasz 'Object []' do ogólnego 'T []' w * wielu * miejscach.Metody te mogą być bezpieczne, ale konwencja, którą opisuję, jest również używana w bibliotekach. – Makoto

+1

Istnieje rzutowanie na 'T []', ale to dlatego, że tablica jest faktycznie nazywana 'T []', ponieważ została utworzona przy użyciu 'T []' jako szablonu. Dodanie 'Object []' i przeniesienie go do 'T []' jest zupełnie inne. [Tutaj patrz, jak to się nie udaje!] (Http://ideone.com/sslO80) Jeśli chodzi o przykład "dół króliczej dziury", rzuca "Object []" na 'T []', ponieważ właśnie ustaliło że 'T' jest w rzeczywistości' Object'. – user2357112

1

Dodawanie do Makoto odpowiedzi, powiedziałbym, że Arrays są kowariantna ze względu na fakt, że ich informacje typu jest dostępny w czasie wykonywania natomiast klasy generyczne są niezmienne jak informacja o typie nie jest dostępny ze względu na typ usunięcia w czasie kompilacji.

kowariantna Tablice: -

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

Matryce niezmienne: -

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

Z tego powodu Generic tablice nie wykraczają także rodzajowych są ograniczone do kompilacji typu bezpieczeństwa i tablic informacje o prawdziwym typie są dostępne nawet w Runtime

4

To nieźle działa Tworzenie ogólnej tablicy, ale robienie tego poprawnie jest tak uciążliwe, że ludzie zwykle go unikają.

Powodem, dla którego jest to uciążliwe, jest to, że generics są wymazywane, a tablice są reifikowane. Oznacza to, że parametry typu są usuwane podczas kompilacji, podczas gdy typ komponentów tablic jest zachowywany. Dlatego środowisko wykonawcze zna typ komponentu każdej tablicy, ale zapomniało argumentów typu wszystkich obiektów, tj.linia

E[] array = new E[10]; 

nie kompiluje ponieważ środowisko wykonawcze musi znać typ komponentu do nowej tablicy, ale zapomniał był E jest.

Rozwiązaniem w odpowiedzi Makoto:

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

nie jest dobrym pomysłem, ponieważ faktycznie tworzy Object[], ale następnie udaje się do kompilatora, która jest E[]. Ponieważ środowisko uruchomieniowe zapomniało już o numerze E, to również rzutowanie powiedzie się w czasie wykonywania, nawet jeśli nie jest poprawne. Jednak środowisko wykonawcze nadal wymusza bezpieczeństwo pamięci, wykonując dodatkową kontrolę, gdy tylko jest to możliwe, tj. Gdy obiekt jest przechowywany w zmiennej, której typ nie jest ogólny. Na przykład:

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 
    } 
} 

Oznacza to, że to rozwiązanie jest hack, który działa tylko w pewnych okolicznościach i spowoduje bardzo zaskakujący zachowanie inaczej (ClassCastException w linii kodu, który nie zawiera gips ...).

Jedynym sposobem, aby utworzyć E[] jest poprzez refleksję, poprzez dostarczanie obiektu klasy naszego pożądanego typu komponent:

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

ale w jaki sposób możemy uzyskać ten obiekt klasy? Jeśli nasz rozmówca wie, może przekazać nam literaturę klasową (np. Integer.class) lub możemy użyć refleksji na innym obiekcie. W twoim przypadku, masz inny E[] pod ręką, więc można poprosić tę tablicę co E jest:

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

To zapewni nowa tablica jest tego samego typu co starego, który jest E[], chyba że ktoś Okłamał nas o typie tej tablicy, wykorzystując obejście Makoto.

Jak widać, możliwe jest utworzenie ogólnej tablicy, ale jest ona tak uciążliwa, że ​​ludzie zwykle robią to, co jest w jej mocy, aby jej uniknąć. Typową alternatywą jest użycie tablicy jakiegoś super typu (w sortowaniu scalonym, Comparable[] może działać nawet lepiej niż Object[], ponieważ nie trzeba było rzutować) lub zamiast tego używać ArrayList.

Powiązane problemy