176

Is there an immutable alternative to the primitive arrays in Java? Making a primitive array final doesn't actually prevent one from doing something like

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

I want the elements of the array to be unchangeable.

  • 50
    Do yourself a favor and stop using arrays in Java for anything besides 1) io 2) heavy number crunching 3) if you need to implement your own List/Collection (which is *rare*). They are extremely inflexible and antiquated... as you have just discovered with this question. – whaley Sep 13 '10 at 14:25
  • 46
    @Whaley, and performances, and code where you don't need "dynamic" arrays. Arrays are still useful in lot of places, it's not that rare. – Colin Hebert Sep 13 '10 at 14:29
  • 10
    @Colin: yes but they're severely constraining; it's best to get into the habit of thinking "do I really need an array here or can I use a List instead?" – Jason S Sep 13 '10 at 15:18
  • 7
    @Colin: Only optimize when you need to. When you find yourself spending half a minute adding something that, for example, enlarges an array or you keep some index outside of the scope of a for-loop, you've already wasted some of your boss' time. Build first, optimize when and where it's needed - and in most applications, that's not in replacing Lists with arrays. – cthulhu Sep 13 '10 at 19:11
  • 4
    @Colin, performance-wise, an `ArrayList` is on par with arrays (since it is implemented internally on top of an array). However, it is generic, thus it is safer to use and more flexible. In most places where an array would be "useful", a collection would be equally useful or better. – Péter Török Sep 14 '10 at 07:54
  • 11
    Well, why nobody mentioned `int[]` and `new int[]` is MUCH easier to type than `List` and `new ArrayList`? XD – lcn Sep 20 '13 at 05:21
  • @lcn In java 7+, you can use `new ArrayList<>` – rpax Apr 15 '15 at 16:50
  • 6
    @Icn because easy-to-type is the last concern on the list. – ash Oct 01 '15 at 17:03
  • 2
    List are convinient, but I do need array to create a `JTable`, or something else. At last data in ArrayList are processed in an array-like way to gain performance advantages. So when you are doing simple tasks, why not array? – WesternGun Aug 24 '16 at 08:24
  • Java 9 - https://stackoverflow.com/a/47817350/1216775 – akhil_mittal Oct 08 '18 at 05:23
  • Here's an alternative that doesn't make a real array. I don't know whether it would ever be the best choice in practice. But it's certainly possible: You could make int data immutable by packing it into a String. Java 9 or later will store String data as one byte per character if no characters have a code point greater than 0xFF. It is efficient in time and space to pack each int into four single-byte characters. Simply choose whether to use little-endian or big-endian byte order. Previous to Java 9, you may have considered using Base16k. – Aaron Mansheim Sep 03 '19 at 15:37

15 Answers15

188

Not with primitive arrays. You'll need to use a List or some other data structure:

