Java中的不可变数组


158

有没有Java原始数组的不变选择?制作一个原始数组final实际上并不能阻止人们做类似的事情

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

我希望数组的元素不可更改。


43
帮自己一个忙,如果需要实现自己的列表/集合(很少见),请停止使用Java中除1)io 2)大量运算3)之外的数组。它们极其僵化和过时...正如您刚刚发现的这个问题。
whaley 2010年

39
@Whaley,性能和代码,不需要“动态”数组。数组在很多地方仍然有用,这并不罕见。
科林·赫伯特

10
@Colin:是的,但是他们受到严重限制;最好养成这样的习惯:“我真的在这里需要数组还是可以使用列表?”
杰森S

7
@Colin:仅在需要时进行优化。当您发现自己花了半分钟添加一些东西(例如,扩大数组)或将某些索引保留在for循环范围之外时,您已经在浪费您的上司时间。首先构建,优化所需的时间和位置-在大多数应用程序中,这并不是用数组替换列表。
fwielstra 2010年

9
好吧,为什么没有人提及int[]new int[]并且Much比List<Integer>and 更容易键入new ArrayList<Integer>?XD
lcn

Answers:


164

不适用于原始数组。您将需要使用列表或其他数据结构:

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

18
我以某种方式不知道Arrays.asList(T ...)。我想我现在可以摆脱ListUtils.list(T ...)。
MattRS 2011年

3
顺便说一句,Arrays.asList给出了一个不可修改的列表
mauhiz

3
@tony ArrayList不是java.util的
mauhiz

11
@mauhiz Arrays.asList不是不可改变的。docs.oracle.com/javase/7/docs/api/java/util/… “返回由指定数组支持的固定大小的列表。(更改为返回列表,将其“ 写”到该数组。)”
Jason小号

7
@JasonS很好,Arrays.asList()的确似乎在这个意义上不可修改的,你不能addremove返回的项目java.util.Arrays.ArrayList不混淆java.util.ArrayList) -这些操作就是不执行。也许@mauhiz试图这样说?但是,您当然可以使用来修改现有元素List<> aslist = Arrays.asList(...); aslist.set(index, element),因此java.util.Arrays.ArrayList当然不是无法修改的。QED添加该注释只是为了强调结果asList与正常结果之间的差异ArrayList
NIA 2016年

73

我的建议是不要使用数组或an,unmodifiableList而要使用为此目的而存在的GuavaImmutableList

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

3
+1番石榴的ImmutableList甚至比Collections.unmodifiableList更好,因为它是一个单独的类型。
sleske 2011年

29
因为ImmutableList是不可变的,所以它通常更好(取决于用例)。Collections.unmodifiableList不是不可变的。相反,这是一种观点,即接收者无法更改,但是原始源可以更改。
查理·柯林斯

2
@CharlieCollins,如果没有任何方法可以访问原始源,Collections.unmodifiableList不足以使List不可变?
savanibharat's

1
@savanibharat是的。
只是一名学生,

20

正如其他人指出的那样,Java中不能有不可变的数组。

如果您绝对需要一个返回不影响原始数组的数组的方法,则您需要每次都克隆该数组:

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

显然,这是相当昂贵的(因为每次调用getter时都会创建一个完整副本),但是如果您不能更改接口(List例如使用a )并且不能冒险让客户更改内部结构,那么可能有必要。

这种技术称为制作防御性副本。


3
他在哪里提到他需要吸气剂?
埃里克·罗伯逊

6
@Erik:他没有,但是这是不可变数据结构的一个非常普遍的用例(我修改了答案以引用一般的方法,因为该解决方案适用于所有地方,即使在吸气剂中更常见)。
Joachim Sauer

7
使用clone()还是Arrays.copy()在这里更好?
kevinarpe13年

据我所记得,根据约书亚·布洛赫(Joshua Bloch)的“有效Java”的观点,clone()绝对是首选方法。
文森特

1
项目13的最后一句话,“明智地覆盖克隆”:“通常,复制功能最好由构造函数或工厂提供。此规则的显着例外是数组,最好使用clone方法复制。
文森特

14

有一种方法可以在Java中创建不可变数组:

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

具有0个元素的数组(显然)不能被突变。

