Translations of this page:

03 - Výjimky, kolekce

Výjimky

Bezpečnostní koncept Javy - nenechává ošetření chyb na libovůli programátora, ale nutí ho chyby ošetřit. V některých případech neošetřená výjimka neprojde překladem!

Hierarchie výjimek

Všechny výjimky jsou potomky obecné třídy Throwable. Typicky se pracuje s výjimkami, které jsou instancemi potomků třídy Exception.

Error

Interní výjimky Java Virtual Machine, aplikační prográmátor se s nimi nesetká, resp. je nemá jak ošetřit. Obecně platí, že jejich vznik znamená pád programu, Typickými představiteli jsou přetečení zásobníku, nedostatek paměti, interní chyba JVM.

RuntimeException

Třída RuntimeException a její potomci definují tzv. asynchronní výjimky, tj. mohou nastat kdekoliv v programu (např. při dělení nulou je vyhozena výjimka ArithmeticException). Na tyto výjimky není nucen programátor reagovat, doporučuje se však jejich ošetření v případech se zvýšeným rizikem výskytu (např. vstup od uživatele). Typickými představiteli jsou ArithmeticException, ClassCastException, IllegalArgumentException, IndexOutOfBoundsException, NegativeArraySizeException, NullPointerException, SecurityException a UnsupportedOperationException.

Exception

Třída Exception a její potomci (kromě RuntimeException a potomků) představují tzv. kontrolované (synchronní) výjimky (vznikají při volání uřčitých metod atp.) a povinností programátora je tyto ošetřit. Jedná se např. o výjimku CloneNotSupportedException a všechny výjimky vstupu a výstupu, potomci třídy IOException. (Ještě jednou pozor na RuntimeException a potomky, kteří představují asynchronní výjimky ačkoliv jsou též potomky Exception!)

Ošetření výjimek

Možnosti oštření:

  • propagace výjimky - bez ošetření v místě vzniku (nechci, neumím), předání volající metodě,
  • kompletní ošetření,
  • částečné ošetření (tj. ošetření a předání výjimky volající metodě).

Propagace výjimky

Předání volající metodě (odsunutí ošetření v čase a místě). Metoda musí deklarovat v hlavičce typ (třídu) výjimky, jakou bude vyhazovat.

/**
 * tahle metoda vyhodi jakoukoliv beznou vyjimku
 */
public void metodaPropagujiciVyjimku() throws Exception {
  ... // blok metody
}

Propagace výjimky

Lepší je specifikovat přímo typ, který má ta výjimka. Pak volající metoda může lépe rozlišovat o jakou výjimku se jedná a léoe na ni reagovat.

Kompletní ošetření

Využívá se tzv. chráněného bloku try-catch, popř. try-catch-finally, kde se může výjimka vyskytnout.

public class Vyjimky {
 
    public static void prekroceniMezePole() {
        // deklarace
        final int DELKA_POLE = ((int) (Math.random()* 10) + 1);
        Integer[] pole = new Integer[DELKA_POLE];
 
        System.out.println("\nDELKA POLE: " + DELKA_POLE);
        for (int i = 0; i < 10; i++) {
            int prvek = new Integer((int) (Math.random()* 10));
            try {
                pole[i] = prvek;
                // "nahodne" vyvolana vyjimka tridy Exception
                if (prvek == 3 || prvek == 9)
                    throw new Exception("EXCEPTION");
            }
            catch (ArrayIndexOutOfBoundsException e) {
                System.err.println("Zachycena vyjimka (ArrayIndexOutOfBoundsException) "+ e);
                System.err.println("Prvek " + prvek + " na pozici " + i + " nemuze byt vlozen - pole je plne.");
                System.err.println("Obsah pole: " + Arrays.asList(pole));
            }
            catch (Exception e) {
                System.err.println("Zachycena vyjimka (Exception) "+ e);
                System.err.println("Obsah pole: " + Arrays.asList(pole));
            }
        }
    }
 
