Реальной защиты от преднамеренно вредоносного кода, работающего в той же JVM, в обычных API, например, API-интерфейсе сбора, нет.
Как легко показать:
public static void main(String[] args) throws InterruptedException {
Object[] array = { "foo", "bar", "baz", "and", "another", "string" };
array[array.length - 1] = new Object() {
@Override
public String toString() {
Collections.shuffle(Arrays.asList(array));
return "string";
}
};
doThing(new ArrayList<String>() {
@Override public Object[] toArray() {
return array;
}
});
}
public static void doThing(List<String> strs) {
List<String> newStrs = new ArrayList<>(strs);
System.out.println("made a safe copy " + newStrs);
for(int i = 0; i < 10; i++) {
System.out.println(newStrs);
}
}
made a safe copy [foo, bar, baz, and, another, string]
[bar, and, string, string, another, foo]
[and, baz, bar, string, string, string]
[another, baz, and, foo, bar, string]
[another, bar, and, foo, string, and]
[another, baz, string, another, and, foo]
[string, and, another, foo, string, foo]
[baz, string, foo, and, baz, string]
[bar, another, string, and, another, baz]
[bar, string, foo, string, baz, and]
[bar, string, bar, another, and, foo]
Как вы можете видеть, ожидая, что List<String>
не гарантирует фактического получения списка String
экземпляров. Из-за стирания типов и необработанных типов на стороне реализации списка даже исправить невозможно.
Другая вещь, которую вы можете обвинить в конструкторе ArrayList
, это доверие к входящей коллекции. toArray
реализация. TreeMap
не затрагивается таким же образом, но только потому, что при передаче массива нет такого выигрыша в производительности, как в конструкции ArrayList
. Ни один из классов не гарантирует защиту в конструкторе.
Обычно нет смысла пытаться писать код, предполагающий намеренно вредоносный код за каждым углом. Слишком много всего можно сделать, чтобы защититься от всего. Такая защита полезна только для кода, который действительно инкапсулирует действие, которое может дать злоумышленнику доступ к чему-либо, к которому он уже не может получить доступ без этого кода.
Если вам нужна безопасность для конкретного кода, используйте
public static void doThing(List<String> strs) {
String[] content = strs.toArray(new String[0]);
List<String> newStrs = new ArrayList<>(Arrays.asList(content));
System.out.println("made a safe copy " + newStrs);
for(int i = 0; i < 10; i++) {
System.out.println(newStrs);
}
}
Тогда вы можете быть уверены, что newStrs
содержит только строки и не может быть изменен другим кодом после его создания.
Или используйте List<String> newStrs = List.of(strs.toArray(new String[0]));
с Java 9 или более новый
Обратите внимание, что Java 10 List.copyOf(strs)
делает то же самое, но в документации не говорится, что он гарантированно не доверяет методу toArray
входящей коллекции. Так что вызов List.of(…)
, который определенно сделает копию в случае, если он возвращает список на основе массива, является более безопасным.
Поскольку ни один вызывающий объект не может изменить способ, массивы работают, сбрасывая входящую коллекцию в массив, а затем заполняя новую коллекцию им, всегда будет делать копию безопасной. Поскольку коллекция может содержать ссылку на возвращенный массив, как показано выше, она может изменить ее на этапе копирования, но не может повлиять на копию в коллекции.
Поэтому любые проверки согласованности следует выполнять после конкретный элемент был извлечен из массива или из результирующей коллекции в целом.