不変とはどういう意味ですか?


400

これはこれまでに尋ねられた最も馬鹿げた質問かもしれませんが、Javaの初心者にはかなり混乱していると思います。

  1. 誰かが不変の意味を明確にできますか?
  2. なぜString不変ですか?
  3. 不変オブジェクトの利点/欠点は何ですか?
  4. StringBuilderString などの可変オブジェクトを優先する必要があるのはなぜですか?

(Javaでの)良い例は本当にありがたいです。


73
ほら、そんな馬鹿げた質問ではなかった。よろしくお願いします!
DOK

2
ちなみに、これは最も馬鹿げた質問だとは思いません:)理解するのはかなり重要な概念だと思います
Jason Coco

1
StringBuilderと言ったとき、変更可能なクラスStringBufferを意味していませんでしたか?StringとStringBufferは、StringとStringBuilderよりも機能が似ています。StringBufferは事実上、変更可能な文字列です。
Derek Mahar、

3
「ビギナー」タグをこの質問に追加して、Javaに不慣れなプログラマーが他の紹介的な質問の検索で見つけられるようにすることを提案できますか?
Derek Mahar、

回答:


268

不変とは、オブジェクトのコンストラクタが実行を完了すると、そのインスタンスを変更できないことを意味します。

これは、他の誰かがその内容を変更することを心配することなく、オブジェクトへの参照を渡すことができることを意味するので便利です。特に並行性を処理する場合、決して変更されないオブジェクトのロックの問題はありません

例えば

class Foo
{
     private final String myvar;

     public Foo(final String initialValue)
     {
         this.myvar = initialValue;
     }

     public String getValue()
     {
         return this.myvar;
     }
}

Foo呼び出し元getValue()が文字列のテキストを変更する可能性があることを心配する必要はありません。

に似たクラスを想像しますが、メンバーとしてFooa StringBuilderではなくString、の呼び出し元がインスタンスの属性getValue()を変更できることがわかります。StringBuilderFoo

また、さまざまな種類の不変性に注意してください。EricLippertがこれについてブログ記事を書いています。基本的には、インターフェイスは不変ですが、実際には変更可能なプライベート状態のオブジェクトを持つことができます(したがって、スレッド間で安全に共有することはできません)。


3
少なくとも1回は値を割り当てるには、1つの引数コンストラクタを追加する必要があると思います。実際に変更する値がないため、現在のコードのポイントは明確ではありません:)。
Georgy Bolyuba 08年

4
フィールドを読み取り専用にする必要があります。フィールドが不変であることを完全に明示的にします。現在、慣例により不変です
JaredPar '11 / 11/11

7
メンバーmyVarは、これが真に不変であるためには最終的なものである必要があります。
2009年

13
myVarにFooの外からアクセスできないのは正しいです。ただし、finalの存在は、将来そのクラスを変更する可能性のあるユーザーに、その値を変更する意図がないことを示します。私はそのような状況ではできるだけ明示的にすることを好む傾向があります。
2009年

2
「finalキーワードを使用するだけでは参照型を不変にすることはできません。finalは再割り当てを防ぐだけです。」en.wikipedia.org/wiki/Immutable_object
Yousha Aleayoub 2015

81

不変オブジェクトは、内部フィールド(または少なくとも、その外部動作に影響を与えるすべての内部フィールド)を変更できないオブジェクトです。

不変文字列には多くの利点があります。

パフォーマンス:次の操作を実行します。

String substring = fullstring.substring(x,y);

substring()メソッドの基礎となるCは、おそらく次のようなものです。

// Assume string is stored like this:
struct String { char* characters; unsigned int length; };

// Passing pointers because Java is pass-by-reference
struct String* substring(struct String* in, unsigned int begin, unsigned int end)
{
    struct String* out = malloc(sizeof(struct String));
    out->characters = in->characters + begin;
    out->length = end - begin;
    return out;
}

