IT rekvalifikace s garancí práce. Seniorní programátoři vydělávají až 160 000 Kč/měsíc a rekvalifikace je prvním krokem. Zjisti, jak na to!
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

Lekce 2 - Java Collections Framework

V minulé lekci, Úvod do kolekcí a genericita v Javě, jsme si udělali úvod do kolekcí a ukázali jsme si, co je to genericita.

V dnešní lekci si řekneme, jak má jazyk Java kolekce implementované. Představíme si základní část z Java Collections Frameworku.

Java Collections Framework

Každý dobrý programovací jazyk nabízí ve standardní knihovně práci s kolekcemi. V jazyce Java tuto část řeší celý framework nazvaný Java Collections Framework. Jedná se o relativně složitou hierarchii rozhraní a tříd, které jsou dostupné všem programátorům. Základní vizualizace tohoto frameworku je vidět na UML diagramu níže:

Zjednodušený diagram Java Collections Frameworku - Kolekce a proudy v Javě

Základní rozhraní, které zaštiťuje každou kolekci v Javě, je rozhraní Collection. Toto rozhraní popisuje základní metody pro práci s každou kolekcí. Výběr nejdůležitějších metod je k dispozici na obrázku níže:

Výčet nejdůležitějších metod v rozhraní Collection - Kolekce a proudy v Javě

Nyní si tyto metody popíšeme:

  • size() - vrátí aktuální počet prvků v kolekci
  • isEmpty() - vrátí true, pokud se v kolekci nenachází žádný prvek, jinak false
  • contains() - vrátí true, pokud kolekce obsahuje prvek z parametru
  • add() - přidá prvek do kolekce; vrátí true, pokud se změnila kolekce (prvek byl přidán), jinak false
  • remove() - odebere prvek z kolekce; vrátí true, pokud se změnila kolekce (prvek existoval a byl odebrán), jinak false
  • clear() - vymaže obsah kolekce

Rozhraní Collection rozšiřuje rozhraní Iterable. Toto rozhraní definuje metody pro procházení nejen kolekcí, ale všech objektů, nad kterými lze iterovat. Rozhraní obsahuje metodu iterator(), kterou musí implementovat všechny kolekce. Ta vrací tzv. iterátor, který si hned vysvětlíme. Dále rozhraní obsahuje dvě default metody s implementací: forEach() a spliterator(), kterým se budeme věnovat v dalších lekcích.

Iterátor

Iterátory jsou objekty, které slouží k procházení kolekcí. Iterátor jsme vlastně již použili, aniž bychom o tom věděli, a to u kolekce ArrayList.

Průchod přes indexy

Když jsme procházeli pole, které není plnohodnotnou kolekcí, měli jsme na výběr dvě konstrukce: přes indexy pomocí cyklu for:

String[] jmena = new String[] {"Karel", "Pepa", "Michal", "Vojta"};

for (int i = 0; i < jmena.length; i++) {
    System.out.println(jmena[i]);
}

A pomocí foreach:

String[] jmena = new String[] {"Karel", "Pepa", "Michal", "Vojta"};

for (String jmeno: jmena) {
    System.out.println(jmeno);
}

Když použijeme foreach nad jednoduchým polem, Java interně stejně použije přístup přes indexy. Foreach je jen tzv. syntax sugar, hezčí syntaxe pro programátora, která se ještě před kompilací automaticky nahradí jiným, typicky složitějším kódem.

Průchod kolekcí iterátorem

Pro procházení skutečných kolekcí, tedy složitějších struktur než je pole, např. ArrayList, můžeme tento syntaktický cukr využít úplně stejně. Jen Java interně použije tzv. iterátor a náš kód se vnitřně přeloží na něco takového:

List<String> prijmeni = new ArrayList<>();
for (Iterator<String> iterator = prijmeni.iterator(); iterator.hasNext(); ) {
    String next = iterator.next();
    System.out.println(next);
    iterator.remove(); // Pokud to kolekce podporuje, tak se odstraní aktuální prvek
}

Znalost iterátorů se nám v praxi vyplatí v případě, když budeme chtít během procházení z kolekce mazat. Tehdy je musíme pro procházení explicitně použít, viz dále. Další využití iterátoru je pro naše vlastní kolekce, na které následně půjde používat foreach cyklus.

Rozhraní Iterator

Na chvíli se zastavíme u rozhraní Iterator, které je vráceno stejnojmennou metodou. Toto rozhraní obsahuje dvě důležité metody: next() a hasNext(). Metody si opět popišme:

  • next() - vrátí následující prvek
  • hasNext() - vrátí true, pokud existuje následující prvek

