Como evito a modificação de um campo privado em uma classe?

165

Imagine que eu tenho essa classe:

public class Test
{
  private String[] arr = new String[]{"1","2"};    

  public String[] getArr() 
  {
    return arr;
  }
}

Agora, eu tenho outra classe que usa a classe acima:

Test test = new Test();
test.getArr()[0] ="some value!"; //!!!

Portanto, este é o problema: eu acessei um campo privado de uma classe de fora! Como posso evitar isso? Quero dizer, como posso tornar esse array imutável? Isso significa que, com todo método getter, você pode trabalhar para acessar o campo privado? (Não quero bibliotecas como o Guava. Só preciso saber a maneira correta de fazer isso).

Hossein
fonte
10
Na verdade, tornando-o finalfaz evitar a modificação do campo . No entanto, impedir a modificação do Objectreferido por um campo é mais complicado.
OldCurmudgeon
22
Há um problema com seu modelo mental se você acha que poder modificar uma matriz para a qual você tem uma referência armazenada em um campo privado é o mesmo que poder modificar um campo privado.
11133 Joren
45
Se é privado, por que expô-lo em primeiro lugar?
Erik Reppen
7
Demorei um pouco para descobrir isso, mas ele sempre aprimora meu código quando eu asseguro que todas as estruturas de dados sejam completamente encapsuladas dentro do objeto - o que significa que não há como "Obter" seu "arr", em vez disso, faça o que for necessário dentro a classe ou forneça um iterador.
Bill K
8
Há mais alguém surpreso por que essa pergunta recebeu tanta atenção?
Roman

Respostas:

163

Você deve retornar uma cópia da sua matriz.

public String[] getArr() {
  return arr == null ? null : Arrays.copyOf(arr, arr.length);
}
OldCurmudgeon
fonte
121
Gostaria de lembrar às pessoas que, embora essa resposta tenha sido votada como a resposta correta para a pergunta, a melhor solução para o problema é, na verdade, como diz sp00m - retornar uma Unmodifiable List.
OldCurmudgeon
48
Não se você realmente precisar retornar uma matriz.
Svish
8
Na verdade, a melhor solução para o problema discutido é frequentemente como @MikhailVladimirov diz: - fornecer ou retornar uma visão da matriz ou coleção.
Christoffer Hammarström
20
mas verifique se é uma cópia profunda, não superficial, dos dados. Para Strings, não faz diferença; para outras classes, como Data, em que o conteúdo da instância pode ser modificado, pode fazer a diferença.
jwenting
16
@Svish, eu deveria ter sido mais drástica: se você retornar uma matriz de uma função API, estará fazendo errado. Em funções privadas dentro de uma biblioteca, pode estar certo. Em uma API (onde a proteção contra modificabilidade desempenha um papel), nunca é.
11133 Konrad Rudolph
377

Se você pode usar uma Lista em vez de uma matriz, Coleções fornece uma lista não modificável :

public List<String> getList() {
    return Collections.unmodifiableList(list);
}
sp00m
fonte
Foi adicionada uma resposta que usa Collections.unmodifiableList(list)como atribuição ao campo, para garantir que a lista não seja alterada pela própria classe.
Maarten Bodewes 29/07
Basta saber: se você descompilar esse método, receberá muitas novas palavras-chave. Isso não prejudica o desempenho quando getList () obtém muito acesso. Por exemplo, no código de renderização.
Thomas15v
2
Observe que isso funciona se os elementos das matrizes forem imutáveis ​​como Stringsão, mas se forem mutáveis, pode ser necessário fazer algo para impedir que o chamador as altere.
Lii
45

O modificador privateprotege apenas o próprio campo de ser acessado de outras classes, mas não as referências de objeto por esse campo. Se você precisar proteger um objeto referenciado, simplesmente não o divulgue. mudança

public String [] getArr ()
{
    return arr;
}

para:

public String [] getArr ()
{
    return arr.clone ();
}

ou para

public int getArrLength ()
{
    return arr.length;
}

public String getArrElementAt (int index)
{
    return arr [index];
}
Mikhail Vladimirov
fonte
5
Este artigo não argumenta contra o uso de clone em matrizes, apenas em objetos que podem ser subclassificados.
22613 Lyn Headley
28

O Collections.unmodifiableListjá foi mencionado - o Arrays.asList()estranhamente não! Minha solução também seria usar a lista de fora e agrupar a matriz da seguinte maneira:

String[] arr = new String[]{"1", "2"}; 
public List<String> getList() {
    return Collections.unmodifiableList(Arrays.asList(arr));
}

O problema de copiar a matriz é: se você estiver fazendo isso toda vez que acessar o código e a matriz for grande, criará muito trabalho para o coletor de lixo, com certeza. Portanto, a cópia é uma abordagem simples, mas muito ruim - eu diria "barato", mas com muita memória! Especialmente quando você está tendo mais do que apenas 2 elementos.

