2009-06-11 10 views
5

私は内部的にリストを格納するJavaでクラスを実装しました。私はクラスを不変にしたい。しかし、クラスの文脈で意味をなさない内部データに対する操作を実行する必要があります。したがって、私はアルゴリズムのセットを定義する別のクラスを持っています。ここでは単純化した例です。今、私の質問は、変更されるのを防ぐための信頼できる方法がありますされJavaの不変オブジェクトとデータへのアクセス

Wrapper.java

import java.util.List; 
import java.util.Iterator; 

public class Wrapper implements Iterable<Double> 
{ 
    private final List<Double> list; 

    public Wrapper(List<Double> list) 
    { 
     this.list = list; 
    } 

    public Iterator<Double> iterator() 
    { 
     return getList().iterator(); 
    } 

    public List<Double> data() { return getList(); } 
} 

Algorithm.java

import java.util.Iterator; 
import java.util.Collection; 

public class Algorithm 
{ 
    public static double sum(Collection<Double> collection) 
    { 
     double sum = 0.0; 
     Iterator<Double> iterator = collection.iterator(); 

     // Throws NoSuchElementException if the Collection contains no elements 
     do 
     { 
      sum += iterator.next(); 
     } 
     while(iterator.hasNext()); 

     return sum; 
    } 
} 

私のクラスは不変であるという事実にもかかわらず、私の内部データですか?読み取り専用のためにdata()メソッドを提供していますが、などのメソッドを使用してデータを変更しないようにする方法はありません。clear()およびremove()今、私はイテレータを介して自分のデータへのアクセスを排他的に提供できることを認識しています。しかし、私はコレクションを渡すのが典型的であると言われています。第二に、データを複数回通過させるアルゴリズムがあれば、滑らかな斜面のように見える複数のイテレータを用意しなければならなくなります。

さて、うまくいけば私の懸念を解決する簡単な解決策があります。私はちょうどJavaに戻っており、のconstをC++で扱う前に、これらのことを考えたことはありません。前もって感謝します!

ああ!私が考えたことがもう一つはあります。私は実際に内部リストのコピーを返すことはできません。 リストには、通常数十万の要素が含まれます。

答えて

23

Collections.unmodifiableListを使用してデータメソッドを変更できます。

指定されたリストの変更不可能なビューを返します

:javadocツールから

public List<Double> data() { return Collections.unmodifiableList(getList()); } 

。この方法により、 モジュールは、 の内部リストへの「読み取り専用」アクセスをユーザーに提供できます。返されたリストに クエリー操作、直接かその 反復子を介して、指定されたリストに 返さリストを修正する試みを「リードスルー」 にUnsupportedOperationExceptionをもたらします。

+0

完璧!私はこれを見ませんでした。ありがとうございました。 –

+1

Wrapperオブジェクトの作成方法については、依然として注意が必要です.Listはコンストラクタに渡されるため、コード内の他の場所への参照がある可能性があります。 Wrapperを本当に不変にしたい場合は、リストの内容をコピーする必要があります。 –

5

Javaには、immutableクラスの構文概念はありません。プログラマとして、操作にアクセスするのはあなた次第ですが、誰かがそれを濫用すると仮定する必要があります。

真に不変なオブジェクトは、状態を変更したり、状態を変更するために使用できる状態変数にアクセスする方法を提供しません。あなたのクラスは今のところ不変ではありません。

不変にする1つの方法は、内部コレクションのコピーを返すことです。その場合は、それを非常にうまく文書化して、ハイパフォーマンスコードで使用するように警告する必要があります。

もう1つの方法は、誰かが値を変更しようとしたときに例外をスローするラッパーコレクションを使用することです(推奨されていませんが、例としてapache-collectionsを参照してください)。標準ライブラリにもコレクションクラスがあります(コレクションクラスを見てください)。

3つ目のオプションは、一部のクライアントがデータを変更しない場合にデータを変更する場合、クラスに異なるインタフェースを提供することです。 IMyXとIMyImmutableXがあるとしましょう。後者は単に「安全な」操作を定義し、前者はそれを拡張して安全でない操作を追加します。

