Unveränderliches Array in Java

158

Gibt es eine unveränderliche Alternative zu den primitiven Arrays in Java? Das Erstellen eines primitiven Arrays finalhindert einen nicht daran, so etwas zu tun

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

Ich möchte, dass die Elemente des Arrays unveränderlich sind.


quelle
43
Tun Sie sich selbst einen Gefallen und beenden Sie die Verwendung von Arrays in Java für andere Zwecke als 1) io 2) schwere Zahlenverarbeitung 3), wenn Sie Ihre eigene Liste / Sammlung implementieren müssen (was selten vorkommt ). Sie sind extrem unflexibel und antiquiert ... wie Sie gerade bei dieser Frage festgestellt haben.
Whaley
39
@Whaley und Performances sowie Code, bei dem Sie keine "dynamischen" Arrays benötigen. Arrays sind an vielen Orten immer noch nützlich, es ist nicht so selten.
Colin Hebert
10
@Colin: Ja, aber sie sind stark einschränkend. Gewöhnen Sie sich am besten an: "Brauche ich hier wirklich ein Array oder kann ich stattdessen eine Liste verwenden?"
Jason S
7
@Colin: Nur bei Bedarf optimieren. Wenn Sie eine halbe Minute damit verbringen, etwas hinzuzufügen, das beispielsweise ein Array vergrößert, oder wenn Sie einen Index außerhalb des Bereichs einer for-Schleife halten, haben Sie bereits einen Teil der Zeit Ihres Chefs verschwendet. Erstellen Sie zuerst, optimieren Sie, wann und wo es benötigt wird - und in den meisten Anwendungen müssen Listen nicht durch Arrays ersetzt werden.
Fwielstra
9
Nun, warum hat niemand erwähnt int[]und new int[]ist viel einfacher zu tippen als List<Integer>und new ArrayList<Integer>? XD
lcn

Antworten:

164

Nicht mit primitiven Arrays. Sie müssen eine Liste oder eine andere Datenstruktur verwenden:

List<Integer> items = Collections.unmodifiableList(Arrays.asList(0,1,2,3));
Jason S.
quelle
18
Irgendwie wusste ich nie etwas über Arrays.asList (T ...). Ich denke, ich kann meine ListUtils.list (T ...) jetzt loswerden.
MattRS
3
Übrigens, Arrays.asList gibt eine nicht veränderbare Liste
Mauhiz
3
@tony, dass ArrayList nicht java.util's ist
mauhiz
11
@mauhiz Arrays.asListist nicht unveränderbar. docs.oracle.com/javase/7/docs/api/java/util/… "Gibt eine Liste mit fester Größe zurück, die vom angegebenen Array unterstützt wird. (Änderungen an der zurückgegebenen Liste" durchschreiben "in das Array.)"
Jason S
7
@JasonS gut, scheint in der Arrays.asList()Tat nicht modifizierbar in dem Sinne, dass Sie nicht addoder removeElemente der zurückgegebenen java.util.Arrays.ArrayList( nicht zu verwechseln mit java.util.ArrayList) - diese Operationen sind einfach nicht implementiert. Vielleicht versucht @mauhiz das zu sagen? Aber natürlich können Sie vorhandene Elemente mit QED ändern List<> aslist = Arrays.asList(...); aslist.set(index, element), was java.util.Arrays.ArrayListsicherlich nicht unerheblich ist. Der Kommentar wurde hinzugefügt , um den Unterschied zwischen dem Ergebnis von asListund normal hervorzuhebenArrayList
NIA
73

Meine Empfehlung ist , nicht um ein Array zu verwenden oder unmodifiableListaber zu verwenden Guava ‚s ImmutableList , die für diesen Zweck vorhanden ist .

ImmutableList<Integer> values = ImmutableList.of(0, 1, 2, 3);
ColinD
quelle
3
+1 Guavas ImmutableList ist sogar besser als Collections.unmodutableList, da es sich um einen separaten Typ handelt.
Sleske
29
ImmutableList ist oft besser (es hängt vom Anwendungsfall ab), weil es unveränderlich ist. Collections.unmodizableList ist nicht unveränderlich. Es ist vielmehr eine Ansicht, die Empfänger nicht ändern können, aber die ursprüngliche Quelle kann sich ändern.
Charlie Collins
2
@CharlieCollins, wenn es keine Möglichkeit gibt, auf die Originalquelle zuzugreifen, Collections.unmodifiableListreicht dies aus, um eine Liste unveränderlich zu machen?
Savanibharat
1
@savanibharat Ja.
Nur ein Student
20

