Array yang tidak dapat diubah di Jawa

158

Apakah ada alternatif abadi untuk array primitif di Jawa? Membuat array primitif finalsebenarnya tidak mencegah seseorang melakukan sesuatu seperti

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

Saya ingin elemen array tidak dapat diubah.


sumber
43
Bantulah diri Anda sendiri dan berhenti menggunakan array di Jawa untuk apa pun selain 1) io 2) angka-angka berat 3) jika Anda perlu mengimplementasikan Daftar / Koleksi Anda sendiri (yang jarang ). Mereka sangat tidak fleksibel dan kuno ... seperti yang baru saja Anda temukan dengan pertanyaan ini.
whaley
39
@ Whaley, dan pertunjukan, dan kode di mana Anda tidak perlu array "dinamis". Array masih berguna di banyak tempat, tidak jarang.
Colin Hebert
10
@Colin: ya tapi mereka sangat membatasi; yang terbaik adalah membiasakan diri berpikir "apakah saya benar-benar membutuhkan array di sini atau bisakah saya menggunakan Daftar?"
Jason S
7
@Colin: Hanya mengoptimalkan ketika Anda perlu. Ketika Anda menemukan diri Anda menghabiskan setengah menit menambahkan sesuatu yang, misalnya, memperbesar array atau Anda menyimpan indeks di luar lingkup for-loop, Anda sudah membuang-buang waktu bos Anda. Bangun terlebih dahulu, optimalkan kapan dan di mana dibutuhkan - dan di sebagian besar aplikasi, itu tidak menggantikan Daftar dengan array.
fwielstra
9
Nah, mengapa tidak ada yang menyebutkan int[]dan new int[]JAUH lebih mudah untuk mengetik daripada List<Integer>dan new ArrayList<Integer>? XD
lcn

Jawaban:

164

Tidak dengan array primitif. Anda harus menggunakan Daftar atau struktur data lain:

List<Integer> items = Collections.unmodifiableList(Arrays.asList(0,1,2,3));
Jason S
sumber
18
Entah bagaimana saya tidak pernah tahu tentang Arrays.asList (T ...). Saya kira saya bisa menyingkirkan ListUtils.list (T ...) saya sekarang.
MattRS
3
Omong-omong, Arrays.asList memberikan daftar yang tidak dapat dimodifikasi
mauhiz
3
@tony ArrayList itu bukan java.util
mauhiz
11
@mauhiz Arrays.asListadalah tidak unmodifiable. docs.oracle.com/javase/7/docs/api/java/util/... "Mengembalikan daftar ukuran tetap yang didukung oleh array yang ditentukan. (Perubahan ke daftar yang dikembalikan" write through "ke array.)"
Jason S
7
@JasonS well, Arrays.asList()memang tampaknya tidak dapat dimodifikasi dalam arti bahwa Anda tidak dapat addatau removeitem yang dikembalikan java.util.Arrays.ArrayList( tidak menjadi bingung dengan java.util.ArrayList) - operasi ini tidak diimplementasikan. Mungkin @mauhiz mencoba mengatakan ini? Tapi tentu saja Anda dapat memodifikasi elemen yang sudah ada List<> aslist = Arrays.asList(...); aslist.set(index, element), jadi java.util.Arrays.ArrayListtentu saja bukan tidak mungkin , QED Menambahkan komentar hanya untuk menekankan perbedaan antara hasil asListdan normalArrayList
NIA
73

Rekomendasi saya adalah untuk tidak menggunakan array atau unmodifiableListtetapi untuk menggunakan Guava 's ImmutableList , yang ada untuk tujuan ini.

ImmutableList<Integer> values = ImmutableList.of(0, 1, 2, 3);
ColinD
sumber
3
+1 Guava ImmutableList bahkan lebih baik daripada Collections.unmodifiableList, karena ini adalah tipe yang terpisah.
sleske
29
ImmutableList seringkali lebih baik (tergantung pada use case) karena tidak dapat diubah. Collections.unmodifiableList tidak dapat diubah. Alih-alih, ini merupakan tampilan yang tidak dapat diubah oleh penerima, tetapi sumber aslinya BISA berubah.
Charlie Collins
2
@CharlieCollins, jika tidak ada cara untuk mengakses sumber asli daripada Collections.unmodifiableListcukup untuk membuat Daftar tidak berubah?
savanibharat
1
@savanibharat Ya.
Hanya seorang siswa
20

