2009-06-11 34 views
5

Tôi đã triển khai một lớp bằng Java, trong đó, lưu trữ một danh sách . Tôi muốn lớp học không thay đổi. Tuy nhiên, tôi cần thực hiện các thao tác trên dữ liệu nội bộ không có ý nghĩa trong ngữ cảnh của lớp. Do đó, tôi có một lớp khác định nghĩa một tập các thuật toán. Dưới đây là một ví dụ đơn giản:Đối tượng không thể thay đổi trong Java và truy cập dữ liệu

Wrapper.java

import java.util.List; 
import java.util.Iterator; 

public class Wrapper implements Iterable<Double> 
{ 
    private final List<Double> list; 

    public Wrapper(List<Double> list) 
    { 
     this.list = list; 
    } 

    public Iterator<Double> iterator() 
    { 
     return getList().iterator(); 
    } 

    public List<Double> data() { return getList(); } 
} 

Algorithm.java

import java.util.Iterator; 
import java.util.Collection; 

public class Algorithm 
{ 
    public static double sum(Collection<Double> collection) 
    { 
     double sum = 0.0; 
     Iterator<Double> iterator = collection.iterator(); 

     // Throws NoSuchElementException if the Collection contains no elements 
     do 
     { 
      sum += iterator.next(); 
     } 
     while(iterator.hasNext()); 

     return sum; 
    } 
} 

Bây giờ, câu hỏi của tôi là, là có một cách đáng tin cậy để tránh người khác sửa đổi dữ liệu nội bộ của tôi mặc dù lớp học của tôi có nghĩa là không thay đổi? Mặc dù tôi đã cung cấp phương thức dữ liệu() cho mục đích chỉ đọc, không có gì để ngăn ai đó sửa đổi dữ liệu qua các phương pháp như xóa()xóa(). Bây giờ, tôi nhận ra rằng tôi có thể cung cấp quyền truy cập vào dữ liệu của mình độc quyền thông qua trình lặp. Tuy nhiên, tôi đã được thông báo rằng việc chuyển một Bộ sưu tập là một điển hình. Thứ hai, nếu tôi có một thuật toán yêu cầu nhiều hơn một lần vượt qua dữ liệu, tôi sẽ phải cung cấp nhiều vòng lặp, có vẻ như một độ dốc trơn trượt.

Được rồi, hy vọng có một giải pháp đơn giản sẽ giải quyết được những lo ngại của tôi. Tôi chỉ đang trở lại Java, và không bao giờ xem xét những điều này trước khi giao dịch với const bằng C++. Cảm ơn trước!

Oh! Có một điều nữa tôi chỉ nghĩ đến. Tôi thực tế không thể trả lại bản sao của Danh sách nội bộ. Danh sách thường chứa hàng trăm nghìn phần tử.

Trả lời

23

Bạn có thể sử dụng Collections.unmodifiableList và sửa đổi phương thức dữ liệu.

public List<Double> data() { return Collections.unmodifiableList(getList()); } 

Từ javadoc:

Trả một cái nhìn unmodifiable của danh mục quy định . Phương pháp này cho phép các mô-đun cung cấp cho người dùng quyền truy cập "chỉ đọc" vào danh sách nội bộ. Hoạt động truy vấn trên danh sách trả về "đọc qua" danh sách được chỉ định, và cố gắng sửa đổi danh sách trả về , cho dù trực tiếp hoặc qua trình lặp số , dẫn đến UnsupportedOperationException.

+0

Hoàn hảo! Tôi không thấy điều này. Cảm ơn bạn. –

+1

Bạn vẫn phải chăm sóc cách thức đối tượng Wrapper được tạo ra: vì Danh sách được truyền cho hàm tạo, có thể có các tham chiếu đến nó ở một nơi khác trong mã. Nếu bạn muốn Trình bao bọc thực sự không thay đổi được, bạn sẽ phải sao chép nội dung của danh sách. –

5

Java không có khái niệm cú pháp về lớp không thay đổi. Đó là vào bạn, là lập trình viên, để cung cấp quyền truy cập vào các hoạt động, nhưng bạn luôn phải giả định ai đó sẽ lạm dụng nó.

Một đối tượng thực sự không thay đổi không cung cấp cách để mọi người thay đổi trạng thái hoặc có quyền truy cập vào các biến trạng thái có thể được sử dụng để thay đổi trạng thái. Lớp học của bạn không phải là bất biến như bây giờ.

Một cách để làm cho nó không thay đổi là trả lại bản sao của bộ sưu tập nội bộ, trong trường hợp đó bạn nên ghi lại nó rất tốt và cảnh báo mọi người sử dụng nó trong mã hiệu suất cao.

Một tùy chọn khác là sử dụng bộ sưu tập bao bọc sẽ loại trừ ngoại lệ trong thời gian chạy nếu ai đó cố thay đổi giá trị (không được khuyến nghị, nhưng có thể, xem bộ sưu tập apache để biết ví dụ). Tôi nghĩ rằng thư viện chuẩn cũng có một thư viện (xem bên dưới lớp Collections).

Tùy chọn thứ ba, nếu một số khách hàng thay đổi dữ liệu trong khi những người khác thì không, là cung cấp các giao diện khác nhau cho lớp học của bạn. Giả sử bạn có IMyX và IMyImmutableX. Sau này chỉ định nghĩa các hoạt động "an toàn", trong khi trước đó mở rộng nó và thêm các hoạt động không an toàn.

