Bagaimana saya mencegah modifikasi bidang pribadi di kelas?

165

Bayangkan saya memiliki kelas ini:

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

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

Sekarang, saya memiliki kelas lain yang menggunakan kelas di atas:

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

Jadi ini masalahnya: Saya telah mengakses bidang pribadi kelas dari luar! Bagaimana saya bisa mencegah ini? Maksud saya, bagaimana saya bisa membuat array ini tidak berubah? Apakah ini berarti bahwa dengan setiap metode pengambil Anda dapat bekerja dengan cara Anda untuk mengakses bidang pribadi? (Saya tidak ingin ada perpustakaan seperti Guava. Saya hanya perlu tahu cara yang tepat untuk melakukan ini).

Hossein
sumber
10
Sebenarnya, membuatnya finalmemang mencegah modifikasi lapangan . Namun, mencegah modifikasi yang Objectdimaksud oleh suatu bidang lebih rumit.
OldCurmudgeon
22
Ada masalah dengan model mental Anda jika Anda berpikir dapat memodifikasi array yang Anda simpan referensi di bidang pribadi sama dengan mampu memodifikasi bidang pribadi.
Joren
45
Jika itu pribadi, mengapa memaparkannya?
Erik Reppen
7
Butuh beberapa saat untuk mencari tahu ini tetapi selalu meningkatkan kode saya ketika saya memastikan bahwa semua struktur data benar-benar dienkapsulasi dalam objek mereka - artinya tidak ada cara untuk "Dapatkan" arr "Anda", daripada melakukan apa pun yang Anda harus di dalam kelas atau berikan iterator.
Bill K
8
Adakah orang lain yang terkejut mengapa pertanyaan ini mendapat banyak perhatian?
Roman

Jawaban:

163

Anda harus mengembalikan salinan array Anda.

public String[] getArr() {
  return arr == null ? null : Arrays.copyOf(arr, arr.length);
}
OldCurmudgeon
sumber
121
Saya ingin mengingatkan orang-orang bahwa walaupun ini telah dipilih sebagai jawaban yang benar untuk pertanyaan itu, solusi terbaik untuk masalah ini sebenarnya seperti yang dikatakan sp00m - untuk mengembalikan sebuah Unmodifiable List.
OldCurmudgeon
48
Tidak jika Anda benar-benar perlu mengembalikan array.
Svish
8
Sebenarnya, solusi terbaik untuk masalah yang dibahas adalah sering seperti yang dikatakan @MikhailVladimirov: - untuk memberikan atau mengembalikan tampilan array atau koleksi.
Christoffer Hammarström
20
tapi pastikan itu salinan yang dalam, bukan salinan data yang dangkal. Untuk Strings tidak ada bedanya, untuk kelas lain seperti Tanggal di mana konten instance dapat dimodifikasi, itu bisa membuat perbedaan.
jwenting
16
@Vish Saya seharusnya lebih drastis: jika Anda mengembalikan array dari fungsi API Anda salah melakukannya. Dalam fungsi pribadi di dalam perpustakaan mungkin benar. Dalam suatu API (di mana perlindungan terhadap modifiability berperan), tidak pernah ada.
Konrad Rudolph
377

Jika Anda dapat menggunakan Daftar alih-alih array, Koleksi menyediakan daftar yang tidak dapat dimodifikasi :

public List<String> getList() {
    return Collections.unmodifiableList(list);
}
sp00m
sumber
Menambahkan jawaban yang digunakan Collections.unmodifiableList(list)sebagai tugas ke bidang, untuk memastikan bahwa daftar tidak diubah oleh kelas itu sendiri.
Maarten Bodewes
Hanya ingin tahu, Jika Anda mendekompilasi metode ini, Anda mendapatkan banyak kata kunci baru. Apakah ini tidak mengganggu kinerja ketika getList () mendapatkan banyak akses. Misalnya dalam membuat kode.
Thomas15v
2
Perhatikan bahwa ini berfungsi jika elemen-elemen array tidak berubah dalam dirinya sendiri seperti apa Stringadanya, tetapi jika mereka bisa berubah, sesuatu mungkin harus dilakukan untuk mencegah penelepon mengubahnya.
Lii
45

Pengubah privatehanya melindungi bidang itu sendiri dari akses dari kelas lain, tetapi bukan referensi objek oleh bidang ini. Jika Anda perlu melindungi objek yang dirujuk, tapi jangan berikan. Perubahan

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

untuk:

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

atau untuk

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

public String getArrElementAt (int index)
{
    return arr [index];
}
Mikhail Vladimirov
sumber
5
Artikel ini tidak menentang penggunaan klon pada array, hanya pada objek yang dapat disubklasifikasikan.
Lyn Headley
28

The Collections.unmodifiableListtelah disebutkan - yang Arrays.asList()anehnya tidak! Solusi saya juga menggunakan daftar dari luar dan membungkus array sebagai berikut:

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