List<Integer> items = Collections.unmodifiableList(Arrays.asList(0,1,2,3));
Jason S
  • 184,598
  • 164
  • 608
  • 970
  • 21
    Somehow I never knew about Arrays.asList(T ...). I guess I can get rid of my ListUtils.list(T ...) now. – MattRS Feb 09 '11 at 21:23
  • 3
    By the way, Arrays.asList gives an unmodifiable list – mauhiz Oct 26 '15 at 14:42
  • 2
    @mauhiz, no, not according to the source, Arrays.asList(array) returns a new ArrayList<>(array), which has perfectly valid set methods... – Tony BenBrahim Nov 02 '15 at 22:39
  • 4
    @tony that ArrayList is not java.util's – mauhiz Nov 02 '15 at 22:48
  • 16
    @mauhiz `Arrays.asList` is *not* unmodifiable. http://docs.oracle.com/javase/7/docs/api/java/util/Arrays.html#asList(T...) "Returns a fixed-size list backed by the specified array. (Changes to the returned list "write through" to the array.)" – Jason S Nov 03 '15 at 03:39
  • @Jason wrapping it with Collections.unmodifiableList does not help – mauhiz Nov 03 '15 at 03:41
  • @JasonS Both Arrays.asList and Collections.unmodifiableList are wrappers which hide/disable mutators, but nothing prevents you from modifying the underlying data structure. See https://repl.it/BWgc – mauhiz Nov 04 '15 at 00:55
  • 1
    @mauhiz, no it is exactly that, in JDK 7 and 8, I am not sure what JDK source you are looking at. Also the Javadoc for Arrays.asList specifically mention modifying the returned list, so it is definitely NOT immutable. – Tony BenBrahim Nov 04 '15 at 03:22
  • 1
    Perhaps mauhiz is trolling us. `Arrays.asList()` returns a modifiable list, `Collections.unmodifiableList()` returns a wrapped list that is unmodifiable via the wrapper, so if you don't let the reference escape some other way, then it's unmodifiable. – Jason S Nov 05 '15 at 05:04
  • 10
    @JasonS well, `Arrays.asList()` indeed _seems_ unmodifiable in the sense that you can't `add` or `remove` items of the returned `java.util.Arrays.ArrayList`(**not to be confused** with `java.util.ArrayList`) - these operations just are not implemented. Maybe @mauhiz tries to say this? But of course you can modify existing elements with `List<> aslist = Arrays.asList(...); aslist.set(index, element)`, so `java.util.Arrays.ArrayList` certainly is **not unmofiable**, Q.E.D. Added the comment just for the sake of emphasizing the difference between the result of `asList` and normal `ArrayList` – NIA Feb 16 '16 at 13:39
79

My recommendation is to not use an array or an unmodifiableList but to use Guava's ImmutableList, which exists for this purpose.

ImmutableList<Integer> values = ImmutableList.of(0, 1, 2, 3);
Jon Ball
  • 3,032
  • 3
  • 23
  • 24
ColinD
  • 108,630
  • 30
  • 201
  • 202
  • 3
    +1 Guava's ImmutableList is even better than Collections.unmodifiableList ,because it is a separate type. – sleske Feb 18 '11 at 10:35
  • 33
    ImmutableList is *often* better (it depends on the use case) because it's immutable. Collections.unmodifiableList is not immutable. Rather, it's a view that receivers can't change, but the original source CAN change. – Charlie Collins Mar 06 '11 at 16:31
  • 3
    @CharlieCollins, if there is no way to access the original source than is `Collections.unmodifiableList` sufficient to make a List immutable? – savanibharat Apr 29 '16 at 19:51
  • 1
    @savanibharat Yes. – Just a student Oct 27 '17 at 07:35
25

As others have noted, you can't have immutable arrays in Java.

If you absolutely need a method that returns an array that doesn't influence the original array, then you'd need to clone the array each time:

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

