Mảng bất biến trong Java


158

Có một sự thay thế bất biến cho các mảng nguyên thủy trong Java không? Tạo một mảng nguyên thủy finalkhông thực sự ngăn người ta làm điều gì đó như

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

Tôi muốn các yếu tố của mảng là không thể thay đổi.


43
Tự mình làm và ngừng sử dụng các mảng trong Java cho bất cứ điều gì ngoài 1) io 2) số nặng giòn 3) nếu bạn cần triển khai Danh sách / Bộ sưu tập của riêng mình ( rất hiếm ). Chúng cực kỳ không linh hoạt và lỗi thời ... như bạn vừa khám phá với câu hỏi này.
cá voi

39
@Whaley, và các buổi biểu diễn và mã nơi bạn không cần mảng "động". Mảng vẫn còn hữu ích ở nhiều nơi, nó không phải là hiếm.
Colin Hebert

10
@Colin: có nhưng họ bị hạn chế nghiêm trọng; tốt nhất là tập thói quen suy nghĩ "tôi thực sự cần một mảng ở đây hay tôi có thể sử dụng Danh sách thay thế?"
Jason S

7
@Colin: Chỉ tối ưu hóa khi bạn cần. Khi bạn thấy mình dành nửa phút để thêm một cái gì đó, ví dụ, mở rộng một mảng hoặc bạn giữ một số chỉ số nằm ngoài phạm vi của một vòng lặp, bạn đã lãng phí một chút thời gian của sếp. Xây dựng trước, tối ưu hóa khi nào và ở đâu cần thiết - và trong hầu hết các ứng dụng, đó không phải là thay thế Danh sách bằng mảng.
fwielstra

9
Chà, tại sao không ai nhắc đến int[]new int[]RẤT dễ gõ hơn List<Integer>new ArrayList<Integer>? XD
lcn

Câu trả lời:


164

Không phải với mảng nguyên thủy. Bạn sẽ cần sử dụng Danh sách hoặc một số cấu trúc dữ liệu khác:

List<Integer> items = Collections.unmodifiableList(Arrays.asList(0,1,2,3));

18
Bằng cách nào đó tôi chưa bao giờ biết về Arrays.asList (T ...). Tôi đoán bây giờ tôi có thể thoát khỏi ListUtils.list (T ...) của mình.
MattRS

3
Nhân tiện, Arrays.asList đưa ra một danh sách không thể thay đổi
mauhiz

3
@tony rằng ArrayList không phải là java.util's
mauhiz

11
@mauhiz không phảiArrays.asListkhông thể thay đổi. docs.oracle.com/javase/7/docs/api/java/util/ triệt "Trả về danh sách kích thước cố định được hỗ trợ bởi mảng đã chỉ định. (Thay đổi danh sách được trả về" ghi qua "vào mảng.)"
Jason S

7
@JasonS tốt, Arrays.asList()thực sự dường như unmodifiable theo nghĩa là bạn không thể addhoặc removemục của trở lại java.util.Arrays.ArrayList( không nên nhầm với java.util.ArrayList) - các hoạt động này chỉ không được thực hiện. Có lẽ @mauhiz cố gắng nói điều này? Nhưng tất nhiên bạn có thể sửa đổi các yếu tố hiện có với List<> aslist = Arrays.asList(...); aslist.set(index, element), vì vậy java.util.Arrays.ArrayListchắc chắn không phảikhông có lợi , QED Đã thêm nhận xét chỉ để nhấn mạnh sự khác biệt giữa kết quả asListvà bình thườngArrayList
NIA

73

Đề nghị của tôi là không sử dụng một mảng hoặc một unmodifiableListmà sử dụng ImmutableList của Guava , tồn tại cho mục đích này.

ImmutableList<Integer> values = ImmutableList.of(0, 1, 2, 3);

3
+1 ImmutableList của Guava thậm chí còn tốt hơn cả Collections.unmodifiableList, vì đây là một loại riêng biệt.
sleske

29
ImmutableList thường tốt hơn (tùy thuộc vào trường hợp sử dụng) bởi vì nó không thay đổi. Bộ sưu tập.unmodifiableList không phải là bất biến. Thay vào đó, đó là một quan điểm rằng người nhận không thể thay đổi, nhưng nguồn ban đầu CÓ THỂ thay đổi.
Charlie Collins

2
@CharlieCollins, nếu không có cách nào để truy cập vào nguồn ban đầu thì Collections.unmodifiableListcó đủ để tạo Danh sách không thay đổi?
savanibharat

