Immutable Array in Java

Immutable array in Java

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

How to make elements of array immutable?

Check out this answer. TL:DR: you need to use other data structure to get an immutable array.

Is there any way to make an ordinary array immutable in Java?

If you want to use it as an array, you can't.

You have to create a wrapper for it, so that you throw an exception on, say, .set(), but no amount of wrapping around will allow you to throw an exception on:

array[0] = somethingElse;

Of course, immutability of elements is another matter entirely!

NOTE: the standard exception to throw for unsupported operations is aptly named UnsupportedOperationException; as it is unchecked you don't need to declare it in your method's throws clause.

Why is an array's length immutable?

The essence of this problem lies in how language designers chose to partition up the tasks that a language needs to perform, and assign those to different constructs.

In Java, the standard array type has a fixed length, because it's intended to be a low-level, bare-bones implementation, that the developer can build on top of.

In contrast, if you want a more fully featured array, check out the ArrayList<> class. The idea is to give you, the developer, a wide range of choices for your tools, so you can always pick something appropriate for the job.

When talking about arrays, the machinery necessary to allow for variable-length arrays is nontrivial. So the creators of Java chose to put this functionality in the ArrayList<> class, rather than making it mandatory overhead for anyone who just wants a simple array.

Why is lengthening an array nontrivial? Computer memory is finite, so we have to store objects next to each other. When you want to lengthen your array, what if there's already another object in the nearby memory addresses?

In this case, there isn't enough space to lengthen the array in-place, so one has to move data around to make room. Usually this is done by finding an larger, empty block of memory, and copying the array over while lengthening it. The mathematics of how to do this in the best way possible (e.g. how much empty space to leave?) are interesting and nontrivial. ArrayList<> abstracts this process for you, so you don't have to handle it yourself, and you can be confident that the designers of the ArrayList<> class chose an implementation that will perform well (on average) for your application.

Why won't declaring an array final make it immutable in Java?

final is only about the reference that is marked by it; there is no such thing as an immutable array in Java. When you say

private final int[] xs = new int[20];

you won't be allowed to say

xs = new int[10];

later on. That's all what final is about. More generally, ensuring that an object is immutable is often hard work and full of a range of subtleties. The language itself doesn't give much to this end out of the box.

Defining my own immutable array in Java

You are reinventing the wheel.
You can have the same result using Collections#unmodifiableList(..).

Collections.unmodifiableList(Arrays.asList(yourArray)); 

Java & Functional Programming Paradigm - How do I make my arrays immutable

  1. You can't mutate the underlying array (or the list returned by Arrays.asList) except by reflection. However, arrays aren't really relevant here; don't confuse arrays and lists!

  2. Since Java 9, List.of(0, 1, 2, 3) achieves the same.

  3. To do operations which produce new collections, you'll have to go through streams. You may also want to consider third-party libraries, e.g. Guava (this link is to their immutable collections page). Or even better PCollections, which provides only immutable collections and more functional implementations of them than Guava does.

How do I create and initialize an immutable array?

Here's the macro definition with sample usage:

macro_rules! array_factory(
($size: expr, $factory: expr) => ({
unsafe fn get_item_ptr<T>(slice: *mut [T], index: usize) -> *mut T {
(slice as *mut T).offset(index as isize)
}

let mut arr = ::std::mem::MaybeUninit::<[_; $size]>::uninit();
unsafe {
for i in 0..$size {
::std::ptr::write(get_item_ptr(arr.as_mut_ptr(), i), $factory(i));
}
arr.assume_init()
}
});
);

fn some_function(i: usize) -> f32 {
i as f32 * 3.125
}

fn main() {
let my_array: [f32; 4] = array_factory!(4, some_function);
println!("{} {} {} {}", my_array[0], my_array[1], my_array[2], my_array[3]);
}

The macro's body is essentially your first version, but with a few changes:

  • The type annotation on the array variable is omitted, because it can be inferred.
  • The array is created uninitialized, because we're going to overwrite all values immediately anyway. Messing with uninitialized memory is unsafe, so we must operate on it from within an unsafe block. Here, we're using MaybeUninit, which was introduced in Rust 1.36 to replace mem::uninitialized1.
  • Items are assigned using std::ptr::write() due to the fact that the array is uninitialized. Assignment would try to drop an uninitialized value in the array; the effects depend on the array item type (for types that implement Copy, like f32, it has no effect; for other types, it could crash).
  • The macro body is a block expression (i.e. it's wrapped in braces), and that block ends with an expression that is not followed by a semicolon, arr.assume_init(). The result of that block expression is therefore arr.assume_init().

Instead of using unsafe features, we can make a safe version of this macro; however, it requires that the array item type implements the Default trait. Note that we must use normal assignment here, to ensure that the default values in the array are properly dropped.

macro_rules! array_factory(
($size: expr, $factory: expr) => ({
let mut arr = [::std::default::Default::default(), ..$size];
for i in 0..$size {
arr[i] = $factory(i);
}
arr
});
)

1 And for a good reason. The previous version of this answer, which used mem::uninitialized, was not memory-safe: if a panic occurred while initializing the array (because the factory function panicked), and the array's item type had a destructor, the compiler would insert code to call the destructor on every item in the array; even the items that were not initialized yet! MaybeUninit avoids this problem because it wraps the value being initialized in ManuallyDrop, which is a magic type in Rust that prevents the destructor from running automatically.



Related Topics



Leave a reply



Submit