В прошлом я говорил, что для безопасного копирования коллекции сделайте что-то вроде:
public static void doThing(List<String> strs) {
List<String> newStrs = new ArrayList<>(strs);
или
public static void doThing(NavigableSet<String> strs) {
NavigableSet<String> newStrs = new TreeSet<>(strs);
Но действительно ли эти «копирующие» конструкторы, подобные статические методы создания и потоки действительно безопасны и где указаны правила? Под безопасностью я подразумеваю базовые гарантии семантической целостности, предлагаемые языком Java и коллекциями, которые применяются против злонамеренного вызывающего, при условии, что они подкреплены разумными данными SecurityManager
и что в них нет недостатков.
Я доволен метод метания ConcurrentModificationException
, NullPointerException
, IllegalArgumentException
, ClassCastException
и т.д., или , возможно , даже висит.
Я выбрал String
в качестве примера аргумент неизменного типа. На этот вопрос меня не интересуют глубокие копии коллекций изменяемых типов, которые имеют свои собственные ошибки.
(Чтобы было ясно, я посмотрел исходный код OpenJDK и у меня есть какой-то ответ за ArrayList
и TreeSet
.)
источник
NavigableSet
и другиеComparable
основанные коллекции могут иногда обнаруживать, что класс не реализуетсяcompareTo()
правильно, и генерировать исключение. Немного неясно, что вы подразумеваете под ненадежными аргументами. Вы имеете в виду, что злодей создает коллекцию плохих строк, и когда вы копируете их в свою коллекцию, происходит что-то плохое? Нет, структура коллекций довольно прочная, она существует примерно с 1.2.HashSet
(и все другие коллекции хеширования в целом), полагаясь на правильность / целостностьhashCode
реализации элементов,TreeSet
иPriorityQueue
зависеть отComparator
(и вы даже не можете создает эквивалентную копию без принятия пользовательского компаратора, если таковой имеется),EnumSet
доверяет целостности определенногоenum
типа, который никогда не проверяется после компиляции, поэтому файл класса, не созданный с помощьюjavac
или созданный вручную, может подорвать его.new TreeSet<>(strs)
гдеstrs
находитсяNavigableSet
. Это не массовая копия, поскольку в результатеTreeSet
будет использован компаратор источника, который даже необходим для сохранения семантики. Если вам достаточно просто обработать содержащиеся элементы,toArray()
это путь; он даже будет поддерживать порядок итераций. Когда у вас все в порядке с «взять элемент, проверить элемент, использовать элемент», вам даже не нужно делать копию. Проблемы начинаются, когда вы хотите проверить все элементы, после чего используются все элементы. Тогда вы не можете доверятьTreeSet
копии с пользовательским компараторомcheckcast
для каждого элемента, имеетtoArray
определенный тип. Мы всегда на этом заканчиваем. Родовые коллекции даже не знают своего фактического типа элемента, поэтому их конструкторы копирования не могут предоставить аналогичную функциональность. Конечно, вы можете отложить проверку до правильного использования, но тогда я не знаю, на что направлены ваши вопросы. Вам не нужна «семантическая целостность», когда вы хорошо справляетесь с проверкой и сбоем непосредственно перед использованием элементов.Ответы:
Нет реальной защиты от намеренно вредоносного кода, работающего в той же JVM в обычных API, таких как API Collection.
Как легко можно продемонстрировать:
Как вы можете видеть, ожидание
List<String>
не гарантирует получение спискаString
экземпляров. Из-за стирания типов и необработанных типов на стороне реализации списка даже исправить невозможно.Другая вещь, за которую вы можете винить
ArrayList
конструктора, это доверие к реализации входящей коллекцииtoArray
.TreeMap
не подвержен такому же влиянию, но только потому, что при передаче массива нет такого выигрыша в производительности, как в конструкцииArrayList
. Ни один класс не гарантирует защиту в конструкторе.Обычно нет смысла пытаться писать код, предполагающий намеренно вредоносный код на каждом шагу. Слишком много всего можно сделать, чтобы защититься от всего. Такая защита полезна только для кода, который действительно инкапсулирует действие, которое может дать злоумышленнику доступ к чему-либо, к которому он уже не может получить доступ без этого кода.
Если вам нужна безопасность для конкретного кода, используйте
Тогда вы можете быть уверены, что
newStrs
он содержит только строки и не может быть изменен другим кодом после его создания.Или используйте
List<String> newStrs = List.of(strs.toArray(new String[0]));
с Java 9 или новее.Обратите внимание, что Java 10
List.copyOf(strs)
делает то же самое, но в документации не говорится, что он не будет доверятьtoArray
методу входящей коллекции . Поэтому вызовList.of(…)
, который обязательно сделает копию в случае, если он возвращает список на основе массива, безопаснее.Поскольку никакой вызывающий объект не может изменить способ, массивы работают, выгрузив входящую коллекцию в массив, а затем заполнив новую коллекцию, всегда сделайте копию безопасной. Поскольку коллекция может содержать ссылку на возвращенный массив, как показано выше, она может изменить ее на этапе копирования, но это не может повлиять на копию в коллекции.
Таким образом, любые проверки согласованности должны выполняться после того, как конкретный элемент был извлечен из массива или из результирующей коллекции в целом.
источник
AccessController.doPrivileged(…)
и т. Д. Но длинный список ошибок, связанных с безопасностью апплета, дает нам подсказку, почему эта технология была заброшена ...new ArrayList<>(…)
качестве конструктора копирования вполне допустимо при условии правильной реализации коллекции. Вы не обязаны исправлять проблемы безопасности, когда уже слишком поздно. Как насчет скомпрометированного оборудования? Операционная система? Как насчет многопоточности?List.copyOf(strs)
не полагается на правильность поступающей коллекции в этом отношении, по очевидной цене.ArrayList
это разумный компромисс на каждый день.toArray()
себя, потому что массивы не могут иметь переопределенного поведения, а затем создавать коллекционную копию массива, напримерnew ArrayList<>(Arrays.asList( strs.toArray(new String[0])))
илиList.of(strs.toArray(new String[0]))
. У обоих также есть побочный эффект применения типа элемента. Лично я не думаю, что они когда-либо позволятcopyOf
скомпрометировать неизменные коллекции, но альтернативы в ответе есть.Я бы предпочел оставить эту информацию в комментариях, но у меня недостаточно репутации, извините :) Я постараюсь объяснить это настолько многословно, насколько смогу.
Вместо чего-то вроде
const
модификатора, используемого в C ++ для обозначения функций-членов, которые не должны изменять содержимое объекта, в Java изначально использовалась концепция «неизменяемости». Инкапсуляция (или OCP, принцип Open-Closed) должна была защищать от любых неожиданных мутаций (изменений) объекта. Конечно, API отражения идет вокруг этого; прямой доступ к памяти делает то же самое; это больше о стрельбе по собственной ноге :)java.util.Collection
сам по себе является изменяемым интерфейсом: у него естьadd
метод, который должен модифицировать коллекцию. Конечно, программист может заключить коллекцию во что-то, что выкинет ... и все исключения во время выполнения произойдут, потому что другой программист не смог прочитать javadoc, который ясно говорит, что коллекция неизменна.Я решил использовать
java.util.Iterable
тип, чтобы выставить неизменную коллекцию в моих интерфейсах. СемантическиIterable
не имеет такой характеристики коллекции, как «изменчивость». Тем не менее, вы (скорее всего) сможете изменять базовые коллекции с помощью потоков.JIC, для показа карт в неизменном виде
java.util.Function<K,V>
можно использовать (get
метод map соответствует этому определению)источник
Iterator
тогда, то это фактически вызывает поэлементное копирование, но это не хорошо. ИспользованиеforEachRemaining
/forEach
, очевидно, будет полной катастрофой. (Я также должен упомянуть, что уIterator
него естьremove
метод.)Iterable
с тем, что я не являюсь неизменным, но не вижу проблемforEach*
)