Dưới đây là một số mẹo về cách tạo các lớp không thể thay đổi. http://java.sun.com/docs/books/tutorial/essential/concurrency/imstrat.html

+0

Tôi thực sự thích gợi ý của Alex B. Như tôi đã đề cập trong bài đăng của mình, tôi không thể trả lại một bản sao của * Danh sách *. Trong câu trả lời của bạn, bạn đề cập đến rằng bạn không khuyên bạn nên quay lại chế độ xem không thể sửa đổi của danh sách, như Alex đã đề xuất. Bạn có thể vui lòng xây dựng? –

+0

@Scott: Có một cách tiếp cận chung trong lập trình có nội dung "Đừng làm người dùng của bạn ngạc nhiên" hoặc "thích lỗi biên dịch cho lỗi thời gian chạy". Nó thường là thích hợp hơn để có trình biên dịch tìm thấy một vấn đề hơn là có nó sụp đổ chương trình của người dùng của bạn trong sản xuất. Với một danh sách không thể sửa đổi, bạn trả về một danh sách hợp lệ, danh sách này có thể được chuyển qua cho đến nhiều sau đó nó có thể đột nhiên "phát nổ" khi ai đó cố sửa đổi nó. – Uri

+0

@Scott: Nếu bạn cần trả lại một danh sách và không thể sao chép nó, thì chi phí bạn sẽ phải thực hiện là điều ngoại lệ thời gian chạy này. Nhưng có thể xem xét đặt tên cho hàm của bạn là "getUnmodifiableList" hoặc một cái gì đó tương tự. Nghiên cứu của tôi cho thấy hầu hết mọi người sẽ không bao giờ thực sự đọc tài liệu về các chức năng có vẻ trực quan, như dữ liệu() – Uri

5

Bạn có thể sử dụng Collections.unmodifiableList không?

Theo tài liệu, tài liệu sẽ trả về chế độ xem không thể sửa đổi (không thay đổi) của List. Điều này sẽ ngăn chặn việc sử dụng các phương pháp như removeadd bằng cách ném một số UnsupportedOperationException.

Tuy nhiên, tôi không thấy rằng nó sẽ không ngăn việc sửa đổi các phần tử thực trong chính danh sách, vì vậy tôi không chắc liệu điều này có đủ không thay đổi hay không. Ít nhất bản thân danh sách không thể sửa đổi được.

Dưới đây là một ví dụ trong đó các giá trị bên trong của List trả về bởi unmodifiableList vẫn có thể được thay đổi:

class MyValue { 
    public int value; 

    public MyValue(int i) { value = i; } 

    public String toString() { 
     return Integer.toString(value); 
    } 
} 

List<MyValue> l = new ArrayList<MyValue>(); 
l.add(new MyValue(10)); 
l.add(new MyValue(42)); 
System.out.println(l); 

List<MyValue> ul = Collections.unmodifiableList(l); 
ul.get(0).value = 33; 
System.out.println(l); 

Output:

[10, 42] 
[33, 42] 

Điều này về cơ bản cho thấy là nếu các dữ liệu chứa trong List có thể thay đổi ngay từ đầu, nội dung của danh sách có thể được thay đổi, ngay cả khi chính danh sách đó không thể thay đổi.

+0

Danh sách của tôi sẽ chỉ bao giờ chứa các đối tượng không thể thay đổi, mở rộng lớp Số (ví dụ: Số nguyên, Đôi, v.v.). –

+0

Ah, thì đó là một điều ít lo lắng hơn :) – coobird

5

Có một số điều để làm cho lớp của bạn không chính xác bất biến. Tôi tin rằng điều này được thảo luận trong Java hiệu quả.

Như đã đề cập trong một số câu trả lời khác, để ngừng sửa đổi thành list thông qua trình lặp lại được trả về, Collections.unmodifiableList cung cấp giao diện chỉ đọc. Nếu đây là một lớp có thể thay đổi, bạn có thể muốn sao chép dữ liệu để danh sách trả về không thay đổi ngay cả khi đối tượng này có.

Danh sách được chuyển cho hàm tạo sau này có thể được sửa đổi, do đó cần được sao chép.

Lớp này có thể phân loại được, vì vậy các phương thức có thể bị ghi đè. Vì vậy, làm cho lớp học final. Tốt hơn là cung cấp một phương thức tạo tĩnh thay cho hàm tạo.

public final class Wrapper implements Iterable<Double> { 
    private final List<Double> list; 

    private Wrapper(List<Double> list) { 
     this.list = Collections.unmodifiableList(new ArrayList<Double>(list)); 
    } 

    public static Wrapper of(List<Double> list) { 
     return new Wrapper(list); 
    } 

    public Iterator<Double> iterator() { 
     return list.iterator(); 
    } 

    public List<Double> data() { 
     return list; 
    } 
} 

Đồng thời, tránh các tab và đặt dấu ngoặc ở đúng vị trí cho Java sẽ hữu ích.

+0

+1 để giới thiệu Java hiệu quả. :) – cwash

+0

Không thực sự là một "vị trí chính xác" cho các dấu ngoặc trong Java bất kỳ hơn là ở C. Vị trí chính xác cho niềng răng là vị trí dẫn đến lỗi ít nhất. –

Các vấn đề liên quan