Команда Java проделала огромную работу по устранению барьеров для функционального программирования в Java 8. В частности, изменения в коллекциях java.util делают большую работу по объединению преобразований в очень быстрые потоковые операции. Учитывая, как хорошо они проделали работу, добавив первоклассные функции и функциональные методы к коллекциям, почему они полностью не смогли обеспечить неизменные коллекции или даже неизменные интерфейсы коллекций?
Без изменения какого-либо существующего кода команда Java может в любое время добавить неизменяемые интерфейсы, такие же как изменяемые, минус методы "set" и заставить существующие интерфейсы расширяться от них, например так:
ImmutableIterable
____________/ |
/ |
Iterable ImmutableCollection
| _______/ / \ \___________
| / / \ \
Collection ImmutableList ImmutableSet ImmutableMap ...
\ \ \_________|______________|__________ |
\ \___________|____________ | \ |
\___________ | \ | \ |
List Set Map ...
Конечно, такие операции, как List.add () и Map.put (), в настоящее время возвращают логическое или предыдущее значение для данного ключа, чтобы указать, была ли операция успешной или неудачной. Неизменяемые коллекции должны обрабатывать такие методы как фабрики и возвращать новую коллекцию, содержащую добавленный элемент, что несовместимо с текущей подписью. Но это можно обойти, используя другое имя метода, например, ImmutableList.append () или .addAt () и ImmutableMap.putEntry (). Результирующая многословность будет более чем перевешена преимуществами работы с неизменяемыми коллекциями, а система типов предотвратит ошибки вызова неправильного метода. Со временем старые методы могут устареть.
Победы неизменных коллекций:
- Простота - рассуждения о коде проще, когда базовые данные не меняются.
- Документация - если метод принимает неизменяемый интерфейс коллекции, вы знаете, что он не собирается изменять эту коллекцию. Если метод возвращает неизменную коллекцию, вы знаете, что не можете ее изменить.
- Параллелизм - неизменяемые коллекции можно безопасно разделять между потоками.
Как человеку, который пробовал языки, которые предполагают неизменность, очень трудно вернуться на Дикий Запад с безудержной мутацией. Коллекции Clojure (абстракция последовательностей) уже имеют все, что обеспечивают коллекции Java 8, плюс неизменяемость (хотя, возможно, использование дополнительной памяти и времени из-за синхронизированных списков ссылок вместо потоков). В Scala есть как изменяемые, так и неизменяемые коллекции с полным набором операций, и, хотя эти операции нетерпеливы, вызов .iterator дает ленивое представление (и существуют другие способы их ленивой оценки). Я не понимаю, как Java может продолжать конкурировать без неизменных коллекций.
Может кто-нибудь указать мне на историю или обсуждение этого? Конечно, это где-то публично.
источник
const
коллекцииCollections.unmodifiable*()
. но не относитесь к ним как к неизменным, когда это не такImmutableList
в этой диаграмме, люди могут перейти в изменчивыйList
? Нет, это очень плохое нарушение LSP.Ответы:
Потому что неизменные коллекции абсолютно требуют обмена, чтобы быть пригодными для использования. В противном случае каждая отдельная операция помещает в кучу целый другой список. Языки, которые являются полностью неизменяемыми, такие как Haskell, генерируют удивительное количество мусора без агрессивной оптимизации и обмена. Наличие коллекции, которую можно использовать только с <50 элементами, не стоит помещать в стандартную библиотеку.
Более того, неизменяемые коллекции часто имеют принципиально иные реализации, чем их изменяемые аналоги. Например
ArrayList
, эффективный неизменяемый объектArrayList
вообще не будет массивом! Он должен быть реализован с помощью сбалансированного дерева с большим коэффициентом ветвления, Clojure использует 32 IIRC. Делать изменчивые коллекции «неизменяемыми», просто добавляя функциональное обновление, является ошибкой производительности, равно как и утечка памяти.Кроме того, совместное использование не является жизнеспособным в Java. Java предоставляет слишком много неограниченных хуков к изменчивости и равенству ссылок, чтобы сделать совместное использование «просто оптимизацией». Возможно, вас немного раздражит, если вы сможете изменить элемент в списке и понять, что вы только что изменили элемент в других 20 версиях этого списка.
Это также исключает огромные классы очень важных оптимизаций для эффективной неизменяемости, совместного использования, объединения потоков, как вы это называете, изменчивость нарушает его. (Это было бы хорошим слоганом для евангелистов ФП)
источник
String
вStringBuffer
, манипулировать им, а затем копировать данные в новый неизменяемыйString
. Использование такого шаблона с наборами и списками может быть так же хорошо, как использование неизменяемых типов, которые предназначены для облегчения производства слегка измененных экземпляров, но могут быть еще лучше ...Изменяемая коллекция не является подтипом неизменной коллекции. Вместо этого изменчивые и неизменные коллекции являются родственными потомками читаемых коллекций. К сожалению, понятия «читабельный», «только для чтения» и «неизменяемый», похоже, сливаются воедино, хотя они означают три разные вещи.
Доступный для чтения базовый класс коллекции или тип интерфейса обещает, что можно читать элементы, и не предоставляет никаких прямых средств изменения коллекции, но не гарантирует, что код, получающий ссылку, не может привести или манипулировать ею таким образом, чтобы разрешить изменение.
Интерфейс коллекции только для чтения не включает никаких новых членов, но должен быть реализован только классом, который обещает, что нет способа манипулировать ссылкой на него таким образом, чтобы изменить коллекцию или получить ссылку на что-либо это могло бы сделать это. Это, однако, не обещает, что коллекция не будет изменена чем-то другим, имеющим ссылку на внутренние компоненты. Обратите внимание, что интерфейс коллекции, доступный только для чтения, может не быть в состоянии предотвратить реализацию изменяемыми классами, но может указывать, что любая любая реализация или класс, производный от реализации, который допускает мутацию, должны рассматриваться как «незаконная» реализация или производная реализации ,
Неизменяемая коллекция - это коллекция, которая всегда будет содержать одни и те же данные, пока существует какая-либо ссылка на них. Любая реализация неизменяемого интерфейса, который не всегда возвращает одни и те же данные в ответ на конкретный запрос, нарушается.
Иногда полезно иметь сильно связанные изменяемые и неизменяемые типы коллекций , которые и реализуют или вытекают из того же «читаемого» типа и имеют читаемые типа включают в себя
AsImmutable
,AsMutable
иAsNewMutable
методу. Такой дизайн может позволить коду, который хочет сохранить данные в коллекции для вызоваAsImmutable
; этот метод сделает защитную копию, если коллекция изменчива, но пропустите копию, если она уже неизменна.источник
int
, с методамиgetLength()
иgetItemAt(int)
.ReadableIndexedIntSequence
, можно создать экземпляр неизменяемого типа на основе массива, скопировав все элементы в массив, но предположим, что конкретная реализация просто вернула 16777216 длины и((long)index*index)>>24
для каждого элемента. Это была бы законная неизменная последовательность целых чисел, но копирование ее в массив было бы огромной тратой времени и памяти.asImmutable
экземпляра определенной выше последовательности и стоимость конструирования неизменяемая копия на основе массива. Я бы сказал, что иметь интерфейсы, определенные для таких целей, вероятно, лучше, чем пытаться использовать специальные подходы; ИМХО, самая большая причина ...Java Collections Framework предоставляет возможность создавать версию коллекции только для чтения с помощью шести статических методов в классе java.util.Collections :
Как кто-то указал в комментариях к исходному вопросу, возвращенные коллекции не могут считаться неизменными, потому что даже если коллекции нельзя изменить (никакие члены не могут быть добавлены или удалены из такой коллекции), фактические объекты, на которые ссылается коллекция могут быть изменены, если их тип объекта позволяет это.
Однако эта проблема останется вне зависимости от того, возвращает ли код один объект или неизменяемую коллекцию объектов. Если тип позволяет мутировать его объекты, то это решение было принято при разработке типа, и я не вижу, как изменение JCF могло бы изменить это. Если важна неизменность, то члены коллекции должны быть неизменяемого типа.
источник
immutableList
другие фабричные методы, которые возвращали бы оболочку, доступную только для чтения, вокруг копии переданного объекта. список, если переданный список не был уже неизменным . Было бы легко создать пользовательские типы, подобные этому, но для одной проблемы: уjoesCollections.immutableList
метода не было бы способа распознать, что ему не нужно копировать возвращаемый объектfredsCollections.immutableList
.Это очень хороший вопрос. Мне нравится развлекать идею о том, что из всего кода, написанного на Java и работающего на миллионах компьютеров по всему миру, каждый день, круглосуточно, около половины всех тактов должно быть потрачено впустую, делая только безопасные копии коллекций, которые возвращается функциями. (И сбор мусора в этих коллекциях за миллисекунды после их создания.)
Процент Java-программистов знают о существовании
unmodifiableCollection()
семейства методовCollections
класса, но даже среди них многие просто не беспокоятся об этом.И я не могу винить их: интерфейс, который притворяется читаемым и пишущим, но выдает «
UnsupportedOperationException
если», если вы допустите ошибку, вызывая любой из его методов «записи», - это довольно злая вещь!Теперь интерфейс, подобный
Collection
которому будет отсутствоватьadd()
,remove()
иclear()
методы не будут интерфейсом «ImmutableCollection»; это будет интерфейс «UnmodifiableCollection». Фактически, никогда не может быть интерфейса «ImmutableCollection», потому что неизменность является природой реализации, а не характеристикой интерфейса. Я знаю, это не очень ясно; позволь мне объяснить.Предположим, кто-то вручает вам такой интерфейс коллекции только для чтения; безопасно ли передавать его в другой поток? Если бы вы точно знали, что это действительно неизменная коллекция, тогда ответом будет «да»; К сожалению, так как интерфейс, вы не знаете , как это реализовано, так что ответ должен быть не : для всех вы знаете, это может быть неизменяемым (для вас) вида коллекции , которая на самом деле изменяемая, (например, то, что вы получаете
Collections.unmodifiableCollection()
), поэтому попытка чтения из него, пока другой поток его модифицирует, приведет к чтению поврежденных данных.Итак, по сути, вы описали набор не «неизменяемых», а «неизменяемых» интерфейсов коллекций. Важно понимать, что «немодифицируемый» просто означает, что любой, у кого есть ссылка на такой интерфейс, не может изменять базовую коллекцию, и они предотвращаются просто потому, что в интерфейсе отсутствуют какие-либо методы модификации, а не потому, что базовая коллекция обязательно является неизменной. Основная коллекция вполне может быть изменчивой; Вы не знаете и не можете это контролировать.
Чтобы иметь неизменные коллекции, они должны быть классами , а не интерфейсами!
Эти неизменяемые классы коллекций должны быть окончательными, поэтому, когда вам дается ссылка на такую коллекцию, вы точно знаете, что она будет вести себя как неизменяемая коллекция, независимо от того, что вы или кто-либо другой, кто имеет ссылку на нее, может делать с этим.
Таким образом, чтобы иметь полный набор коллекций на языке Java (или на любом другом декларативном императивном языке), нам потребуется следующее:
Набор неизменяемых коллекционных интерфейсов .
Набор интерфейсов изменяемой коллекции , расширяющих неизменяемые.
Набор классов изменяемой коллекции, реализующих изменяемые интерфейсы, а также, кроме того, немодифицируемые интерфейсы.
Набор неизменяемых классов коллекций , реализующих неизменяемые интерфейсы, но в основном передаваемых как классы, чтобы гарантировать неизменность.
Я реализовал все вышеперечисленное для удовольствия, и я использую их в проектах, и они работают как шарм.
Причина, по которой они не являются частью среды выполнения Java, вероятно, заключается в том, что считалось, что это будет слишком / слишком сложно / слишком сложно для понимания.
Лично я думаю, что то, что я описал выше, даже недостаточно; Еще одна вещь, которая кажется необходимой, это набор изменяемых интерфейсов и классов для структурной неизменности . (Который может быть просто назван "Жестким", потому что префикс "StructurallyImmutable" слишком длинный).
источник
List<T> add(T t)
- все методы-мутаторы должны возвращать новую коллекцию, которая отражает изменение. 2. К счастью или хуже, интерфейсы часто представляют собой контракт в дополнение к подписи. Сериализуемый является одним из таких интерфейсов. Точно так же Comparable требует, чтобы вы правильно реализовали свойcompareTo()
метод для правильной работы и в идеале были совместимы сequals()
иhashCode()
.add()
. Но я полагаю, что если бы методы-мутаторы были добавлены в неизменяемые классы, то они должны были бы также возвращать неизменяемые классы. Так что, если там скрывается проблема, я ее не вижу.Suppose someone hands you such a read-only collection interface; is it safe to pass it to another thread?
Предположим, кто-то передает вам экземпляр изменяемого интерфейса коллекции. Безопасно ли вызывать какой-либо метод для него? Вы не знаете, что реализация не зацикливается вечно, не генерирует исключение или полностью игнорирует контракт интерфейса. Почему двойной стандарт специально для неизменных коллекций?SortedSet
на подклассы набора с несоответствующей реализацией. Или мимоходом непоследовательнымComparable
. При желании можно сломать почти все. Я думаю, именно это @Doval подразумевал под "двойными стандартами".Неизменяемые коллекции могут быть глубоко рекурсивными, по сравнению с другими, и не чрезмерно неэффективными, если равенство объектов осуществляется с помощью secureHash. Это называется меркльский лес. Это может быть для каждой коллекции или внутри их частей, например (самобалансирующееся двоичное) дерево AVL для отсортированной карты.
Если все java-объекты в этих коллекциях не имеют уникального идентификатора или какой-либо цепочки битов для хэширования, коллекция не имеет хэша для уникального имени.
Пример: на моем ноутбуке 4x1,6 ГГц я могу запускать 200K sha256s в секунду наименьшего размера, который умещается в 1 цикле хеширования (до 55 байт), по сравнению с 500K-операциями HashMap или 3M-операциями в хеш-таблице long. 200K / log (collectionSize) новых коллекций в секунду достаточно быстры для некоторых вещей, где важна целостность данных и анонимная глобальная масштабируемость.
источник
Представление. Коллекции по своей природе могут быть очень большими. Копирование 1000 элементов в новую структуру с 1001 элементом вместо вставки одного элемента просто ужасно.
Параллелизм. Если у вас запущено несколько потоков, они могут захотеть получить текущую версию коллекции, а не версию, которая была передана 12 часов назад, когда поток начался.
Место хранения. С неизменными объектами в многопоточной среде вы можете получить десятки копий «одного и того же» объекта в разные моменты его жизненного цикла. Не имеет значения для объекта «Календарь» или «Дата», но когда он содержит коллекцию из 10 000 виджетов, это убьет вас.
источник