1
@savanibharat Vâng.
Chỉ là một học sinh

20

Như những người khác đã lưu ý, bạn không thể có các mảng bất biến trong Java.

Nếu bạn thực sự cần một phương thức trả về một mảng không ảnh hưởng đến mảng ban đầu, thì bạn cần sao chép mảng mỗi lần:

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

Rõ ràng điều này khá tốn kém (vì bạn sẽ tạo một bản sao đầy đủ mỗi khi bạn gọi getter), nhưng nếu bạn không thể thay đổi giao diện ( Liství dụ sử dụng ) và không thể mạo hiểm cho khách hàng thay đổi nội bộ của bạn, thì nó có thể là cần thiết

Kỹ thuật này được gọi là tạo một bản sao phòng thủ.


3
Trường hợp anh ta đề cập rằng anh ta cần một getter?
Erick Robertson

6
@Erik: anh ấy đã không làm, nhưng đó là trường hợp sử dụng rất phổ biến đối với các cấu trúc dữ liệu bất biến (tôi đã sửa đổi câu trả lời để tham khảo các phương thức nói chung, vì giải pháp áp dụng ở mọi nơi, ngay cả khi nó phổ biến hơn trong getters).
Joachim Sauer

7
Nó là tốt hơn để sử dụng clone()hoặc Arrays.copy()ở đây?
kevinarpe

Theo như tôi nhớ, theo "Java hiệu quả" của Joshua Bloch, clone () chắc chắn là cách ưa thích.
Vincent

1
Câu cuối cùng của Mục 13, "Ghi đè một cách thận trọng": "Theo quy định, chức năng sao chép được cung cấp tốt nhất bởi các nhà xây dựng hoặc nhà máy. Một ngoại lệ đáng chú ý của quy tắc này là các mảng, được sao chép tốt nhất bằng phương pháp sao chép."
Vincent

14

Có một cách để tạo một mảng bất biến trong Java:

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

Mảng có 0 phần tử (rõ ràng) không thể bị đột biến.

Điều này thực sự có thể có ích nếu bạn đang sử dụng List.toArrayphương thức để chuyển đổi một Listmảng. Vì ngay cả một mảng trống cũng chiếm một số bộ nhớ, bạn có thể lưu phân bổ bộ nhớ đó bằng cách tạo một mảng trống không đổi và luôn truyền nó cho toArrayphương thức. Phương thức đó sẽ phân bổ một mảng mới nếu mảng bạn vượt qua không đủ dung lượng, nhưng nếu có (danh sách trống), nó sẽ trả về mảng bạn đã truyền, cho phép bạn sử dụng lại mảng đó bất cứ khi nào bạn gọi toArrayvào trống rỗng 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

3
Nhưng điều này không giúp OP đạt được một mảng bất biến với dữ liệu trong đó.
Sridhar Sarnobat

1
@ Sridhar-Sarnobat Đó là loại câu trả lời. Không có cách nào trong Java để tạo ra một mảng bất biến với dữ liệu trong đó.
Brigham

1
Tôi thích cú pháp đơn giản này tốt hơn: Chuỗi ký tự tĩnh riêng tư [] EMPTY_STRING_ARRAY = {}; (Rõ ràng là tôi chưa tìm ra cách thực hiện "mini-Markdown". Nút xem trước sẽ rất hay.)
Wes

6

Một câu trả lời khác

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

việc sử dụng sao chép mảng đó trong hàm tạo là gì, vì chúng tôi không cung cấp bất kỳ phương thức setter nào?
vijaya kumar

Một nội dung của mảng đầu vào vẫn có thể được sửa đổi sau này. Chúng tôi muốn duy trì trạng thái ban đầu của nó, vì vậy chúng tôi sao chép nó.
aeracode

4

Kể từ Java 9, bạn có thể sử dụng List.of(...), JavaDoc .

Phương pháp này trả về một bất biến Listvà rất hiệu quả.


3

Nếu bạn cần (vì lý do hiệu suất hoặc để tiết kiệm bộ nhớ) bản địa 'int' thay vì 'java.lang.Integer', thì có lẽ bạn sẽ cần phải viết lớp trình bao bọc của riêng mình. Có nhiều cách triển khai IntArray khác nhau trên mạng, nhưng không có (tôi tìm thấy) là bất biến: Koders IntArray , Lucene IntArray . Có lẽ có những người khác.


3