Se você olhar para o código fonte Arrays.asListe Collections.unmodifiableListrealmente não houver muito criado. O primeiro apenas quebra a matriz sem copiá-la, o segundo apenas quebra a lista, tornando as alterações indisponíveis.

michael_s
fonte
Você assume aqui que a matriz, incluindo as seqüências de caracteres, é copiada cada vez. Não é, apenas as referências às seqüências imutáveis ​​são copiadas. Contanto que sua matriz não seja enorme, a sobrecarga é insignificante. Se a matriz for enorme, vá para listas e immutableListaté o fim!
Maarten Bodewes
Não, eu não fiz essa suposição - onde? É sobre as referências que são copiadas - não importa se é uma String ou qualquer outro objeto. Apenas dizendo que, para matrizes grandes, eu não recomendaria copiar a matriz e que as operações Arrays.asListe Collections.unmodifiableListsão provavelmente mais baratas para matrizes maiores. Talvez alguém possa calcular quantos elementos são necessários, mas eu já vi código em for-loops com matrizes contendo milhares de elementos sendo copiados apenas para evitar modificações - isso é insano!
22614
6

Você também pode usar o ImmutableListque deve ser melhor que o padrão unmodifiableList. A classe faz parte das bibliotecas do Guava criadas pelo Google.

Aqui está a descrição:

Ao contrário de Collections.unmodifiableList (java.util.List), que é uma visão de uma coleção separada que ainda pode ser alterada, uma instância de ImmutableList contém seus próprios dados privados e nunca será alterada

Aqui está um exemplo simples de como usá-lo:

public class Test
{
  private String[] arr = new String[]{"1","2"};    

  public ImmutableList<String> getArr() 
  {
    return ImmutableList.copyOf(arr);
  }
}
iTech
fonte
Use esta solução se você não puder confiar na Testclasse para deixar a lista retornada inalterada . Você precisa garantir que ImmutableListseja retornado e que os elementos da lista também sejam imutáveis. Caso contrário, minha solução também deve ser segura.
Maarten Bodewes
3

Neste ponto de vista, você deve usar a cópia da matriz do sistema:

public String[] getArr() {
   if (arr != null) {
      String[] arrcpy = new String[arr.length];
      System.arraycopy(arr, 0, arrcpy, 0, arr.length);
      return arrcpy;
   } else
      return null;
   }
}
GM Ramesh
fonte
-1, pois não faz nada ao chamar clonee não é tão conciso.
Maarten Bodewes
2

Você pode retornar uma cópia dos dados. O chamador que optar por alterar os dados estará alterando apenas a cópia

public class Test {
    private static String[] arr = new String[] { "1", "2" };

    public String[] getArr() {

        String[] b = new String[arr.length];

        System.arraycopy(arr, 0, b, 0, arr.length);

        return b;
    }
}
artfullyContrived
fonte
2

O problema do problema é que você está retornando um ponteiro para um objeto mutável. Opa Você torna o objeto imutável (a solução de lista não modificável) ou retorna uma cópia do objeto.

Em geral, a finalidade dos objetos não protege os objetos de serem alterados se forem mutáveis. Esses dois problemas são "beijar primos".

ncmathsadist
fonte
Bem, Java tem referências, enquanto C tem ponteiros. Disse o suficiente. Além disso, o modificador "final" pode ter vários impactos, dependendo de onde é aplicado. A inscrição em um membro da classe fará com que você atribua valor apenas ao membro exatamente uma vez com o operador "=" (atribuição). Isso não tem nada a ver com imutabilidade. Se você substituir a referência de um membro por um novo (alguma outra instância da mesma classe), a instância anterior permanecerá inalterada (mas poderá ser GCed se nenhuma outra referência estiver mantendo-os).
Gyorgyabraham
1

Retornar uma lista não modificável é uma boa idéia. Mas uma lista que não é modificável durante a chamada para o método getter ainda pode ser alterada pela classe ou classes derivadas da classe.

Em vez disso, você deve deixar claro para quem estende a classe que a lista não deve ser modificada.

Portanto, no seu exemplo, isso pode levar ao seguinte código:

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class Test {
    public static final List<String> STRINGS =
        Collections.unmodifiableList(
            Arrays.asList("1", "2"));

    public final List<String> getStrings() {
        return STRINGS;
    }
}

No exemplo acima, tornei o STRINGScampo público. Em princípio, você pode acabar com a chamada do método, pois os valores já são conhecidos.

Você também pode atribuir as strings a um private final List<String>campo que não pode ser modificado durante a construção da instância da classe. O uso de argumentos constantes ou de instanciação (do construtor) depende do design da classe.

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class Test {
    private final List<String> strings;

    public Test(final String ... strings) {
        this.strings = Collections.unmodifiableList(Arrays
                .asList(strings));
    }

    public final List<String> getStrings() {
        return strings;
    }
}
Maarten Bodewes
fonte
0

Sim, você deve retornar uma cópia da matriz:

 public String[] getArr()
 {
    return Arrays.copyOf(arr);
 }
Kofemann
fonte