如果您正在使用List.toArray方法将a转换List为数组,则实际上可以派上用场。由于即使是空数组也会占用一些内存,因此您可以通过创建一个恒定的空数组并将其始终传递给该toArray方法来保存该内存分配。该方法将分配一个新的数组,如果你传递数组没有足够的空间,但如果它(列表为空),它会回报你传递的数组,让您重新使用数组调用任何时间toArray上的空的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

3
但这无助于OP实现其中包含数据的不可变数组。
Sridhar Sarnobat '16

1
@ Sridhar-Sarnobat这就是答案的重点。在Java中,无法通过其中的数据创建不可变的数组。
百翰

1
我更喜欢这种简单的语法:private static final String [] EMPTY_STRING_ARRAY = {}; (显然,我还没有弄清楚如何进行“迷你降价”。预览按钮会很好。)
Wes

6

另一个答案

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

由于我们未提供任何setter方法,因此在构造函数中复制该数组有什么用?
vijaya kumar

输入数组的内容以后仍可以修改。我们想要保留其原始状态,因此我们将其复制。
aeracode

4

从Java 9,你可以用List.of(...)的JavaDoc

此方法返回一个不变的List且非常有效的方法。


3

如果您需要(出于性能原因或为了节省内存)本机“ int”而不是“ java.lang.Integer”,则可能需要编写自己的包装器类。网上有各种各样的IntArray实现,但是(我发现)没有一成不变的:Koders IntArrayLucene IntArray。可能还有其他人。


3

从Guava 22开始,com.google.common.primitives您可以从软件包中使用三个新类,与相比,它们具有更低的内存占用量ImmutableList

他们也有一个建设者。例:

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

或者,如果在编译时知道大小,则:

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

这是获取Java原语数组不变视图的另一种方法。


1

不,这是不可能的。但是,可以执行以下操作:

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

这需要使用包装器,并且是列表,而不是数组,但是它是您将得到的最接近的列表。


4
无需编写所有这些valueOf(),自动装箱即可解决。也Arrays.asList(0, 2, 3, 4)将更加简洁。
Joachim Sauer 2010年

@Joachim:使用的要点valueOf()是利用内部Integer对象缓存来减少内存消耗/回收。
Esko 2010年

4
@Esko:阅读自动装箱规格。它的作用完全相同,因此这里没有区别。
Joachim Sauer 2010年

1
@John你永远不会得到一个NPE转换成if的int方法Integer。只是必须要反过来小心。
ColinD 2010年

1
@约翰:你是对的,这很危险。但是与其完全避免它,不如更好地了解这种危险并避免这种情况,可能更好。
Joachim Sauer 2010年

1

在某些情况下,使用Google Guava库中的此静态方法会减轻重量: List<Integer> Ints.asList(int... backingArray)

例子:

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

1

如果要避免变异和装箱,则没有开箱即用的方法。但是,您可以创建一个在其中保存基本数组并通过方法提供对元素的只读访问的类。


1

Java9中of(E ... elements)方法可用于仅使用一行创建不可变列表:

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

上面的方法返回一个包含任意数量元素的不可变列表。并且将任何整数添加到此列表将导致java.lang.UnsupportedOperationException异常。此方法还接受单个数组作为参数。

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

0

尽管确实Collections.unmodifiableList()可以使用,但有时您可能拥有一个大型库,其中包含已经定义了用于返回数组的方法(例如String[])。为了防止破坏它们,您实际上可以定义将存储值的辅助数组:

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

去测试:

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

这并不完美,但是至少您拥有“伪不可变数组”(从类的角度来看),并且这不会破坏相关代码。


-3

那么..数组对于作为常量(如果是)作为变量参数传递很有用。


什么是“变体参数”?从来没有听说过。
sleske 2011年

2
使用数组作为常量正是很多人失败的地方。引用是常量,但是数组的内容是可变的。一个呼叫者/客户可以更改您的“常量”的内容。这可能不是您要允许的。使用ImmutableList。
查理·柯林斯

@CharlieCollins甚至可以通过反射来破解。就可变性而言,Java是不安全的语言……并且这不会改变。
显示名称

2
@SargeBorsch是的,但是任何进行基于反射的黑客都应该通过修改API发布者可能不希望他们访问的内容来了解​​自己正在玩火。而如果API返回int[],则调用者很可能会假设他们可以使用该数组执行所需的操作而不会影响API的内部。
daiscog '16
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.