Java 8 lambda lấy và xóa phần tử khỏi danh sách


88

Đưa ra danh sách các phần tử, tôi muốn lấy phần tử có thuộc tính đã cho xóa nó khỏi danh sách. Giải pháp tốt nhất mà tôi tìm thấy là:

ProducerDTO p = producersProcedureActive
                .stream()
                .filter(producer -> producer.getPod().equals(pod))
                .findFirst()
                .get();
producersProcedureActive.remove(p);

Có thể kết hợp get và remove trong một biểu thức lambda không?


9
Điều này thực sự có vẻ giống như một trường hợp cổ điển về thời điểm chỉ sử dụng một vòng lặp và trình lặp thay thế.
chrylis -cautilyoptimistic-

1
@chrylis Tôi thực lòng không đồng ý;) Chúng ta đã quá quen với việc lập trình mệnh lệnh, đến nỗi bất kỳ cách nào khác nghe có vẻ quá kỳ lạ. Hãy tưởng tượng nếu thực tế diễn ra theo chiều ngược lại: chúng ta đã rất quen với việc lập trình hàm và một mô hình mệnh lệnh mới được thêm vào Java. Bạn có nói rằng đây sẽ là trường hợp cổ điển cho luồng, vị từ và tùy chọn không?
fps

9
Đừng gọi get()ở đây! Bạn không biết liệu nó có trống hay không. Bạn sẽ ném ra một ngoại lệ nếu phần tử không có ở đó. Thay vào đó, hãy sử dụng một trong các phương pháp an toàn như ifPresent, orElse, orElseGet hoặc orElseThrow.
Brian Goetz

@FedericoPeraltaSchaffner Có lẽ là vấn đề sở thích. Nhưng tôi cũng sẽ đề xuất không kết hợp cả hai. Khi tôi thấy một số mã nhận được giá trị bằng cách sử dụng các luồng, tôi thường cho rằng các hoạt động trong luồng không có tác dụng phụ. Việc trộn lẫn loại bỏ phần tử có thể dẫn đến mã có thể gây hiểu nhầm cho người đọc.
Matthias Wimmer

Chỉ cần làm rõ: Bạn có muốn loại bỏ tất cả các phần tử trong listđó Predicatelà true hay chỉ là phần tử đầu tiên (có thể là 0, một hoặc nhiều phần tử)?
Kedar Mhaswade 29/02/16

Câu trả lời:


148

Để xóa phần tử khỏi danh sách

objectA.removeIf(x -> conditions);

ví dụ:

objectA.removeIf(x -> blockedWorkerIds.contains(x));

List<String> str1 = new ArrayList<String>();
str1.add("A");
str1.add("B");
str1.add("C");
str1.add("D");

List<String> str2 = new ArrayList<String>();
str2.add("D");
str2.add("E");

str1.removeIf(x -> str2.contains(x)); 

str1.forEach(System.out::println);

ĐẦU RA: A B C


tôi đề nghị đây là câu trả lời hay nhất
Sergey Pauk.

1
Điều này là rất gọn gàng. Bạn có thể cần triển khai mã bằng / mã băm nếu điều này không được thực hiện cho các đối tượng của riêng bạn. (Trong ví dụ này, chuỗi được sử dụng theo mặc định).
bram000

15
IMHO câu trả lời không xem xét phần "get": removeIflà một giải pháp thanh lịch để xóa các phần tử khỏi tập hợp, nhưng nó không trả về phần tử đã bị loại bỏ.
Marco Stramezzi

Đây là một chế độ rất đơn giản để loại trừ một đối tượng khỏi java ArrayList. Thkns rất nhiều. Làm việc tốt với tôi.
Marcelo Rebouças

2
Điều này không trả lời câu hỏi. Yêu cầu là xóa phần tử khỏi danh sách và chuyển mục / mục đã xóa sang danh sách mới.
Shanika Ediriweera

34

Mặc dù các chủ đề là khá cũ, vẫn được cho là cung cấp giải pháp sử dụng Java8.

Sử dụng removeIfchức năng. Thời gian phức tạp làO(n)

producersProcedureActive.removeIf(producer -> producer.getPod().equals(pod));

Tham chiếu API: removeIf docs

Giả định: producersProcedureActivelà mộtList

LƯU Ý: Với cách tiếp cận này, bạn sẽ không thể lấy được mục đã xóa.


Ngoài việc xóa phần tử khỏi danh sách, OP vẫn muốn tham chiếu đến phần tử.
eee

@eee: Cảm ơn rất nhiều vì đã chỉ ra điều đó. Tôi đã bỏ lỡ phần đó từ câu hỏi ban đầu của OP.
asifsid88

