Раніше я казав, що безпечно копіювати колекцію, роблячи щось на кшталт:
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з певним типом. Ми завжди на цьому закінчуємося. Узагальнені колекції навіть не знають свого фактичного типу елементів, тому їх конструктори копій не можуть забезпечити подібну функціональність. Звичайно, ви можете відкласти будь-яку перевірку до прямого використання, але тоді я не знаю, на що спрямовані ваші запитання. Вам не потрібна "смислова цілісність", коли ви добре перевіряєте і не працюєте безпосередньо перед використанням елементів.