ここでは、不変クラスを作成するためのヒントを紹介します。 http://java.sun.com/docs/books/tutorial/essential/concurrency/imstrat.html

+0

私はAlex Bの提案が本当に好きです。私の記事で述べたように、私は* List *のコピーを返すことはできません。あなたの答えでは、アレックスが示唆したように、リストの変更不可能なビューを返すことはお勧めしないと言います。あなたは詳細を教えていただけますか? –

+0

@Scott:「ユーザーに驚かないでください」、または「ランタイムエラーに対してコンパイル時エラーが発生する」という一般的なプログラミング方法があります。通常は、コンパイラがユーザのプログラムを実稼働環境でクラッシュさせるのではなく、問題を見つけることが望ましいです。変更不可能なリストを使用すると、有効なリストが返されます。その後、誰かが変更しようとすると突然 "爆発"することがあります。 – Uri

+0

@Scott:リストを返す必要があり、それをコピーできない場合は、実行時に例外が発生します。しかし、おそらくあなたの関数 "getUnmodifiableList"またはそのような名前を付けることを検討してください。私の研究によれば、ほとんどの人は、データのように直感的に見える関数のドキュメントを決して実際に読んでいないことがわかった。 – Uri

5

Collections.unmodifiableListを使用できますか?

ドキュメントによれば、Listの変更不可能な(不変の)ビューが返されます。これはremoveaddのようなメソッドの使用を防ぐためにUnsupportedOperationExceptionを投げることによって防止されます。

しかし、リスト自体の実際の要素の変更を防ぐことはできませんので、十分に不変であるかどうかはわかりません。少なくともリスト自体は変更できません。

はここunmodifiableListによって返さListの内部値はまだ変更することができます例を示します

class MyValue { 
    public int value; 

    public MyValue(int i) { value = i; } 

    public String toString() { 
     return Integer.toString(value); 
    } 
} 

List<MyValue> l = new ArrayList<MyValue>(); 
l.add(new MyValue(10)); 
l.add(new MyValue(42)); 
System.out.println(l); 

List<MyValue> ul = Collections.unmodifiableList(l); 
ul.get(0).value = 33; 
System.out.println(l); 

出力:

[10, 42] 
[33, 42] 

これは基本的に示しているが、データが中に含まれている場合ということですListが最初に変更可能な場合、リスト自体が不変であっても、リストの内容を変更することができます。

+0

My ListはNumberクラス(例えばInteger、Doubleなど)を拡張するImmutableオブジェクトのみを含む。 –

+0

ああ、それは心配することは少なく1つです:) – coobird

5

クラスを正しく変更しないようにするには、いくつかのことがあります。私はこれが有効なJavaで議論されていると信じています。

返されたイテレータを使用してlistの変更を停止するには、Collections.unmodifiableListに読み取り専用のインターフェイスがあります。これが可変クラスの場合、このオブジェクトが行っても返されたリストが変わらないようにデータをコピーすることができます。

コンストラクタに渡されるリストは後で変更される可能性があるため、コピーする必要があります。

クラスはサブクラス化可能であるため、メソッドをオーバーライドできます。だからクラスfinalを作る。コンストラクタの代わりに静的な作成メソッドを提供する方が良い。

public final class Wrapper implements Iterable<Double> { 
    private final List<Double> list; 

    private Wrapper(List<Double> list) { 
     this.list = Collections.unmodifiableList(new ArrayList<Double>(list)); 
    } 

    public static Wrapper of(List<Double> list) { 
     return new Wrapper(list); 
    } 

    public Iterator<Double> iterator() { 
     return list.iterator(); 
    } 

    public List<Double> data() { 
     return list; 
    } 
} 

また、タブを避け、Javaの正しい位置にブレースを入れておくと便利です。

+0

+1の効果的なJavaをお勧めします。 :) – cwash

+0

実際にJavaの中カッコには「正しい位置」がありません。カッコ内の位置よりも正しい位置です。カスケードの正しい位置は、バグが最も少ない位置です。 –