stringtranslate.com

Неизменяемый объект

В объектно-ориентированном (ОО) и функциональном программировании неизменяемый объект (неизменяемый [1] объект) — это объект , состояние которого не может быть изменено после его создания. [2] Это контрастирует с изменяемыми объектами (изменяемыми объектами), которые могут быть изменены после его создания. [3] В некоторых случаях объект считается неизменяемым, даже если некоторые внутренне используемые атрибуты изменяются, но состояние объекта выглядит неизменным с внешней точки зрения. Например, объект, который использует мемоизацию для кэширования результатов дорогостоящих вычислений, все еще может считаться неизменяемым объектом.

Строки и другие конкретные объекты обычно выражаются как неизменяемые объекты для улучшения читаемости и эффективности выполнения в OO-программировании. Неизменяемые объекты также полезны, поскольку они по своей сути потокобезопасны . [2] Другие преимущества заключаются в том, что их проще понимать и рассуждать о них, и они предлагают более высокую безопасность, чем изменяемые объекты. [2]

Концепции

Неизменяемые переменные

В императивном программировании значения, хранящиеся в переменных программы, содержимое которых никогда не меняется, называются константами , чтобы отличать их от переменных, которые могут быть изменены во время выполнения. Примерами служат коэффициенты перевода из метров в футы или значение числа Пи с несколькими знаками после запятой.

Поля, доступные только для чтения, могут быть вычислены во время работы программы (в отличие от констант, которые известны заранее), но никогда не изменяются после инициализации.

Слабая и сильная неизменяемость

Иногда говорят, что некоторые поля объекта являются неизменяемыми. Это означает, что нет способа изменить эти части состояния объекта, даже если другие части объекта могут быть изменяемыми ( слабо неизменяемыми ). Если все поля неизменяемы, то объект неизменяем. Если весь объект не может быть расширен другим классом, объект называется строго неизменяемым . [4] Это может, например, помочь явно обеспечить соблюдение определенных инвариантов относительно определенных данных в объекте, остающихся неизменными в течение всего срока службы объекта. В некоторых языках это делается с помощью ключевого слова (например, constв C++ , finalв Java ), которое обозначает поле как неизменяемое. Некоторые языки делают это наоборот: в OCaml поля объекта или записи по умолчанию неизменяемы и должны быть явно помечены как , mutableчтобы быть таковыми.

Ссылки на объекты

В большинстве объектно-ориентированных языков на объекты можно ссылаться с помощью ссылок . Примерами таких языков являются Java , C++ , C# , VB.NET и многие языки сценариев , такие как Perl , Python и Ruby . В этом случае имеет значение, может ли состояние объекта меняться, когда объекты совместно используются с помощью ссылок.

Ссылки на объекты и копирование

Если известно, что объект неизменяем, то предпочтительнее создать ссылку на него, а не копировать весь объект. Это делается для экономии памяти путем предотвращения дублирования данных и избежания вызовов конструкторов и деструкторов; это также приводит к потенциальному повышению скорости выполнения.

Метод копирования ссылок гораздо сложнее использовать для изменяемых объектов, поскольку если любой пользователь ссылки на изменяемый объект изменяет ее, все другие пользователи этой ссылки видят изменение. Если это не предполагаемый эффект, может быть сложно уведомить других пользователей, чтобы они отреагировали правильно. В таких ситуациях защитное копирование всего объекта, а не ссылки, обычно является простым, но дорогостоящим решением. Шаблон наблюдателя является альтернативным методом обработки изменений в изменяемых объектах.

Копирование при записи

Метод, который сочетает преимущества изменяемых и неизменяемых объектов и поддерживается напрямую почти во всем современном оборудовании, называется копирование при записи (COW). При использовании этого метода, когда пользователь просит систему скопировать объект, она вместо этого просто создает новую ссылку, которая по-прежнему указывает на тот же объект. Как только пользователь пытается изменить объект с помощью определенной ссылки, система создает настоящую копию, применяет к ней изменение и устанавливает ссылку для ссылки на новую копию. Другие пользователи не затрагиваются, поскольку они по-прежнему ссылаются на исходный объект. Поэтому в COW все пользователи, по-видимому, имеют изменяемую версию своих объектов, хотя в случае, если пользователи не изменяют свои объекты, сохраняются преимущества неизменяемых объектов в плане экономии места и скорости. Копирование при записи популярно в системах виртуальной памяти , поскольку позволяет им экономить место в памяти, при этом правильно обрабатывая все, что может сделать прикладная программа.