Seperti yang telah dicatat orang lain, Anda tidak dapat memiliki array yang tidak dapat diubah di Jawa.

Jika Anda benar-benar membutuhkan metode yang mengembalikan array yang tidak mempengaruhi array asli, maka Anda harus mengkloning array setiap kali:

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

Jelas ini agak mahal (karena Anda akan membuat salinan lengkap setiap kali Anda memanggil getter), tetapi jika Anda tidak dapat mengubah antarmuka (untuk menggunakan List misalnya) dan tidak dapat mengambil risiko klien mengubah internal Anda, maka mungkin perlu.

Teknik ini disebut membuat salinan defensif.

Joachim Sauer
sumber
3
Di mana dia menyebutkan bahwa dia membutuhkan seorang pengambil?
Erick Robertson
6
@ Erik: dia tidak, tapi itu kasus penggunaan yang sangat umum untuk struktur data yang tidak dapat diubah (saya memodifikasi jawaban untuk merujuk pada metode secara umum, karena solusinya berlaku di mana-mana, bahkan jika itu lebih umum pada pengambil).
Joachim Sauer
7
Apakah lebih baik menggunakan clone()atau di Arrays.copy()sini?
kevinarpe
Sejauh yang saya ingat, menurut Joshua Bloch "Java Efektif", clone () jelas merupakan cara yang disukai.
Vincent
1
Kalimat terakhir dari Item 13, "Override clone judiciously": "Sebagai aturan, fungsi penyalinan paling baik diberikan oleh konstruktor atau pabrik. Pengecualian penting untuk aturan ini adalah array, yang paling baik disalin dengan metode clone."
Vincent
14

Ada satu cara untuk membuat array yang tidak dapat diubah di Jawa:

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

Array dengan 0 elemen (jelas) tidak dapat dimutasi.

Ini sebenarnya bisa berguna jika Anda menggunakan List.toArraymetode untuk mengonversi Listsebuah array. Karena bahkan array kosong membutuhkan beberapa memori, Anda dapat menyimpan alokasi memori itu dengan membuat array kosong konstan, dan selalu meneruskannya ke toArraymetode. Metode itu akan mengalokasikan array baru jika array yang Anda lewati tidak memiliki cukup ruang, tetapi jika itu (daftar itu kosong), itu akan mengembalikan array yang Anda lewati, memungkinkan Anda untuk menggunakan kembali array itu setiap kali Anda memanggil toArraysebuah mengosongkan 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
sumber
3
Tetapi ini tidak membantu OP mencapai array yang tidak dapat diubah dengan data di dalamnya.
Sridhar Sarnobat
1
@ Sridhar-Sarnobat Itulah inti dari jawabannya. Tidak ada cara di Jawa untuk membuat array yang tidak dapat diubah dengan data di dalamnya.
Brigham
1
Saya suka sintaksis yang lebih sederhana ini lebih baik: private static final String [] EMPTY_STRING_ARRAY = {}; (Jelas saya belum menemukan cara melakukan "mini-Markdown". Tombol pratinjau akan menyenangkan.)
Wes
6

Satu lagi jawaban

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"});
}
aeracode
sumber
apa gunanya menyalin array itu di konstruktor, karena kami tidak menyediakan metode penyetel apa pun?
vijaya kumar
Konten array input masih dapat dimodifikasi nanti. Kami ingin mempertahankan status aslinya, jadi kami menyalinnya.
aeracode
4

Pada Java 9 Anda dapat menggunakan List.of(...), JavaDoc .

Metode ini mengembalikan kekal Listdan sangat efisien.

ruller
sumber
3

Jika Anda perlu (karena alasan kinerja atau untuk menghemat memori) asli 'int' bukan 'java.lang.Integer', maka Anda mungkin perlu menulis kelas pembungkus Anda sendiri. Ada berbagai implementasi IntArray di internet, tetapi tidak ada (saya menemukan) tidak berubah: Koders IntArray , Lucene IntArray . Mungkin ada yang lain.

Thomas Mueller
sumber
3