chỉ cần lưu ý điều này sẽ loại bỏ tất cả các mục phù hợp với điều kiện. Nhưng OP dường như chỉ cần xóa mục đầu tiên (OP đã sử dụng findFirst ())
nantitv. 14/03/18

17

Cân nhắc sử dụng trình vòng lặp vani java để thực hiện tác vụ:

public static <T> T findAndRemoveFirst(Iterable<? extends T> collection, Predicate<? super T> test) {
    T value = null;
    for (Iterator<? extends T> it = collection.iterator(); it.hasNext();)
        if (test.test(value = it.next())) {
            it.remove();
            return value;
        }
    return null;
}

Ưu điểm :

  1. Nó là đơn giản và hiển nhiên.
  2. Nó chỉ đi qua một lần và chỉ đến phần tử phù hợp.
  3. Bạn có thể làm điều đó trên bất kỳ công cụ nào Iterablengay cả khi không có stream()hỗ trợ (ít nhất là những người triển khai remove()trên trình lặp của họ) .

Nhược điểm :

  1. Bạn không thể làm điều đó tại chỗ dưới dạng một biểu thức duy nhất (phương thức bổ trợ hoặc biến bắt buộc)

Đối với

Có thể kết hợp get và remove trong một biểu thức lambda không?

các câu trả lời khác cho thấy rõ ràng rằng điều đó có thể xảy ra, nhưng bạn nên biết

  1. Tìm kiếm và xóa có thể lướt qua danh sách hai lần
  2. ConcurrentModificationException có thể bị ném khi xóa phần tử khỏi danh sách đang được lặp lại

4
Tôi thích giải pháp này, nhưng lưu ý rằng nó có một nhược điểm nghiêm trọng mà bạn đã bỏ qua: nhiều triển khai có thể remove()lặp lại có các phương thức ném UOE. (Tất nhiên không phải là những thứ dành cho bộ sưu tập JDK, nhưng tôi nghĩ thật không công bằng khi nói "hoạt động trên bất kỳ Lặp lại nào".)
Brian Goetz

Tôi nghĩ rằng chúng ta có thể giả định rằng nếu một phần tử có thể được gỡ bỏ nói chung , nó có thể được gỡ bỏ bởi iterator
Vasily Liaskovsky

5
Bạn có thể cho rằng điều đó, nhưng đã xem xét hàng trăm triển khai trình lặp, đó sẽ là một giả định tồi. (Tôi vẫn thích cách tiếp cận; bạn chỉ đang bán quá nhiều nó.)
Brian Goetz

2
@Brian Goetz: việc defaulttriển khai removeIftạo ra cùng một giả định, nhưng tất nhiên, nó được định nghĩa Collectionthay vì Iterable
Holger

14

Giải pháp trực tiếp sẽ là gọi ifPresent(consumer)Tùy chọn được trả về bởi findFirst(). Người tiêu dùng này sẽ được gọi khi tùy chọn không trống. Lợi ích cũng là nó sẽ không ném ra một ngoại lệ nếu hoạt động tìm kiếm trả về một tùy chọn trống, giống như mã hiện tại của bạn sẽ làm; thay vào đó, sẽ không có gì xảy ra.

Nếu bạn muốn trả về giá trị loại bỏ, bạn có thể mapcác Optionalđể kết quả của sự kêu gọi remove:

producersProcedureActive.stream()
                        .filter(producer -> producer.getPod().equals(pod))
                        .findFirst()
                        .map(p -> {
                            producersProcedureActive.remove(p);
                            return p;
                        });

Nhưng lưu ý rằng remove(Object)thao tác sẽ duyệt lại danh sách để tìm phần tử cần loại bỏ. Nếu bạn có một danh sách có quyền truy cập ngẫu nhiên, chẳng hạn như một ArrayList, tốt hơn là tạo Luồng qua các chỉ mục của danh sách và tìm chỉ mục đầu tiên phù hợp với vị từ:

IntStream.range(0, producersProcedureActive.size())
         .filter(i -> producersProcedureActive.get(i).getPod().equals(pod))
         .boxed()
         .findFirst()
         .map(i -> producersProcedureActive.remove((int) i));

Với giải pháp này, remove(int)hoạt động hoạt động trực tiếp trên chỉ mục.


3
Đây là bệnh lý cho một danh sách liên kết.
chrylis -cautilyoptimistic-

1
@chrylis Thực sự là giải pháp chỉ mục. Tùy thuộc vào việc triển khai danh sách, cái này sẽ thích cái này hơn cái kia. Đã thực hiện một chỉnh sửa nhỏ.
Tunaki

