Java 8 lambda listeden öğe alma ve kaldırma


91

Öğelerin listesini göz önüne alındığında, belirli bir özelliğiyle eleman almak istiyorum ve listeden çıkarın. Bulduğum en iyi çözüm:

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

Bir lambda ifadesinde get ve remove birleştirmek mümkün müdür?


9
Bu gerçekten ne zaman sadece döngü ve yineleyici kullanılacağına dair klasik bir durum gibi görünüyor.
chrylis

1
@chrylis Kibarca katılmıyorum;) Zorunlu programlamaya o kadar alışkınız ki, başka herhangi bir yol kulağa çok egzotik geliyor. Gerçekliğin tersi olduğunu hayal edin: işlevsel programlamaya çok alışkınız ve Java'ya yeni bir zorunlu paradigma eklendi. Bunun akışlar, tahminler ve isteğe bağlı durumlar için klasik durum olacağını söyleyebilir misiniz?
fps

9
get()Burayı arama ! Boş olup olmadığı hakkında hiçbir fikrin yok. Öğe orada değilse bir istisna atarsınız. Bunun yerine, ifPresent, orElse, orElseGet veya orElseThrow gibi güvenli yöntemlerden birini kullanın.
Brian Goetz

@FedericoPeraltaSchaffner Muhtemelen bir zevk meselesi. Ama ikisini birden birleştirmemeyi de öneririm. Akışları kullanarak bir değer alan bir kod gördüğümde, normalde akıştaki işlemlerin yan etkisiz olduğunu varsayıyorum. Öğeyi kaldırırken karıştırmak, okuyuculara yanlış yönlendirebilecek koda neden olabilir.
Matthias Wimmer

Sadece açıklığa kavuşturmak için: İçinde doğru listolduğu tüm öğeleri mi Predicateyoksa yalnızca ilkini mi (muhtemelen sıfır, bir veya birçok öğeden) kaldırmak istiyor musunuz ?
Kedar Mhaswade

Yanıtlar:


154

Elemanı listeden çıkarmak için

objectA.removeIf(x -> conditions);

Örneğin:

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

ÇIKIŞ: A B C


bunu en iyi cevap olarak öneririm
Sergey Pauk

1
Bu çok güzel. Kendi nesneleriniz için bu yapılmadıysa, equals / hashCode'u uygulamanız gerekebilir. (Bu örnekte, varsayılan olarak bunlara sahip olan Dize kullanılmıştır).
bram000

17
IMHO cevabı "alma" kısmını dikkate almıyor: removeIfbir koleksiyondaki öğeleri kaldırmak için zarif bir çözümdür, ancak kaldırılan öğeyi döndürmez.
Marco Stramezzi

Bir nesneyi java ArrayList'den dışlamak için çok basit bir mod. Çok şey var. Benim için iyi çalış.
Marcelo Rebouças

3
Bu soruya cevap vermiyor. Gerekli, öğeyi listeden kaldırmak ve kaldırılan öğeyi / öğeleri yeni bir listeye almaktır.
Shanika Ediriweera

35

İş parçacığı oldukça eski olmasına rağmen, yine de çözüm - kullanım sağladığı düşünülüyor Java8.

removeIfFonksiyonu kullanın . Zaman karmaşıklığıO(n)

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

API referansı: removeIf docs

Varsayım: producersProcedureActivebirList

NOT: Bu yaklaşımla, silinen öğenin muhafazasını alamazsınız.


Listeden elemanı silmeye ek olarak, OP yine de elemana bir referans istiyor.
eee

@eee: Bunu belirttiğiniz için çok teşekkürler. OP'nin orijinal sorusundaki o kısmı kaçırdım.
asifsid88

Sadece bunun koşulla eşleşen tüm öğeleri kaldıracağını unutmayın. Ancak OP'nin yalnızca ilk öğeyi kaldırması gerekiyor gibi görünüyor (OP kullanılan findFirst ())
nantitv

18

Görevi gerçekleştirmek için vanilya java yineleyicileri kullanmayı düşünün:

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

Avantajlar :

  1. Açık ve net.
  2. Yalnızca bir kez ve yalnızca eşleşen öğeye kadar hareket eder.
  3. Bunu herhangi bir destek Iterableolmadan bile yapabilirsiniz (en azından kendi yineleyicilerinde uygulayanlar ) .stream()remove()