    public static void main(String[] args) {
        prekroceniMezePole();
    }
}

Úplné ošetření

Všiměte si hlavně pořadí ošetření výjimek (pořadí bloků catch) - je nutné postupovat od potomků k rodičům, v opačném případě bude kompilátor hlásit chybu exception already caught (Java 1.5 a výše). Výjimka je totiž ošetřena v prvním vyhovujícím bloku (pozor, je samozřejmě aplikován polymorfismus!). Program poté pokračuje za posledním blokem catch, tj. je proveden pouze jeden catch blok!

Částečné ošetření

Výjimka je částečně ošetřena a následně předána volající metodě pomocí klíčového slova throw. Kombinace předchozích postupů.

public void metodaCastecneOsetrujiciVyjimku() throws Exception {
  ...
  try {
    ...
  }
  catch (Exception e) {
    ...
    throw new Exception("neumím s ní nic udělat"+ e.toString); // predani (vyhozeni) vyjimky volajici metode
  }
}

Částečné ošetření

Pokud specifikujeme pomocí throws, že budeme výjimku produkovat a nechceme ji měnit, není nutné rizikový kód uzavírat do try-catch bloku. Java kontroluje, že Váš kód může takovou výjimku produkovat, tedy nelze u metody specifikovat, že vyvolává výjimku FileNotFoundException aniž bychom volali nějakou metodu pracující se soubory.

Metody třídy Exception

Pro základní použití stačí následující metody. Exception a všichni potomci mají též vhodně přetíženou metodu toString, jsou potomky Object-u.

public String getMessage() // zprava spojena s vyjimkou
public void printStackTrace() // vypis zasobniku JVM - debug

Metody třídy Exception

Další metody jako getCause, initCause, getStackTrace, setStackTrace atp. viz Java API.

Vyhození výjimky

Výjimku lze vyhodit i mimo blok catch pomocí klíčového slova throw a volání konstruktoru příslušné výjimky (viz Úplné ošetření výjimek). To je vhodné jednak pro testování a pro vytváření metod, které v kritické situaci vyhodí výjimku.

Vlastní výjimky

Vlastní výjimky lze vytvářet děděním typicky od třídy Exception. Výjimka bude synchronní, pokud by nám stačila asynchronní, podědíme od třídy RuntimeException. Práce s vlastními výjimkami je totožná práci s již existujícími (cizími).

try-catch-finally

V případě kritické chyby a propagaci výjimky po částečném ošetření volajícím metodám může být nutné ukončit práci s přidělenými zdroji (ztráta dat, deadlock). Proto existuje blok finally, který se provede vždy. To jest:

  • pokud v bloku try je příkaz return,
  • pokud je výjimka propagována volající metodě,
  • před dalším pokračováním ve vykonávání pokud je catch blok normálně ukončen.
try {
  ...
}
catch (Exception e) {
  ...
}
finally {
  ... // ulozeni prace - uzavreni souboru, vraceni zdroju
}

try-catch-finally

Kolekce

Rozšíření práce s poli

Pole jsou známa od počátku programovacích jazyků. Mezi hlavní výhody patří rychlý přístup k prvkům pole pomocí indexu. Operaci výběru lze provést v konstantním čase. Dále je to typová jednota a v Javě též možnost uchovávat prvky primitivních datových typů.

Nevýhodou klasických polí je hlavně nemožnost změny velikosti a absence podpůrných metod, například pro vyhledávání prvků podle hodnoty.

Třída Arrays

Rozšíření práce s poli, v balíku java.util - poskytuje následující metody:

public static List asList(); // prevede pole na kolekci typu List, vhodna napr. pro vypisy
public static boolean equals(); // porovnani poli
public static void fill(); // naplneni pole konst. hodnotou
public static void sort(); // setrideni pole
public static binarySearch(); // binarni vyhledavani v setridenem poli metodou sort()

třída Arrays