Masalah dengan menyalin array adalah: jika Anda melakukannya setiap kali Anda mengakses kode dan array besar, Anda pasti akan membuat banyak pekerjaan untuk pemulung. Jadi salinannya adalah pendekatan yang sederhana tetapi sangat buruk - saya akan mengatakan "murah", tetapi memori mahal! Terutama ketika Anda memiliki lebih dari 2 elemen.

Jika Anda melihat kode sumber Arrays.asListdan Collections.unmodifiableListsebenarnya tidak banyak yang dibuat. Yang pertama hanya membungkus array tanpa menyalinnya, yang kedua hanya membungkus daftar, membuat perubahan itu tidak tersedia.

michael_s
sumber
Anda membuat asumsi di sini bahwa array termasuk string disalin setiap kali. Bukan, hanya referensi ke string yang tidak dapat diubah yang disalin. Selama array Anda tidak besar, biaya overhead dapat diabaikan. Jika array sangat besar, buka daftar dan teruskan immutableList!
Maarten Bodewes
Tidak, saya tidak membuat asumsi itu - di mana? Ini tentang referensi yang disalin - tidak masalah apakah itu sebuah String atau objek lain. Hanya mengatakan bahwa untuk array besar saya tidak akan merekomendasikan menyalin array dan bahwa Arrays.asListdan Collections.unmodifiableListoperasi mungkin lebih murah untuk array yang lebih besar. Mungkin seseorang dapat menghitung berapa banyak elemen yang diperlukan, tetapi saya telah melihat kode di-loop dengan array berisi ribuan elemen yang disalin hanya untuk mencegah modifikasi - itu gila!
michael_s
6

Anda juga bisa menggunakan ImmutableListmana yang harus lebih baik dari standar unmodifiableList. Kelas adalah bagian dari perpustakaan Guava yang dibuat oleh Google.

Berikut uraiannya:

Tidak seperti Collections.unmodifiableList (java.util.List), yang merupakan pandangan dari koleksi terpisah yang masih dapat berubah, turunan ImmutableList berisi data pribadi sendiri dan tidak akan pernah berubah

Berikut ini adalah contoh sederhana cara menggunakannya:

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

  public ImmutableList<String> getArr() 
  {
    return ImmutableList.copyOf(arr);
  }
}
ITech
sumber
Gunakan solusi ini jika Anda tidak dapat mempercayai Testkelas untuk membiarkan daftar yang dikembalikan tidak berubah . Anda perlu memastikan bahwa ImmutableListitu dikembalikan, dan bahwa elemen-elemen dari daftar juga tidak berubah. Kalau tidak, solusi saya harus aman juga.
Maarten Bodewes
3

pada titik pandang ini Anda harus menggunakan salinan array sistem:

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
sumber
-1 karena tidak melakukan apa pun atas panggilan clone, dan tidak sesingkat itu.
Maarten Bodewes
2

Anda dapat mengembalikan salinan data. Penelepon yang memilih untuk mengubah data hanya akan mengubah salinan

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;
    }
}
artriContrived
sumber
2

Inti masalahnya adalah Anda mengembalikan pointer ke objek yang bisa diubah. Ups. Entah Anda membuat objek tidak berubah (solusi daftar tidak dapat dimodifikasi) atau Anda mengembalikan salinan objek.

Secara umum, finalitas objek tidak melindungi objek dari perubahan jika mereka bisa berubah. Dua masalah ini adalah "mencium sepupu."

ncmathsadist
sumber
Well Java memiliki referensi, sedangkan C memiliki pointer. Cukup kata. Selanjutnya, pengubah "final" dapat memiliki banyak dampak tergantung pada di mana ia diterapkan. Menerapkan anggota kelas akan memaksa Anda untuk hanya memberikan nilai kepada anggota tepat sekali dengan operator "=" (penugasan). Ini tidak ada hubungannya dengan kekekalan. Jika Anda mengganti referensi anggota dengan yang baru (beberapa instance lain dari kelas yang sama), instance sebelumnya akan tetap tidak berubah (tetapi mungkin GCed jika tidak ada referensi lain yang menahannya).
gyorgyabraham
1

Mengembalikan daftar yang tidak dapat dimodifikasi adalah ide yang bagus. Tetapi daftar yang dibuat tidak dapat dimodifikasi selama panggilan ke metode pengambil masih dapat diubah oleh kelas, atau kelas yang diturunkan dari kelas.

Alih-alih, Anda harus menjelaskan kepada siapa pun yang memperluas kelas bahwa daftar tidak boleh dimodifikasi.

Jadi, dalam contoh Anda ini dapat mengarah ke kode berikut:

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

Dalam contoh di atas saya telah membuat STRINGSbidang publik, pada prinsipnya Anda bisa menghilangkan panggilan metode, karena nilai sudah diketahui.

Anda juga bisa menetapkan string ke private final List<String>bidang yang tidak dapat dimodifikasi selama konstruksi instance kelas. Menggunakan argumen konstanta atau instantiasi (dari konstruktor) tergantung pada desain kelas.

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
sumber
0

Ya, Anda harus mengembalikan salinan array:

 public String[] getArr()
 {
    return Arrays.copyOf(arr);
 }
kofemann
sumber