Niezmienna tablica w Javie

158

Czy istnieje niezmienna alternatywa dla pierwotnych tablic w Javie? Tworzenie prymitywnej tablicy w finalrzeczywistości nie uniemożliwia zrobienia czegoś takiego

final int[] array = new int[] {0, 1, 2, 3};
array[0] = 42;

Chcę, aby elementy tablicy były niezmienne.


źródło
43
Zrób sobie przysługę i przestań używać tablic w Javie do czegokolwiek poza 1) io 2) ciężkim przetwarzaniem liczb 3), jeśli musisz zaimplementować własną listę / kolekcję (co jest rzadkie ). Są niezwykle nieelastyczne i przestarzałe ... jak właśnie odkryłeś w tym pytaniu.
whaley
39
@Whaley i wydajności oraz kod, w którym nie potrzebujesz tablic „dynamicznych”. Tablice są nadal przydatne w wielu miejscach, nie jest to takie rzadkie.
Colin Hebert,
10
@Colin: tak, ale są bardzo ograniczające; najlepiej jest nabrać nawyku myślenia „czy naprawdę potrzebuję tutaj tablicy, czy zamiast niej mogę użyć listy?”
Jason S
7
@Colin: Optymalizuj tylko wtedy, gdy tego potrzebujesz. Kiedy stwierdzasz, że spędzasz pół minuty na dodawaniu czegoś, co na przykład powiększa tablicę lub utrzymujesz jakiś indeks poza zakresem pętli for, straciłeś już trochę czasu swojego szefa. Najpierw buduj, optymalizuj, kiedy i gdzie jest to potrzebne - aw większości aplikacji nie polega to na zastępowaniu list tablicami.
fwielstra
9
Cóż, dlaczego nikt nie wspomniał int[]i new int[]czy DUŻO łatwiej jest pisać niż List<Integer>i new ArrayList<Integer>? XD
lcn

Odpowiedzi:

164

Nie z prymitywnymi tablicami. Będziesz musiał użyć listy lub innej struktury danych:

List<Integer> items = Collections.unmodifiableList(Arrays.asList(0,1,2,3));
Jason S.
źródło
18
Jakoś nigdy nie wiedziałem o Arrays.asList (T ...). Myślę, że mogę teraz pozbyć się mojego ListUtils.list (T ...).
MattRS
3
Nawiasem mówiąc, Arrays.asList podaje niemodyfikowalną listę
mauhiz
3
@tony that ArrayList is not java.util's
mauhiz
11
@mauhiz Arrays.asListjest nie niezmienne. docs.oracle.com/javase/7/docs/api/java/util/… "Zwraca listę o stałym rozmiarze wspieraną przez określoną tablicę. (Zmiany w zwróconej liście" zapisuj "do tablicy.)"
Jason S
7
@JasonS cóż, Arrays.asList()rzeczywiście wydaje się niemodyfikowalny w tym sensie, że nie możesz addlub removeelementy zwrócone java.util.Arrays.ArrayList( nie mylić z java.util.ArrayList) - te operacje po prostu nie są zaimplementowane. Może @mauhiz próbuje to powiedzieć? Ale oczywiście można modyfikować istniejące elementy za pomocą List<> aslist = Arrays.asList(...); aslist.set(index, element), więc na java.util.Arrays.ArrayListpewno nie jest to niemożliwe , QED Dodano komentarz tylko po to, aby podkreślić różnicę między wynikiem asLista normalnymArrayList
NIA
73

Moja rada jest taka, aby nie używać tablica czy unmodifiableListjednak używać Guava „s ImmutableList , która istnieje do tego celu.

ImmutableList<Integer> values = ImmutableList.of(0, 1, 2, 3);
ColinD
źródło
3
ImmutableList +1 Guavy jest nawet lepsze niż Collections.unmodifiableList, ponieważ jest to odrębny typ.
sleske
29
ImmutableList jest często lepsza (zależy to od przypadku użycia), ponieważ jest niezmienna. Collections.unmodifiableList nie jest niezmienna. Jest to raczej pogląd, którego odbiorniki nie mogą zmienić, ale oryginalne źródło MOŻE się zmienić.
Charlie Collins
2
@CharlieCollins, jeśli nie ma możliwości uzyskania dostępu do oryginalnego źródła, to Collections.unmodifiableListwystarczy, aby lista była niezmienna?
savanibharat
1
@savanibharat Yes.
Tylko student
20

Jak zauważyli inni, w Javie nie można mieć niezmiennych tablic.

Jeśli absolutnie potrzebujesz metody zwracającej tablicę, która nie wpływa na oryginalną tablicę, musisz sklonować tablicę za każdym razem:

public int[] getFooArray() {
  return fooArray == null ? null : fooArray.clone();
}

Oczywiście jest to dość kosztowne (ponieważ utworzysz pełną kopię za każdym razem, gdy wywołasz getter), ale jeśli nie możesz zmienić interfejsu (aby użyć Listna przykład) i nie możesz ryzykować, że klient zmieni twoje wewnętrzne, to może to być konieczne.

