No passado, eu disse para copiar com segurança uma coleção, algo como:
public static void doThing(List<String> strs) {
List<String> newStrs = new ArrayList<>(strs);
ou
public static void doThing(NavigableSet<String> strs) {
NavigableSet<String> newStrs = new TreeSet<>(strs);
Mas esses construtores de "cópia", métodos e fluxos de criação estática semelhantes, são realmente seguros e onde as regras são especificadas? Por segurança, quero dizer, são as garantias básicas de integridade semântica oferecidas pela linguagem Java e coleções impostas a um chamador mal-intencionado, assumindo o backup de um razoável SecurityManager
e que não há falhas.
Estou feliz com o lançamento método ConcurrentModificationException
, NullPointerException
, IllegalArgumentException
, ClassCastException
, etc., ou talvez mesmo pendurado.
Eu escolhi String
como exemplo de um argumento de tipo imutável. Para esta pergunta, não estou interessado em cópias profundas para coleções de tipos mutáveis que têm suas próprias dicas.
(Para ser claro, observei o código-fonte do OpenJDK e tenho algum tipo de resposta para ArrayList
e TreeSet
.)
fonte
NavigableSet
outrasComparable
coleções baseadas podem às vezes detectar se uma classe não é implementadacompareTo()
corretamente e gerar uma exceção. Não está claro o que você quer dizer com argumentos não confiáveis. Você quer dizer que um malfeitor cria uma coleção de Strings ruins e quando você as copia para sua coleção algo ruim acontece? Não, a estrutura de coleções é bastante sólida, existe desde a versão 1.2.HashSet
(e todas as outras coleções de hash em geral) depende da correção / integridade dahashCode
implementação dos elementosTreeSet
ePriorityQueue
depende daComparator
(e você nem pode crie uma cópia equivalente sem aceitar o comparador personalizado, se houver),EnumSet
confie na integridade doenum
tipo específico que nunca é verificado após a compilação, para que um arquivo de classe, não gerado comjavac
ou artesanal, possa subvertê-lo.new TreeSet<>(strs)
ondestrs
está umNavigableSet
. Esta não é uma cópia em massa, pois o resultadoTreeSet
usará o comparador da fonte, que é até necessário para manter a semântica. Se você está bem apenas processando os elementos contidos,toArray()
é o caminho a percorrer; ele ainda manterá a ordem da iteração. Quando você está bem com “pegar elemento, validar elemento, usar elemento”, você nem precisa fazer uma cópia. Os problemas começam quando você deseja verificar todos os elementos, seguido pelo uso de todos os elementos. Então você não pode confiar em umaTreeSet
cópia com comparador personalizadocheckcast
para cada elemento étoArray
com um tipo específico. Estamos sempre terminando. As coleções genéricas nem conhecem seu tipo de elemento real, portanto, seus construtores de cópias não podem fornecer uma funcionalidade semelhante. Obviamente, você pode adiar qualquer verificação para o uso anterior correto, mas não sei o que suas perguntas estão buscando. Você não precisa de "integridade semântica" quando estiver bem em verificar e falhar imediatamente antes de usar elementos.Respostas:
Não há proteção real contra códigos maliciosos intencionalmente executados na mesma JVM em APIs comuns, como a API Collection.
Como pode ser facilmente demonstrado:
Como você pode ver, esperar que um
List<String>
não garanta uma lista deString
instâncias. Devido à eliminação de tipos e tipos brutos, não há nem mesmo uma correção possível no lado da implementação da lista.A outra coisa pela qual você pode culpar
ArrayList
o construtor é a confiança natoArray
implementação da coleção recebida .TreeMap
não é afetado da mesma maneira, mas apenas porque não há ganho de desempenho ao passar a matriz, como na construção de umArrayList
. Nenhuma classe garante uma proteção no construtor.Normalmente, não faz sentido tentar escrever código assumindo intencionalmente código malicioso em cada esquina. Há muito o que fazer, para proteger contra tudo. Essa proteção é útil apenas para códigos que realmente encapsulam uma ação que pode dar a um chamador mal-intencionado acesso a algo que já não podia acessar sem esse código.
Se você precisar de segurança para um código específico, use
Então, você pode ter certeza de que
newStrs
contém apenas cadeias e não pode ser modificado por outro código após sua construção.Ou use
List<String> newStrs = List.of(strs.toArray(new String[0]));
com o Java 9 ou mais recenteObserve que o Java 10
List.copyOf(strs)
faz o mesmo, mas sua documentação não afirma que é garantido que não confie notoArray
método da coleção recebida . AssimList.of(…)
, chamar , que definitivamente fará uma cópia caso retorne uma lista baseada em array, é mais seguro.Como nenhum chamador pode alterar a maneira, as matrizes funcionam, despejar a coleção recebida em uma matriz e, em seguida, preencher a nova coleção, sempre tornará a cópia segura. Como a coleção pode conter uma referência à matriz retornada, como demonstrado acima, ela pode alterá-la durante a fase de cópia, mas não pode afetar a cópia na coleção.
Portanto, qualquer verificação de consistência deve ser feita após o elemento específico ter sido recuperado da matriz ou da coleção resultante como um todo.
fonte
AccessController.doPrivileged(…)
etc. Mas a longa lista de erros relacionados à segurança de applets nos dá uma dica de por que essa tecnologia foi abandonada…new ArrayList<>(…)
como construtor de cópia é bom, assumindo implementações de coleção corretas. Não é seu dever corrigir problemas de segurança quando já é tarde demais. E quanto ao hardware comprometido? O sistema operacional? Que tal multi-threading?List.copyOf(strs)
não depende da exatidão da coleção recebida a esse respeito, pelo preço óbvio.ArrayList
é um compromisso razoável para o dia a dia.toArray()
, porque as matrizes não podem ter um comportamento substituído, seguido pela criação de uma cópia de coleção da matriz, comonew ArrayList<>(Arrays.asList( strs.toArray(new String[0])))
ouList.of(strs.toArray(new String[0]))
. Ambos também têm o efeito colateral de impor o tipo de elemento. Pessoalmente, acho que nunca permitirãocopyOf
comprometer as coleções imutáveis, mas as alternativas estão lá, na resposta.Eu preferiria deixar essas informações em comentário, mas não tenho reputação suficiente, desculpe :) Vou tentar explicá-las o mais detalhadamente possível.
Em vez de algo como o
const
modificador usado em C ++ para marcar funções-membro que não deveriam modificar o conteúdo do objeto, originalmente em Java era usado o conceito de "imutabilidade". O encapsulamento (ou OCP, Princípio Aberto-Fechado) deveria proteger contra quaisquer mutações inesperadas (mudanças) de um objeto. É claro que a API de reflexão anda por aí; o acesso direto à memória faz o mesmo; isso é mais sobre fotografar a própria perna :)java.util.Collection
em si é uma interface mutável: possui umadd
método que deve modificar a coleção. É claro que o programador pode agrupar a coleção em algo que será lançado ... e todas as exceções de tempo de execução acontecerão porque outro programador não conseguiu ler o javadoc, o que claramente diz que a coleção é imutável.Decidi usar
java.util.Iterable
type para expor coleção imutável em minhas interfaces. SemanticamenteIterable
, não possui tal característica de coleção como "mutabilidade". Ainda (provavelmente) você poderá modificar coleções subjacentes por meio de fluxos.JIC, para expor mapas de maneira imutável
java.util.Function<K,V>
pode ser usado (oget
método do mapa se encaixa nessa definição)fonte
Iterator
nota, isso praticamente força uma cópia elementar, mas isso não é legal. UsarforEachRemaining
/forEach
obviamente será um desastre completo. (Eu também tenho que mencionar queIterator
tem umremove
método.)Iterable
é não ser verdade imutável, mas não vê qualquer problema comforEach*
)