1
@chrylis: trong trường hợp xảy ra, LinkedListbạn có lẽ không nên sử dụng API luồng vì không có giải pháp nào mà không duyệt qua ít nhất hai lần. Nhưng tôi không biết bất kỳ tình huống thực tế nào trong đó lợi thế học tập của danh sách liên kết có thể bù đắp chi phí thực tế của nó. Vì vậy, giải pháp đơn giản là không bao giờ sử dụng LinkedList.
Holger

2
Ồ quá nhiều chỉnh sửa… hiện tại giải pháp đầu tiên không cung cấp phần tử bị loại bỏ vì remove(Object)chỉ trả về một phần tử booleancho biết có phần tử cần xóa hay không.
Holger

3
@Marco Stramezzi: rất tiếc, bình luận giải thích nó đã bị xóa. Không có boxed()bạn nhận được một OptionalIntmà chỉ có thể maptừ intđến int. Không giống như IntStream, không có mapToObjphương pháp. Với boxed(), bạn sẽ nhận được một Optional<Integer>cho phép mapđến một đối tượng tùy ý, tức là được ProducerDTOtrả về bởi remove(int). Diễn viên từ Integerđến intlà cần thiết để phân biệt giữa remove(int)remove(Object).
Holger

9

Sử dụng có thể sử dụng bộ lọc của Java 8 và tạo một danh sách khác nếu bạn không muốn thay đổi danh sách cũ:

List<ProducerDTO> result = producersProcedureActive
                            .stream()
                            .filter(producer -> producer.getPod().equals(pod))
                            .collect(Collectors.toList());

5

Tôi chắc rằng đây sẽ là một câu trả lời không phổ biến, nhưng nó hoạt động ...