どの文字もコピーする必要がないことに注意してください Stringオブジェクトが変更可能である場合(文字は後で変更される可能性があります)、すべての文字をコピーする必要があります。そうしないと、部分文字列の文字への変更が後で他の文字列に反映されます。

並行性:不変オブジェクトの内部構造が有効である場合、それは常に有効です。異なるスレッドがそのオブジェクト内で無効な状態を作成する可能性はありません。したがって、不変オブジェクトはスレッドセーフです。

ガベージコレクション:ガベージコレクターは、不変オブジェクトについて論理的な決定を行う方がはるかに簡単です。

ただし、不変性には欠点もあります。

パフォーマンス:待って、パフォーマンスは不変性の利点だと言ったと思いました!まあ、それは時々ありますが、常にではありません。次のコードを見てください。

foo = foo.substring(0,4) + "a" + foo.substring(5);  // foo is a String
bar.replace(4,5,"a"); // bar is a StringBuilder

2行とも、4番目の文字を文字「a」に置き換えます。2番目のコードが読みやすくなるだけでなく、より高速になります。fooの基礎となるコードをどのように実行する必要があるかを見てください。部分文字列は簡単ですが、スペース5には既に文字があり、他の何かがfooを参照している可能性があるため、変更することはできません。文字列全体をコピーする必要があります(もちろん、この機能の一部は、実際の基盤となるCの関数に抽象化されていますが、ここでのポイントは、すべて1か所で実行されるコードを示すことです)。

struct String* concatenate(struct String* first, struct String* second)
{
    struct String* new = malloc(sizeof(struct String));
    new->length = first->length + second->length;

    new->characters = malloc(new->length);

    int i;

    for(i = 0; i < first->length; i++)
        new->characters[i] = first->characters[i];

    for(; i - first->length < second->length; i++)
        new->characters[i] = second->characters[i - first->length];

    return new;
}

// The code that executes
struct String* astring;
char a = 'a';
astring->characters = &a;
astring->length = 1;
foo = concatenate(concatenate(slice(foo,0,4),astring),slice(foo,5,foo->length));

連結は2回呼び出されることに注意してください。つまり、文字列全体をループする必要があります。これをbar操作のCコードと比較します。

bar->characters[4] = 'a';

変更可能な文字列操作は明らかにはるかに高速です。

結論:ほとんどの場合、不変の文字列が必要です。しかし、文字列に多くの追加と挿入を行う必要がある場合は、速度のために可変性が必要です。並行処理の安全性とガベージコレクションの利点を望む場合、重要なのは、変更可能なオブジェクトをメソッドに対してローカルに保つことです。

// This will have awful performance if you don't use mutable strings
String join(String[] strings, String separator)
{
    StringBuilder mutable;
    boolean first = true;

    for(int i = 0; i < strings.length; i++)
    {
        if(!first) first = false;
        else mutable.append(separator);

        mutable.append(strings[i]);
    }

    return mutable.toString();
}

mutableオブジェクトはローカル参照であるため、同時実行の安全性を気にする必要はありません(これに触れるスレッドは1つだけです)。また、他のどこからも参照されないため、スタックにのみ割り当てられるため、関数呼び出しが完了するとすぐに割り当てが解除されます(ガベージコレクションを心配する必要はありません)。また、可変性と不変性の両方のパフォーマンス上のメリットをすべて享受できます。


4
よく読んでください!if(first)でなく、if(!first)でなければならないことを1つだけ思います
Siddhartha

フィールドが不変である必要はなく、オブジェクトの定義された監視可能な状態が不変である必要があります。そこに含まれる状態をカプセル化する手段として別のオブジェクトへの参照を保持するオブジェクトは、それが外界に公開する状態のすべてのカプセル化された側面が同様に不変である場合にのみ不変である可能性があります。フィールドが不変タイプである必要はなく、十分でもないことに注意してください。重要なのは目に見える状態です。
スーパーキャット2014年

7
Passing pointers because Java is pass-by-referenceJavaは「値渡し」ではありませんか?
クリスティアングトゥ2014年