Kể từ Guava 22, từ gói com.google.common.primitivesbạn có thể sử dụng ba lớp mới, có dung lượng bộ nhớ thấp hơn so với ImmutableList.

Họ cũng có một người xây dựng. Thí dụ:

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

hoặc, nếu kích thước được biết tại thời gian biên dịch:

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

Đây là một cách khác để có được một khung nhìn bất biến của một mảng cho các nguyên hàm Java.


1

Không, điều này là không thể. Tuy nhiên, người ta có thể làm một cái gì đó như thế này:

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

Điều này đòi hỏi phải sử dụng các hàm bao, và là một Danh sách, không phải là một mảng, nhưng là gần nhất bạn sẽ nhận được.


4
Không cần phải viết tất cả những cái đó valueOf(), autoboxing sẽ đảm nhiệm việc đó. Cũng Arrays.asList(0, 2, 3, 4)sẽ ngắn gọn hơn nhiều.
Joachim Sauer

@Joachim: Điểm sử dụng valueOf()là sử dụng bộ đệm đối tượng Integer bên trong để giảm mức tiêu thụ / tái chế bộ nhớ.
Esko

4
@Esko: đọc thông số kỹ thuật tự động. Nó thực hiện chính xác điều tương tự, vì vậy không có sự khác biệt ở đây.
Joachim Sauer

1
@ John Bạn sẽ không bao giờ nhận được một NPE chuyển đổi intthành một Integermặc dù; chỉ cần cẩn thận theo cách khác.
ColinD

1
@ John: bạn nói đúng, nó có thể nguy hiểm. Nhưng thay vì tránh nó hoàn toàn, có lẽ tốt hơn là hiểu nguy hiểm và tránh điều đó.
Joachim Sauer

1

Trong một số trường hợp, sẽ sử dụng phương pháp tĩnh này từ thư viện Google Guava: List<Integer> Ints.asList(int... backingArray)

Ví dụ:

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

1

Nếu bạn muốn tránh cả khả năng biến đổi và quyền anh, không có cách nào thoát khỏi hộp. Nhưng bạn có thể tạo một lớp chứa mảng nguyên thủy bên trong và cung cấp quyền truy cập chỉ đọc vào các phần tử thông qua (các) phương thức.


1

Phương thức (E ... phần tử) trong Java9 có thể được sử dụng để tạo danh sách bất biến chỉ bằng một dòng:

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

Phương thức trên trả về một danh sách bất biến chứa một số phần tử tùy ý. Và thêm bất kỳ số nguyên nào vào danh sách này sẽ dẫn đến java.lang.UnsupportedOperationExceptionngoại lệ. Phương thức này cũng chấp nhận một mảng duy nhất làm đối số.

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

0

Mặc dù đúng là như vậy Collections.unmodifiableList(), đôi khi bạn có thể có một thư viện lớn có các phương thức đã được xác định để trả về mảng (ví dụ String[]). Để tránh phá vỡ chúng, bạn thực sự có thể xác định các mảng phụ sẽ lưu trữ các giá trị:

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

Để kiểm tra:

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

Không hoàn hảo, nhưng ít nhất bạn có "mảng giả bất biến" (theo quan điểm của lớp) và điều này sẽ không phá vỡ mã liên quan.


-3

Chà .. mảng rất hữu ích để vượt qua như hằng số (nếu có) như các tham số biến thể.


"Thông số biến thể" là gì? Chưa bao giờ nghe về nó.
sleske

2
Sử dụng mảng như hằng số chính xác là nơi nhiều người FAIL. Tham chiếu là hằng số, nhưng nội dung của mảng là có thể thay đổi. Một người gọi / khách hàng có thể thay đổi nội dung "hằng số" của bạn. Đây có lẽ không phải là thứ bạn muốn cho phép. Sử dụng một danh sách bất biến.
Charlie Collins

@CharlieCollins thậm chí điều này có thể bị hack thông qua sự phản chiếu. Java là ngôn ngữ không an toàn về khả năng biến đổi ... và điều này sẽ không thay đổi.
Tên hiển thị

2
@SargeBorsch Điều đó đúng, nhưng bất kỳ ai thực hiện hack dựa trên phản xạ đều phải biết rằng họ đang chơi với lửa bằng cách sửa đổi những thứ mà nhà xuất bản API có thể không muốn họ truy cập. Trong khi đó, nếu một API trả về một int[], người gọi có thể cho rằng họ có thể làm những gì họ muốn với mảng đó mà không ảnh hưởng đến các phần bên trong của API.
daiscog
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.