Obviously this is rather expensive (as you'll create a full copy each time you call the getter), but if you can't change the interface (to use a List for example) and can't risk the client changing your internals, then it may be necessary.

This technique is called making a defensive copy.

Joachim Sauer
  • 302,674
  • 57
  • 556
  • 614
  • 3
    Where did he mention that he needed a getter? – Erick Robertson Sep 13 '10 at 14:22
  • 9
    @Erik: he didn't, but that's a very common use case for immutable data structures (I modified the answer to refer to methods in general, as the solution applies everywhere, even if it's more common in getters). – Joachim Sauer Sep 13 '10 at 14:53
  • 8
    Is it better to use `clone()` or `Arrays.copy()` here? – kevinarpe Jul 16 '13 at 12:44
  • 1
    As far as I remember, according to Joshua Bloch's "Effective Java", clone() is definitely the preferred way. – Vincent Aug 08 '18 at 08:01
  • 6
    Last sentence of Item 13, "Override clone judiciously": "As a rule, copy functionality is best provided by constructors or factories. A notable exception to this rule is arrays, which are best copied with the clone method." – Vincent Aug 08 '18 at 08:08
15

There is one way to make an immutable array in Java:

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

Arrays with 0 elements (obviously) cannot be mutated.

This can actually come in handy if you are using the List.toArray method to convert a List to an array. Since even an empty array takes up some memory, you can save that memory allocation by creating a constant empty array, and always passing it to the toArray method. That method will allocate a new array if the array you pass doesn't have enough space, but if it does (the list is empty), it will return the array you passed, allowing you to reuse that array any time you call toArray on an empty 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
  • 14,395
  • 3
  • 38
  • 48
  • 4
    But this doesn't help the OP achieve an immutable array with data in it. – Sridhar Sarnobat Jul 22 '16 at 21:11
  • 2
    @Sridhar-Sarnobat That's kind of the point of the answer. There is no way in Java to make an immutable array with data in it. – Brigham Jul 26 '16 at 17:30
  • 1
    I like this simpler syntax better: private static final String[] EMPTY_STRING_ARRAY = {}; (Obviously I haven't figured out how to do "mini-Markdown". A preview button would be nice.) – Wes Jan 27 '17 at 18:39
11

As of Java 9 you can use List.of(...), JavaDoc.

This method returns an immutable List and is very efficient.

rmuller
  • 12,062
  • 4
  • 64
  • 92
  • Actually, no, this [does not work](https://ideone.com/mCI17x). Passing in `int[]` to `List.of( array )` throws `error: incompatible types: inference variable E has incompatible bounds`. Auto-boxing cannot convert `int[]` to `Integer[]`. See [this Question](https://stackoverflow.com/q/17520964/642706) and [this Answer](https://stackoverflow.com/a/17522023/642706). – Basil Bourque Oct 23 '22 at 21:54
7

Another one answer

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"});
}
æ-ra-code
  • 2,140
  • 30
  • 28
5

Since Guava 22, from package com.google.common.primitives you can use three new classes, which have a lower memory footprint compared to ImmutableList.

They also have a builder. Example:

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

or, if the size is known at compile-time:

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

This is another way of getting an immutable view of an array for Java primitives.

yurez
  • 2,826
  • 1
  • 28
  • 22
JeanValjean
  • 17,172
  • 23
  • 113
  • 157
4

If you need (for performance reason or to save memory) native 'int' instead of 'java.lang.Integer', then you would probably need to write your own wrapper class. There are various IntArray implementations on the net, but none (I found) was immutable: Koders IntArray, Lucene IntArray. There are probably others.

Thomas Mueller
  • 48,905
  • 14
  • 116
  • 132
2

The of(E... elements) method in Java9 can be used to create immutable list using just a line:

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

The above method returns an immutable list containing an arbitrary number of elements. And adding any integer to this list would result in java.lang.UnsupportedOperationExceptionexception. This method also accepts a single array as an argument.

String[] array = ... ;
List<String[]> list = List.<String[]>of(array);
akhil_mittal
  • 23,309
  • 7
  • 96
  • 95
1

In some situations, it will be lighter weight to use this static method from Google Guava library: List<Integer> Ints.asList(int... backingArray)

Examples:

  • List<Integer> x1 = Ints.asList(0, 1, 2, 3)
  • List<Integer> x1 = Ints.asList(new int[] { 0, 1, 2, 3})
kevinarpe
  • 20,319
  • 26
  • 127
  • 154
1

If you want to avoid both mutability and boxing, there is no way out of the box. But you can create a class which holds primitive array inside and provides read-only access to elements via method(s).

Display Name
  • 8,022
  • 3
  • 31
  • 66
1

No, this is not possible. However, one could do something like this:

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

This requires using wrappers, and is a List, not an array, but is the closest you will get.

  • 6
    No need to write all those `valueOf()`s, autoboxing will take care of that. Also `Arrays.asList(0, 2, 3, 4)` would be much more concise. – Joachim Sauer Sep 13 '10 at 13:50
  • @Joachim: Point of using `valueOf()` is to utilize the internal Integer object cache to reduce memory consumption/recycling. – Esko Sep 13 '10 at 13:55
  • 4
    @Esko: read the autoboxing spec. It does exactly the same thing, so there's no difference here. – Joachim Sauer Sep 13 '10 at 13:59
  • Sorry, it's just a habit. I've been burned by autoboxing several times due to NPEs caused by poorly designed interfaces. So I tend to avoid using it. –  Sep 13 '10 at 14:04
  • 1
    @John You'll never get a NPE converting an `int` to an `Integer` though; just have to be careful the other way around. – ColinD Sep 13 '10 at 14:34
  • 1
    @John: you're right, it can be dangerous. But instead of avoiding it completely, it's probably better to understand the danger and avoid that. – Joachim Sauer Sep 13 '10 at 14:58
  • @Esko: The Integer cache only works with figures -127 to +127 - see also the source code (http://www.java2s.com/Open-Source/Java-Document/6.0-JDK-Core/lang/java/lang/Integer.java.htm). I'd assume autoboxing is optimized enough through the compiler or JIT anyways, and doubt your approach vs regular autoboxing would be significantly faster. – cthulhu Sep 13 '10 at 19:16
1

Implement java.util.function.IntUnaryOperator:

class ImmutableArray implements IntUnaryOperator {
  private final int[] array;
  ImmutableArray(int[] array) {
     this.array = Arrays.copyOf(array, array.length);
  }
  @Override
    public int applyAsInt(int index) {
    return array[index];
  }
}

Access the array: array[i] becomes immutableArray.applyAsInt(i).

  • I benchmarked primitive for loop retrieval with a modulus operation with 100_000_000 elements. The above PrimitiveArray took ~220ms; there was no significant difference with a primitive array. The same op on ArrayList took 480 ms, and the loading process took 21 seconds, depleted my heap space first try, and I had to increase this setting on the JVM. Loading of PrimitiveArray had taken 2 seconds.

iteration

  • if you want to iterate, implement Iterable and provide

    public java.util.PrimitiveIterator.OfInt iterator() { return Arrays.stream(array).iterator(); }

    This provides access to int nextInt method.

  • From PrimitiveIterator you also get method forEachRemaining(PrimitiveConsumer) which is helpful to replace an existing enhanced for loop.

  • Iterating manually with PrimitiveIterator.OfInt yielded performance of ~300ms.

John
  • 741
  • 9
  • 18
-1

While it's true that Collections.unmodifiableList() works, sometimes you may have a large library having methods already defined to return arrays (e.g. String[]). To prevent breaking them, you can actually define auxiliary arrays that will store the values:

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

To test:

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

Not perfect, but at least you have "pseudo immutable arrays" (from the class perspective) and this will not break related code.

miguelt
  • 384
  • 4
  • 8
-2

Well.. arrays are useful to pass as constants (if they were) as variants parameters.

Martin
  • 30
  • 4
  • What's a "variants parameter"? Never heard of it. – sleske Feb 18 '11 at 10:36
  • 2
    Using arrays as constants is *exactly* where many people FAIL. The reference is constant, but the contents of the array are mutable. One caller/client could change the contents of your "constant." This is probably not something you want to allow. Use an ImmutableList. – Charlie Collins Mar 06 '11 at 16:34
  • @CharlieCollins even this can be hacked via reflection. Java is unsafe language in terms of mutability... and this isn't going to change. – Display Name Jan 19 '14 at 04:42
  • 2
    @SargeBorsch That's true, but anyone doing reflection-based hacking ought to know they are playing with fire by modifying things that the API publisher might not have wanted them to access. Whereas, if an API returns an `int[]`, the caller might well assume they can do what they want with that array without affecting the internals of the API. – daiscog Oct 11 '16 at 14:59