@CristianGutuはい、そうです。JAVAは「Pass by Value」ではなく「Pass by Value」です
Arsh Kaushal

参照は値として渡されます!!
devv

31

上記のウィキペディアの定義を使用する場合、実際にはStringは不変ではありません。

文字列の状態は、ポスト構造を変更します。hashcode()メソッドを見てください。文字列はローカルフィールドのハッシュコード値をキャッシュしますが、hashcode()の最初の呼び出しまでそれを計算しません。このハッシュコードの遅延評価により、Stringは、状態が変化する不変オブジェクトとして興味深い位置に配置されますが、リフレクションを使用しないと変化したことが確認できません。

したがって、不変の定義は、変更されたことが確認できないオブジェクトである必要があります。

作成後に不変オブジェクトの状態が変化しても誰もそれを(リフレクションなしで)見ることができない場合、オブジェクトはまだ不変ですか?


1
良い考え-変更されたことが観察できないオブジェクト、および外部から変更する方法はありません。hashCode()のプライベートフィールドは、オブジェクトの外部から見える状態に対して重要ではない内部変更です。
mparaz 2009

2
実際、リフレクションを使用すると、変化したことが確認できます。リフレクションを許可すると、SedgewickのStringsは変更可能になります
ミゲル

24

不変オブジェクトとは、プログラムで変更できないオブジェクトです。これらは、マルチスレッド環境や、複数のプロセスがオブジェクトの値を変更(変更)できる他の環境に特に適しています。

ただし、明確にするために、StringBuilderは実際には可変オブジェクトであり、不変オブジェクトではありません。通常のjava Stringは不変です(つまり、作成された後は、オブジェクトを変更せずに基になる文字列を変更することはできません)。

たとえば、文字列値と文字列色を持つColoredStringというクラスがあるとします。

public class ColoredString {

    private String color;
    private String string;

    public ColoredString(String color, String string) {
        this.color  = color;
        this.string = string;
    }

    public String getColor()  { return this.color;  }
    public String getString() { return this.string; }

    public void setColor(String newColor) {
        this.color = newColor;
    }

}

この例では、新しいColoredStringクラスを作成せずに、キープロパティの1つを変更(変更)できるため、ColoredStringは変更可能であると言われています。これが悪い可能性がある理由は、たとえば、複数のスレッドを持つGUIアプリケーションがあり、ColoredStringsを使用してウィンドウにデータを印刷しているとします。として作成されたColoredStringのインスタンスがある場合

new ColoredString("Blue", "This is a blue string!");

次に、文字列は常に「青」であると想定します。ただし、別のスレッドがこのインスタンスを取得して呼び出された場合

blueString.setColor("Red");

突然、そしておそらく予想外に、「青」のストリングが必要なときに「赤」のストリングができます。このため、オブジェクトのインスタンスを渡すときに、ほとんどの場合、不変オブジェクトが優先されます。変更可能なオブジェクトが本当に必要な場合は、通常、特定の制御フィールドからコピーを渡すだけでオブジェクトを保護します。

要約すると、Javaでは、java.lang.Stringは不変オブジェクト(作成後は変更できません)であり、java.lang.StringBuilderは新しいオブジェクトを作成せずに変更できるため、変更可能なオブジェクトです。


フィールドを読み取り専用にする必要があります。現在、クラスは慣例により不変です。イミュータブルが意図的なものであるかどうかは、将来の開発者にはわかりません。フィールドを読み取り専用にすると、将来の
開発者

@JaredPar-実際には、クラスはまったく不変ではありません...これは、問題になる可能性がある理由を示すための可変クラスの例です。
Jason Coco

1
@JaredPar-ああ、それは完全に大丈夫です:)私はそれをより明確にするために少し書き直すつもりでしたが、ダグラスのものはすでによく書かれていてお気に入りのようですので、私は別の例として私のものを残します; しかし、誰かが実際にそれを編集して、私が面白いと思ったプロパティをfinalにしました:)
Jason Coco