Ta technika nazywa się robieniem kopii obronnej.

Joachim Sauer
źródło
3
Gdzie wspomniał, że potrzebuje gettera?
Erick Robertson,
6
@Erik: nie, ale to bardzo powszechny przypadek użycia niezmiennych struktur danych (zmodyfikowałem odpowiedź, aby odnosić się do metod w ogóle, ponieważ rozwiązanie ma zastosowanie wszędzie, nawet jeśli jest bardziej powszechne w getterach).
Joachim Sauer
7
Lepiej użyć clone()czy Arrays.copy()tutaj?
kevinarpe,
O ile pamiętam, według Joshua Blocha „Effective Java”, clone () jest zdecydowanie preferowaną metodą.
Vincent
1
Ostatnie zdanie punktu 13, „Zastąp klonowanie rozsądnie”: „Z reguły funkcję kopiowania najlepiej zapewniają konstruktorzy lub fabryki. Godnym uwagi wyjątkiem od tej reguły są tablice, które najlepiej kopiować metodą clone”.
Vincent
14

Istnieje jeden sposób na utworzenie niezmiennej tablicy w Javie:

final String[] IMMUTABLE = new String[0];

Tablice z elementami 0 (oczywiście) nie mogą być mutowane.

Może się to przydać, jeśli używasz List.toArraymetody do konwersji a Listna tablicę. Ponieważ nawet pusta tablica zajmuje trochę pamięci, możesz zapisać tę alokację pamięci, tworząc stałą pustą tablicę i zawsze przekazując ją do toArraymetody. Że metoda będzie przydzielić nową tablicę jeśli tablica zdać nie ma wystarczająco dużo miejsca, ale jeśli to robi (lista jest pusta), zwróci macierz, którą przeszedł, co pozwala na ponowne użycie tej tablicy każdej chwili zadzwonić toArrayna zasadzie pusty List.

final static String[] EMPTY_STRING_ARRAY = new String[0];

List<String> emptyList = new ArrayList<String>();
return emptyList.toArray(EMPTY_STRING_ARRAY); // returns EMPTY_STRING_ARRAY
Brigham
źródło
3
Ale to nie pomaga OP w uzyskaniu niezmiennej tablicy z danymi.
Sridhar Sarnobat
1
@ Sridhar-Sarnobat To jest pewien punkt odpowiedzi. W Javie nie ma możliwości utworzenia niezmiennej tablicy z danymi.
Brigham
1
Bardziej podoba mi się ta prostsza składnia: prywatne statyczne końcowe String [] EMPTY_STRING_ARRAY = {}; (Oczywiście nie wymyśliłem, jak zrobić „mini-Markdown”. Przydałby się przycisk podglądu).
Wes
6

Jeszcze jedna odpowiedź

static class ImmutableArray<T> {
    private final T[] array;

    private ImmutableArray(T[] a){
        array = Arrays.copyOf(a, a.length);
    }

    public static <T> ImmutableArray<T> from(T[] a){
        return new ImmutableArray<T>(a);
    }

    public T get(int index){
        return array[index];
    }
}

{
    final ImmutableArray<String> sample = ImmutableArray.from(new String[]{"a", "b", "c"});
}
aeracode
źródło
Jaki jest pożytek z kopiowania tej tablicy w konstruktorze, skoro nie dostarczyliśmy żadnej metody ustawiającej?
vijaya kumar
Zawartość tablicy wejściowej nadal można modyfikować później. Chcemy zachować jego pierwotny stan, więc go kopiujemy.
aeracode
4

Jak Java 9 można użyć List.of(...), Javadoc .

Ta metoda zwraca niezmienną Listi jest bardzo wydajna.

rmuller
źródło
3

Jeśli potrzebujesz (ze względu na wydajność lub w celu zaoszczędzenia pamięci) natywnego „int” zamiast „java.lang.Integer”, prawdopodobnie będziesz musiał napisać własną klasę opakowującą. W sieci są różne implementacje IntArray, ale żadna (znalazłem) nie była niezmienna: Koders IntArray , Lucene IntArray . Prawdopodobnie są inni.

Thomas Mueller
źródło
3

Od Guava 22, z pakietu com.google.common.primitivesmożna użyć trzech nowych klas, które mają mniejszy ślad pamięci w porównaniu do ImmutableList.

Mają też budowniczego. Przykład:

int size = 2;
ImmutableLongArray longArray = ImmutableLongArray.builder(size)
  .add(1L)
  .add(2L)
  .build();

lub, jeśli rozmiar jest znany w czasie kompilacji:

ImmutableLongArray longArray = ImmutableLongArray.of(1L, 2L);

To kolejny sposób na uzyskanie niezmiennego widoku tablicy dla prymitywów języka Java.

Jean Valjean
źródło
1

Nie, to niemożliwe. Można jednak zrobić coś takiego:

List<Integer> temp = new ArrayList<Integer>();
temp.add(Integer.valueOf(0));
temp.add(Integer.valueOf(2));
temp.add(Integer.valueOf(3));
temp.add(Integer.valueOf(4));
List<Integer> immutable = Collections.unmodifiableList(temp);