Dezavantajları :

  1. Bunu tek bir ifade olarak yerinde yapamazsınız (yardımcı yöntem veya değişken gereklidir)

Gelince

Bir lambda ifadesinde get ve remove birleştirmek mümkün müdür?

diğer cevaplar bunun mümkün olduğunu açıkça gösteriyor, ancak farkında olmalısınız

  1. Arama ve kaldırma, listeyi iki kez dolaşabilir
  2. ConcurrentModificationException yinelenen listeden öğe kaldırılırken atılabilir

5
Bu çözümü beğendim, ancak gözden kaçırdığınız ciddi bir dezavantajı olduğunu unutmayın: birçok yinelenebilir uygulamanın UOE'yi fırlatan remove()yöntemleri vardır. (Tabii ki JDK koleksiyonları için olanlar değil, ama bence "herhangi bir yinelenebilir üzerinde çalışıyor" demek haksızlık olur.)
Brian Goetz

Sanırım bir öğe genel olarak kaldırılabiliyorsa , yineleyici
varsayabiliriz

5
Bunu varsayabilirsiniz, ancak yüzlerce yineleyici uygulamasına bakmışsanız, bu kötü bir varsayım olur. (Bu yaklaşımı hala beğeniyorum; aşırı satıyorsunuz.)
Brian Goetz

2
@Brian Goetz: defaultuygulaması removeIfda aynı varsayımı yapıyor, ama tabii ki, bunun Collectionyerine Iterable
Holger

14

Doğrudan çözüm, ifPresent(consumer)tarafından döndürülen İsteğe Bağlı olarak çağırmak olacaktır findFirst(). Bu tüketici, isteğe bağlı boş olmadığında çağrılacaktır. Bunun yararı da, bulma işlemi, mevcut kodunuzun yapacağı gibi boş bir isteğe bağlı boş döndürdüyse bir istisna atmamasıdır; bunun yerine hiçbir şey olmayacak.

Kaldırılan değer döndürmek istiyorsanız, yapabilirsiniz arama sonucuna :mapOptionalremove

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

Ancak, remove(Object)işlemin, kaldırılacak öğeyi bulmak için listeden tekrar geçeceğini unutmayın . Bir gibi rastgele erişime sahip bir listeniz varsa, listenin ArrayListdizinleri üzerinde bir Akış oluşturmak ve yüklemle eşleşen ilk dizini bulmak daha iyi olacaktır:

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

Bu çözüm ile remove(int)işlem direkt olarak indeks üzerinde işlemektedir.


3
Bu, bağlantılı bir liste için patolojiktir.
chrylis

1
@chrylis Dizin çözümü gerçekten olabilir. Liste uygulamasına bağlı olarak biri diğerine tercih edilirdi. Küçük bir düzenleme yaptım.
Tunaki

1
@chrylis: Bir durumda, LinkedListbelki de en az iki kez dolaşmadan çözüm olmadığı için akış API'sini kullanmamalısınız. Ancak, bağlantılı bir listenin akademik avantajının, gerçek genel giderini telafi edebileceği herhangi bir gerçek hayat senaryosu bilmiyorum. Yani basit çözüm asla kullanmamaktır LinkedList.
Holger

2
Oh çok fazla düzenleme… şimdi ilk çözüm kaldırılan öğeyi sağlamıyor, çünkü remove(Object)yalnızca kaldırılacak bir öğenin booleanolup olmadığını bildiriyor.
Holger

3
@Marco Stramezzi: Maalesef bunu açıklayan yorum kaldırıldı. Olmadan boxed()bir olsun OptionalInthangi yalnızca can mapdan intiçin int. Aksine IntStream, mapToObjyöntem yoktur . İle boxed(), rastgele bir nesneye, yani tarafından döndürülmeye Optional<Integer>izin veren bir elde edersiniz . Den döküm için arasındaki belirsizliği için gerekli olan ve . mapProducerDTOremove(int)Integerintremove(int)remove(Object)
Holger

9

Java 8 filtresini kullanabilir ve eski listeyi değiştirmek istemiyorsanız başka bir liste oluşturabilirsiniz:

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