24
  1. 大規模なアプリケーションでは、文字列リテラルが大量のメモリを占有するのが一般的です。したがって、メモリを効率的に処理するために、JVMは「文字列定数プール」と呼ばれる領域を割り当てます(メモリ内で参照されていない文字列でもchar []、長さのint、およびそのhashCodeのもう1つを持ち運ぶことに注意してください。対照的に、最大8バイトの即値が必要です
  2. コンパイラが文字列リテラルに遭遇すると、プールをチェックして、同じリテラルがすでに存在するかどうかを確認します。そして、見つかった場合、新しいリテラルへの参照は既存の文字列に向けられ、新しい「文字列リテラルオブジェクト」は作成されません(既存の文字列は単に追加の参照を取得します)。
  3. したがって、文字列の可変性によりメモリが節約されます...
  4. しかし、変数のいずれかが値を変更すると、実際には-変更されるのは参照のみであり、メモリ内の値ではありません(したがって、それを参照している他の変数には影響しません)...

文字列s1 = "古い文字列";

//s1 variable, refers to string in memory
        reference                 |     MEMORY       |
        variables                 |                  |

           [s1]   --------------->|   "Old String"   |

文字列s2 = s1;

//s2 refers to same string as s1
                                  |                  |
           [s1]   --------------->|   "Old String"   |
           [s2]   ------------------------^

s1 = "新しい文字列";

//s1 deletes reference to old string and points to the newly created one
           [s1]   -----|--------->|   "New String"   |
                       |          |                  |
                       |~~~~~~~~~X|   "Old String"   |
           [s2]   ------------------------^

元の文字列「メモリ内」は変更されませんでしたが、参照変数が変更され、新しい文字列を参照するようになりました。また、s2がない場合でも、「古い文字列」はメモリに残りますが、アクセスできなくなります...


16

「不変」とは、値を変更できないことを意味します。Stringクラスのインスタンスがある場合、値を変更すると思われるメソッドを呼び出すと、実際には別のStringが作成されます。

String foo = "Hello";
foo.substring(3);
<-- foo here still has the same value "Hello"

変更を保存するには、次のようにする必要がありますfoo = foo.sustring(3);

コレクションを操作する場合、不変vs可変はおかしい場合があります。(:考えるヒントが値を変更後、あなたはマップのキーとして変更可能なオブジェクトを使用する場合に発生となるかについて考えequalshashCode)。


13

java.time

少し遅いかもしれませんが、不変オブジェクトとは何かを理解するために、新しいJava 8 Date and Time API(java.time)の次の例を検討してください。おそらくご存じのとおり、Java 8のすべての日付オブジェクトは不変なので、次の例では

LocalDate date = LocalDate.of(2014, 3, 18); 
date.plusYears(2);
System.out.println(date);

出力:

2014-03-18

plusYears(2)は新しいオブジェクトを返すため、これは最初の日付と同じ年を出力しますが、古いオブジェクトは不変オブジェクトであるため、変更されません。いったん作成されると、それをさらに変更することはできず、日付変数はまだそれを指します。

したがって、このコード例では、インスタンス化され、への呼び出しによって返された新しいオブジェクトをキャプチャして使用する必要がありますplusYears

LocalDate date = LocalDate.of(2014, 3, 18); 
LocalDate dateAfterTwoYears = date.plusYears(2);

date.toString()…2014-03-18

dateAfterTwoYears.toString()…2016-03-18


8

SCJP Sun Certified Programmer for Java 5 Study Guideの説明が本当に気に入っています。

Javaのメモリ効率を高めるために、JVMは「文字列定数プール」と呼ばれる特別なメモリ領域を確保しています。コンパイラは文字列リテラルを検出すると、プールをチェックして、同じ文字列がすでに存在するかどうかを確認します。一致が見つかった場合、新しいリテラルへの参照は既存の文字列に送られ、新しい文字列リテラルオブジェクトは作成されません。


同一の不変オブジェクトでこれを実行できるはずですが、実行に時間がかかりすぎると思います。
Zan Lynx

8

不変のオブジェクトは、作成後に状態を変更できません。

可能な限り不変オブジェクトを使用する主な理由は3つあります。これらすべてが、コードに導入するバグの数を減らすのに役立ちます。

  • オブジェクトの状態を別のメソッドで変更できないことがわかっている場合は、プログラムの動作を推測する方がはるかに簡単です。
  • 不変オブジェクトは自動的にスレッドセーフ(安全に公開されている場合)であるため、ピン留めが難しいマルチスレッドバグの原因になることはありません。
  • 不変オブジェクトは常に同じハッシュコードを持つため、HashMap(または同様のもの)のキーとして使用できます。ハッシュテーブル内の要素のハッシュコードが変更された場合、テーブルエントリを検索しようとすると誤った場所が検索されるため、テーブルエントリは事実上失われます。これがStringオブジェクトが不変である主な理由です-それらはHashMapキーとして頻繁に使用されます。

オブジェクトの状態が不変であることがわかっている場合、たとえば、計算されたハッシュをキャッシュするなど、コードで行うことができる他の最適化もいくつかありますが、これらは最適化であるため、それほど興味深いものではありません。


5

1つの意味は、値がコンピューターに格納される方法に関係しています。たとえば、.Net文字列の場合、メモリ内の文字列は変更できないことを意味します。変更していると思われる場合、実際には新しい文字列を作成していますメモリ内の文字列で、既存の変数(別の場所にある文字の実際のコレクションへの単なるポインタ)が新しい文字列を指すようにします。


4
String s1="Hi";
String s2=s1;
s1="Bye";

System.out.println(s2); //Hi  (if String was mutable output would be: Bye)
System.out.println(s1); //Bye

s1="Hi":オブジェクトs1は "Hi"値で作成されました。

s2=s1s2s1オブジェクトを参照してオブジェクトが作成されます。

s1="Bye":以前のs1オブジェクトの値は変更されません。これs1は、String型があり、String型が不変の型であるため、コンパイラは代わりに「Bye」値を使用して新しいStringオブジェクトを作成し、s1それを参照します。ここでs2値を出力すると、「Hi」の値を持つs2以前のs1オブジェクトを参照するため、結果は「Bye」ではなく「Hi」になります。


少し説明を追加できますか?
minigeek 2017年

3

不変とは、オブジェクトが作成されると、そのメンバー以外は変更されないことを意味します。String内容は変更できないため、不変です。例えば:

String s1 = "  abc  ";
String s2 = s1.trim();

上記のコードでは、文字列s1は変更されず、を使用して別のオブジェクト(s2)が作成されましたs1


3

不変とは、単に変更できない、または変更できないことを意味します。文字列オブジェクトが作成されると、そのデータまたは状態は変更できません

以下の例を考えてみてください。

class Testimmutablestring{  
  public static void main(String args[]){  
    String s="Future";  
    s.concat(" World");//concat() method appends the string at the end  
    System.out.println(s);//will print Future because strings are immutable objects  
  }  
 }  

下の図を考えて考えてみましょう。

ここに画像の説明を入力してください

この図では、「Future World」として作成された新しいオブジェクトを確認できます。しかし、「未来」を変えないでください。Because String is immutables、まだ「未来」を参照してください。「未来の世界」と呼ぶ必要があるなら

String s="Future";  
s=s.concat(" World");  
System.out.println(s);//print Future World

文字列オブジェクトがJavaで不変なのはなぜですか?

Javaは文字列リテラルの概念を使用しているためです。5つの参照変数があり、すべて1つのオブジェクト「Future」を参照しているとします。1つの参照変数がオブジェクトの値を変更すると、すべての参照変数に影響します。そのため、Javaでは文字列オブジェクトが不変です。


2

いったんインスタンス化されると、変更できません。のインスタンスがハッシュテーブルなどのキーとして使用される可能性のあるクラスを考えます。Javaのベストプラクティスを確認してください。


0

不変オブジェクト

作成後に状態を変更できない場合、オブジェクトは不変と見なされます。不変オブジェクトへの最大限の依存は、シンプルで信頼できるコードを作成するための適切な戦略として広く受け入れられています。

不変オブジェクトは、並行アプリケーションで特に役立ちます。それらは状態を変更できないため、スレッドの干渉によって破損したり、不整合な状態で観察されたりすることはありません。

プログラマは、オブジェクトを適切に更新するのではなく、新しいオブジェクトを作成するコストを心配するため、不変オブジェクトを採用することに消極的です。オブジェクト作成の影響は過大評価されることが多く、不変オブジェクトに関連するいくつかの効率によって相殺される可能性があります。これらには、ガベージコレクションによるオーバーヘッドの減少、および変更可能なオブジェクトを破損から保護するために必要なコードの排除が含まれます。

次のサブセクションでは、インスタンスが変更可能なクラスを受け取り、不変のインスタンスを持つクラスを派生させます。そうすることで、彼らはこの種の変換に関する一般的なルールを与え、不変オブジェクトのいくつかの利点を示します。

ソース


0

受け入れられた答えはすべての質問に答えるわけではないので。11年6か月後に答えを迫られます。

誰かが不変の意味を明確にできますか?

あなたが不変のオブジェクトを意味していることを願っています不変の参照について考えることができたため)

