Làm cách nào để lặp lại và sửa đổi Bộ Java?


80

Giả sử tôi có Tập hợp các Số nguyên và tôi muốn tăng từng Số nguyên trong Tập hợp. Tôi sẽ làm điều này như thế nào?

Tôi có được phép thêm và xóa các phần tử khỏi tập hợp trong khi lặp lại nó không?

Tôi có cần tạo một tập hợp mới mà tôi sẽ "sao chép và sửa đổi" các phần tử trong khi tôi đang lặp lại tập hợp ban đầu không?

CHỈNH SỬA: Điều gì sẽ xảy ra nếu các phần tử của tập hợp là bất biến?

Câu trả lời:


92

Bạn có thể xóa một cách an toàn khỏi một tập hợp trong quá trình lặp lại với một đối tượng Iterator; cố gắng sửa đổi một tập hợp thông qua API của nó trong khi lặp lại sẽ phá vỡ trình lặp. lớp Set cung cấp một trình lặp thông qua getIterator ().

tuy nhiên, các đối tượng Integer là bất biến; chiến lược của tôi sẽ là lặp qua tập hợp và đối với mỗi Số nguyên i, thêm i + 1 vào một số tập hợp tạm thời mới. Khi bạn hoàn tất quá trình lặp, hãy xóa tất cả các phần tử khỏi tập hợp ban đầu và thêm tất cả các phần tử của tập hợp tạm thời mới.

Set<Integer> s; //contains your Integers
...
Set<Integer> temp = new Set<Integer>();
for(Integer i : s)
    temp.add(i+1);
s.clear();
s.addAll(temp);

1
Sẽ không dễ dàng hơn nếu để bộ gốc cho bộ thu gom rác và tiếp tục sử dụng bộ mới? Tập hợp tạm thời được thu thập hoặc tập hợp gốc, tôi cũng chỉ giữ tập hợp tạm thời và không bận tâm đến việc thay đổi bản gốc? Tuy nhiên, điều này là không thể trong trường hợp của tôi vì bộ gốc là cuối cùng, vì vậy tôi đã chấp nhận.
Buttons840

@JonathanWeatherhead Ý của bạn là từ thứ hai cannotđúng hơn là can? Như trong, You cannot safely removethay vì You can safely remove? Có vẻ như mâu thuẫn trong đoạn đầu tiên đó.
Basil Bourque

@BasilBourque nope Tôi có nghĩa là 'có thể'. Phương thức Iterator :: remove () là an toàn, như trạng thái tài liệu. docs.oracle.com/javase/7/docs/api/java/util/Iterator.html
Jonathan Weatherhead

error: incompatible types: Object cannot be converted to Integer
alhelal

40

Bạn có thể làm những gì bạn muốn nếu bạn sử dụng một đối tượng trình lặp để xem qua các phần tử trong tập hợp của mình. Bạn có thể xóa chúng khi đang di chuyển, không sao cả. Tuy nhiên, việc xóa chúng khi đang ở trong vòng lặp for ("tiêu chuẩn", của từng loại) sẽ khiến bạn gặp rắc rối:

Set<Integer> set = new TreeSet<Integer>();
    set.add(1);
    set.add(2);
    set.add(3);

    //good way:
    Iterator<Integer> iterator = set.iterator();
    while(iterator.hasNext()) {
        Integer setElement = iterator.next();
        if(setElement==2) {
            iterator.remove();
        }
    }

    //bad way:
    for(Integer setElement:set) {
        if(setElement==2) {
            //might work or might throw exception, Java calls it indefined behaviour:
            set.remove(setElement);
        } 
    }

Theo nhận xét của @ mrgloom, đây là chi tiết hơn về lý do tại sao cách "tồi tệ" được mô tả ở trên, cũng ... tệ:

Nếu không đi vào quá nhiều chi tiết về cách Java thực hiện điều này, ở mức độ cao, chúng ta có thể nói rằng cách "xấu" là không tốt vì nó được quy định rõ ràng như vậy trong các tài liệu Java:

https://docs.oracle.com/javase/8/docs/api/java/util/ConcurrentModificationException.html

quy định, trong số những người khác, rằng (tôi nhấn mạnh):

" Ví dụ: thường không được phép cho một luồng sửa đổi Bộ sưu tập trong khi một luồng khác đang lặp lại nó. Nói chung, kết quả của việc lặp lại không được xác định trong những trường hợp này. triển khai do JRE cung cấp) có thể chọn bỏ ngoại lệ này nếu hành vi này được phát hiện "(...)

