2010-09-13 15 views
121

Czy istnieje niezmienna alternatywa dla prymitywnych macierzy w Javie? Dokonywanie prymitywną tablicę final nie faktycznie zapobiec jednemu z robienia czegoś podobnegoNiezmienna tablica w Javie

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

Chcę elementy tablicy być niezmienne.

+42

Zrób sobie przysługę i zatrzymać za pomocą tablic w Javie na wszystko oprócz 1) io 2) liczba ciężkich pożerające 3) Jeśli trzeba zaimplementować własną listę/zbiór (co jest rzadkością * *). Są niezwykle nieelastyczne i przestarzałe ... jak właśnie odkryłeś to pytanie. – whaley

+31

@Whaley, a wydajność i kod, w którym nie potrzebujesz "dynamicznych" tablic. Tablice są nadal przydatne w wielu miejscach, nie jest to rzadkością. –

+10

@Colin: tak, ale są poważnie ograniczające; najlepiej jest wejść w nawyk myślenia "czy naprawdę potrzebuję tutaj tablicy lub czy mogę zamiast tego użyć listy?" –

Odpowiedz

132

Nie w prymitywnych tablicach. Musisz użyć Listy lub innej struktury danych:

List<Integer> items = Collections.unmodifiableList(Arrays.asList(0,1,2,3)); 
+15

Jakoś nigdy nie wiedziałem o Arrays.asList (T ...). Myślę, że mogę teraz pozbyć się mojej ListUtils.list (T ...). – MattRS

+1

Nawiasem mówiąc, Arrays.asList podaje niezmodyfikowaną listę – mauhiz

+2

@mauhiz, nie, nie zgodnie ze źródłem, tablica Arrays.asList (array) zwraca nową tablicę ArrayList <> (array), która ma całkowicie poprawne metody zestawu ... –

1

Nie, nie jest to możliwe. Jednakże, można 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 za pomocą owijarki i jest na liście, nie tablicą, ale jest najbliżej dostaniesz.

+4

Nie trzeba pisać tych wszystkich wartości 'valueOf(), automatycznie zajmie się autoboxing. Również 'Arrays.asList (0, 2, 3, 4)' byłoby znacznie bardziej zwięzłe. –

+0

@Joachim: Punktem użycia 'valueOf()' jest wykorzystanie wewnętrznej pamięci podręcznej obiektów Integer w celu zmniejszenia zużycia/recyklingu pamięci. – Esko

+4

@Esko: przeczytaj specyfikację autoboxing. Robi dokładnie to samo, więc nie ma tu żadnej różnicy. –

17

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

Jeśli koniecznie musisz metodę, która zwraca tablicę, która nie wpływa na oryginalną tablicę, potem trzeba by sklonować tablicę za każdym razem:

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

Oczywiście jest to dość drogie (jak ty utwórz pełną kopię za każdym razem, gdy wywołasz getter), ale jeśli nie możesz zmienić interfejsu (na przykład użyć List) i nie możesz ryzykować zmiany ustawień wewnętrznych przez klienta, może to być konieczne.

Ta technika nazywa się tworzeniem kopii obronnej.

+3

Gdzie wspomniał, że potrzebował gettera? –

+5

@Erik: nie zrobił tego, ale jest to bardzo powszechny przypadek użycia niezmiennych struktur danych (zmodyfikowałem odpowiedź, aby odnieść się do metod w ogóle, ponieważ rozwiązanie stosuje się wszędzie, nawet jeśli jest bardziej powszechne w plikach pobierających). –

+4

Czy lepiej użyć tutaj 'clone()' lub 'Arrays.copy()'? – kevinarpe

65

Moja rekomendacja to nie używać tablicy lub unmodifiableList, ale używać Guava 's ImmutableList, która istnieje w tym celu.

ImmutableList<Integer> values = ImmutableList.of(0, 1, 2, 3); 
+3

+1 ImmutableList Guava jest nawet lepszy niż Collections.unmodifiableList, ponieważ jest to oddzielny typ. – sleske

+26

ImmutableList jest * często * lepszy (zależy od przypadku użycia), ponieważ jest niezmienny. Collections.unmodifiableList nie jest niezmienny. Przeciwnie, jest to pogląd, że odbiorniki nie mogą się zmienić, ale oryginalne źródło MOŻE się zmienić. –