Wie andere angemerkt haben, können Sie in Java keine unveränderlichen Arrays haben.

Wenn Sie unbedingt eine Methode benötigen, die ein Array zurückgibt, das das ursprüngliche Array nicht beeinflusst, müssen Sie das Array jedes Mal klonen:

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

Dies ist natürlich ziemlich teuer (da Sie jedes Mal, wenn Sie den Getter aufrufen, eine vollständige Kopie erstellen), aber wenn Sie die Schnittstelle nicht ändern können ( Listum beispielsweise eine zu verwenden) und nicht riskieren können, dass der Client Ihre Interna ändert, dann es kann notwendig sein.

Diese Technik wird als Erstellen einer defensiven Kopie bezeichnet.

Joachim Sauer
quelle
3
Wo erwähnte er, dass er einen Getter brauchte?
Erick Robertson
6
@Erik: hat er nicht, aber das ist ein sehr häufiger Anwendungsfall für unveränderliche Datenstrukturen (ich habe die Antwort dahingehend geändert, dass sie sich auf Methoden im Allgemeinen bezieht, da die Lösung überall gilt, auch wenn sie in Gettern häufiger vorkommt).
Joachim Sauer
7
Ist es besser zu benutzen clone()oder Arrays.copy()hier?
Kevinarpe
Soweit ich mich erinnere, ist laut Joshua Blochs "Effective Java" clone () definitiv der bevorzugte Weg.
Vincent
1
Letzter Satz von Punkt 13, "Klon mit Bedacht überschreiben": "In der Regel wird die Kopierfunktion am besten von Konstruktoren oder Fabriken bereitgestellt. Eine bemerkenswerte Ausnahme von dieser Regel sind Arrays, die am besten mit der Klonmethode kopiert werden."
Vincent
14

Es gibt eine Möglichkeit, ein unveränderliches Array in Java zu erstellen:

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

Arrays mit 0 Elementen können (offensichtlich) nicht mutiert werden.

Dies kann tatsächlich nützlich sein, wenn Sie die List.toArrayMethode zum Konvertieren von a Listin ein Array verwenden. Da selbst ein leeres Array Speicherplatz beansprucht, können Sie diese Speicherzuordnung speichern, indem Sie ein konstantes leeres Array erstellen und es immer an die toArrayMethode übergeben. Diese Methode weist ein neues Array zu, wenn das übergebene Array nicht über genügend Speicherplatz verfügt. Wenn dies jedoch der Fall ist (die Liste ist leer), wird das übergebene Array zurückgegeben, sodass Sie dieses Array bei jedem Aufruf toArrayeines Arrays wiederverwenden können leer 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
quelle
3
Dies hilft dem OP jedoch nicht dabei, ein unveränderliches Array mit darin enthaltenen Daten zu erreichen.
Sridhar Sarnobat
1
@ Sridhar-Sarnobat Das ist der Punkt der Antwort. In Java gibt es keine Möglichkeit, ein unveränderliches Array mit darin enthaltenen Daten zu erstellen.
Brigham
1
Diese einfachere Syntax gefällt mir besser: private static final String [] EMPTY_STRING_ARRAY = {}; (Offensichtlich habe ich nicht herausgefunden, wie man "Mini-Markdown" macht. Eine Vorschau-Schaltfläche wäre schön.)
Wes
6

Noch eine Antwort

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
quelle
Was nützt es, dieses Array im Konstruktor zu kopieren, da wir keine Setter-Methode bereitgestellt haben?
Vijaya Kumar
Ein Inhalt des Eingabearrays kann später noch geändert werden. Wir wollen seinen ursprünglichen Zustand beibehalten, also kopieren wir ihn.
Aeracode
4

Ab Java 9 können Sie verwenden List.of(...), JavaDoc .

Diese Methode gibt eine unveränderliche zurück Listund ist sehr effizient.

Müller
quelle
3

Wenn Sie (aus Leistungsgründen oder um Speicherplatz zu sparen) natives 'int' anstelle von 'java.lang.Integer' benötigen, müssen Sie wahrscheinlich Ihre eigene Wrapper-Klasse schreiben. Es gibt verschiedene IntArray-Implementierungen im Netz, aber keine (ich fand) war unveränderlich: Koders IntArray , Lucene IntArray . Es gibt wahrscheinlich andere.

Thomas Müller
quelle
3

Seit Guava 22 können Sie ab Paket com.google.common.primitivesdrei neue Klassen verwenden, die im Vergleich zu weniger Speicherplatz haben ImmutableList.

Sie haben auch einen Baumeister. Beispiel:

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

oder, wenn die Größe zur Kompilierungszeit bekannt ist:

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