Metody jsou přetíženy pro základní datové typy a pro typ Object (viz dokumentace).

Pro použití metody Arrays.equals() pro strukturované datové typy je nutné zastínit metodu equals(), kterou dědí všechny objekty ze třídy Object. Přitom je nutné dodržet podmínky: reflexivita, symetrie, tranzitivita (jedná se o relaci ekvivalence) a dále konzistenci a nerovnost s null. Též je třeba zastínit metodu hashCode() zděděnou od Object, protože pokud equals() vrací true pro dva objekty, pak by jejich hashCode() měl být stejný. Podrobněji viz přednášky či Herout: Bohatství knihoven, str. 159, 162.

Rozhraní Comparable

Slouží pro implementaci přirozeného řazení - objekt má jediné kritérium, podle kterého se řadí - metodou Arrays.sort(). Pro všechy základní datové typy je tato metoda přetížena, pro objektové datové typy ji musíme napsat (viz příklad).

  balík:
    java.lang
  metody:
    public int compareTo(Object o)

Rozhraní Comparable

Očekává se, že metoda compareTo(Object o) bude vracet:

-1, či jiné záporné číslo Tento objekt je menší než druhý objekt (předaný jako parametrem).
0 Objekty jsou si rovny.
1, či jiné kladné číslo Tento objekt je větší než druhý objekt (předaný jako parametrem).

Pozor na nutnost přetypování formálního parametru o na potřebný datový typ!

Rozhraní Comparator

Slouží k implementaci absolutního řazení nebo řazení v případech, kdy nelze jednoduše implementovat rozhraní Comparable (např. hotová třída, do které nemůžeme zasahovat, nebo jde o objekty, které chceme řadit podle různých kritérií. Např. Studenty budeme jednou řadit podle data narození, jindy podle jména a jindy podle studijního průměru). Opět se využívá přetížených metod třídy Arrays - všechny přijímají jako poslední parametr objekt implementující rozhraní java.util.Comparator.

  balik:
    java.util
  metody:
    int compare(Object o1, Object o2)

Rozhraní Comparator

Rozhraní kolekcí

Rozhraní Collection

Rozhraní poskytující metody pro práci s prvky kolekcí. Je základem rozhraní List a Set. Mezi nejdůležitější metody patří následující.

// VLASTNOSTI
boolean isEmpty() // pravda, kdyz je kolekce prazdna
int size() // pocet prvku v kolekci
 
// VKLADANI, RUSENI PRVKU
boolean add(Object o) // vlozeni prvku do kolekce
boolean add(Collection c) // vlozeni vsech prvku z kolekce c
 
void clear() // kompletni vymazani kolekce
boolean remove(Object o) // odstraneni prvku o (pokud je v kolekci vicekrat, odstrani se nahodne jeden)
boolean remove(Collection c) // odstraneni prvku, ktere jsou zaroven v kolekci c
boolean retainAll(Collection c) // odstraneni prvku, ktere nejsou zaroven v kolekci c
 
// TESTY NA PRVKY
boolean contains(Object o) // pravda, kdyz o je prvkem
boolean contains(Collection c) // pravda, kdyz vsechny prvky c jsou prvkem
 
// ZISKANI ITERATORU
Iterator iterator()
 
// PREVOD KOLEKCE NA POLE
Object[] toArray() // prevod na pole Objectu
Object[] toArray(Object[] a) // prevod na pole konkretniho typu

Rozhraní Collection

Rozhraní List

Přidává metody usnadňující práci se seznamy, hlavně možnost indexace! Seznamy umožňují uložit tentýž prvek (metoda equals()) vícekrát.

void add(int index, Object o) // pridani prvku na index, ostatni prvky budou posunuty
Object get(int index) // vraci prvek ulozeny na index
Object set(int index, Object o) // zmena prvku na index, vraci ulozeny prvek
Object remove(int index) // odstraneni prvku na index, posun indexu, vraci ulozeny prvek
 
