Оглавление

Java Collections 1

core java — part I, chapters 9.4–9.6

Views and Wrappers

View — это коллекция, которая достаётся из какого-то collections framework объекта, и позволяет манипулировать исходной коллекцией через свои методы. (Например, если достать множество ключей из Map через метод keySet(), то удаление ключей из key set повлечёт удаление ключей из отображения. Кстати, добавлять так новые ключи нельзя, эта keySet() коллекция просто выбросит UnsupportedOperationException.)

Легковесные оболочки для коллекций. Приводит в пример:

Unmodifiable views. Понянтно, что из коллекций можно доставать всякие view’ы вроде там какого-то subrange из списка или подмножества, и их мутация будет как-то влиять на оригинальную коллекцию. Но иногда так бывает, что вот все эти прикольные свойства view’ов не нужны и мы просто хотим достать, например, подмножество. В таком случае нужно:

Замечание про equals для unmodifiableCollection(Collection). Так как на этом уровне абстракции нельзя корректно определить равенство двух коллекций, то UnmodifiableCollection‘ы сравниваются просто по ссылке. Аналогично hashCode() вернёт не хеш коллекции внутри обёртки, а просто хеш, основанный на ссылке.

Synchronized views. Можно получить синхронизированную коллекцию из любой коллекции с помощью методов ` Collections.synchronizedSomething(Something)`. Тогда, если два треда одновременно пытаются получить доступ к коллекции, то каждый вызов метода будет завершён прежде, чем кто-либо воспользуется коллекцией.

Checked views. Это довольно странная штука: это обёртка для коллекции, которая убеждается в том, что добавляемые в неё элементы имеют правильный тип. Достаётся через Collections.checkedSomething. При этом, очевидно, из-за type erasure это не будет работать с коллекциями из генерик типов, потому что в рантайме Pair<String> не отличим от Pair<Integer>, так что от такого тут защиты нет. Плюс в том, что ошибка выявляется прямо в проблемном месте, а не, когда ты пытаешься достать элемент из коллекции. Видимо, это может быть полезно, когда работаешь с легаси кодом, но с учётом выше указанной проблем с генерик типами, выглядит как-то так себе.

Выбрасывать UnsupportedOperationException в реализациях своих коллекций — это тупо. В случае с collections framework’ом — это вынужденная необходимость, иначе пришлось бы создавать кучу дополнительных интерфейсов (коллекция, которую нельзя изменять, коллекция, в которую нельзя добавлять, но можно удалять (key set для отображений, например), коллекция, которую можно изменять), и это бы всё слишком сильно усложнило. Однако в общем случае, если класс реализует интерфейс, то по сути это означает, что он обязуется так или иначе реализовать всем указанные методы. Если с этим возникают проблемы, то стоит задуматься о том, должен ли данный класс реализовывать данный интерфейс или вообще нужны ли некоторые методы.

Algorithms

Реализация алгоритмов для коллекций. Когда дело доходит для реализации каких-то алгоритмов на коллекциях, то лучше всего реализовывать их для максимально абстрактных коллекций, насколько это возможно, чтобы не надо было лишний раз переписывать код заново для разных более конкретных коллекций. И при реализации нужно думать о наиболее абстрактных инструментах взаимодействия с коллекциями. То есть, например, если требуется найти максимальный элемент в коллекции, то следует реализовать его для Collection<? extends Comparable> через итераторы, потому что все коллекции являются Iterable (а не через какие-нибудь random access, например, или ещё что-то такое).

Comparator.reverseOrder(). Кстати, я как-то не знал о таком, это просто компаратор для двух Comparable объектов, который пользуется их сравнимостью и просто возвращает c2.compareTo(c1) вместо c1.compareTo(c2).

Простые алгоритмы, реализованные в классе Collections. Не знал о некоторых из тех, что там есть:

Use interfaces rather than concrete implementation when designing algorithms on collections. В случае передаваемых значений, чтобы сделать более общий алгоритм, который бы работал на любых конкретных реализациях каких-то интерфейсов, в случае с возвращаемыми значениями — чтобы, если ты потом передумаешь и захочешь возвращать какую-то другую конкретную коллекцию, то не пришлось бы менять свой API.

Тут, кстати, опять всплывает та дурацкая штука с тем, что, например, RandomAccess — это маркерный интерфейс, так что в реализации какого-нибудь shuffle придётся сделать дополнительную проверку на это, и, возможно, использовать другую реализацию или скопировать список в массив. Короче, нужно помнить о таких мелочах. Возможно, некоторые из них не удастся предотвратить, и придётся явно указать в API, что, например метод sort не может работать с unmodifiable коллекциями.

Возвращение коллекций, которые являются instance field’ами. С тем, чтобы возвращать коллекции из каких-то классов, кстати, есть проблема в том, что если надо уберечь возвращённую коллекцию от мутаций, то придётся возвращать либо unmodifiable wrapper для коллекции, либо её копию. Но тут нужно помнить о том, что если возвращаешь unmodifiable wrapper, то он зависит от оригинальной коллекции, и изменяется вместе с её изменениями. А ещё можно возвращать Iterable по коллекции вместо самой коллекции, и это, наверное, лучший способ, в том числе в плане производительности.

Передача коллекций в качестве аргумента в методе. Тут проблема с изменениями коллекций стоит не так резко, потому что тебе, как разработчику, просто достаточно не изменять переданную коллекцию, если этого не требует метод по своему смыслу и если это не указано в API. Однако в некоторых случаях всё равно удобнее принимать на вход не коллекцию, а итератор или Function<Key, Value>. Так точно метод не сможет изменить коллекцию, а ещё в некоторых случаях не придётся создавать временные коллекции. Например, если есть коллекция из каких-то элементов, а метод на вход принимает коллекцию не самих элементов, а их полей, то лучше будет передать итератор, ну или вот етот Function<Key, Value>, если у исходной коллекции есть случайный доступ. Это будет лучше в том числе и в плане производительности (original post, benchmark).

Legacy Collections

До того, как появилось Collections API со всеми етими генерик коллекциями, в джавве уже были кое-какие реализации всех основных коллекций, которые потом были интегрированы внутрь Collections API.

image-20200329205238590

Здесь: