Jak zapobiec modyfikacji pola prywatnego w klasie?


165

Wyobraź sobie, że mam tę klasę:

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

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

Teraz mam inną klasę, która używa powyższej klasy:

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

Oto więc problem: uzyskałem dostęp do prywatnego pola klasy z zewnątrz! Jak mogę temu zapobiec? Mam na myśli, jak mogę uczynić tę tablicę niezmienną? Czy to oznacza, że ​​każdą metodą pobierającą możesz wypracować sobie drogę do uzyskania dostępu do pola prywatnego? (Nie chcę żadnych bibliotek, takich jak Guava. Muszę tylko wiedzieć, jak to zrobić).


10
Właściwie, co finalrobi uniemożliwić modyfikację tej dziedzinie . Jednak zapobieżenie modyfikacji tego, do którego Objectodwołuje się pole, jest bardziej skomplikowane.
OldCurmudgeon

22
Istnieje problem z twoim modelem mentalnym, jeśli myślisz, że możliwość modyfikowania tablicy, do której masz odniesienie przechowywane w polu prywatnym, jest tym samym, co możliwość modyfikowania pola prywatnego.
Joren,

45
Jeśli jest prywatny, po co go w ogóle ujawniać?
Erik Reppen

7
Zajęło mi trochę czasu, zanim to rozgryzłem, ale zawsze poprawia mój kod, gdy upewniam się, że wszystkie struktury danych są całkowicie zamknięte w swoim obiekcie - co oznacza, że ​​nie ma sposobu, aby "uzyskać" swój "arr", zamiast tego rób to, gdziekolwiek musisz w środku klasę lub podaj iterator.
Bill K

8
Czy ktokolwiek inny też jest zaskoczony, dlaczego to pytanie przyciągnęło tak dużo uwagi?
Roman

Odpowiedzi:


163

Musisz zwrócić kopię swojej tablicy.

public String[] getArr() {
  return arr == null ? null : Arrays.copyOf(arr, arr.length);
}

121
Chciałbym przypomnieć ludziom, że chociaż uznano to za właściwą odpowiedź na pytanie, najlepszym rozwiązaniem problemu jest tak naprawdę, jak mówi sp00m - aby zwrócić plik Unmodifiable List.
OldCurmudgeon

48
Nie, jeśli faktycznie potrzebujesz zwrócić tablicę.
Svish

8
W rzeczywistości najlepszym rozwiązaniem omawianego problemu jest często to, co mówi @MikhailVladimirov: - udostępnienie lub zwrócenie widoku tablicy lub kolekcji.
Christoffer Hammarström

20
ale upewnij się, że jest to głęboka, a nie płytka kopia danych. W przypadku ciągów nie ma to znaczenia, w przypadku innych klas, takich jak Date, w których można modyfikować zawartość instancji, może to mieć znaczenie.
jwenting

16
@Svish Powinienem był być bardziej drastyczny: jeśli zwracasz tablicę z funkcji API, robisz to źle. W przypadku funkcji prywatnych wewnątrz biblioteki może to mieć rację. W API (gdzie ochrona przed modyfikowalnością odgrywa rolę) nigdy tak nie jest.
Konrad Rudolph

377

Jeśli możesz użyć listy zamiast tablicy, Kolekcje udostępnia niemodyfikowalną listę :

public List<String> getList() {
    return Collections.unmodifiableList(list);
}

Dodano odpowiedź używaną Collections.unmodifiableList(list)jako przypisanie do pola, aby upewnić się, że lista nie zostanie zmieniona przez samą klasę.
Maarten Bodewes

Tylko się zastanawiam, jeśli zdekompilujesz tę metodę, otrzymasz wiele nowych słów kluczowych. Czy to nie szkodzi wydajności, gdy getList () uzyskuje dużo dostępu. Na przykład w kodzie renderującym.
Thomas15v

2
Zauważ, że to działa, jeśli elementy tablic są niezmienne w sobie, jak Stringjest, ale jeśli są zmienne, może być konieczne zrobienie czegoś, aby uniemożliwić wywołującemu ich zmianę.
Lii

45

Modyfikator privatechroni tylko samo pole przed dostępem z innych klas, ale nie chroni odniesienia do obiektów przez to pole. Jeśli chcesz chronić obiekt, do którego istnieje odniesienie, po prostu go nie udostępniaj. Zmiana

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

do:

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

lub

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

public String getArrElementAt (int index)
{
    return arr [index];
}

5
W tym artykule nie sprzeciwiamy się używaniu clone na tablicach, tylko na obiektach, które mogą być podklasy.
Lyn Headley,

28

O Collections.unmodifiableListtym już zostało wspomniane - o Arrays.asList()dziwo nie! Moim rozwiązaniem byłoby również użycie listy z zewnątrz i zawinięcie tablicy w następujący sposób:

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