ProducerDTO[] p = new ProducerDTO[1];
producersProcedureActive
            .stream()
            .filter(producer -> producer.getPod().equals(pod))
            .findFirst()
            .ifPresent(producer -> {producersProcedureActive.remove(producer); p[0] = producer;}

p[0] sẽ giữ phần tử được tìm thấy hoặc là null.

"Thủ thuật" ở đây là vượt qua vấn đề "hiệu quả cuối cùng" bằng cách sử dụng tham chiếu mảng có hiệu quả là cuối cùng, nhưng đặt phần tử đầu tiên của nó.


1
Nó trường hợp này, nó không phải là xấu, nhưng không phải là một sự cải tiến trong khả năng để chỉ gọi .orElse(null)để có được ProducerDTOhoặc null...
Holger

Trong trường hợp đó, có thể dễ dàng hơn nếu chỉ có .orElse(null)và có một if, không?
Tunaki

@Holger nhưng làm thế nào bạn có thể gọi remove()quá bằng cách sử dụng orElse(null)?
Bohemian

1
Chỉ cần sử dụng kết quả. if(p!=null) producersProcedureActive.remove(p);vẫn ngắn hơn biểu thức lambda trong ifPresentcuộc gọi của bạn .
Holger

@holger Tôi đã giải thích mục tiêu của câu hỏi là tránh nhiều câu - tức là giải pháp 1 dòng
Bohemian

4

Với Bộ sưu tập Eclipse, bạn có thể sử dụng detectIndexcùng với remove(int)bất kỳ java.util.List nào.

List<Integer> integers = Lists.mutable.with(1, 2, 3, 4, 5);
int index = Iterate.detectIndex(integers, i -> i > 2);
if (index > -1) {
    integers.remove(index);
}

Assert.assertEquals(Lists.mutable.with(1, 2, 4, 5), integers);

Nếu bạn sử dụng MutableListkiểu từ Bộ sưu tập Eclipse, bạn có thể gọi detectIndexphương thức trực tiếp trên danh sách.

MutableList<Integer> integers = Lists.mutable.with(1, 2, 3, 4, 5);
int index = integers.detectIndex(i -> i > 2);
if (index > -1) {
    integers.remove(index);
}

Assert.assertEquals(Lists.mutable.with(1, 2, 4, 5), integers);

Lưu ý: Tôi là người cam kết cho Bộ sưu tập Eclipse


2

Khi chúng tôi muốn đưa nhiều phần tử từ một Danh sách vào một danh sách mới (lọc bằng cách sử dụng một vị từ) và xóa chúng khỏi danh sách hiện có , tôi không thể tìm thấy câu trả lời thích hợp ở bất kỳ đâu.

Đây là cách chúng ta có thể thực hiện bằng cách sử dụng phân vùng Java Streaming API.

Map<Boolean, List<ProducerDTO>> classifiedElements = producersProcedureActive
    .stream()
    .collect(Collectors.partitioningBy(producer -> producer.getPod().equals(pod)));

// get two new lists 
List<ProducerDTO> matching = classifiedElements.get(true);
List<ProducerDTO> nonMatching = classifiedElements.get(false);

// OR get non-matching elements to the existing list
producersProcedureActive = classifiedElements.get(false);

Bằng cách này, bạn loại bỏ hiệu quả các phần tử đã lọc khỏi danh sách ban đầu và thêm chúng vào danh sách mới.

Tham khảo 5.2. Collectors.partitioningBằng phần của bài viết này .


1

Như những người khác đã đề xuất, đây có thể là một trường hợp sử dụng cho các vòng lặp và các tệp lặp. Theo tôi, đây là cách tiếp cận đơn giản nhất. Nếu bạn muốn sửa đổi danh sách tại chỗ, nó không thể được coi là lập trình chức năng "thực". Nhưng bạn có thể sử dụng Collectors.partitioningBy()để có được một danh sách mới với các phần tử đáp ứng điều kiện của bạn và một danh sách mới gồm những phần tử không. Tất nhiên với cách tiếp cận này, nếu bạn có nhiều yếu tố thỏa mãn điều kiện, tất cả những yếu tố đó sẽ nằm trong danh sách đó và không chỉ là yếu tố đầu tiên.


Tốt hơn nhiều để lọc luồng và thu thập kết quả vào danh sách mới
fps

1

Logic dưới đây là giải pháp mà không cần sửa đổi danh sách ban đầu

List<String> str1 = new ArrayList<String>();
str1.add("A");
str1.add("B");
str1.add("C");
str1.add("D");

List<String> str2 = new ArrayList<String>();
str2.add("D");
str2.add("E");

List<String> str3 = str1.stream()
                        .filter(item -> !str2.contains(item))
                        .collect(Collectors.toList());

str1 // ["A", "B", "C", "D"]
str2 // ["D", "E"]
str3 // ["A", "B", "C"]

0

Kết hợp ý tưởng ban đầu của tôi và câu trả lời của bạn, tôi đã đạt được điều dường như là giải pháp cho câu hỏi của chính tôi:

public ProducerDTO findAndRemove(String pod) {
    ProducerDTO p = null;
    try {
        p = IntStream.range(0, producersProcedureActive.size())
             .filter(i -> producersProcedureActive.get(i).getPod().equals(pod))
             .boxed()
             .findFirst()
             .map(i -> producersProcedureActive.remove((int)i))
             .get();
        logger.debug(p);
    } catch (NoSuchElementException e) {
        logger.error("No producer found with POD [" + pod + "]");
    }
    return p;
}

Nó cho phép loại bỏ đối tượng bằng cách sử dụng remove(int)không duyệt lại danh sách (theo đề xuất của @Tunaki) nó cho phép trả lại đối tượng đã loại bỏ cho trình gọi hàm.

Tôi đọc câu trả lời của bạn gợi ý tôi nên chọn các phương pháp an toàn như ifPresentthay vìget nhưng tôi không tìm thấy cách sử dụng chúng trong trường hợp này.

Có bất kỳ nhược điểm quan trọng nào trong loại giải pháp này không?

Chỉnh sửa lời khuyên sau của @Holger

Đây phải là chức năng tôi cần

public ProducerDTO findAndRemove(String pod) {
    return IntStream.range(0, producersProcedureActive.size())
            .filter(i -> producersProcedureActive.get(i).getPod().equals(pod))      
            .boxed()                                                                
            .findFirst()
            .map(i -> producersProcedureActive.remove((int)i))
            .orElseGet(() -> {
                logger.error("No producer found with POD [" + pod + "]"); 
                return null; 
            });
}

2
Bạn không nên sử dụng getvà bắt ngoại lệ. Đó không chỉ là phong độ tồi mà còn có thể gây ra hiệu suất không tốt. Giải pháp sạch thậm chí còn đơn giản hơn,return /* stream operation*/.findFirst() .map(i -> producersProcedureActive.remove((int)i)) .orElseGet(() -> { logger.error("No producer found with POD [" + pod + "]"); return null; });
Holger

0

nhiệm vụ là: lấy ✶ ✶ xóa phần tử khỏi danh sách

p.stream().collect( Collectors.collectingAndThen( Collector.of(
    ArrayDeque::new,
    (a, producer) -> {
      if( producer.getPod().equals( pod ) )
        a.addLast( producer );
    },
    (a1, a2) -> {
      return( a1 );
    },
    rslt -> rslt.pollFirst()
  ),
  (e) -> {
    if( e != null )
      p.remove( e );  // remove
    return( e );    // get
  } ) );
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.