Интернирование

Практика постоянного использования ссылок вместо копий равных объектов известна как интернирование . Если используется интернирование, два объекта считаются равными тогда и только тогда, когда их ссылки, обычно представленные в виде указателей или целых чисел, равны. Некоторые языки делают это автоматически: например, Python автоматически интернирует короткие строки . Если алгоритм, реализующий интернирование, гарантированно делает это в каждом возможном случае, то сравнение объектов на равенство сводится к сравнению их указателей — существенный выигрыш в скорости в большинстве приложений. (Даже если алгоритм не гарантированно будет всеобъемлющим, все еще существует возможность быстрого улучшения случая, когда объекты равны и используют одну и ту же ссылку.) Интернирование, как правило, полезно только для неизменяемых объектов.

Безопасность потока

Неизменяемые объекты могут быть полезны в многопоточных приложениях. Несколько потоков могут работать с данными, представленными неизменяемыми объектами, не беспокоясь об изменении данных другими потоками. Поэтому неизменяемые объекты считаются более потокобезопасными, чем изменяемые объекты.

Нарушение неизменяемости

Неизменяемость не означает, что объект, хранящийся в памяти компьютера , не может быть записан. Скорее, неизменяемость — это конструкция времени компиляции , которая указывает, что программист может сделать через обычный интерфейс объекта, а не обязательно то, что он может сделать абсолютно (например, обойдя систему типов или нарушив корректность const в C или C++ ).

Детали, специфичные для конкретного языка

В 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 являются неизменяемыми.

Похожими шаблонами являются Immutable Interface и Immutable Wrapper.

В чистых функциональных языках программирования невозможно создавать изменяемые объекты без расширения языка (например, с помощью библиотеки изменяемых ссылок или интерфейса внешней функции ), поэтому все объекты являются неизменяемыми.

Ада

В языке Ada любой объект объявляется либо переменным (т. е. изменяемым; обычно это неявное значение по умолчанию), либо constant(т. е. неизменяемым) с помощью constantключевого слова.

 тип  Some_type  is  new  Integer ;  -- может быть чем-то более сложным  x :  const  Some_type :=  1 ;  -- неизменяемый  y :  Some_type ;  -- изменяемый

Параметры подпрограммы неизменяемы в режиме in и изменяемы в режимах in out и out .

 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 AnImmutableType { public readonly double _value ; public AnImmutableType ( double x ) { _value = x ; } public AnImmutableType Square () { return new AnImmutableType ( _value * _value ); } }                             

В C# есть записи, которые являются неизменяемыми. [8] [9]

запись Person ( string FirstName , string LastName );    

С++

В C++ реализация с корректным значением constCart позволила бы пользователю создавать экземпляры класса, а затем использовать их как const(неизменяемые) или как изменяемые, по желанию, предоставляя две различные версии метода items(). (Обратите внимание, что в C++ не обязательно — и фактически невозможно — предоставлять специализированный конструктор для constэкземпляров.)

класс Корзина { public : Корзина ( std :: vector < Элемент > элементы ) : элементы_ ( элементы ) {}        std :: vector < Элемент >& items () { return items_ ; } const std :: vector < Элемент >& items () const { return items_ ; }              int ComputeTotalCost () const { /* возвращает сумму цен */ }      private : std :: vector < Элемент > items_ ; };  

Обратите внимание, что если есть член данных, являющийся указателем или ссылкой на другой объект, то изменить объект, на который он указывает или на который ссылается, можно только в неконстантном методе.

C++ также обеспечивает абстрактную (в отличие от побитовой) неизменяемость с помощью mutableключевого слова, которое позволяет изменять переменную-член изнутри constметода.

класс Корзина { public : Корзина ( std :: vector < Элемент > элементы ) : элементы_ ( элементы ) {}        const std :: vector < Элемент >& items () const { return items_ ; }        int ComputeTotalCost () const { if ( total_cost_ ) { return * total_cost_ ; }          int total_cost = 0 ; for ( const auto & item : items_ ) { total_cost += item.Cost ( ) ; } total_cost_ = total_cost ; return total_cost ; }                     private : std :: vector < Item > items_ ; mutable std :: Optional < int > total_cost_ ; };     

Д