" Lưu ý rằng ngoại lệ này không phải lúc nào cũng chỉ ra rằng một đối tượng đã được sửa đổi đồng thời bởi một luồng khác. Nếu một luồng duy nhất đưa ra một chuỗi các lệnh gọi phương thức vi phạm hợp đồng của một đối tượng, thì đối tượng có thể ném ra ngoại lệ này. Ví dụ: nếu một luồng sửa đổi trực tiếp một bộ sưu tập trong khi nó đang lặp lại bộ sưu tập bằng một trình vòng lặp không nhanh, trình vòng lặp sẽ ném ra ngoại lệ này. "

Để đi sâu hơn vào chi tiết: một đối tượng có thể được sử dụng trong vòng lặp forEach cần triển khai giao diện "java.lang.Iterable" (javadoc tại đây ). Điều này tạo ra một Iterator (thông qua phương thức "Iterator" được tìm thấy trong giao diện này), được khởi tạo theo yêu cầu và sẽ chứa nội bộ một tham chiếu đến đối tượng Iterable mà từ đó nó được tạo ra. Tuy nhiên, khi một đối tượng Iterable được sử dụng trong vòng lặp forEach, phiên bản của trình lặp này sẽ bị ẩn đối với người dùng (bạn không thể tự mình truy cập nó theo bất kỳ cách nào).

Điều này, cùng với thực tế là một Iterator khá trạng thái, tức là để thực hiện phép thuật của nó và có các phản hồi nhất quán cho các phương thức "tiếp theo" và "hasNext" của nó, nó cần đối tượng hỗ trợ không bị thay đổi bởi một thứ khác ngoài chính trình lặp trong khi nó đang lặp lại, hãy làm cho nó để nó sẽ ném ra một ngoại lệ ngay khi nó phát hiện có điều gì đó đã thay đổi trong đối tượng sao lưu khi nó đang lặp lại nó.

Java gọi đây là sự lặp lại "không nhanh": tức là có một số hành động, thường là những hành động sửa đổi một thể hiện Iterable (trong khi một Iterator đang lặp lại nó). Phần "fail" của khái niệm "fail-fast" đề cập đến khả năng của một Iterator phát hiện khi nào các hành động "thất bại" như vậy xảy ra. Phần "nhanh" của "không nhanh" (và theo ý kiến ​​của tôi nên được gọi là "nỗ lực nhanh nhất"), sẽ kết thúc quá trình lặp lại qua ConcurrentModificationException ngay khi nó có thể phát hiện ra rằng hành động "không thành công" có xảy ra.


1
Bạn có thể làm rõ tại sao cách xấu không thành công?
mrgloom

@mrgloom, về cơ bản, tôi đã bổ sung thêm chi tiết về lý do tại sao ConcurrentModificationException có thể được ném ra ngay cả khi chỉ có một luồng hoạt động trên một đối tượng Iterable
Shivan Dragon

Đối với một số trường hợp bạn cũng có java.lang.UnsupportedOperationException
Aguid

4

Tôi không thích lắm về ngữ nghĩa của trình lặp, hãy coi đây là một tùy chọn. Nó cũng an toàn hơn khi bạn xuất bản ít trạng thái nội bộ hơn

private Map<String, String> JSONtoMAP(String jsonString) {

    JSONObject json = new JSONObject(jsonString);
    Map<String, String> outMap = new HashMap<String, String>();

    for (String curKey : (Set<String>) json.keySet()) {
        outMap.put(curKey, json.getString(curKey));
    }

    return outMap;

}

0

Bạn có thể tạo một trình bao bọc có thể thay đổi của int nguyên thủy và tạo Tập hợp những thứ đó:

class MutableInteger
{
    private int value;
    public int getValue()
    {
        return value;
    }
    public void setValue(int value)
    {
        this.value = value;
    }
}

class Test
{
    public static void main(String[] args)
    {
        Set<MutableInteger> mySet = new HashSet<MutableInteger>();
        // populate the set
        // ....

        for (MutableInteger integer: mySet)
        {
            integer.setValue(integer.getValue() + 1);
        }
    }
}

Tất nhiên nếu bạn đang sử dụng HashSet, bạn nên triển khai phương thức hash, equals trong MutableInteger nhưng điều đó nằm ngoài phạm vi của câu trả lời này.


0

Thứ nhất, tôi tin rằng cố gắng làm nhiều việc cùng một lúc nói chung là một thói quen xấu và tôi khuyên bạn nên suy nghĩ lại những gì bạn đang cố gắng đạt được.

Nó phục vụ như một câu hỏi lý thuyết tốt và từ những gì tôi thu thập được, việc CopyOnWriteArraySettriển khai java.util.Setgiao diện đáp ứng các yêu cầu khá đặc biệt của bạn.

http://download.oracle.com/javase/1,5.0/docs/api/java/util/concurrent/CopyOnWriteArraySet.html

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.