Problem z kopiowaniem tablicy polega na tym, że jeśli robisz to za każdym razem, gdy uzyskujesz dostęp do kodu, a tablica jest duża, na pewno stworzysz dużo pracy dla garbage collectora. Tak więc kopiowanie jest prostym, ale bardzo złym podejściem - powiedziałbym „tanie”, ale drogie w pamięci! Zwłaszcza, gdy masz więcej niż tylko 2 elementy.

Jeśli spojrzeć na kod źródłowy Arrays.asListi Collections.unmodifiableListjest faktycznie niewiele utworzony. Pierwsza po prostu zawija tablicę bez jej kopiowania, druga po prostu zawija listę, uniemożliwiając jej zmiany.


Zakładasz tutaj, że tablica zawierająca ciągi znaków jest kopiowana za każdym razem. Tak nie jest, kopiowane są tylko odwołania do niezmiennych ciągów. Dopóki macierz nie jest ogromna, narzut jest znikomy. Jeśli tablica jest ogromna, idź na listy i immutableListdo końca!
Maarten Bodewes

Nie, nie przyjąłem takiego założenia - gdzie? Chodzi o referencje, które są kopiowane - nie ma znaczenia, czy jest to String, czy jakikolwiek inny obiekt. Mówiąc tylko, że w przypadku dużych tablic nie polecałbym kopiowania tablicy i że operacje Arrays.asListi Collections.unmodifiableListsą prawdopodobnie tańsze w przypadku większych tablic. Może ktoś może obliczyć, ile elementów jest potrzebnych, ale widziałem już kod w pętlach for z tablicami zawierającymi tysiące elementów kopiowanych tylko po to, aby zapobiec modyfikacji - to szaleństwo!
michael_s

6

Możesz także użyć tego, ImmutableListco powinno być lepsze niż standardowe unmodifiableList. Klasa jest częścią bibliotek Guava, które zostały utworzone przez Google.

Oto opis:

W przeciwieństwie do Collections.unmodifiableList (java.util.List), który jest widokiem oddzielnej kolekcji, która wciąż może się zmieniać, instancja ImmutableList zawiera własne dane prywatne i nigdy się nie zmieni

Oto prosty przykład, jak go używać:

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

  public ImmutableList<String> getArr() 
  {
    return ImmutableList.copyOf(arr);
  }
}

Użyj tego rozwiązania, jeśli nie możesz ufać Testklasie, która pozostawi zwróconą listę niezmienioną . Musisz się upewnić, że ImmutableListzostanie zwrócony i że elementy listy są również niezmienne. W przeciwnym razie moje rozwiązanie również powinno być bezpieczne.
Maarten Bodewes

3

z tego punktu widzenia powinieneś użyć kopii tablicy systemowej:

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;
   }
}

-1, ponieważ nie robi nic poza wywołaniem clonei nie jest tak zwięzły.
Maarten Bodewes

2

Możesz zwrócić kopię danych. Dzwoniący, który zdecyduje się zmienić dane, zmieni tylko kopię

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;
    }
}

2

Istotą problemu jest to, że zwracasz wskaźnik do zmiennego obiektu. Ups. Albo wyrenderujesz obiekt jako niezmienny (rozwiązanie listy niemodyfikowalnej), albo zwrócisz kopię obiektu.

Ogólnie rzecz biorąc, ostateczność obiektów nie chroni przed zmianą obiektów, jeśli są one zmienne. Te dwa problemy to „całowanie kuzynów”.


Cóż, Java ma odniesienia, podczas gdy C ma wskaźniki. Wystarczająco powiedziane. Ponadto „ostateczny” modyfikator może mieć wiele skutków w zależności od tego, gdzie jest stosowany. Zastosowanie do członka klasy wymusi przypisanie wartości członkowi tylko raz za pomocą operatora „=” (przypisanie). Nie ma to nic wspólnego z niezmiennością. Jeśli zastąpisz odwołanie elementu członkowskiego nowym (inną instancją z tej samej klasy), poprzednia instancja pozostanie niezmieniona (ale może zostać przypisana do GC, jeśli żadne inne odwołania ich nie przechowują).
gyorgyabraham

1

Zwrócenie niemodyfikowalnej listy to dobry pomysł. Ale lista, która jest niemodyfikowalna podczas wywołania metody pobierającej, może być nadal zmieniana przez klasę lub klasy, które są pochodnymi klasy.

Zamiast tego powinieneś jasno powiedzieć każdemu, kto rozszerza klasę, że lista nie powinna być modyfikowana.

W twoim przykładzie może to prowadzić do następującego kodu:

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;
    }
}

W powyższym przykładzie utworzyłem plik STRINGS upubliczniłem pole, w zasadzie możesz pozbyć się wywołania metody, ponieważ wartości są już znane.

Możesz także przypisać ciągi do private final List<String>pola, którego nie można modyfikować podczas konstruowania instancji klasy. Użycie argumentów stałych lub argumentów instancji (konstruktora) zależy od projektu klasy.

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;
    }
}

0

Tak, powinieneś zwrócić kopię tablicy:

 public String[] getArr()
 {
    return Arrays.copyOf(arr);
 }
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.