David Štrupl
Způsobení chyby
V jazyce Java máme možnost chyby (výjimky) nejen zpracovávat, ale také je můžeme způsobovat. K vyvolání chyby slouží příkaz throw. Jako parametr musí být objekt -- potomek třídy Throwable, který bude reprezentovat nastalou chybu.
Celé způsobení chyby může vypadat např. takto:
...
throw new FileNotFoundException();
Příkaz throw musí být buď uveden ve stráženém bloku, nebo musíme označit metodu, která jej obsahuje. Metodu způsobující výjimku označíme klíčovým slovem throws následovaným druhem výjimky:
void mojeMetoda() throws FileNotFoundException {
...
throw new FileNotFoundException();
}
Z deklarace (a tedy také z dokumentace) k jednotlivým balíkům (knihovnám) je potom vždy jasně patrné, které metody mohou způsobovat výjimky.
Druhy chyb
Jak již bylo uvedeno výše, všechny způsobitelné chyby jsou potomky třídy Throwable. Tyto třídy lze dále rozdělit na tři velké skupiny -- potomky tříd: Error, Exception a RuntimeException.
Třída Error a její potomci reprezentují chyby JVM. Příkladem takové chyby může být OutOfMemoryError. Tyto chyby by v uživatelských programech asi ani neměly být zpracovávány, protože se jedná o chyby, z nichž je velice obtížné se zotavit.
Třída Exception a její potomci jsou standardní chyby, které uživatel vyvolává a ošetřuje. Na tyto výjimky se v plné míře vztahuje ustanovení předchozího odstavce o tom, že musí být buď chyceny, nebo deklarovány příkazem throws.
Třída RuntimeException, přestože je potomkem třídy Exception, má jednu zvláštnost -- výjimky tohoto druhu nemusí být deklarovány. Do této kategorie spadají totiž běžné výjimky NullPointerException, ArrayIndexOutOfBoundsException a podobně, které by musely být deklarovány prakticky u všech metod.
Definování vlastních výjimek
Pokud naše metodu způsobuje nějaký nový druh chyby, můžeme si vytvořit vlastní druh výjimky. Tento postup je naprosto běžný. Stačí vytvořit potomka třídy Exception:
class MojeException extends Exception {
public MojeException() { }
public MojeException(String what) {
super(what); // parametr popisující chybu
}
}
Máme-li nový druh chyby deklarován, můžeme jej použít podobně jako již existující výjimky:
throw new MojeException();
Vytváření vlastních výjimek je doporučený postup při psaní metod, které mohou skončit neúspěchem. Příkladem může být neexistence souboru, nemožnost navázat síťové spojení apod. Ve všech těchto případech se hodí použití mechanismu výjimek.
Abstraktní třídy
Výhodnou vlastností objektové knihovny je možnost použití tzv. abstraktních tříd. Jsou to třídy, které obsahují rozsáhlý kód, ale k jejichž zdárnému fungování něco chybí. To něco je třeba doplnit v potomcích takové třídy. Abstraktní třída obsahuje jednu nebo více abstraktních metod, tj. metod, které nemají uveden kód. Abstraktní metody se mohou vyskytovat pouze v abstraktní třídě:
abstract class MojeAbstraktni {
abstract void fce();
void pracuj() {
...
fce(); // volání abstraktní metody
}
}
Pokud chceme vytvořit potomka uvedené třídy, musíme v něm dodat (implementovat) kód všech abstraktních metod:
class Konkretni extends MojeAbstraktni {
void fce() { // deklarace musí být stejná jako v abstraktní metodě
// implementace fce
}
}
... // na jiném místě v kódu
Konkretni x = new Konkretni();
x.pracuj(); // volá pracuj zděděnou od MojeAbstraktni
// z pracuj se volá fce definovaná v Konkretni
Nyní je třeba odpovědět na otázku, k čemu nám může být použití abstraktních tříd a metod dobré. Při psaní knihovny autor často potřebuje pracovat s proměnnými nějaké třídy, kterou uživatel knihovny bude upravovat podle svého. Autor knihovny tedy nadeklaruje abstraktní třídu a napíše v ní všechny metody, které může napsat on. Metody, u nichž nezná jejich implementaci, protože tu bude vytvářet uživatel knihovny, pouze deklaruje a označí jako abstraktní. To mu však nebrání tyto abstraktní metody ve svém kódu volat. Při jejich skutečném volání bude příslušná proměnná ukazovat na konkrétní objekt, který bude mít všechny metody definovány.
Interface
Jedním z posledních rysů jazyka Java, se kterým se seznámíme, je použití tzv. interfaců. Je trochu podobné použití abstraktních tříd -- opět se používají jako rozhraní knihovny. Pokud chceme mít toto rozhraní ještě flexibilnější než při použití abstraktních tříd, můžeme právě deklarovat interface. Interface je vlastně množina deklarací metod a konstant:
public interface Rozhrani {
public void f1();
public int f2(int param1);
}
Pokud máme interface definován, můžeme deklarovat proměnné tohoto typu a používat je ve svém kódu:
...
void mojeMetoda(Rozhrani x) {
x.f1();
int k = x.f2(10);
...
}
Proměnné s typem rozhraní jsou z hlediska jazyka obyčejné proměnné typu odkazu na nějaký objekt. Při volání metody používající proměnnou typu rozhraní musíme za hodnotu této proměnné dosadit odkaz na objekt, který tzv. implementuje uvedené rozhraní. To, že nějaká třída implementuje rozhraní, je uvedeno v její deklaraci:
class A implements Rozhrani {
...
public void f1() { ... } // deklarace odpovídající Rozhrani
public int f2(int param1) { ... }
}
...
A y = new A(); // vytvoření objektu, který implementuje rozhraní
mojeMetoda(y); // dosazení příslušného objektu do proměnné x
Pokud naše třída implementuje rozhraní, musí obsahovat všechny metody uvedené v daném rozhraní. Tato vlastnost umožní fungování kódu, ve kterém se vyskytovaly proměnné typu rozhraní. Výhodou rozhraní oproti dědění z abstraktní metody je to, že za klíčovým slovem implements může být libovolný počet interfaců -- dědění od abstraktní třídy je omezeno pouze na jednoho rodiče.
Práce s grafickým uživatelským rozhraním (GUI)
Standardní knihovny jazyka Java obsahují několik balíků pro práci s GUI. Všechny tyto knihovny jsou napsány tak, že jednou napsaný program by měl běžet stejně v prostředí MS Windows, Xwindows a ve všech systémech podporujících nějakým způsobem práci s okénky. To znamená, že podpora pro grafiku musí být integrální součástí JVM (Java Virtual Machine). Hlavní třídy jsou obsaženy v balíku java.awt.
AWT -- Abstract Window Toolkit
Tato knihovna obsahuje všechny důležité třídy pro práci s uživatelským rozhraním. Skládá se jednak z několika abstraktních tříd, které nám umožňují vytvářet vlastní elementy uživatelského rozhraní. Velmi důležitou částí jsou naprogramované všechny základní elementy uživatelského rozhraní. Tyto elementy můžeme pak velice jednoduchým způsobem používat ve svých programech.
Rozhraní AWT prodělalo řadu změn od verze 1.0 do verze 1.1. V tomto seriálu si zkusíme představit rysy, které jsou společné všem verzím, další diskuse ponecháme na specializované pojednání.
Použití již hotových komponent uživatelského rozhraní je velice jednoduché -- tyto komponenty stačí přidávat do svého programu pomocí příkazu add:
import java.awt.*;
import java.applet.*;
public class Test extends Applet{
Button okButton = new Button("OK");
Checkbox c = new Checkbox("Skrt");
TextField t = new TextField(15);
public void init() {
add(okButton);
add(c);
add(t);
}
}
Tento příklad přidá do appletu tlačítko (Button), zaškrtávací políčko (Checkbox) a vstupní řádku (TextField). Po přeložení, vložení do HTML dokumentu a spuštění se v appletu objeví přidané komponenty.
V knihovně AWT najdeme několik abstraktních tříd, od nichž jsou odvozeny všechny třídy reprezentující konkrétní elementy GUI. Základní třída se jmenuje Component. Tato třída reprezentuje nejjednodušší komponentu, která se může zobrazovat -- tj. mezi její vlastnosti patří souřadnice umístění, velikost apod. Mezi potomky třídy Component patří:
- Button -- reprezentuje tlačítko
- Checkbox -- zaškrtávací políčko
- List -- seznam s výběrem
- Choice -- řádka s výběrem prvků
- TextField -- vstupní řádka
- TextArea -- editační plocha (víceřádkový vstup textu)
- Label -- popiska
Použití těchto komponent bylo ilustrováno na uvedeném příkladu. To znamená, že nejprve je třeba pomocí volání new a konstruktoru vytvořit příslušnou komponentu. Tuto lze pak vložit příkazem add do tzv. kontejneru, neboli komponenty, která může obsahovat další komponenty. Toto vložení musíme provést před začátkem provádění programu -- metoda init v appletu se provede před tím, než je applet zobrazen.
Kontejner je reprezentován objektem, který je potomkem třídy Container. Třída Container je rovněž potomkem třídy Component. Třída Applet je potomkem třídy Container, tj. applet je vždy kontejner, do kterého můžeme vkládat jednotlivé komponenty pomocí metody add. Metoda add je definována ve třídě Container a všechny její potomci ji dědí.
Píšeme-li applet, můžeme přidávat komponenty přímo do něj. Pokud však píšeme samostatnou aplikaci, je potřeba nejprve vytvořit okno, které bude tvořit hlavní okno našeho rozhraní. K tomuto účelu obsahuje knihovna AWT třídu Frame. Tato třída slouží k vytváření dalších oken na obrazovce. Objekt třídy Frame je rovněž potomkem Containeru, tj. umožňuje nám do něj vkládat komponenty podobně jako do appletu. Chceme-li v samostatné aplikaci vytvořit nové okno, můžeme napsat do metody main např. toto:
public static void main(String[] args) {
Frame f = new Frame();
f.add(new Button("Ok"));
f.show();
}
V tomto případě jsme vyvolali metodu add na objekty třídy Frame. Po zavolání metody show uvidíme na obrazovce nové okénko s jedním tlačítkem. Tento program nereaguje na žádné události, dokonce ani na pokus o zavření okna. Pokud chceme, aby program něco dělal, musíme přidat ještě reakce na tvz. události.
Události
Uvedené příklady ukazovaly, jak vytvořit uživatelské rozhraní. To však samo o sobě nestačí. Ještě je třeba napsat kód, který bude něco provádět, např. při stisknutí tlačítka. Musíme naprogramovat reakci na události, které mohou v programu nastat.
Chceme-li např. zareagovat na stisk tlačítka, můžeme přepsat metodu action u apletu:
import java.awt.*;
import java.applet.*;
public class Test2 extends Applet{
Button okButton = new Button("OK");
public void init() {
add(okButton);
}
public boolean action(Event e, Object what){
if (e.target == okButton) {
Systém.out.println("Stisknut button");
}
return true;
}
}
Podobným způsobem je možné reagovat na události, které vzniknou kliknutím myši, stisknutím klávesy na klávesnici apod. Jména metod, které zpracovávají události, se bohužel změnila od verze 1.0 k verzi 1.1, takže je vždy potřeba dávat pozor na to, kterou verzi překladače a prostředí máme k dispozici. Verze 1.1 podporuje i volání metod z verze 1.0, tj. zpětná kompatibilita byla zachována, musíme však dávat pozor, abychom nepoužili volání z verze 1.1, máme-li k dispozici pouze verzi 1.0 (např. v prohlížeči).
Layout Manager
V předchozích příkladech jsme si ukazovali, jak přidávat komponenty do kontejnerů pomocí metody add. Při prohlížení běžících programů vás jistě napadlo, že jsme nikdy nezadávali žádné souřadnice, kam se dané komponenty mají zobrazit. To většinou není třeba, neboť v knihovně AWT máme k dispozici objekty, které se o umisťování komponent starají za nás. Těmto objektům se říká Layout Managery, protože implementují rozhraní Layout Manager.
Použití Layout Managerů by se mohlo zdát na první pohled zbytečné a těžkopádné -- proč bychom nemohli umisťovat komponenty na přesně určené souřadnice? Problém s umisťováním na absolutní souřadnice je v tom, že nikdy nevíme, jak bude vypadat fyzické zařízení, na kterém se budou naše komponenty zobrazovat -- nemůžeme vědět, jaké bude mít uživatel k dispozici rozlišení obrazovky, jak velké budou fonty atd. Layout Manager by nás jako programátory měl od těchto detailů odstínit.
S každým kontejnerem (potomkem třídy Container) je svázán jeden Layout Manager. Pokud jej nezměníme, je použit defaultní -- pro třídu Panel (a tedy i pro jejího potomka Applet) je použit FlowLayout.
FlowLayout
Objekt této třídy pracuje tak, že umisťuje jednotlivé komponenty za sebou podle pořadí vkládání příkazem add. Pokud se další komponenta nevejde na řádek, pokusí se ji umístit na další řádek. Toto chování je vhodné, pokud do kontejneru umisťujeme malé množství objektů (např. dva Buttony apod.). Pokud chceme tento LayoutManager použít i v objektech, pro které není defaultní, můžeme použít příkaz:
setLayout(new FlowLayout());
Tento příkaz je třeba vyvolat na příslušný kontejner, např. na Frame.
GridLayout
Pokud chceme umisťovat objekty do mřížky, můžeme příkazem
setLayout(new GridLayout(x,y));
aktivovat nový GridLayout. Parametry x a y určují rozměry mřížky. Podívejme se na applet, do kterého vložíme čtyři tlačítka:
import java.awt.*;
import java.applet.*;
public class Test2 extends Applet{
public void init() {
setLayout(new GridLayout(2,2));
add(new Button("Prvni"));
add(new Button("Druhy"));
add(new Button("Treti"));
add(new Button("Ctvrty"));
}
}
Po spuštění si můžeme vyzkoušet, co se bude dít, budeme-li měnit velikost našeho appletu.
BorderLayout
Dalším z nejběžnějších správců je BorderLayout -- umožňuje nám určit, na kterém místě se přidávaná komponenta objeví. Používá k tomu variantu příkazu add, která má navíc ještě jeden argument, který je předán Layout Manageru. Při použití tohoto objektu již nezáleží na pořadí vkládání komponent jako v předchozích případech. Použití si opět budeme ilustrovat na příkladu appletu:
import java.awt.*;
import java.applet.*;
public class Test3 extends Applet{
public void init() {
setLayout(new BorderLayout());
add("North", new Button("Prvni"));
add("South", new Button("Druhy"));
add("East", new Button("Treti"));
add("West", new Button("Ctvrty"));
add("Center", new Button("Ctvrty"));
}
}
Ostatní
V knihovně AWT máme kromě již uvedených Layout Managerů ještě možnost použít následující objekty:
- CardLayout -- slouží k vytvoření několika karet, které můžeme na obrazovce střídat. Je to užitečné v případech, kdy chceme měnit vzhled rozhraní za běhu.
- GridBagLayout -- tento správce je nejsložitější ze standardních správců. Pomocí objektů třídy GridBagConstraints umožňuje u každé komponenty stanovit vlastnosti při změně velikosti kontejneru.
Pokud nechceme použít žádný Layout Manager, můžeme pomocí příkazu:
setLayout(null);
vypnout jejich používání. Potom se musíme starat o souřadnice, na které dané komponenty umisťujeme, a proto to není doporučovaný způsob psaní aplikací v Javě.
Vytváření GUI
Při vytváření uživatelského rozhraní nemusíme používat jeden Layout Manager pro celé okno, do kterého umisťujeme komponenty. Můžeme si plochu okna rozdělit a v každé části použít jiný druh správce rozmístění. Rozdělení plochy okna nám umožní nejjednodušší potomek třídy Container -- Panel.
Panel je komponenta, která slouží k umisťování dalších komponent. Můžeme jej umístit do libovolného kontejneru, tj. můžeme vytvořit hierarchickou strukturu panelů, které realizují rozmisťování komponent. U každého panelu nastavíme Layout Manager tak, aby celkový vzhled GUI odpovídal naší představě.
V následujícím příkladu si ukážeme použití několika panelů pro lepší rozvržení komponent v appletu:
import java.awt.*;
import java.applet.*;
public class Test4 extends Applet{
public void init() {
Panel p1, p2, p3;
setLayout(new BorderLayout());
add("East", p1 = new Panel());
add("South", p2 = new Panel());
add("West", p3 = new Panel());
p1.setLayout(new GridLayout(3,1));
p1.add(new Checkbox("Jedna"));
p1.add(new Checkbox("Dve"));
p1.add(new Checkbox("Tri"));
p3.setLayout(new GridLayout(3,1));
p3.add(new Checkbox("Maslo"));
p3.add(new Checkbox("Pivo"));
p3.add(new Checkbox("Rohlik"));
p2.add(new Button("Ok"));
}
}
Tato ukázka po spuštění zobrazí applet, ve kterém budou jednotlivé komponenty umístěny ve skupinách určených jednotlivými panely.