Wymaga to użycia opakowań i jest listą, a nie tablicą, ale jest najbliższą, jaką otrzymasz.


źródło
4
Nie musisz pisać tych wszystkich valueOf(), autobox się tym zajmie. Również Arrays.asList(0, 2, 3, 4)byłoby znacznie bardziej zwięzły.
Joachim Sauer
@Joachim: Celem użycia valueOf()jest wykorzystanie wewnętrznej pamięci podręcznej obiektu Integer w celu zmniejszenia zużycia / recyklingu pamięci.
Esko
4
@Esko: przeczytaj specyfikację autoboxingu. Robi dokładnie to samo, więc nie ma tutaj różnicy.
Joachim Sauer
1
@John Nigdy nie dostaniesz NPE konwertującego an intna Integerchociaż; po prostu trzeba uważać na odwrót.
ColinD,
1
@John: masz rację, to może być niebezpieczne. Ale zamiast całkowicie go unikać, prawdopodobnie lepiej zrozumieć niebezpieczeństwo i go unikać.
Joachim Sauer
1

W niektórych sytuacjach lżejsze będzie użycie tej statycznej metody z biblioteki Google Guava: List<Integer> Ints.asList(int... backingArray)

Przykłady:

  • List<Integer> x1 = Ints.asList(0, 1, 2, 3)
  • List<Integer> x1 = Ints.asList(new int[] { 0, 1, 2, 3})
kevinarpe
źródło
1

Jeśli chcesz uniknąć zarówno zmienności, jak i boksu, nie ma wyjścia z pudełka. Ale możesz stworzyć klasę, która zawiera wewnątrz pierwotną tablicę i zapewnia dostęp tylko do odczytu do elementów za pośrednictwem metod.

Wyświetlana nazwa
źródło
1

Metoda of (E ... elements) w Javie9 może służyć do tworzenia niezmiennej listy przy użyciu tylko linii:

List<Integer> items = List.of(1,2,3,4,5);

Powyższa metoda zwraca niezmienną listę zawierającą dowolną liczbę elementów. Dodanie dowolnej liczby całkowitej do tej listy spowodowałoby java.lang.UnsupportedOperationExceptionwyjątek. Ta metoda akceptuje również pojedynczą tablicę jako argument.

String[] array = ... ;
List<String[]> list = List.<String[]>of(array);
akhil_mittal
źródło
0

Chociaż prawdą jest, że to Collections.unmodifiableList()działa, czasami możesz mieć dużą bibliotekę z już zdefiniowanymi metodami zwracania tablic (np String[].). Aby zapobiec ich uszkodzeniu, możesz faktycznie zdefiniować tablice pomocnicze, które będą przechowywać wartości:

public class Test {
    private final String[] original;
    private final String[] auxiliary;
    /** constructor */
    public Test(String[] _values) {
        original = new String[_values.length];
        // Pre-allocated array.
        auxiliary = new String[_values.length];
        System.arraycopy(_values, 0, original, 0, _values.length);
    }
    /** Get array values. */
    public String[] getValues() {
        // No need to call clone() - we pre-allocated auxiliary.
        System.arraycopy(original, 0, auxiliary, 0, original.length);
        return auxiliary;
    }
}

Testować:

    Test test = new Test(new String[]{"a", "b", "C"});
    System.out.println(Arrays.asList(test.getValues()));
    String[] values = test.getValues();
    values[0] = "foobar";
    // At this point, "foobar" exist in "auxiliary" but since we are 
    // copying "original" to "auxiliary" for each call, the next line
    // will print the original values "a", "b", "c".
    System.out.println(Arrays.asList(test.getValues()));

Nie jest to doskonałe, ale przynajmniej masz „pseudo niezmienne tablice” (z punktu widzenia klasy) i to nie zepsuje powiązanego kodu.

miguelt
źródło
-3

No cóż ... tablice są przydatne do przekazywania jako stałe (jeśli były) jako parametry wariantów.

Jaskółka oknówka
źródło
Co to jest „parametr wariantów”? Nigdy nie słyszałem o tym.
sleske
2
Używanie tablic jako stałych jest dokładnie tym, w którym wielu osobom zawodzi. Odwołanie jest stałe, ale zawartość tablicy jest zmienna. Jeden dzwoniący / klient może zmienić zawartość Twojej „stałej”. Prawdopodobnie nie chcesz na to pozwolić. Użyj ImmutableList.
Charlie Collins,
@CharlieCollins nawet to może zostać zhakowane przez odbicie. Java jest językiem niebezpiecznym pod względem zmienności ... i to się nie zmieni.
Nazwa wyświetlana
2
@SargeBorsch To prawda, ale każdy, kto zajmuje się hakowaniem opartym na odbiciu, powinien wiedzieć, że igra z ogniem, modyfikując rzeczy, do których wydawca API mógł nie mieć dostępu. Mając na uwadze, że jeśli API zwraca wartość int[], wywołujący może założyć, że może robić, co chce z tą tablicą bez wpływu na wewnętrzne funkcje API.
daiscog