オブジェクトは不変です。一度作成されると、常に同じ値を表します(値を変更するメソッドはありません)。

なぜString不変ですか?

Sting.javaソースコードを調べて確認できる上記の定義を尊重してください

不変オブジェクトの利点/欠点は何ですか?不変タイプは次のとおりです。

  • バグからより安全。

  • 理解しやすいです。

  • 変更の準備ができています。

StringBuilderなどの変更可能なオブジェクトをStringよりも優先する必要があるのはなぜですか?

質問を絞り込むなぜプログラミングに可変のStringBuilderが必要なのですか? これの一般的な用途は、次のように多数の文字列を連結することです。

String s = "";
for (int i = 0; i < n; ++i) {
    s = s + n;
}

不変文字列を使用して、これは多くの一時的なコピーを作成します。文字列の最初の数( "0")は、最終的な文字列を構築する過程で実際にn回コピーされ、2番目の数はn-1回コピーされます。オン。実際には、n個の要素のみを連結したとしても、すべてのコピーを実行するには、O(n2)時間かかります。

StringBuilderは、このコピーを最小限に抑えるように設計されています。toString()呼び出しで最後の文字列を要求するときに、最後まで文字列のコピーをまったく行わないようにするために、シンプルですが巧妙な内部データ構造を使用します。

StringBuilder sb = new StringBuilder();
for (int i = 0; i < n; ++i) {
  sb.append(String.valueOf(n));
}
String s = sb.toString();

可変オブジェクトを使用する理由の1つは、優れたパフォーマンスを得ることです。もう1つは共有に便利です。プログラムの2つの部分は、共通の可変データ構造を共有することで、より便利に通信できます。

さらにここで見つけることができます:https : //web.mit.edu/6.005/www/fa15/classes/09-immutability/#useful_immutable_types


-1

不変オブジェクトは、作成後に変更できないオブジェクトです。典型的な例は文字列リテラルです。

ますます人気が高まるADプログラミング言語には、「不変」キーワードによる「不変性」の概念があります。-それについてこのDr.Dobbの記事をチェックhttp://dobbscodetalk.com/index.php?option=com_myblog&show=Invariant-Strings.html&Itemid=29を。問題を完全に説明しています。


D 2.020以降、キーワードは不変から不変に変更されたと思います。ポイントはわかりませんが、「今は不変が実装されています」と書かれています。 digitalmars.com/d/2.0/changelog.html#new2_020
he_the_great
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.