int indexOf(Object o) // vraci index prvniho nalezeneho prvku shodneho s o (equals())
int lastIndexOf(Object o) // vraci index posledniho nalezeneho prvku shodneho s o (equals())
List subList(int start, int end) // podseznam od start do end - 1

Rozhraní List

Rozhraní Set

Podpora pro implementaci množin. Množiny neumožňují uložit tentýž prvek (metoda equals()) vícekrát. Rozhraní nepřináší žádné podstatné metody navíc k metodám rodičovského rozhraní Collection.

Seznamy

Třída ArrayList

Nejoblíbenější implementace seznamu (= rozhraní List).

Rozdíl kapacita x velikost (int size()). Zajištění kapacity - konstruktorem ArrayList(int vychoziKapacita), kde parametrem je výchozí kapacita nebo metodou boolean ensureCapacity(int capacity). Předpokládejme, že máme ArrayList plný, tedy velikost je rovna kapacitě. Pokud přidáme další prvek dojde k vytvoření nového vnitřního pole o velikosti 1,5 násobku původní velikosti a překopírování hodnot do nového pole. Toto zaručí, že vložení jednoho prvku je v průměrném případě lineární.

V Javě 1.5 a výše se využívá tzv. generics (šablony), kolekce mezi ně patří - datový typ, který se bude ukládat do kolekce musí být znám při překladu, jinak bude zobrazeno varování. Pokud typem bude Object nebo použijeme zástupný symbol ?, lze samozřejmě do kolekce uložit cokoliv. Více na http://java.sun.com/developer/technicalArticles/J2SE/generics/.

Třída LinkedList

Tato třída implementuje rozhraní List pomocí obousměrně zřetězeného seznamu. Vhodná, pokud

  • provádíme časté změny v seznamu,
  • potřebujeme pracovat se začátkem a koncem seznamu.

Naopak nevhodná je tato třída pro přímý (indexovaný) přístup k prvkům seznamu, protože indexovaný přístup je lineární vzhledem k velikosti indexu nikoli konstantní jako je tomu u ArrayListu.

Implementuje rozhraní Collection a List a dále nabízí metody pro práci s prvním a posledním prvkem seznamu.

  // vlozeni prvku na zacatek/konec seznamu
  void addFirst(Object prvek)
  void addLast(Object prvek)
 
  // odebrani prvku ze zacatku/konce seznamu - vraci odebirany objekt
  Object removeFirst()
  Object removeLast()
 
  // vraceni prvku ze zacatku/konce seznamu
  Object getFirst()
  Object getLast()

Třída LinkedList

Třída Collections

Obdoba třídy Arrays. Poskytuje tytéž metody pro kolekce a další (min, max atp.) navíc. Je z balíku java.util. Metody jsou statické, veřejně přístupné. Existují vždy ve variantě bez objektu /s objektem Comparator pro přirozené / absolutní řazení. Běžně používané metody následují.

// naplneni kolekce hodnotou
void fill(Collection c, Object o) // naplneni kolekce stejnou hodnotou
 
// setrideni kolekce
void sort(Collection c)
void sort(Collection c, Comparator cmp)
 
// binarni vyhledavani v setridene kolekci metodou sort()
binarySearch(List list, Object key)
binarySearch(List list, Object key, Comparator cmp)
 
// nejvetsi / nejmensi prvek
Object max(Collection c)
Object max(Collection c, Comparator cmp)
Object min(Collection c)
Object min(Collection c, Comparator cmp)
 
// zamichani kolekce
void suffle(Collection c)
void suffle(Collection c, Random rnd)
 
// hledani podseznamu, -1 znamena nenalezeno
int indexOfSubList(List list, List subList)
int lastIndexOfSubList(List list, List subList)

Třída Collections

Třída Iterator

