Áttekintés
Ebben a gyors bemutatóban négy különböző módszerről fogunk beszélni, amelyekkel eltávolíthatunk bizonyos predikátumoknak megfelelő elemeket a Java gyűjteményekből.
Megnézzük természetesen a figyelmeztetéseket is.
A gyűjteményünk definiálása
Először két olyan megközelítést fogunk bemutatni, amelyek az eredeti adatszerkezetet mutálják. Ezután két másik lehetőségről fogunk beszélni, amelyek az elemek eltávolítása helyett az eredeti Collection egy másolatát hozzák létre nélkülük.
Példáink során végig a következő Collectiont használjuk, hogy bemutassuk, hogyan érhetjük el ugyanazt az eredményt különböző módszerekkel:
Collection<String> names = new ArrayList<>();names.add("John");names.add("Ana");names.add("Mary");names.add("Anthony");names.add("Mark");
Elemek eltávolítása Iterátorral
A Java Iterátor lehetővé teszi számunkra, hogy egy Collectionen belül minden egyes elemet bejárjunk és eltávolítsunk.
Ezhez először is az iterátor metódus segítségével le kell hívnunk egy iterátort az elemei felett. Ezután a next segítségével végiglátogathatjuk az egyes elemeket, majd a remove segítségével eltávolíthatjuk őket:
Iterator<String> i = names.iterator();while(i.hasNext()) { String e = i.next(); if (e.startsWith("A")) { i.remove(); }}
Az egyszerűség ellenére van néhány figyelmeztetés, amit figyelembe kell vennünk:
- A gyűjteménytől függően ConcurrentModificationException kivételekbe ütközhetünk
- Az elemek felett iterálnunk kell, mielőtt eltávolíthatnánk őket
- A gyűjteménytől függően a remove a várttól eltérően viselkedhet. Pl: ArrayList.Iterator eltávolítja az elemet a gyűjteményből, és a következő adatokat balra tolja, míg a LinkedList.Iterator egyszerűen a következő elemhez igazítja a mutatót. Mint ilyen, a LinkedList.Iterator sokkal jobban teljesít az elemek eltávolításakor, mint az ArrayList.Iterator
Java 8 és a Collection.removeIf()
A Java 8 bevezetett egy új módszert a Collection interfészbe, amely a Predicate használatával tömörebb módot biztosít az elemek eltávolítására:
names.removeIf(e -> e.startsWith("A"));
Nagyon fontos, hogy az Iterator megközelítéssel ellentétben a removeIf hasonlóan jól teljesít a LinkedList és az ArrayList esetében is.
A Java 8-ban az ArrayList felülírja az alapértelmezett – az Iteratorra támaszkodó – implementációt, és más stratégiát valósít meg: először végigmegy az elemeken, és megjelöli azokat, amelyek megfelelnek a Predicate-ünknek; ezután másodszor is végigmegy, hogy eltávolítsa (és eltolja) az első iterációban megjelölt elemeket.
A Java 8 és a Stream bevezetése
A Java 8 egyik legfontosabb újdonsága a Stream (és a gyűjtők) bevezetése volt. Sokféleképpen hozhatunk létre Stream-et egy forrásból. A legtöbb, a Stream példányt érintő művelet azonban nem változtatja meg a forrását, az API inkább arra összpontosít, hogy másolatokat hozzon létre a forrásból, és azokon végezzen el bármilyen műveletet, amire szükségünk lehet.
Nézzük meg, hogyan használhatjuk a Streamet és a Collectorokat a Predicate-ünknek megfelelő és nem megfelelő elemek megkeresésére/szűrésére.
5.1. A Stream és a Collectorok használata. Elemek eltávolítása Stream segítségével
A Stream segítségével az elemek eltávolítása, vagy inkább szűrése meglehetősen egyszerű, csak létre kell hoznunk egy példányt a Stream-ből a Collection-ünkkel, meg kell hívnunk a szűrőt a Predicate-ünkkel, majd a Collectors segítségével összegyűjteni az eredményt:
Collection<String> filteredCollection = names .stream() .filter(e -> !e.startsWith("A")) .collect(Collectors.toList());
A Stream kevésbé invazív, mint az előző megközelítések, elősegíti az izolációt és lehetővé teszi több példány létrehozását ugyanabból a forrásból. Azonban szem előtt kell tartanunk, hogy az alkalmazásunk által használt memóriát is megnöveli.
5.2. Collectors.partitioningBy
A Stream.filter és a Collectors kombinálása meglehetősen praktikus, bár előfordulhat, hogy olyan forgatókönyvekbe ütközünk, ahol egyező és nem egyező elemekre is szükségünk van. Ilyen esetekben kihasználhatjuk a Collectors.partitioningBy előnyeit:
Map<Boolean, List<String>> classifiedElements = names .stream() .collect(Collectors.partitioningBy((String e) -> !e.startsWith("A")));String matching = String.join(",", classifiedElements.get(true));String nonMatching = String.join(",", classifiedElements.get(false));
Ez a módszer egy Map-et ad vissza, amely csak két kulcsot tartalmaz, true és false, amelyek mindegyike egy olyan listára mutat, amely az illeszkedő, illetve nem illeszkedő elemeket tartalmazza.
Következtetés
Ebben a cikkben megnéztünk néhány módszert, amellyel elemeket távolíthatunk el a Collections-ből, és néhány fenntartásukat.