+2

@CharlieCollins, jeśli nie ma sposobu uzyskania dostępu do oryginalnego źródła, niż "Collections.unmodifiableList" wystarcza, aby lista stała się niezmienna? – savanibharat

3

Jeśli potrzebujesz (z przyczyn wydajności lub oszczędzanie pamięci) native „int” zamiast „java.lang.Integer”, to prawdopodobnie trzeba pisać własne klasy otoki. Istnieją różne implementacje IntArray w sieci, ale żaden (I found) był niezmienny: Koders IntArray, Lucene IntArray. Są prawdopodobnie inne.

-3

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

+0

Co to jest "parametr wariantów"? Nigdy nie słyszałem o tym. – sleske

+0

Przepraszam, varargs;) – Martin

+2

Używanie tablic jako stałych to * dokładnie *, w którym wielu ludzi NIE DZIAŁA. Odniesienie jest stałe, ale zawartość tablicy jest zmienna. Jeden rozmówca/klient może zmienić zawartość "stałej". Prawdopodobnie nie jest to coś, na co chcesz zezwolić. Użyj ImmutableList. –

11

Jest to jeden ze sposobów dokonania niezmienny układ Java:

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

Tablice z 0 elementów (oczywiście) nie może zostać zmutowana.

To może faktycznie się przydać, jeśli używasz metody List.toArray do konwertowania List do tablicy. Ponieważ nawet pusta tablica zajmuje trochę pamięci, można zapisać tę alokację pamięci, tworząc stałą pustą tablicę i zawsze przekazując ją do metody toArray. Ta metoda przydzieli nową tablicę, jeśli przekazana tablica nie ma wystarczającej ilości miejsca, ale jeśli tak (lista jest pusta), zwróci tablicę, którą przekazałeś, umożliwiając ponowne użycie tej tablicy za każdym razem, gdy wywołasz toArray na 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 
+1

Dobra rada dotycząca pustych tablic może być niezmienna. – kevinarpe

+1

Ale to nie pomaga OP osiągnąć niezmienną tablicę z danymi w nim. –

+1

@ Sridhar-Sarnobat To punkt zwrotny. W języku Java nie ma możliwości stworzenia niezmiennej tablicy zawierającej dane. – Brigham

4

Kolejna 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"}); 
} 
+0

jaki jest pożytek z kopiowania tej tablicy w konstruktorze, ponieważ nie dostarczyliśmy żadnej metody ustawiającej? –

+0

Zawartość tablicy wejściowej nadal można zmodyfikować później. Chcemy zachować jego pierwotny stan, więc go skopiujmy. – aeracode

1

W niektórych sytuacjach będzie lżejsze, aby skorzystać z tej metody statycznej 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})
1

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

0

Chociaż prawdą jest, że działa Collections.unmodifiableList(), czasami może istnieć duża biblioteka, która ma już zdefiniowane metody zwracania tablic (na przykład String[]). Aby zapobiec ich łamanie, rzeczywiście można określić pomocnicze tablice, 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; 
    } 
} 

do testu:

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 idealne, ale przynajmniej masz „pseudo niezmienne tablic” (z perspektywa klasowa) i nie złamie to powiązanego kodu.

2

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

Mają też budowniczym. Przykład:

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

lub jeśli wielkość jest znana w czasie kompilacji:

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

Jest inny sposób na uzyskanie niezmienny widok tablicy dla pierwotnych Java.

1

Od wersji Java 9 można używać List.of(...), JavaDoc.

Ta metoda zwraca niezmienną wartość List i jest bardzo wydajna.

0

Sposób Java9of(E... elements) mogą być wykorzystywane do tworzenia listy niezmienny przy użyciu tylko linię:

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

Powyższy sposób powraca niezmiennej listę zawierającą dowolną liczbę elementów.Dodanie dowolnej liczby całkowitej do tej listy spowoduje wyjątek java.lang.UnsupportedOperationException. Ta metoda akceptuje również pojedynczą tablicę jako argument.

String[] array = ... ; 
List<String[]> list = List.<String[]>of(array); 
Powiązane problemy