Iterátor zajišťuje přístup k prvkům ve všech kolekcích (vč. Map). Jedná se o postupný průchod kolekcí. Iterátory jsou vždy na jedno použití! Nový iterátor vždy ukazuje před první prvek!

  // deklarace kolekce a naplneni
  Collection c = new ArrayList<Integer>(10);
  Collections.fill(c, new Integer(1));
 
  // ziskani iteraoru a pruchod koleci s odstranovanim prvku
  for (Iterator it = c.iterator(); it.hasNext(); ) {
    it.next();
    it.remove()
  }

Třída Iterator

Pozor na metodu remove():

  • nesmí být volána dříve než metoda next(), jinak je vyhozena výjimka IllegalStateException - metoda remove() maže právě vrácený prvek,
  • nevrací referenci na mazaný objekt, jako metoda remove() z rozhraní Collection.

Stejně tak je třeba testovat, zda v kolekci existuje ještě další prvek metodou hasNext(), pokud by neexistoval a přesto jsme zavolali next(), bude vyhozena výjimka NoSuchElementException.

Pro kolekce implementující List existuje ListIterator, který umožňuje obousměrný průchod kolekcí.

Množiny

Implementují rozhraní Set. Prvek se v množině může vyskytovat pouze jednou, při opětovném pokusu o přidání vrátí metoda boolean add(Object prvek) false. Narozdíl od seznamů nelze s množinami pracovat pomocí indexů. Pro průchody je třeba využívat iterátory.

Třída HashSet

Implementuje rozhraní Set, které nepřidává žádné podstatné metody oproti Collection.

Rozhraní SortedSet a třída TreeSet

Toto rozhraní a tato třída udržuje prvky v kolekci seřazeny (TreeSet - ve stromové struktuře). Pro řazení se využívá buď přirozeného řazení metodou compareTo, tj. prvky musí implementovat rozhraní Comparable nebo je třeba vytvořit objekto komparátoru, který se předá kolekci jako poslední parametr konstruktoru.

Rozhraní SortedSet navíc přidává následující metody.

// prvni, posledni prvek
Object first()
Object last()
 
// podmnoziny (dolniHranice je vzdy zahrnuta, horniHranice neni nikdy zahrnuta!)
SortedSet headSet(Object horniHranice)
SortedSet tailSet(Object dolniHranice)
SortedSet subSet(Object dolniHranice, Object horniHranice)

Rozhraní SortedSet

Vztah množiny a podmnožiny

Množinu i podmnožiny lze modifikovat bez vyhození výjimky (narozdíl od seznamů, kde modifikace původního seznamu a následná práce s podseznamem vede k vyhození výjimky). U seřazené množiny není možné přidat prvek menší(větší) než nejmenší(největší) prvek v podmnožině - kvůli správnému zařazení do nadmnožiny - vede na výjimku IllegalArgumentException.

Mapy

Jiným názvem slovníky (dictionary), umožňují ukládat dvojice klíč-hodnota. Implementují rozhraní Map a POZOR neimplementují rozhraní Collection, tj. nejsou s kolekcemi zaměnitelné!

Mapy jsou v Javě 1.5 a výše implementovány stejně jako ostatní kolekce pomocí generics a je třeba na to nezapomenout!

Rozhraní Map

Základní rozhraní, poskytuje nejběžnější metody.

// VLASTNOSTI
boolean isEmpty() // pravda, kdyz je mapa prazdna
int size() // pocet prvku v mape
 
// VKLADANI, RUSENI PRVKU
Object put(Object klic, Object hodnota) // vlozeni prvku do mapy
void putAll(Map m) // vlozeni vsech prvku z mapy m
 
void clear() // kompletni vymazani kolekce
Object remove(Object klic) // odstraneni prvku s danym klicem
 
// TESTY NA PRVKY
boolean containsKey(Object key) // pravda, kdyz klic je klicem
boolean containsValue(Object hodnota) // pravda, kdyz vsechny mapa obsahuje hodnotu
 