5

Eminim bu popüler olmayan bir cevap olacaktır, ama işe yarıyor ...

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

p[0] ya bulunan öğeyi tutacak ya da boş olacaktır.

Buradaki "hile" , etkili bir şekilde nihai olan ancak ilk elemanını ayarlayan bir dizi referansı kullanarak "etkili bir şekilde nihai" problemin üstesinden gelmektir.


1
Bu durumda, bu kadar kötü olacağını değil, ama sadece çağrıya olasılığı üzerinde bir gelişme değil .orElse(null)almak ProducerDTOya null...
Holger

Bu durumda, sadece sahip olmak .orElse(null)ve sahip olmak daha kolay olabilir if, hayır?
Tunaki

@Holger ama kullanarak siz remove()de nasıl çağırabilirsiniz orElse(null)?
Bohemian

1
Sadece sonucu kullanın. if(p!=null) producersProcedureActive.remove(p);hala ifPresentaramanızdaki lambda ifadesinden daha kısa .
Holger

@holger Sorunun amacını birden fazla ifadeden kaçınmak olarak yorumladım - yani 1 satırlık çözüm
Bohemian

4

İle Eclipse Koleksiyonları kullanabilirsiniz detectIndexbirlikte remove(int)herhangi java.util.List üzerinde.

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

MutableListEclipse Collections'daki türü kullanırsanız, detectIndexyöntemi doğrudan listeden çağırabilirsiniz .

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

Not: Eclipse Koleksiyonları için bir kaydediciyim


2

Bir Listeden birden fazla öğeyi yeni bir listeye almak (bir yüklem kullanarak filtrelemek) ve bunları mevcut listeden çıkarmak istediğimizde , hiçbir yerde uygun bir cevap bulamadım.

Java Streaming API bölümlemesini kullanarak bunu nasıl yapabileceğimiz aşağıda açıklanmıştır.

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

Bu şekilde, filtrelenmiş öğeleri orijinal listeden etkili bir şekilde kaldırır ve yeni bir listeye eklersiniz.

5.2'ye bakın . Collectors.partitioningBu makalenin bölümüne göre .


1

Başkalarının da önerdiği gibi, bu döngüler ve yinelemeler için bir kullanım durumu olabilir. Bence bu en basit yaklaşım. Listeyi yerinde değiştirmek istiyorsanız, bu zaten "gerçek" işlevsel programlama olarak kabul edilemez. Ancak Collectors.partitioningBy(), koşulunuzu karşılayan öğeler içeren yeni bir liste ve karşılamayanların yeni bir listesini almak için kullanabilirsiniz . Elbette bu yaklaşımla, koşulu karşılayan birden fazla öğeye sahipseniz, bunların tümü o listede olacak, sadece ilk değil.


Akışı filtrelemek ve sonuçları yeni bir listede toplamak çok daha iyi
fps

1

Aşağıdaki mantık, orijinal listeyi değiştirmeden çözümdür

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

İlk fikrim ve cevaplarınızı birleştirerek, kendi sorumun çözümü gibi görünen şeye ulaştım:

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

Bu kullanarak nesneyi kaldırmak sağlar remove(int)(@Tunaki önerdiği gibi) bunu çapraz listeyi yeniden ve bu fonksiyon arayana kaldırılan nesneyi dönmesini sağlar.

Ben güvenli yöntemler gibi seçmemi önermek cevaplarınızı okumak ifPresentyerineget ama bu senaryoda kullanmanın bir yolunu bulamıyorum.

Bu tür bir çözümde önemli bir dezavantaj var mı?

@Holger tavsiyesini düzenleyin

İhtiyacım olan işlev bu olmalı

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
İstisnayı kullanmamalı getve yakalamamalısınız. Bu sadece kötü stil değil, aynı zamanda kötü performansa da neden olabilir. Temiz çözüm daha da basitreturn /* stream operation*/.findFirst() .map(i -> producersProcedureActive.remove((int)i)) .orElseGet(() -> { logger.error("No producer found with POD [" + pod + "]"); return null; });
Holger

0

görev şudur: ✶ al ve ✶ listeden öğeyi kaldır

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
  } ) );
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.