Matriz imutável em Java

158

Existe uma alternativa imutável para as matrizes primitivas em Java? Criar um array primitivo finalnão impede que alguém faça algo como

final int[] array = new int[] {0, 1, 2, 3};
array[0] = 42;

Eu quero que os elementos da matriz sejam imutáveis.


fonte
43
Faça um favor a si mesmo e pare de usar matrizes em Java para qualquer coisa além de 1) io 2) processamento de número pesado 3) se você precisar implementar sua própria Lista / Coleção (o que é raro ). Eles são extremamente inflexíveis e antiquados ... como você acabou de descobrir com esta pergunta.
Whaley
39
@Whaley, performances e código em que você não precisa de matrizes "dinâmicas". Matrizes ainda são úteis em muitos lugares, não é tão raro.
Colin Hebert
10
@ Colin: sim, mas eles são severamente restritivos; é melhor adquirir o hábito de pensar "eu realmente preciso de uma matriz aqui ou posso usar uma lista?"
Jason S
7
@ Colin: Otimize apenas quando você precisar. Quando você passa meio minuto adicionando algo que, por exemplo, aumenta uma matriz ou mantém algum índice fora do escopo de um loop for, você já perdeu um tempo do seu chefe. Crie primeiro, otimize quando e onde for necessário - e na maioria dos aplicativos, isso não substitui Listas por matrizes.
Fevi13 Sep10
9
Bem, por que ninguém mencionou int[]e new int[]é MUITO mais fácil digitar do que List<Integer>e new ArrayList<Integer>? XD
lcn 20/09/2013

Respostas:

164

Não com matrizes primitivas. Você precisará usar uma lista ou outra estrutura de dados:

List<Integer> items = Collections.unmodifiableList(Arrays.asList(0,1,2,3));
Jason S
fonte
18
De alguma forma, eu nunca soube sobre Arrays.asList (T ...). Acho que posso me livrar do meu ListUtils.list (T ...) agora.
9789 Mattels
3
By the way, Arrays.asList dá uma lista unmodifiable
mauhiz
3
@tony que ArrayList não é java.util
mauhiz
11
@mauhiz nãoArrays.asList é modificável. docs.oracle.com/javase/7/docs/api/java/util/… "Retorna uma lista de tamanho fixo apoiada pela matriz especificada. (Alterações na lista retornada" write through "to the array.)"
Jason S
7
@JasonS bem, Arrays.asList()na verdade, parece não modificável no sentido de que você não pode addou removeitens da retornou java.util.Arrays.ArrayList( não deve ser confundida com java.util.ArrayList) - estas operações apenas não são implementadas. Talvez @mauhiz tente dizer isso? Mas é claro que você pode modificar elementos com existente List<> aslist = Arrays.asList(...); aslist.set(index, element), então java.util.Arrays.ArrayListcertamente é não unmofiable , QED Adicionado o comentário apenas por uma questão de enfatizar a diferença entre o resultado de asListe normalArrayList
NIA
73

Minha recomendação é não usar uma matriz ou uma, unmodifiableListmas usar o ImmutableList do Guava , que existe para esse fim.

ImmutableList<Integer> values = ImmutableList.of(0, 1, 2, 3);
ColinD
fonte
3
O ImmutableList do Guava +1 é ainda melhor que o Collections.unmodifiableList, porque é um tipo separado.
sleske
29
O ImmutableList geralmente é melhor (depende do caso de uso) porque é imutável. Collections.unmodifiableList não é imutável. Pelo contrário, é uma visão de que os receptores não podem mudar, mas a fonte original PODE mudar.
Charlie Collins
2
@CharlieCollins, se não houver maneira de acessar a fonte original, o Collections.unmodifiableListsuficiente para tornar uma Lista imutável?
savanibharat
1
@savanibharat Sim.
Apenas um aluno
20

Como outros observaram, você não pode ter matrizes imutáveis ​​em Java.

Se você precisar absolutamente de um método que retorne uma matriz que não influencie a matriz original, será necessário cloná-la sempre:

public int[] getFooArray() {
  return fooArray == null ? null : fooArray.clone();
}

Obviamente, isso é bastante caro (como você cria uma cópia completa toda vez que chama o getter), mas se você não pode alterar a interface (para usar um Listpor exemplo) e não pode arriscar que o cliente altere seus internos, pode ser necessário.

Essa técnica é chamada de fazer uma cópia defensiva.

Joachim Sauer
fonte
3
Onde ele mencionou que precisava de um getter?
Erick Robertson
6
@Erik: ele não fez isso, mas esse é um caso de uso muito comum para estruturas de dados imutáveis ​​(modifiquei a resposta para se referir a métodos em geral, pois a solução se aplica a todos os lugares, mesmo que seja mais comum em getters).
Joachim Sauer
7
É melhor usar clone()ou Arrays.copy()aqui?
Kevinarpe
Tanto quanto me lembro, de acordo com o "Java Efetivo" de Joshua Bloch, o clone () é definitivamente o caminho preferido.
Vincent Vincent
1
Última frase do Item 13, "Substituir clone criteriosamente": "Como regra, a funcionalidade de cópia é melhor fornecida por construtores ou fábricas. Uma exceção notável a essa regra são matrizes, que são melhor copiadas com o método clone".
Vincent Vincent
14

Há uma maneira de criar uma matriz imutável em Java:

final String[] IMMUTABLE = new String[0];

Matrizes com 0 elementos (obviamente) não podem ser mutadas.

Isso pode ser útil se você estiver usando o List.toArraymétodo para converter um Listem uma matriz. Como até mesmo uma matriz vazia ocupa um pouco de memória, você pode salvar essa alocação de memória criando uma matriz vazia constante e sempre passando-a para o toArraymétodo Esse método alocará uma nova matriz se a matriz que você passar não tiver espaço suficiente, mas se tiver (a lista está vazia), ela retornará a matriz que você passou, permitindo que você reutilize essa matriz sempre que você chamar toArrayum vazio List.

final static String[] EMPTY_STRING_ARRAY = new String[0];

List<String> emptyList = new ArrayList<String>();
return emptyList.toArray(EMPTY_STRING_ARRAY); // returns EMPTY_STRING_ARRAY
Brigham
fonte
3
Mas isso não ajuda o OP a obter uma matriz imutável com dados.
Sridhar Sarnobat
1
@ Sridhar-Sarnobat Esse é o ponto da resposta. Não há como o Java criar um array imutável com dados nele.
Brigham
1
Eu gosto dessa sintaxe mais simples: private static final String [] EMPTY_STRING_ARRAY = {}; (Obviamente, eu ainda não descobri como fazer "mini-Markdown" Um botão seria bom..)
Wes
6

Outra resposta

static class ImmutableArray<T> {
    private final T[] array;

    private ImmutableArray(T[] a){
        array = Arrays.copyOf(a, a.length);
    }

    public static <T> ImmutableArray<T> from(T[] a){
        return new ImmutableArray<T>(a);
    }

    public T get(int index){
        return array[index];
    }
}

{
    final ImmutableArray<String> sample = ImmutableArray.from(new String[]{"a", "b", "c"});
}
código de conduta
fonte
Qual é a utilidade de copiar essa matriz no construtor, já que não fornecemos nenhum método setter?
vijaya Kumar
Um conteúdo da matriz de entrada ainda pode ser modificado posteriormente. Queremos preservar seu estado original, para copiá-lo.
aeracode
4

A partir de Java 9 você pode usar List.of(...), JavaDoc .

Este método retorna um imutável Liste é muito eficiente.

rmuller
fonte
3

Se você precisar (por motivos de desempenho ou economizar memória) nativo 'int' em vez de 'java.lang.Integer', provavelmente precisará escrever sua própria classe de wrapper. Existem várias implementações do IntArray na rede, mas nenhuma (eu achei) era imutável: Koders IntArray , Lucene IntArray . Provavelmente existem outros.

Thomas Mueller
fonte
3

Desde o Guava 22, do pacote, com.google.common.primitivesvocê pode usar três novas classes, com menor espaço de memória em comparação com o ImmutableList.

Eles também têm um construtor. Exemplo:

int size = 2;
ImmutableLongArray longArray = ImmutableLongArray.builder(size)
  .add(1L)
  .add(2L)
  .build();

ou, se o tamanho for conhecido em tempo de compilação:

ImmutableLongArray longArray = ImmutableLongArray.of(1L, 2L);

Essa é outra maneira de obter uma visão imutável de uma matriz para primitivas Java.

Jean Valjean
fonte
1

Não, isso não é possível. No entanto, alguém poderia fazer algo assim:

List<Integer> temp = new ArrayList<Integer>();
temp.add(Integer.valueOf(0));
temp.add(Integer.valueOf(2));
temp.add(Integer.valueOf(3));
temp.add(Integer.valueOf(4));
List<Integer> immutable = Collections.unmodifiableList(temp);

Isso requer o uso de wrappers e é uma lista, não uma matriz, mas é o mais próximo que você vai chegar.


fonte
4
Não há necessidade de escrever todos esses valueOf()s, o autoboxing cuidará disso. Também Arrays.asList(0, 2, 3, 4)seria muito mais conciso.
Joachim Sauer
@Joachim: O ponto de uso valueOf()é utilizar o cache interno de objetos Inteiros para reduzir o consumo / reciclagem de memória.
Esko
4
@ Esko: leia a especificação do autoboxing. Faz exatamente a mesma coisa, então não há diferença aqui.
Joachim Sauer
1
@ John Você nunca obterá um NPE convertendo um intem um Integerporém; só tem que ter cuidado ao contrário.
colind
1
@ John: você está certo, pode ser perigoso. Mas, em vez de evitá-lo completamente, provavelmente é melhor entender o perigo e evitá-lo.
Joachim Sauer
1

Em algumas situações, será mais leve usar esse método estático da biblioteca do Google Guava: List<Integer> Ints.asList(int... backingArray)

Exemplos:

  • List<Integer> x1 = Ints.asList(0, 1, 2, 3)
  • List<Integer> x1 = Ints.asList(new int[] { 0, 1, 2, 3})
kevinarpe
fonte
1

Se você deseja evitar a mutabilidade e o boxe, não há como sair da caixa. Mas você pode criar uma classe que contenha uma matriz primitiva dentro e forneça acesso somente leitura aos elementos via método (s).

Nome em Exibição
fonte
1

O método of (E ... elements) em Java9 pode ser usado para criar uma lista imutável usando apenas uma linha:

List<Integer> items = List.of(1,2,3,4,5);

O método acima retorna uma lista imutável que contém um número arbitrário de elementos. E adicionar qualquer número inteiro a esta lista resultaria em java.lang.UnsupportedOperationExceptionexceção. Este método também aceita uma única matriz como argumento.

String[] array = ... ;
List<String[]> list = List.<String[]>of(array);
akhil_mittal
fonte
0

Embora seja verdade que Collections.unmodifiableList()funciona, às vezes você pode ter uma biblioteca grande com métodos já definidos para retornar matrizes (por exemplo String[]). Para evitar quebrá-los, você pode realmente definir matrizes auxiliares que armazenarão os valores:

public class Test {
    private final String[] original;
    private final String[] auxiliary;
    /** constructor */
    public Test(String[] _values) {
        original = new String[_values.length];
        // Pre-allocated array.
        auxiliary = new String[_values.length];
        System.arraycopy(_values, 0, original, 0, _values.length);
    }
    /** Get array values. */
    public String[] getValues() {
        // No need to call clone() - we pre-allocated auxiliary.
        System.arraycopy(original, 0, auxiliary, 0, original.length);
        return auxiliary;
    }
}

Testar:

    Test test = new Test(new String[]{"a", "b", "C"});
    System.out.println(Arrays.asList(test.getValues()));
    String[] values = test.getValues();
    values[0] = "foobar";
    // At this point, "foobar" exist in "auxiliary" but since we are 
    // copying "original" to "auxiliary" for each call, the next line
    // will print the original values "a", "b", "c".
    System.out.println(Arrays.asList(test.getValues()));

Não é perfeito, mas pelo menos você tem "matrizes pseudo-imutáveis" (da perspectiva da classe) e isso não quebra o código relacionado.

miguelt
fonte
-3

Bem ... matrizes são úteis para passar como constantes (se fossem) como parâmetros de variantes.

Martin
fonte
O que é um "parâmetro de variantes"? Nunca ouvi falar disso.
sleske
2
O uso de matrizes como constantes é exatamente onde muitas pessoas FALHAM. A referência é constante, mas o conteúdo da matriz é mutável. Um chamador / cliente pode alterar o conteúdo da sua "constante". Provavelmente, isso não é algo que você deseja permitir. Use um ImmutableList.
Charlie Collins
@CharlieCollins, mesmo isso pode ser invadido por meio de reflexão. Java é uma linguagem insegura em termos de mutabilidade ... e isso não vai mudar.
Nome de exibição
2
@SargeBorsch Isso é verdade, mas qualquer pessoa que pratique hackers baseados em reflexão deve saber que está brincando com fogo modificando coisas que o editor da API pode não querer que acessem. Considerando que, se uma API retornar um int[], o responsável pela chamada poderá assumir que pode fazer o que quiser com essa matriz sem afetar os internos da API.
Daiscog 11/10