В D существуют два квалификатора типа , constи immutable, для переменных, которые не могут быть изменены. [10] В отличие от C++ const, Java finalи C# readonlyони транзитивны и рекурсивно применяются ко всему, что доступно через ссылки такой переменной. Разница между constи immutableзаключается в том, к чему они применяются: constявляется свойством переменной: могут законно существовать изменяемые ссылки на указанное значение, т. е. значение может фактически изменяться. Напротив, immutableявляется свойством указанного значения: значение и все, к чему можно транзитивно добраться из него, не могут измениться (без нарушения системы типов, что приводит к неопределенному поведению ). Любая ссылка на это значение должна быть помечена constили immutable. По сути, для любого неквалифицированного типа T, const(T)является непересекающимся объединением T(изменяемого) и immutable(T).

класс C { /*изменяемый*/ Объект mField ; константный Объект cField ; неизменяемый Объект iField ; }           

Для изменяемого Cобъекта mFieldв него можно записывать. Для const(C)объекта, mFieldкоторый нельзя изменить, он наследует const; iFieldпо-прежнему неизменяем, поскольку это более сильная гарантия. Для immutable(C), все поля неизменяемы.

В такой функции:

void func ( C m , const C c , immutable C i ) { /* внутри фигурных скобок */ }          

Внутри фигурных скобок cможет ссылаться на тот же объект m, что и , поэтому мутации в могут также mкосвенно изменяться . Также может ссылаться на тот же объект, что и , но поскольку значение тогда является неизменяемым, никаких изменений не происходит. Однако и не могут юридически ссылаться на тот же объект.ccimi

На языке гарантий mutable не имеет никаких гарантий (функция может изменить объект), constявляется только внешней гарантией того, что функция ничего не изменит, и immutableявляется двунаправленной гарантией (функция не изменит значение, и вызывающий объект не должен его изменять).

Значения, которые инициализируются constили immutableдолжны быть инициализированы прямым присваиванием в точке объявления или конструктором .

Поскольку constпараметры забывают, было ли значение изменяемым или нет, аналогичная конструкция, inout, действует, в некотором смысле, как переменная для информации об изменяемости. Функция типа const(S) function(const(T))возвращает const(S)типизированные значения для изменяемых, константных и неизменяемых аргументов. Напротив, функция типа inout(S) function(inout(T))возвращает Sдля изменяемых Tаргументов, const(S)для const(T)значений и immutable(S)для immutable(T)значений.

Приведение неизменяемых значений к изменяемым приводит к неопределенному поведению при изменении, даже если исходное значение происходит из изменяемого источника. Приведение изменяемых значений к неизменяемым может быть допустимым, если после этого не остается изменяемых ссылок. «Выражение может быть преобразовано из изменяемого (...) в неизменяемое, если выражение уникально и все выражения, на которые оно транзитивно ссылается, являются либо уникальными, либо неизменяемыми». [10] Если компилятор не может доказать уникальность, приведение может быть выполнено явно, и программист должен убедиться, что не существует изменяемых ссылок.

Тип stringявляется псевдонимом для immutable(char)[], т. е. типизированного фрагмента памяти неизменяемых символов. [11] Создание подстрок дешево, так как оно просто копирует и изменяет указатель и поле длины, и безопасно, так как базовые данные не могут быть изменены. Объекты типа const(char)[]могут ссылаться на строки, а также на изменяемые буферы.

Создание поверхностной копии константного или неизменяемого значения удаляет внешний слой неизменяемости: Копирование неизменяемой строки ( immutable(char[])) возвращает строку ( immutable(char)[]). Неизменяемый указатель и длина копируются, и копии изменяемы. Указанные данные не были скопированы и сохраняют свой квалификатор, в примере immutable. Их можно удалить, сделав более глубокую копию, например, с помощью dupфункции .

Ява

Классическим примером неизменяемого объекта является экземпляр класса StringJava

String s = "ABC" ; s . toLowerCase (); // Это ничего не даст!    

Метод toLowerCase()не изменяет данные "ABC", которые sсодержатся. Вместо этого создается новый объект String и ему придаются данные "abc" во время его создания. Метод возвращает ссылку на этот объект String toLowerCase(). Чтобы заставить String sсодержать данные "abc", необходим другой подход:

s = s .toLowerCase ( );  

Теперь String ссылается на новый объект String, содержащий "abc". В синтаксисе объявленияs класса String нет ничего , что делало бы его неизменяемым; скорее, ни один из методов класса String никогда не влияет на данные, которые содержит объект String, тем самым делая его неизменяемым.

Ключевое слово final( подробная статья ) используется при реализации неизменяемых примитивных типов и ссылок на объекты, [12] но оно не может само по себе сделать сами объекты неизменяемыми. Смотрите примеры ниже:

Переменные примитивного типа ( int, long, short, и т.д.) могут быть переназначены после определения. Этого можно избежать, используя final.

int i = 42 ; //int — примитивный тип i = 43 ; // OK       final 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

В JavaScript все примитивные типы (Undefined, Null, Boolean, Number, BigInt, String, Symbol) являются неизменяемыми, но пользовательские объекты, как правило, изменяемы.

function doSomething ( x ) { /* изменяет ли изменение x здесь оригинал? */ }; var str = 'a string' ; var obj = { an : 'object' }; doSomething ( str ); // строки, числа и типы bool неизменяемы, функция получает копию doSomething ( obj ); // объекты передаются по ссылке и могут быть изменены внутри function 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]      

Использование неизменяемого состояния стало растущей тенденцией в JavaScript с момента появления React , который отдает предпочтение Flux-подобным шаблонам управления состоянием, таким как Redux . [14]

Перл

В Perl можно создать неизменяемый класс с помощью библиотеки Moo, просто объявив все атрибуты доступными только для чтения:

пакет Immutable ; использовать Moo ;  имеет значение => ( is => 'ro' , # только для чтения default => 'data' , # можно переопределить, указав в конструкторе # значение: Immutable->new(value => 'something else'); );            1 ;

Раньше создание неизменяемого класса требовало двух шагов: во-первых, создание методов доступа (автоматически или вручную), которые предотвращают изменение атрибутов объекта, и, во-вторых, предотвращение прямого изменения данных экземпляра экземпляров этого класса (они обычно хранились в ссылке на хэш и могли быть заблокированы с помощью функции lock_hash класса Hash::Util):

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 ;                                       

Или с помощью вручную написанного метода доступа:

пакет Immutable ; использовать строгий ; использовать предупреждения ; использовать 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 ;                     

Питон

В Python некоторые встроенные типы (числа, булевы значения, строки, кортежи, frozensets) являются неизменяемыми, но пользовательские классы, как правило, изменяемы. Чтобы имитировать неизменяемость в классе, можно переопределить установку и удаление атрибутов, чтобы вызвать исключения:

class  ImmutablePoint : """Неизменяемый класс с двумя атрибутами 'x' и 'y'."""  __слоты__  =  [ 'x' ,  'y' ] def  __setattr__ ( self ,  * args ):  raise  TypeError ( "Невозможно изменить неизменяемый экземпляр." ) __делаттр__  =  __сетаттр__ def  __init__ ( self ,  x ,  y ):  # Мы больше не можем использовать self.value = value для хранения данных экземпляра  # поэтому мы должны явно вызвать суперкласс  super () . __setattr__ ( 'x' ,  x )  super () . __setattr__ ( 'y' ,  y )

Стандартные вспомогательные библиотеки collections.namedtuple и typing.NamedTuple, доступные с Python 3.6 и выше, создают простые неизменяемые классы. Следующий пример примерно эквивалентен приведенному выше, плюс некоторые функции, похожие на кортежи:

от  ввода  import  NamedTuple import  collectionsТочка  =  коллекции . namedtuple ( 'Точка' ,  [ 'x' ,  'y' ])# следующий код создает именованный кортеж, аналогичный указанному выше классу  Point ( NamedTuple ):  x :  int  y :  int

Представленные в Python 3.7, классы данных позволяют разработчикам эмулировать неизменяемость с замороженными экземплярами. Если замороженный класс данных создан, он dataclassesпереопределит __setattr__()и __delattr__()вызовет FrozenInstanceErrorпри вызове.

из  классов данных  импортировать  класс данных@dataclass ( frozen = True ) класс  Точка :  x :  целое  y :  целое

Ракетка

Racket существенно отличается от других реализаций Scheme , делая свой основной тип пар («cons cells») неизменяемым. Вместо этого он предоставляет параллельный изменяемый тип пар через mcons, mcarи set-mcar!т. д. Кроме того, поддерживаются многие неизменяемые типы, например, неизменяемые строки и векторы, и они широко используются. Новые структуры неизменяемы по умолчанию, если только поле или вся структура специально не объявлены изменяемыми:

( struct foo1 ( x y )) ; все поля неизменяемые ( struct foo2 ( x [ y #:mutable ])) ; одно изменяемое поле ( struct foo3 ( x y ) #:mutable ) ; все поля изменяемы              

Язык также поддерживает неизменяемые хеш-таблицы, реализованные функционально, и неизменяемые словари.

Ржавчина

Система владения Rust позволяет разработчикам объявлять неизменяемые переменные и передавать неизменяемые ссылки. По умолчанию все переменные и ссылки являются неизменяемыми. Изменяемые переменные и ссылки явно создаются с помощью mutключевого слова.

Константные элементы в Rust всегда неизменяемы.

// константные элементы всегда неизменяемы const ALWAYS_IMMUTABLE : bool = true ;   struct  Object { x : размер использования , y : размер использования , }   fn  main () { // явно объявляем изменяемую переменную let mut mutable_obj = Object { x : 1 , y : 2 }; mutable_obj . x = 3 ; // хорошо                пусть mutable_ref = & mut mutable_obj ; mutable_ref . x = 1 ; // хорошо         пусть immutable_ref = & mutable_obj ; immutable_ref . x = 3 ; // ошибка E0594        // по умолчанию переменные неизменяемы let immutable_obj = Object { x : 4 , y : 5 }; immutable_obj . x = 6 ; // ошибка E0596             пусть mutable_ref2 = & mut immutable_obj ; // ошибка E0596       пусть immutable_ref2 = & immutable_obj ; immutable_ref2 . x = 6 ; // ошибка E0594 }        

Скала

В Scala любая сущность (узко, привязка) может быть определена как изменяемая или неизменяемая: в объявлении можно использовать val(значение) для неизменяемых сущностей и var(переменная) для изменяемых. Обратите внимание, что даже если неизменяемая привязка не может быть переназначена, она все равно может ссылаться на изменяемый объект, и все еще возможно вызывать изменяющие методы для этого объекта: привязка неизменяема , но базовый объект может быть изменяемым.

Например, следующий фрагмент кода:

val maxValue = 100 вар currentValue = 1      

определяет неизменяемую сущность maxValue(целочисленный тип выводится во время компиляции) и изменяемую сущность с именем currentValue.

По умолчанию классы коллекций, такие как Listи Mapявляются неизменяемыми, поэтому методы обновления возвращают новый экземпляр, а не мутируют существующий. Хотя это может показаться неэффективным, реализация этих классов и их гарантии неизменяемости означают, что новый экземпляр может повторно использовать существующие узлы, что, особенно в случае создания копий, очень эффективно. [15] [ требуется лучший источник ]

Смотрите также

Ссылки

В статье содержится часть материала из книги «Perl Design Patterns Book».

  1. ^ "неизменяемое прилагательное - Определение, изображения, произношение и примечания по использованию - Оксфордский словарь для продвинутых учащихся на OxfordLearnersDictionaries.com". www.oxfordlearnersdictionaries.com .
  2. ^ abc Goetz et al. Java Concurrency in Practice . Addison Wesley Professional, 2006, Раздел 3.4. Неизменяемость
  3. ^ «6.005 — Разработка программного обеспечения».
  4. ^ Дэвид О'Мира (апрель 2003 г.). "Изменяемые и неизменяемые объекты: убедитесь, что методы не могут быть переопределены". Java Ranch . Получено 14.05.2012 . Предпочтительный способ — сделать класс final. Иногда это называют "сильной неизменяемостью". Это не позволяет кому-либо расширить ваш класс и случайно или намеренно сделать его изменяемым.
  5. ^ ab Bloch, Joshua (2018). "Effective Java: Programming Language Guide" (третье изд.). Addison-Wesley. ISBN 978-0134685991.
  6. ^ «Встроенные функции — документация Python v3.0». docs.python.org .
  7. ^ Скит, Джон (23 марта 2019 г.). C# in Depth . Мэннинг. ISBN 978-1617294532.
  8. ^ "Использование типов записей - Учебник по C# - C#". learn.microsoft.com . 14 ноября 2023 г. . Получено 23 февраля 2024 г. .
  9. ^ "Records - C# reference - C#". learn.microsoft.com . 25 мая 2023 г. . Получено 23 февраля 2024 г. .
  10. ^ ab D Спецификация языка § 18
  11. ^ Спецификация языка D § 12.16 (Термины массив и срез используются взаимозаменяемо.)
  12. ^ "Как создать неизменяемый класс и объект в Java – пример учебника". Javarevisited.blogspot.co.uk. 2013-03-04 . Получено 2014-04-14 .
  13. ^ "Неизменяемые объекты". javapractices.com . Получено 15 ноября 2012 г. .
  14. ^ «Неизменяемость в JavaScript: противоположная точка зрения». Desalasworks .
  15. ^ "Scala 2.8 Collections API – Конкретные неизменяемые классы коллекций". Scala-lang.org . Получено 2014-04-14 .

Внешние ссылки