Pomocí těchto 2 metod je Java následně schopná kolekci od začátku do konce projet.

Od Javy verze 8 jsou na rozhraní také metody:

  • remove() - odstraní prvek z kolekce, pokud tuto operaci kolekce podporuje, jinak se vyvolá výjimka UnsupportedOperationException; toto je jediný správný způsob, jak lze odstranit prvek z kolekce, když jí procházíme
  • forEachRemaining() - projde každý prvek kolekce a aplikuje na něj příslušnou akci

Vlastní iterátor

Ukažme si jak implementovat vlastní iterátor, tedy objekt umožňující průchod nějakou kolekcí. Uvažujme, že jsme si vytvořili vlastní kolekci SimpleList, která jen obaluje obyčejné pole, které ji přijde v konstruktoru. Třídě nebudeme přidávat žádné metody, pouze ji implementujeme rozhraní Iterable a metodu iterator(), která vrátí anonymní implementaci rozhraní iterátor:

public class SimpleList<Type> implements Iterable <Type> {

 private Type[] arrayList;
 private int currentSize;

 public SimpleList(Type[] newArray) {
  this.arrayList = newArray;
  this.currentSize = arrayList.length;
 }

 @Override
 public Iterator <Type> iterator() {
  Iterator <Type> it = new Iterator<Type> () {

   private int currentIndex = 0;

   @Override
   public boolean hasNext() {
    return currentIndex < currentSize && arrayList[currentIndex] != null;
   }

   @Override
   public Type next() {
    return arrayList[currentIndex++];
   }
  };
  return it;
 }
}

Třída SimpleList přijme v konstruktoru pole, nad kterým se bude vytvářet iterátor. Je důležité, aby volání metody iterator() vždy vrátilo novou instanci Iteratoru. Iterátor lze použít pouze k procházení kolekce od začátku do konce. Pokud chceme iterovat odzadu, je třeba nejdříve vytvořit kolekci, která bude převrácená a až nad ní vytvořit nový iterátor. V metodě hasNext() zjišťujeme, zda-li může iterátor vrátit další prvek, nebo již došel nakonec. Metodou next() vrátíme aktuální prvek a zvýšíme index pole.

Všimněte si, že jsme rozhraní Iterator implementovali jako anonymní třídu. Samozřejmě bychom si i mohli deklarovat plnohodnotnou třídu, např. SimpleIterator, a v metodě iterator() vracet její instanci.

Potomci Collection

Rozhraní Collection je rozšířeno o metody podle způsobu použití pomocí rozhraní List, Set a Queue. Úplně samostatně leží rozhraní Map, které obsahuje metody pro práci s kolekcemi typu "klíč - hodnota". Základní metody těchto rozhraní jsou implementovány v abstraktních třídách podle typu rozhraní: AbstractList, AbstractSet, AbstractQueue a AbstractMap. Abstraktní třídy jsou zde použity, protože některé konkrétní implementace rozhraní mohou sdílet implementaci základních metod (size(), isEmpty()), ale budou mít rozdílné metody jako je add(), remove(). Dále jsou tyto abstraktní třídy užitečné v případě, že si budete chtít implementovat vlastní kolekci, ale chcete mít již nějaký základ implementovaný.

Abych byl úplně přesný, tak všechny výše vyjmenované abstraktní třídy kromě AbstractMap ještě dědí od společné abstraktní třídy AbstractCollection. Všechny třídy lze najít v balíčku java.util. Tyto třídy mají jednu společnou vlastnost: nejsou thread-safe. To znamená, že nemají zabezpečení pro modifikaci prvků z více vláken. Tento problém je v Javě řešen pomocí tříd, které se nacházejí v balíčku java.util.concurrent. Zde se mimo jiné nacházejí stejnojmenné třídy s podporou modifikace z více vláken. Například pro ArrayList tu existuje thread-safe verze v podobě CopyOnWriteArrayList.

V dalších lekcích postupně probereme nejdůležitější rozhraní List, Set, Queue a Map a jejich implementace, konkrétně ArrayList, LinkedList, HashSet a HashMap.

Příště nás čeká Seznam (List) pomocí pole v Javě.


 

Předchozí článek
Úvod do kolekcí a genericita v Javě
Všechny články v sekci
Kolekce a proudy v Javě
Přeskočit článek
(nedoporučujeme)
Seznam (List) pomocí pole v Javě
Článek pro vás napsal Petr Štechmüller
Avatar
Uživatelské hodnocení:
356 hlasů
Autor se věnuje primárně programování v Javě, ale nebojí se ani webových technologií.
Aktivity