Li o artigo “Não haverá coleções imutáveis em Java - nem agora nem nunca” e pensei que o problema da ausência de listas imutáveis em Java, o que deixa o autor triste, é bastante solucionável em uma escala limitada. Eu ofereço meus pensamentos e partes de código sobre esse assunto.
(Este é um artigo de resposta, leia o artigo original primeiro.)
UnmodifiableList vs ImmutableList
A primeira pergunta que surge é: por que eu preciso de uma UnmodifiableList , se houver uma ImmutableList ? Como resultado da discussão, duas idéias sobre o significado de UnmodifiableList são vistas nos comentários do artigo original:
- o método recebe um UnmodifiableList, não pode alterá-lo, mas sabe que o conteúdo pode ser alterado por outro thread (e sabe como lidar com isso corretamente)
- outros threads não afetam, UnmodifiableListeImmutableListsão equivalentes a um método, masUnmodifiableListusado como um método mais "leve".
A primeira opção parece muito rara na prática. Portanto, se você puder fazer uma implementação "fácil" do ImmutableList , o UnmodifiableList se tornará pouco necessário. Portanto, no futuro, esqueceremos isso e implementaremos apenas o ImmutableList .
Declaração do problema
Implementaremos a opção ImmutableList :
- A API deve ser idêntica à API de Listcomum na parte "leitura". A parte "escrita" deve estar ausente.
- ImmutableListe- Listnão devem ser relacionados por relações de herança. Por que isso - entende o artigo original.
- Faz sentido fazer a implementação por analogia com ArrayList. Esta é a opção mais fácil.
- A implementação deve evitar copiar matrizes sempre que possível.
Implementação ImmutableList
Primeiro, lidamos com a API. Examinamos as interfaces Collection e List e copiamos a parte "leitura" delas para nossas novas interfaces.
 public interface ReadOnlyCollection<E> extends Iterable<E> { int size(); boolean isEmpty(); boolean contains(Object o); Object[] toArray(); <T> T[] toArray(T[] a); boolean containsAll(Collection<?> c); } public interface ReadOnlyList<E> extends ReadOnlyCollection<E> { E get(int index); int indexOf(Object o); int lastIndexOf(Object o); ListIterator<E> listIterator(); ListIterator<E> listIterator(int index); ReadOnlyList<E> subList(int fromIndex, int toIndex); } 
Em seguida, crie a classe ImmutableList . A assinatura é semelhante a ArrayList (mas implementa a interface ReadOnlyList vez de List ).
 public class ImmutableList<E> implements ReadOnlyList<E>, RandomAccess, Cloneable, Serializable 
Copiamos a implementação da classe de ArrayList e refatoramos com firmeza, descartando tudo relacionado à parte "escrita", verificando a modificação simultânea etc.
Os construtores serão os seguintes:
 public ImmutableList() public ImmutableList(E[] original) public ImmutableList(Collection<? extends E> original) 
O primeiro cria uma lista vazia. O segundo cria uma lista, copiando a matriz. Não podemos ficar sem copiar se queremos alcançar imutáveis. O terceiro é mais interessante. Um construtor ArrayList semelhante também copia dados da coleção. Faremos o mesmo, a menos que orginal seja uma instância de ArrayList ou Arrays$ArrayList (é o que é retornado pelo método Arrays.asList() ). Podemos assumir com segurança que esses casos cobrirão 90% das chamadas do construtor.
Nesses casos, "roubaremos" a matriz original por meio de reflexões (há esperança de que isso seja mais rápido do que copiar matrizes de gigabytes). A essência do "roubo":
- chegamos ao campo privado original, que armazena a matriz (ArrayList.elementData)
- copie o link para a matriz para nós mesmos
- colocar no campo de origem null
 protected static final Field data_ArrayList; static { try { data_ArrayList = ArrayList.class.getDeclaredField("elementData"); data_ArrayList.setAccessible(true); } catch (NoSuchFieldException | SecurityException e) { throw new IllegalStateException(e); } } public ImmutableList(Collection<? extends E> original) { Object[] arr = null; if (original instanceof ArrayList) { try { arr = (Object[]) data_ArrayList.get(original); data_ArrayList.set(original, null); } catch (@SuppressWarnings("unused") IllegalArgumentException | IllegalAccessException e) { arr = null; } } if (arr == null) {  
Como contrato, assumimos que, quando o construtor é chamado, a lista mutável é ImmutableList em um ImmutableList . A lista original não pode ser usada depois disso. Ao tentar usar, um NullPointerException chega. Isso garante que a matriz "roubada" não seja alterada e nossa lista seja realmente imutável (exceto no caso em que alguém chegue à matriz através de reflexões).
Outras classes
Suponha que decidamos usar um ImmutableList em um projeto real.
O projeto interage com as bibliotecas: recebe e envia várias listas. Na grande maioria dos casos, essas listas serão um ArrayList . A implementação descrita de ImmutableList permite converter rapidamente o ArrayList resultante em um ImmutableList . Também é necessário implementar a conversão para listas enviadas para bibliotecas: ImmutableList to List . Para uma conversão rápida, você precisa de um invólucro ImmutableList que implemente List , lançando exceções ao tentar gravar na lista (semelhante a Collections.unmodifiableList ).
Além disso, o próprio projeto processa as listas de alguma forma. Faz sentido criar uma classe MutableList que represente uma lista mutável, com uma implementação baseada em ArrayList . Nesse caso, você pode refatorar o projeto substituindo todos os ArrayList classe que declare explicitamente a intenção: ImmutableList ou MutableList .
Precisa de uma conversão rápida de ImmutableList para MutableList e vice-versa. Ao mesmo tempo, diferentemente da conversão de ArrayList em ImmutableList , não podemos mais estragar a lista original.
A conversão normalmente será lenta, com a cópia da matriz. Mas, nos casos em que o MutableList recebido nem sempre muda, é possível criar um wrapper: MutableList , que salva uma referência ao ImmutableList e o usa para métodos de "leitura" e, se um método de "gravação" é chamado, esquece-se do ImmutableList depois de copiar seu conteúdo array para si mesmo e, em seguida, ele já funciona com seu array (algo remotamente semelhante está em CopyOnWriteArrayList ).
A conversão de "volta" implica receber uma captura instantânea do conteúdo da MutableList no momento em que o método é chamado. Novamente, na maioria dos casos, você não pode fazer isso sem copiar uma matriz, mas pode criar um wrapper para otimizar os casos de várias conversões entre as quais o conteúdo do MutableList não foi alterado. Outra opção para converter "back": alguns dados são coletados no MutableList e, quando a coleta de dados é concluída, o MutableList precisa ser convertido para sempre em um ImmutableList . Também é implementado sem problemas com outro wrapper.
Total
Os resultados do experimento na forma de código são publicados aqui
ImmutableList próprio ImmutableList é ImmutableList , descrito na seção "Outras classes" (ainda não?).
Podemos assumir que a premissa do artigo original, “coleções imutáveis em Java não serão” é incorreta.
Se você quiser, é bem possível usar uma abordagem semelhante. Sim, com pequenas muletas. Sim, não dentro de todo o sistema, mas apenas em seus projetos (embora se muitos penetrem, ele será gradualmente puxado para as bibliotecas).
Uma coisa: se houver um desejo ... (Taiti, Taiti ... Nós não estávamos em nenhum Taiti! Eles nos alimentam bem aqui.)