Dies ist eine weitere Möglichkeit, eine unveränderliche Ansicht eines Arrays für Java-Grundelemente zu erhalten.

Jean Valjean
quelle
1

Nein das ist nicht möglich. Man könnte jedoch so etwas tun:

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);

Dies erfordert die Verwendung von Wrappern und ist eine Liste, kein Array, aber die nächstgelegene, die Sie erhalten.


quelle
4
Sie müssen nicht alle diese valueOf()s schreiben , Autoboxing wird sich darum kümmern. Wäre Arrays.asList(0, 2, 3, 4)auch viel prägnanter.
Joachim Sauer
@Joachim: Der Verwendungszweck valueOf()besteht darin, den internen Integer- Objektcache zu verwenden, um den Speicherverbrauch / das Recycling zu reduzieren.
Esko
4
@Esko: Lesen Sie die Autoboxing-Spezifikation. Es macht genau das Gleiche, also gibt es hier keinen Unterschied.
Joachim Sauer
1
@ John Du wirst niemals eine NPE bekommen, die eine intin eine Integerobwohl umwandelt ; muss nur umgekehrt vorsichtig sein.
ColinD
1
@ John: Du hast recht, es kann gefährlich sein. Aber anstatt es vollständig zu vermeiden, ist es wahrscheinlich besser, die Gefahr zu verstehen und dies zu vermeiden.
Joachim Sauer
1

In einigen Situationen ist die Verwendung dieser statischen Methode aus der Google Guava-Bibliothek leichter: List<Integer> Ints.asList(int... backingArray)

Beispiele:

  • List<Integer> x1 = Ints.asList(0, 1, 2, 3)
  • List<Integer> x1 = Ints.asList(new int[] { 0, 1, 2, 3})
Kevinarpe
quelle
1

Wenn Sie sowohl Veränderlichkeit als auch Boxen vermeiden möchten, gibt es keinen Ausweg. Sie können jedoch eine Klasse erstellen, die ein primitives Array enthält und über Methoden einen schreibgeschützten Zugriff auf Elemente bietet.

Anzeigename
quelle
1

Mit der Methode of (E ... elements) in Java9 kann eine unveränderliche Liste mit nur einer Zeile erstellt werden:

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

Die obige Methode gibt eine unveränderliche Liste zurück, die eine beliebige Anzahl von Elementen enthält. Das Hinzufügen einer Ganzzahl zu dieser Liste würde zu einer java.lang.UnsupportedOperationExceptionAusnahme führen. Diese Methode akzeptiert auch ein einzelnes Array als Argument.

String[] array = ... ;
List<String[]> list = List.<String[]>of(array);
akhil_mittal
quelle
0

Es stimmt zwar, dass dies Collections.unmodifiableList()funktioniert, aber manchmal haben Sie eine große Bibliothek, in der bereits Methoden definiert sind, um Arrays zurückzugeben (z String[]. B. ). Um zu verhindern, dass sie beschädigt werden, können Sie Hilfsarrays definieren, in denen die Werte gespeichert werden:

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

Zu testen:

    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()));

Nicht perfekt, aber zumindest haben Sie "pseudo-unveränderliche Arrays" (aus der Klassenperspektive) und dies wird den zugehörigen Code nicht beschädigen.

Miguelt
quelle
-3

Nun ... Arrays sind nützlich, um sie als Konstanten (falls vorhanden) als Variantenparameter zu übergeben.

Martin
quelle
Was ist ein "Variantenparameter"? Noch nie davon gehört.
Sleske
2
Die Verwendung von Arrays als Konstanten ist genau dort , wo viele Menschen scheitern. Die Referenz ist konstant, aber der Inhalt des Arrays ist veränderbar. Ein Anrufer / Client könnte den Inhalt Ihrer "Konstante" ändern. Dies möchten Sie wahrscheinlich nicht zulassen. Verwenden Sie eine ImmutableList.
Charlie Collins
@CharlieCollins auch dies kann durch Reflexion gehackt werden. Java ist eine unsichere Sprache in Bezug auf Veränderlichkeit ... und das wird sich nicht ändern.
Anzeigename
2
@SargeBorsch Das stimmt, aber jeder, der reflexionsbasiertes Hacken durchführt, sollte wissen, dass er mit dem Feuer spielt, indem er Dinge ändert, auf die der API-Publisher möglicherweise nicht wollte, dass sie zugreifen. Wenn eine API eine zurückgibt int[], kann der Aufrufer davon ausgehen, dass er mit diesem Array das tun kann, was er will, ohne die Interna der API zu beeinflussen.
Daiscog