Javaの不変配列


158

Javaのプリミティブ配列の不変の代替はありますか?プリミティブ配列を作成finalしても、実際に次のようなことを行うことは妨げられません

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

配列の要素を変更できないようにしたい。


43
1)io 2)大量の処理3)独自のリスト/コレクションを実装する必要がある場合(まれです)以外の場合は、Javaで配列を使用しないでください。あなたはこの質問で発見したように、彼らは非常に柔軟性がなく時代遅れです...
クジラ

39
@Whaley、パフォーマンス、および「動的」配列が不要なコード。配列は依然として多くの場所で役立ちますが、それほど珍しいことではありません。
Colin Hebert

10
@Colin:はい、しかし彼らは厳しく制約しています。「ここに本当に配列が必要なのか、代わりにリストを使用できるのか」と考える習慣をつけるのが最善です。
Jason S

7
@Colin:必要なときにのみ最適化します。たとえば、配列を拡大する何かを追加したり、forループのスコープ外にインデックスを保持したりするために、30分を費やしていることに気付いた場合、すでに上司の時間を浪費しています。最初に作成し、必要なときに必要な場所で最適化します。ほとんどのアプリケーションでは、リストを配列に置き換えるのではありません。
fwielstra

9
さて、なぜ誰も言及していないint[]new int[]比べて入力する方がはるかに簡単ですList<Integer>new ArrayList<Integer>?XD
lcn

回答:


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 S

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

73

私の推奨は、配列またはを使用するのではなく、この目的のために存在するGuavaImmutableListunmodifiableListを使用することです。

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

3
+1グアバのImmutableListは、別の型であるため、Collections.unmodifiableListよりも優れています。
sleske、2011

29
ImmutableListは不変であるため、多くの場合(ユースケースによって異なります)の方優れています。Collections.unmodifiableListは不変ではありません。むしろ、それはレシーバーは変更できないが、元のソースは変更できるという見方です。
チャーリーコリンズ

2
@CharlieCollins、元のソースにアクセスする方法がなくCollections.unmodifiableList、リストを不変にするのに十分ですか?
savanibharat

1
@savanibharatはい。
ちょうど学生

20

他の人が指摘したように、Javaで不変の配列を持つことはできません。

元の配列に影響を与えない配列を返すメソッドが絶対に必要な場合は、毎回配列を複製する必要があります。

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

明らかにこれはかなり高価です(ゲッターを呼び出すたびに完全なコピーを作成するため)。ただし、インターフェースを変更できず(Listたとえばを使用するため)、クライアントが内部を変更する危険を冒すことができない場合は、必要な場合があります。

この手法は、防御コピーの作成と呼ばれます。


3
彼はどこでゲッターが必要だと言ったのですか?
エリックロバートソン

6
@エリック:彼はそうしませんでしたが、それは不変のデータ構造の非常に一般的なユースケースです(解決策はゲッターでより一般的であるとしても、ソリューションがどこにでも適用されるため、メソッドを一般的に参照するように答えを変更しました)。
Joachim Sauer

7
使用する方が良いですかclone()Arrays.copy()ここですか?
kevinarpe 2013

私が覚えている限りでは、Joshua Blochの「Effective Java」によると、clone()が間違いなく推奨される方法です。
Vincent

1
項目13の最後の文、「クローンを慎重にオーバーライドする」:「原則として、コピー機能はコンストラクターまたはファクトリーによって提供されるのが最適です。このルールの注目すべき例外は配列であり、クローンメソッドでコピーするのが最適です。」
Vincent

14

Javaで不変の配列を作成する方法が1つあります。

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

(明らかに)要素が0の配列は変更できません。

これは、List.toArrayメソッドを使用して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

1
@ Sridhar-Sarnobatそれが答えのポイントのようなものです。Javaでは、データを含む不変の配列を作成する方法はありません。
ブリガム

1
私はこの単純な構文の方が好きです:private static final String [] EMPTY_STRING_ARRAY = {}; (明らかに、私は "mini-Markdown"の実行方法を理解していません。プレビューボタンがいいでしょう。)
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 2017年

入力配列の内容は引き続き後で変更できます。元の状態を保持したいので、コピーします。
aeracode 2017年

4

Java 9以降ではList.of(...)JavaDocを使用できます。

このメソッドは不変Listを返し、非常に効率的です。


3

(パフォーマンス上の理由から、またはメモリを節約するために)「java.lang.Integer」ではなくネイティブの「int」が必要な場合は、おそらく独自のラッパークラスを作成する必要があります。ネット上にはさまざまなIntArray実装がありますが、不変なものはありません(私が見つけたもの):Koders IntArrayLucene IntArray。おそらく他にもあります。


3

Guava 22以降では、パッケージからcom.google.common.primitives3つの新しいクラスを使用できます。これらは、と比較してメモリフットプリントが低くなっています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

@Joachim:使用のポイントはvalueOf()、内部のIntegerオブジェクトキャッシュを利用してメモリの消費/リサイクルを削減することです。
Esko 2013

4
@Esko:オートボクシングの仕様を読んでください。まったく同じことを行うので、ここで違いはありません。
ヨアヒムザウアー

1
あなたが変換NPE得ることは決してないだろう@ジョンintIntegerかかわらを。逆に注意する必要があります。
ColinD

1
@ジョン:あなたが正しい、それは危険なことができます。しかし、完全に回避するのではなく、危険を理解して回避する方がおそらく良いでしょう。
Joachim Sauer

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

Java9of(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

2
定数として配列を使用することで、正確に多くの人が失敗するところ。参照は定数ですが、配列の内容は変更可能です。1つの呼び出し元/クライアントが「定数」の内容を変更する可能性があります。これはおそらく許可したくないものです。ImmutableListを使用します。
チャーリーコリンズ

@CharlieCollinsでもこれはリフレクションを介してハッキングされる可能性があります。可変性の点でJavaは安全ではない言語です...そしてこれは変更されません。
表示名

2
@SargeBorsch確かにそうですが、リフレクションベースのハッキングを行う人は誰でも、APIパブリッシャーがアクセスしてほしくないものを変更することによって、火を使って遊んでいることを知る必要があります。一方、APIがを返す場合、int[]呼び出し元は、APIの内部に影響を与えることなく、その配列を使用して必要なことを実行できると想定する場合があります。
daiscog 2016年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.