オブジェクト指向プログラミング(OO)および関数型プログラミングにおいて、不変オブジェクト(変更不可能なオブジェクト)とは、作成後に状態を変更できないオブジェクトのことです。[1]これは、作成後に変更可能な可変オブジェクト(変更可能なオブジェクト)とは対照的です。 [ 2 ]場合によっては、内部で使用される属性が変更されても、外部から見るとオブジェクトの状態が変化しないように見えるため、オブジェクトは不変とみなされます。たとえば、メモ化を使用して高コストな計算結果をキャッシュするオブジェクトは、依然として不変オブジェクトとみなされる可能性があります。
オブジェクト指向プログラミングでは、文字列やその他の具体的なオブジェクトは、可読性と実行効率を向上させるために、通常は不変オブジェクトとして表現されます。不変オブジェクトは、本質的にスレッドセーフであるため、有用です。[ 2 ]その他の利点としては、理解しやすく推論しやすく、可変オブジェクトよりも高いセキュリティを提供することが挙げられます。[ 2 ]
命令型プログラミングでは、プログラム変数に格納される値のうち、実行中に内容が変更されない値は定数と呼ばれ、実行中に変更される可能性のある変数と区別されます。例としては、メートルからフィートへの変換係数や、円周率の小数点以下数桁までの値などが挙げられます。
読み取り専用フィールドは、プログラムの実行時に計算される場合があります(定数とは異なり、定数は事前にわかっているため)。ただし、初期化された後は変更されることはありません。
オブジェクトの特定のフィールドが不変であると表現されることがあります。これは、オブジェクトの他の部分は変更可能であっても(弱不変)、オブジェクトの状態のそれらの部分を変更する方法がないことを意味します。すべてのフィールドが不変であれば、オブジェクトは不変です。オブジェクト全体が別のクラスによって拡張できない場合、オブジェクトは強不変と呼ばれます。[ 4 ]これは、たとえば、オブジェクトのライフサイクルを通じてオブジェクト内の特定のデータに関する特定の不変条件を明示的に強制するのに役立つ可能性があります。一部の言語では、フィールドを不変として指定するキーワード(たとえば、C++、Java)を使用してこれを行います。一部の言語では、これを逆にしています。OCaml ではconst、オブジェクトまたはレコードfinalのフィールドはデフォルトで不変であり、不変にするには明示的にマークする必要があります。mutable
ほとんどのオブジェクト指向言語では、オブジェクトは参照を使用して参照できます。そのような言語の例としては、Java、C++、C#、VB.NET、そしてPerl、Python、Rubyなどの多くのスクリプト言語が挙げられます。この場合、オブジェクトが参照を介して共有される際に、オブジェクトの状態が変化する可能性があるかどうかが重要になります。
オブジェクトが不変であることがわかっている場合は、オブジェクト全体をコピーするのではなく、そのオブジェクトへの参照を作成する方が望ましい。これは、データの重複を防ぎ、コンストラクタやデストラクタの呼び出しを回避することでメモリを節約するためであり、実行速度の向上にもつながる可能性がある。
参照コピーの手法は、可変オブジェクトには適用がはるかに困難です。なぜなら、可変オブジェクト参照を使用するユーザーがそのオブジェクトを変更すると、その参照を使用する他のすべてのユーザーが変更を認識するからです。これが意図した効果でない場合、他のユーザーに通知して正しく対応させるのは困難です。このような状況では、参照ではなくオブジェクト全体を防御的にコピーするのが、通常は簡単だがコストのかかる解決策です。オブザーバーパターンは、可変オブジェクトの変更を処理するための代替手法です。
可変オブジェクトと不変オブジェクトの利点を融合させ、現代のほとんどすべてのハードウェアで直接サポートされている技術に、コピーオンライト(COW)があります。この技術では、ユーザーがシステムにオブジェクトのコピーを要求すると、システムは単に同じオブジェクトを指す新しい参照を作成します。ユーザーが特定の参照を介してオブジェクトを変更しようとすると、システムは実際のコピーを作成し、そのコピーに変更を適用し、参照を新しいコピーを指すように設定します。他のユーザーは元のオブジェクトを参照しているため、影響を受けません。したがって、COWでは、すべてのユーザーがオブジェクトの可変バージョンを持っているように見えますが、ユーザーがオブジェクトを変更しない場合は、不変オブジェクトの省スペースと速度の利点が維持されます。コピーオンライトは、アプリケーションプログラムが行う可能性のあるあらゆる操作を正しく処理しながらメモリ領域を節約できるため、仮想メモリシステムで広く使用されています。
等しいオブジェクトのコピーの代わりに常に参照を使用する手法は、インターニングと呼ばれます。インターニングを使用する場合、2 つのオブジェクトは、ポインタまたは整数として表される参照が等しい場合に限り、等しいとみなされます。一部の言語ではこれを自動的に行います。たとえば、 Python は短い文字列を自動的にインターンします。インターニングを実装するアルゴリズムが、可能なすべての場合にインターニングを実行することが保証されている場合、オブジェクトの等価性の比較はポインタの比較に簡略化され、ほとんどのアプリケーションで大幅な速度向上につながります。(アルゴリズムが網羅的であることが保証されていなくても、オブジェクトが等しく、同じ参照を使用する場合に高速パスケースの改善が実現する可能性があります。)インターニングは一般的に、不変オブジェクトにのみ有効です。
不変オブジェクトは、マルチスレッドアプリケーションにおいて有用です。複数のスレッドが、他のスレッドによるデータの変更を気にすることなく、不変オブジェクトで表されるデータに対して操作を行うことができます。そのため、不変オブジェクトは可変オブジェクトよりもスレッドセーフであると考えられています。
不変性とは、コンピュータのメモリに格納されたオブジェクトが書き込み不可能であることを意味するものではありません。むしろ、不変性とはコンパイル時の構成要素であり、プログラマがオブジェクトの通常のインターフェースを通じて何ができるかを示すものであり、必ずしもプログラマが絶対的に何ができるか(例えば、型システムを回避したり、 CやC++のconstの正当性に違反したりすること)を示すものではありません。
Python、Java [ 5 ] : 80、および.NET Frameworkでは、文字列は不変オブジェクトです。Java と .NET Framework の両方に、可変バージョンの文字列があります。Java [ 5 ] : 84では、これらはStringBufferとStringBuilder(Java の可変バージョンString) であり、.NET ではStringBuilder(.NET の可変バージョンString) です。Python 3 には、という名前の可変文字列 (バイト) バリアントがありますbytearray。[ 6 ]
さらに、 Javaのプリミティブ型ラッパークラスはすべて不変です。
同様のパターンとしては、不変インターフェースと不変ラッパーがあります。
純粋な関数型プログラミング言語では、言語を拡張しない限り(例えば、可変参照ライブラリや外部関数インターフェースを介して)、可変オブジェクトを作成することはできないため、すべてのオブジェクトは不変です。
Adaでは、オブジェクトは変数(つまり可変。通常は暗黙のデフォルト)またはconstant(つまり不変)のいずれかで宣言されますconstant。キーワードを使用します。
type Some_type is new Integer ; -- もっと複雑な型にもなり得るx : constant Some_type := 1 ; -- 不変y : Some_type ; -- 可変サブルーチンのパラメータは、入力モードでは変更できませんが、入力出力モードと出力モードでは変更可能です。
procedure Do_it ( a : in Integer ; b : in out Integer ; c : out Integer ) is begin -- a は不変b := b + a ; c := a ; end Do_it ;C#では、ステートメントを使用してクラスのフィールドの不変性を強制できますreadonly。[ 7 ] : 239 すべてのフィールドを不変として強制することで、型は不変型になります。
class ImmutableType { public readonly double Value { get ; }public ImmutableType ( double x ) { Value = x ; }public ImmutableType Square () { return new ImmutableType ( Value * Value ); } }レコードPerson ( string FirstName , string LastName );C++では、const を正しく実装することでShoppingCart、ユーザーはクラスのインスタンスを作成し、const必要に応じてそれらを (不変) または可変として使用できます。これは、メソッドの 2 つの異なるバージョンを提供することによって実現されますitems()。(C++ では、インスタンス専用のコンストラクタを提供する必要はなく、実際には不可能であることに注意してくださいconst。)
import std ;using std :: vector ; using std :: views :: transform ;class ShoppingCart { private : vector <Merchandise> items ; public : explicit Cart ( const vector <Merchandise> & items ) : items { items } { }vector < Merchandise >& items () { return items ; }const vector < Merchandise >& items () const { return items ; }double computeTotalCost () const { return std :: ranges :: accumulation ( items | transform ([]( const Merchandise & m ) -> double { return m . getPrice (); }), 0.0 ); } };データメンバーが別のオブジェクトへのポインタまたは参照である場合、そのポインタまたは参照先のオブジェクトを変更できるのは、非constメソッド内のみです。
C++では、キーワードを介して抽象的な(ビット単位の不変性とは対照的な)不変性も提供されておりmutable、これによりメソッド内からメンバ変数constを変更できます。
import std ;using std :: optional ; using std :: vector ; using std :: views :: transform ;class ShoppingCart { private : vector <Merchandise> items ; mutable optional <int> totalCost ; public : explicit Cart ( const vector <Merchandise> & items ) : items { items } { }const vector < Merchandise >& items () const { return items ; }int computeTotalCost () const { if ( ! totalCost ) { totalCost = std :: ranges :: accumulate ( items | transform ([]( const Merchandise & m ) -> double { return m.getPrice ( ); }), 0.0 ); } return * totalCost ; } } ;Dには、変更できない変数を表す2 つの型修飾子とconstがあります。 [ 10 ] C++ 、Java 、C# のとは異なり、これらは推移的で、そのような変数の参照を通じて到達可能なものすべてに再帰的に適用されます。との違いは、それらが適用される対象です。は変数のプロパティです。参照される値への可変参照が合法的に存在する可能性があり、つまり、値は実際に変更できます。対照的に、は参照される値のプロパティです。値とそこから推移的に到達可能なものは、(型システムを壊さずに、未定義の動作につながることなく) 変更できません。その値の参照はすべてまたは でマークする必要があります。基本的に、修飾されていない型 の場合、は(可変)と の非交和です。immutableconstfinalreadonlyconstimmutableconstimmutableconstimmutableTconst(T)Timmutable(T)
class Example { Object mutableField ; // 可変型const Object constField ;不変型Object immutableField ; }可変Exampleオブジェクトの場合、そのフィールドmutableFieldには書き込みが可能です。const(Example)オブジェクトの場合、mutableFieldは変更できません。はを継承しますconst。immutableFieldはより強力な保証であるため、依然として不変です。の場合immutable(Example)、すべてのフィールドは不変です。
次のような関数では:
void fn ( Example m , const Example c , immutable Example i ) { // 中括弧の中}中括弧内では、cは と同じオブジェクトを参照する可能性があるmため、 への変更は間接的にも m変更する可能性があります。また、は と同じオブジェクトを参照する可能性もありますが、その場合、値は不変であるため、変更はありません。ただし、と は同じオブジェクトを合法的に参照することはできません。ccimi
保証の用語で言えば、mutable には保証がなく (関数がオブジェクトを変更する可能性がある)、constは関数が何も変更しないという外部のみの保証であり、 immutableは双方向の保証 (関数は値を変更せず、呼び出し元も値を変更してはならない) である。
宣言時に直接代入するか、コンストラクタによって初期化されるconstか、初期化されなければならない値。immutable
constパラメータは値が可変かどうかを忘れてしまうため、同様の構造inoutである は、ある意味で可変性情報のための変数として機能します。 型の関数は、可変、定数、および不変の引数に対して型付き値const(S) function(const(T))を返しますconst(S)。対照的に、 型の関数は、可変引数に対して を、値に対して を、値に対して をinout(S) function(inout(T))返します。STconst(S)const(T)immutable(S)immutable(T)
不変の値を可変にキャストすると、元の値が可変の元から来ている場合でも、変更時に未定義の動作が発生します。可変の値を不変にキャストすることは、その後可変の参照が残らない場合に合法です。「式は、式が一意であり、推移的に参照するすべての式が一意であるか不変である場合に、可変から不変に変換できます。」[ 10 ]コンパイラが一意性を証明できない場合は、キャストを明示的に行うことができ、可変の参照が存在しないことを保証するのはプログラマの責任です。
この型はstringの別名でありimmutable(char)[]、つまり、不変文字の型付きメモリ スライスです。[ 11 ]部分文字列の作成は、ポインタと長さフィールドをコピーして変更するだけなので、コストが低く、基となるデータは変更できないので安全です。 型のオブジェクトはconst(char)[]文字列だけでなく、可変バッファも参照できます。
const または不変値の浅いコピーを作成すると、不変性の外側の層が削除されます。不変文字列 ( immutable(char[])) をコピーすると、文字列 ( immutable(char)[]) が返されます。不変ポインタと長さがコピーされ、コピーは可変です。参照データはコピーされず、例のように修飾子が保持されますimmutable。関数などを使用して、より深いコピーを作成することで、修飾子を削除できますdup。
String不変オブジェクトの典型的な例は、Javaクラスのインスタンスです。
String s = "ABC" ; s . toLowerCase (); // これは何も効果がありません!このメソッドは、格納されているtoLowerCase()データ「ABC」を変更しませんs。代わりに、新しいStringオブジェクトがインスタンス化され、その構築時にデータ「abc」が渡されます。このStringオブジェクトへの参照がtoLowerCase()メソッドによって返されます。Stringオブジェクトにsデータ「abc」を格納するには、別の方法が必要です。
s = s.toLowerCase ( ) ;これで、String は「abc」を含む新しい String オブジェクトを参照するようになりました。String クラスの宣言sの構文には、それを不変に強制する要素は何もありません。むしろ、String クラスのメソッドは String オブジェクトが保持するデータに影響を与えないため、String は不変となるのです。
キーワードfinal(詳細記事)は、不変なプリミティブ型とオブジェクト参照を実装する際に使用されますが、[ 12 ]それ自体ではオブジェクト自体を不変にすることはできません。以下の例を参照してください。
プリミティブ型の変数(int、、など)はlong、short定義後に再割り当てされる可能性があります。これを防ぐには、を使用しますfinal。
int i = 42 ; // int はプリミティブ型ですi = 43 ; // OKfinal int j = 42 ; j = 43 ; // コンパイルエラー。j は final なので再代入できませんキーワードを使用するだけでは、参照型を不変にすることはできませんfinal。finalは再代入を防ぐだけです。
final MyObject m = new MyObject (); // m は参照型ですm . data = 100 ; // OK。オブジェクト m の状態を変更できます (m は可変であり、final はこの事実を変えません) m = new MyObject (); // コンパイルエラーになります。m は final なので再代入できませんプリミティブ ラッパー ( Integer、Long、Short、Double、Float、Character、 ) もすべて不変です。不変クラスはByte、Booleanいくつかの簡単なガイドラインに従うことで実装できます。[ 13 ]
JavaScriptでは、すべてのプリミティブ型(Undefined、Null、Boolean、Number、BigInt、String、Symbol)は不変ですが、カスタムオブジェクトは一般的に可変です。
function doSomething ( x ) { /* ここで x を変更すると元の値が変更されますか? */ }; var str = 'a string' ; var obj = { an : 'object' }; doSomething ( str ); // 文字列、数値、ブール型は不変で、関数はコピーを取得しますdoSomething ( obj ); // オブジェクトは参照渡しされ、関数内で変更可能ですdoAnotherThing ( str , obj ); // `str` は変更されていませんが、`obj` は変更されている可能性があります。オブジェクトの不変性をシミュレートするには、プロパティを読み取り専用(書き込み可能:false)として定義することができます。
var obj = { } ; Object.defineProperty ( obj , 'foo' , { value : ' bar' , writable : false } ) ; obj.foo = 'bar2' ; //暗黙的に無視されるしかし、上記の方法では新しいプロパティを追加できてしまいます。あるいは、Object.freezeを使用して既存のオブジェクトを不変にすることもできます。
var obj = { foo : 'bar' }; Object . freeze ( obj ); obj . foo = 'bars' ; // プロパティを編集できません。無視されます。obj . foo2 = 'bar2' ; // プロパティを追加できません。無視されます。ECMA262の実装により、JavaScriptは再割り当て不可能な不変参照を作成できるようになりました。ただし、const宣言を使用しても、読み取り専用参照の値が不変になるわけではなく、単にその名前に新しい値を割り当てることができないだけです。
const ALWAYS_IMMUTABLE = true ;try { ALWAYS_IMMUTABLE = false ; } catch ( err ) { console.log ( "不変参照を再割り当てできません。" ) ; }const arr = [ 1 , 2 , 3 ]; arr . push ( 4 ); console . log ( arr ); // [1, 2, 3, 4]不変状態の使用は、 Reactの導入以来JavaScriptで増加傾向にあり、 ReduxのようなFluxライクな状態管理パターンが推奨されている。[ 14 ]
Perlでは、Mooライブラリを使用して、すべての属性を読み取り専用と宣言するだけで、不変クラスを作成できます。
package Immutable ; use Moo ;has value => ( is => 'ro' , # 読み取り専用default => 'data' , # コンストラクタに値を指定することで上書きできます: Immutable->new(value => 'something else'); );1 ;不変クラスを作成するには、従来は 2 つの手順が必要でした。まず、オブジェクト属性の変更を防止するアクセサーを (自動または手動で) 作成し、次に、そのクラスのインスタンスのインスタンスデータの直接変更を防止する (これは通常ハッシュ参照に格納され、Hash::Util の lock_hash 関数でロックすることができました)。
package Immutable ; use strict ; use warnings ; use base qw(Class::Accessor) ; # 読み取り専用アクセサを作成します__PACKAGE__ -> mk_ro_accessors ( qw(value) ); use Hash::Util 'lock_hash' ;sub new { my $class = shift ; return $class if ref ( $class ); die "new の引数はキー => 値のペアである必要があります\n" unless ( @_ % 2 == 0 ); my %defaults = ( value => 'data' , ); my $obj = { %defaults , @_ , }; bless $obj , $class ; # オブジェクトデータの変更を防止lock_hash %$obj ; } 1 ;または、手動で記述したアクセサーを使用する場合:
package Immutable ; use strict ; use warnings ; use Hash::Util 'lock_hash' ;sub new { my $class = shift ; return $class if ref ( $class ); die "new の引数はキーと値のペアである必要があります\n" unless ( @_ % 2 == 0 ); my %defaults = ( value => 'data' , ); my $obj = { %defaults , @_ , }; bless $obj , $class ; # オブジェクトデータの変更を防止lock_hash %$obj ; }# 読み取り専用アクセサsub value { my $self = shift ; if ( my $new_value = shift ) { # 新しい値を設定しようとしていますdie "このオブジェクトは変更できません\n" ; } else { return $self -> { value } } } 1 ;PHPでは、バージョン8.1以降は読み取り専用プロパティ、バージョン8.2以降は読み取り専用クラスが存在します。[ 15 ] [ 16 ]
readonly class BlogData { public string $title ;パブリックステータス$status ;public function __construct ( string $title , Status $status ) { $this -> title = $title ; $this -> status = $status ; } }Pythonでは、一部の組み込み型(数値、ブール値、文字列、タプル、フローズンセット)は不変ですが、カスタムクラスは一般的に可変です。クラスの不変性をシミュレートするには、属性の設定と削除をオーバーライドして例外を発生させることができます。
from typing import Any , NoReturnclass ImmutablePoint : """2つの属性「x」と「y」を持つ不変クラス。"""__slots__ : list [ str ] = [ "x" , "y" ]def __setattr__ ( self , * args : tuple [ Any , ... ]) -> NoReturn : raise TypeError ( "不変インスタンスを変更できません。" )__delattr__ : Callable [[ Tuple [ Any , ... ]], NoReturn ] = __setattr__def __init__ ( self , x : int , y : int ) -> None : # インスタンスデータを格納するために self.value = value を使用できなくなったため、 # スーパークラスを明示的に呼び出す必要がありますsuper () . __setattr__ ( "x" , x ) super () . __setattr__ ( "y" , y )Python 3.6以降で利用可能な標準ライブラリのヘルパー関数とを使用するcollections.namedtupleとtyping.NamedTuple、単純な不変クラスを作成できます。次の例は、上記の例とほぼ同等ですが、タプルのような機能がいくつか追加されています。
from typing import NamedTuple import collectionsポイント: NamedTuple = collections.namedtuple ( "Point" , [ " x" , " y" ])# 以下は上記のクラスと同様の名前付きタプルを作成します。 Point ( NamedTuple ): x : int y : intPython 3.7 で導入されたこの機能により、開発者はフリーズされたインスタンスdataclassesを使用して不変性をエミュレートできます。フリーズされたデータクラスが構築された場合、呼び出されたときに例外を発生させるようにオーバーライドされます。dataclasses__setattr__()__delattr__()FrozenInstanceError
from dataclasses import dataclass@dataclass ( frozen = True ) class Point : x : int y : intRacket は、コアとなるペア型 (「cons セル」) を不変にすることで、他のScheme実装とは大きく異なります。代わりに、 、mconsなどを介して、並行する可変ペア型を提供します。さらに、不変文字列やベクトルなど、多くの不変型がサポートされており、これらは広く使用されています。フィールドが明示的に可変として宣言されていない限り、または構造体全体が可変として宣言されていない限り、新しい構造体はデフォルトで不変です。mcarset-mcar!
( struct foo1 ( x y )) ; すべてのフィールドが不変( struct foo2 ( x [ y #:mutable ])) ; 1 つの可変フィールド( struct foo3 ( x y ) #:mutable ) ; すべてのフィールドが可変この言語は、関数型プログラミングで実装された不変ハッシュテーブルと不変辞書もサポートしています。
Rust の所有権システムでは、開発者は不変変数を宣言し、不変参照を渡すことができます。デフォルトでは、すべての変数と参照は不変です。可変変数と参照は、mutキーワードを使用して明示的に作成されます。
Rustにおける定数項目は常に不変である。
// 定数項目は常に不変ですconst ALWAYS_IMMUTABLE : bool = true ;struct MyPair { x : usize , y : usize , }fn main () { //可変変数を明示的に宣言するlet mut mutable_obj = MyPair { x : 1 , y : 2 } ; mutable_obj.x = 3 ; // OKlet mutable_ref = & mut mutable_obj ; mutable_ref . x = 1 ; // OKlet immutable_ref = & mutable_obj ; immutable_ref.x = 3 ; //エラーE0594// デフォルトでは、変数は不変ですlet immutable_obj = MyPair { x : 4 , y : 5 }; immutable_obj . x = 6 ; // エラー E0596let mutable_ref2 = & mut immutable_obj ; // エラー E0596let immutable_ref2 = & immutable_obj ; immutable_ref2 . x = 6 ; // エラー E0594 }Scalaでは、任意のエンティティ(狭義にはバインディング)を可変または不変として定義できます。宣言では、val不変エンティティには(value)を、var可変エンティティには(variable)を使用できます。不変バインディングは再割り当てできませんが、可変オブジェクトを参照することは可能であり、そのオブジェクトに対して変更メソッドを呼び出すこともできます。つまり、バインディングは不変ですが、基となるオブジェクトは可変である可能性があるということです。
例えば、以下のコードスニペット:
val maxValue = 100 var currentValue = 1は、不変エンティティmaxValue(整数型はコンパイル時に推論される)と、という名前の可変エンティティを定義しますcurrentValue。
デフォルトでは、Listやなどのコレクション クラスはMap不変であるため、更新メソッドは既存のインスタンスを変更するのではなく、新しいインスタンスを返します。これは非効率的に聞こえるかもしれませんが、これらのクラスの実装と不変性の保証により、新しいインスタンスは既存のノードを再利用できるため、特にコピーを作成する場合には非常に効率的です。[ 17 ]
この記事には、Perlデザインパターンブックからの抜粋が含まれています。
される方法は、クラスを final にすることです。これは「強力な不変性」と呼ばれることもあります。これにより、クラスを拡張して、意図的または偶発的に可変にすることができなくなります。