Přehled
V tomto krátkém tutoriálu si povíme o čtyřech různých způsobech odstraňování prvků z kolekcí Java, které odpovídají určitým predikátům.
Přirozeně se také podíváme na některá upozornění.
Definování naší kolekce
Nejprve si ukážeme dva přístupy, které mutují původní datovou strukturu. Poté si povíme o dalších dvou možnostech, které místo odstranění prvků vytvoří kopii původní kolekce bez nich.
Ve všech našich příkladech budeme používat následující kolekci, abychom si ukázali, jak můžeme dosáhnout stejného výsledku pomocí různých metod:
Collection<String> names = new ArrayList<>();names.add("John");names.add("Ana");names.add("Mary");names.add("Anthony");names.add("Mark");
Odstranění prvků pomocí Iterátoru
Iterátor jazyka Java nám umožňuje procházet i odstraňovat každý jednotlivý prvek v kolekci.
Pro to musíme nejprve získat iterátor nad jejími prvky pomocí metody iterátor. Poté můžeme jednotlivé prvky navštívit pomocí next a odstranit je pomocí remove:
Iterator<String> i = names.iterator();while(i.hasNext()) { String e = i.next(); if (e.startsWith("A")) { i.remove(); }}
Přes tuto jednoduchost bychom měli vzít v úvahu některá upozornění:
- V závislosti na kolekci můžeme narazit na výjimky ConcurrentModificationException
- Před odstraněním prvků musíme iterovat nad prvky
- V závislosti na kolekci se remove může chovat jinak, než očekáváme. Např: ArrayList.Iterator odstraní prvek z kolekce a posune následující data doleva, zatímco LinkedList.Iterator jednoduše upraví ukazatel na další prvek. LinkedList.Iterator se proto při odstraňování prvků chová mnohem lépe než ArrayList.Iterator
Java 8 a Collection.removeIf()
Java 8 zavedla do rozhraní Collection novou metodu, která poskytuje stručnější způsob odstraňování prvků pomocí Predicate:
names.removeIf(e -> e.startsWith("A"));
Důležité je poznamenat, že na rozdíl od přístupu Iterator se removeIf chová podobně dobře jak v LinkedListu, tak v ArrayListu.
V Javě 8 ArrayList přepisuje výchozí implementaci – která se spoléhá na Iterator – a implementuje jinou strategii: nejprve iteruje nad prvky a označí ty, které odpovídají našemu Predikátu; poté iteruje podruhé, aby odstranil (a posunul) prvky, které byly označeny v první iteraci.
Java 8 a zavedení proudu
Jednou z hlavních novinek v Javě 8 bylo přidání proudu (a kolektorů). Existuje mnoho způsobů, jak vytvořit Stream ze zdroje. Většina operací, které ovlivňují instanci Streamu, však nebude mutovat jeho zdroj, API se spíše zaměřuje na vytváření kopií zdroje a provádění libovolných operací, které v nich můžeme potřebovat.
Podívejme se, jak můžeme použít Stream a Collectors k nalezení/filtraci prvků, které odpovídají a neodpovídají našemu Predikátu.
5.1. Odstraňování prvků pomocí Streamu
Odstraňování, nebo spíše filtrování prvků pomocí Streamu je poměrně jednoduché, stačí vytvořit instanci Streamu pomocí naší Kolekce, vyvolat filtr s naším Predikátem a poté shromáždit výsledek pomocí Kolektorů:
Collection<String> filteredCollection = names .stream() .filter(e -> !e.startsWith("A")) .collect(Collectors.toList());
Streaming je méně invazivní než předchozí přístupy, podporuje izolaci a umožňuje vytvoření více kopií ze stejného zdroje. Měli bychom však mít na paměti, že také zvyšuje paměťovou náročnost naší aplikace.
5.2. Collectors.partitioningBy
Kombinace Stream.filter i Collectors je docela šikovná, i když můžeme narazit na scénáře, kdy potřebujeme shodné i neshodné prvky. V takových případech můžeme využít Collectors.partitioningBy:
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));
Tato metoda vrací Mapu, která obsahuje pouze dva klíče, true a false, z nichž každý ukazuje na seznam, který obsahuje odpovídající, respektive neodpovídající prvky.
Závěr
V tomto článku jsme se podívali na některé metody pro odstraňování prvků z Collections a na některá jejich úskalí.
.