Sejak Guava 22, dari paket com.google.common.primitivesAnda dapat menggunakan tiga kelas baru, yang memiliki jejak memori lebih rendah dibandingkan dengan ImmutableList.

Mereka juga memiliki pembangun. Contoh:

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

atau, jika ukurannya diketahui pada waktu kompilasi:

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

Ini adalah cara lain untuk mendapatkan tampilan array yang tidak berubah dari Java primitif.

JeanValjean
sumber
1

Tidak, ini tidak mungkin. Namun, orang dapat melakukan sesuatu seperti ini:

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

Ini membutuhkan menggunakan pembungkus, dan merupakan Daftar, bukan array, tetapi yang terdekat Anda akan dapatkan.


sumber
4
Tidak perlu menulis semua valueOf()itu, autoboxing akan membereskannya. Juga Arrays.asList(0, 2, 3, 4)akan jauh lebih ringkas.
Joachim Sauer
@ Joachim: Tujuan penggunaan valueOf()adalah memanfaatkan cache objek Integer internal untuk mengurangi konsumsi / daur ulang memori.
Esko
4
@ Esko: baca spec autoboxing. Itu melakukan hal yang persis sama, jadi tidak ada perbedaan di sini.
Joachim Sauer
1
@ John Anda tidak akan pernah mendapatkan NPE mengkonversi intke Integermeskipun; hanya harus berhati-hati sebaliknya.
ColinD
1
@ John: Anda benar, ini bisa berbahaya. Tapi daripada menghindarinya sepenuhnya, mungkin lebih baik untuk memahami bahaya dan menghindarinya.
Joachim Sauer
1

Dalam beberapa situasi, akan lebih ringan untuk menggunakan metode statis ini dari perpustakaan Google Guava: List<Integer> Ints.asList(int... backingArray)

Contoh:

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

Jika Anda ingin menghindari sifat berubah-ubah dan tinju, tidak ada jalan keluar dari kotak. Tapi Anda bisa membuat kelas yang menyimpan larik primitif di dalamnya dan menyediakan akses hanya baca ke elemen melalui metode.

Nama tampilan
sumber
1

Metode (elemen ... elemen) di Java9 dapat digunakan untuk membuat daftar tetap menggunakan hanya satu baris:

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

Metode di atas mengembalikan daftar yang tidak berubah yang mengandung sejumlah elemen yang berubah-ubah. Dan menambahkan bilangan bulat ke daftar ini akan menghasilkan java.lang.UnsupportedOperationExceptionpengecualian. Metode ini juga menerima satu array sebagai argumen.

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

Meskipun benar itu Collections.unmodifiableList()berfungsi, kadang-kadang Anda mungkin memiliki perpustakaan besar yang memiliki metode yang sudah didefinisikan untuk mengembalikan array (misalnya String[]). Untuk mencegahnya, Anda dapat mendefinisikan array bantu yang akan menyimpan nilai:

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

Untuk menguji:

    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()));

Tidak sempurna, tetapi setidaknya Anda memiliki "pseudo immutable array" (dari perspektif kelas) dan ini tidak akan merusak kode terkait.

miguelt
sumber
-3

Nah .. array berguna untuk dilewatkan sebagai konstanta (jika mereka) sebagai parameter varian.

Martin
sumber
Apa itu "parameter varian"? Tidak pernah mendengar hal tersebut.
sleske
2
Menggunakan array sebagai konstanta adalah persis di mana banyak orang GAGAL. Referensi konstan, tetapi isi array dapat berubah. Satu penelepon / klien dapat mengubah konten "konstan" Anda. Ini mungkin bukan sesuatu yang ingin Anda izinkan. Gunakan ImmutableList.
Charlie Collins
@CharlieCollins bahkan ini dapat diretas melalui refleksi. Java adalah bahasa yang tidak aman dalam hal bisa berubah ... dan ini tidak akan berubah.
Nama Tampilan
2
@ SargeBorsch Itu benar, tetapi siapa pun yang melakukan peretasan berbasis refleksi harus tahu mereka bermain dengan api dengan memodifikasi hal-hal yang mungkin tidak ingin diakses oleh penerbit API. Sedangkan, jika suatu API mengembalikan suatu int[], pemanggil mungkin berasumsi mereka dapat melakukan apa yang mereka inginkan dengan array itu tanpa mempengaruhi internal API.
daiscog