// ZISKANI PRVKU
Object get(Object klic) // vrati hodnotu odpovidajici klici
 
// PREVOD MAPY NA KOLEKCI
Set keySet() // mnozina klicu
Set entrySet() // mnozina dvojic klic-hodnota
Collection values() // kolekce hodnot (moznost opakovani!)

Rozhraní Map

Metody převádějící mapu na jinou kolekci vždy vytvářejí pohled - mělkou kopii! Zvláštností při práci s pohledy (kolekcemi vycházejícími z rozhraní Collection) je, že použití metod jako removeAll(), retainAll atp. z rozhraní Collection projeví i v mapě.

Práce s mapou

U klíčů se očekává se vhodná implementace metod equals() a hashCode(). V případě použití metody containsValue() je tytéž metody nutné implementovat i pro hodnoty.

Nelze získat iterátor pro průchod mapou! Je několik možností jak to obejít - iterátor pro množinu klíčů, hodnot nebo lépe iterátor pro množinu dvojic klíč-hodnota. Dvojice klíč-hodnota je reprezentována instancí rozhraní Map.Entry, které poskytuje následující metody.

Object getKey() // vrati klic
Object getValue() // vrati hodnotu
 
Object setValue(Object novaHodnota) // nastavi novou hodnotu (modifikace mapy) a vrati starou hodnotu

Rozhraní Map.Entry

Opět se jedná o práci s pohledem, platí vše co bylo o pohledech řečeno.

Třída HashMap

Konkrétní implementace rozhraní Map, rychlost mapy záleží na kvalitě metody hashCode() prvku.

Rozhraní SortedMap a třída TreeMap

Obdoba SortedSet a TreeSet. Seřazená mapa, umožňuje vytvářet pohledy v intervalu klíčů, pracovat s nejnižšším/nejvyšším prvkem.

Porovnávání v třídě TreeMap může být přirozené, pak prvky musí implementovat rozhraní Comparable nebo lze využít komparátoru, který se předá konstruktoru mapy TreeMap(Comparator cmp).

Object firstKey() // vrati hodnotu patrici k prvnimu klici
Object lastKey() // vrati hodnotu patrici k poslednimu klici
 
SortedMap headMap(Object klicHorniHranice) // pohled na mapu od zacatku az do klicHorniHranice, vyjma klicHorniHranice
SortedMap tailMap(Object klicDolniHranice) // pohled na mapu od klicDolniHranice vcetne az do konce mapy
SortedMap tailMap(Object klicDolniHranice, Object klicHorniHranice) // spojeni predchozcich

Třída TreeMap

Vhodný objekt pro uložení do kolekce

  1. Vhodná implementace metody hashCode(). V Javě se typicky používá zřetězení pro kolekce založené na hashích tak, aby nedocházelo k jevu clusterování (většina prvků spadne do několika málo bucketů, většina je volné, řetězy jsou dlouhé - lineárni časová složitost vzhledem k velikosti kolekce). Teorie hashovacích funkcí a jejich návrh je ale samozřejmě mimo rámec tohoto předmětu. Jako základní doporučení lze využít Herout: Bohatství knihoven str. 166.
  2. Implementace metody equals() tak aby se jednalo o ekvivalenci a navíc byla konzistentní a žádná instance se nerovnala null.

Shrnutí: Třída vhodná pro uložení do kolekce by měla mít neměnné instance, změna se realizuje náhradou novou instancí, měla by mít překryté metody equals() a hashCode a také toString(), která bude vracet smysluplné informace o instanci. Též by měla implementovat rozhraní Comparable pro přirozené řazení. Nakonec bychom si měli dát pozor na možnost výskytu výjimek třídy RuntimeException a potomků - na takové výjimky nás neupozorní kompilátor!

 
/webdev/www/courses/Y36PJV/data/pages/cviceni/cviceni03.txt · Poslední úprava: 2009/03/12 17:09 autor: balikm
 
Recent changes RSS feed Creative Commons License Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki