![]() Specializovaný týdeník o výpočetní technice o Internetu (CW 50/97) Programování v jazyce Java
David Štrupl
Řetězce Řetězce se používají k ukládání textových konstant. Syntaxe použití řetězců je podobná jazyku C, ale stejně jako u polí (a možná ještě více) jsou i zde rozdíly. Vyplývají z toho, že řetězce jsou v Javě objekty. Tedy na rozdíl od jazyka C řetězce v Javě nejsou pole znaků. Pro práci s řetězci jsou v balíku java.lang dvě třídy: String a StringBuffer. První z nich slouží k ukládání konstantních řetězců a jsou s ní spojeny nové syntaktické konstrukce. Třída StringBuffer je obyčejná pomocná třída sloužící k manipulaci s jednotlivými znaky řetězce. Do proměnných typu String lze přiřazovat textové (String) konstanty:
String s = "Ja jsem String";
Zápis s uvozovkami vlastně vytváří nový objekt třídy String. Na tento objekt lze vyvolávat všechny metody definované ve třídě String stejně jako na příslušnou proměnnou:
String t = s.toUpperCase();
Objekty třídy String mají konstantní hodnotu, tj. řetězec jednou uložený do proměnné třídy String už nelze měnit. Metody, které zdánlivě mění hodnoty jednotlivých znaků, vždy vytvářejí nový objekt třídy String s příslušnými změnami. Důvodem pro toto (na první pohled nepochopitelné) chování řetězců je to, že konstantní řetězce jsou používány ve virtuálním stroji k ukládání jmen, a jsou tedy v class souborech ukládány zvláštním způsobem. Běžnému programátorovi nemusí konstantnost řetězců vadit, protože jejich použití v programu je relativně jednoduché a pochopitelné. Jediné, na co musíme dávat při použití objektů třídy String pozor, je porovnávání řetězců. Použití operátoru == totiž většinou nepřinese kýžený efekt, protože tento operátor u typu reference na objekt (jímž typ String bezesporu je) porovnává odkazy a nikoliv obsahy příslušných objektů. Chceme-li porovnat dva řetězce, použijeme nejpravděpodobněji metodu equals nebo equalsIgnoreCase:
String s = "ahoj";
Kromě použití textových konstant je s typem String spojena ještě jedna zvláštnost -- lze na něj použít operátor +. Při sčítání řetězců dojde k jejich zřetězení. Výsledkem součtu je nový řetězec s příslušným obsahem. Operátor + lze použít buď samostatně, nebo v kombinaci s přiřazením:
String t = "Ja jsem" + "retez";
Ani při jednom ze způsobů zápisu však nedochází k modifikaci žádného ze sčítaných řetězců, protože, jak již bylo řečeno, typ String reprezentuje konstantní řetězce. Vždy je naalokován nový objekt a vrácen odkaz na něj. Pokud chceme sami měnit hodnoty jednotlivých znaků, použijeme třídu StringBuffer. Objekty třídy String můžeme převést na objekty třídy StringBuffer pomocí konstruktoru třídy StringBuffer, který má jako parametr String:
String s = "retez";
S proměnnou třídy StringBuffer můžeme potom pracovat pomocí jejích metod, které přistupují k jednotlivým znakům nebo nějakým jiným způsobem modifikují obsah řetězce. Převod zpět do podoby řetězce je možný např. použitím metody toString().
Chyby Nyní se podíváme na to, jak si prostředí Javy poradí s chybami, které mohou nastat při běhu aplikací. Jednotným mechanismem uplatňovaným jak v prostředí virtuálního stroje Java (JVM), tak v uživatelských programech, je mechanismus zpracování výjimek. Základní principy byly opět převzaty z jazyka C++.
Zpracování výjimek S výjimkami jste se mohli setkat, pokud se vám něco nepodařilo a váš program skončil svoji činnost předčasně ohlášením nějaké chyby. Pokud se vám něco takového stalo, jistě jste si všimli, že prostředí Javy vypíše např. toto:
Exception occurred during event dispatching: java.lang.NullPointerException
Každá chyba (výjimka, Exception) je reprezentována jedním objektem. Tento objekt je vytvořen v místě, kde k chybě došlo. Z objektu reprezentujícího chybu se můžeme dozvědět, o jakou chybu se jedná a kde nastala. Třída, která reprezentuje chyby, je potomkem třídy Throwable. To však není všechno. Pokud víme, že některá část programu může způsobit výjimku, můžeme část kódu označit jako tzv. chráněný blok a na konci tohoto bloku určit, co se má provést, pokud při jeho provádění dojde k výjimce. Strážený blok zapíšeme pomocí příkazu:
try {
Příkazy stráženého bloku jsou prováděny postupně -- stejně jako v každém jiném bloku příkazů. Pokud však nastane během jejich provádění nějaká výjimka, bude se její typ porovnávat s typem uvedeným v klauzuli catch. Provádění stráženého bloku je přerušeno a nastává zpracování chyby. Klauzule catch jsou probírány postupně, při nalezení první, která dokáže chybu zpracovat, je proveden blok jejího kódu. Po provedení kódu zpracovávajícího chybu výpočet pokračuje dalším příkazem za příkazem try. Porovnávání typů výjimek probíhá tak, že typ uvedený za catch musí být buď stejný jako typ chyby, nebo musí být jejím předkem v hierarchii dědění. Pokud se typ chyby nevyskytuje v žádné z klauzulí catch, chyba nebyla příkazem try ošetřena a provádění celého bloku, ve kterém se příkaz try nachází, skončí neúspěchem. V bloku klauzulí catch může být jeden příkaz finally, jehož blok se provede vždy při ukončení stráženého bloku -- v případě, že nastala chyba, i v případě, že žádná nenastala:
try {
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 {
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 {
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 {
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 {
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 {
Pokud máme interface definován, můžeme deklarovat proměnné tohoto typu a používat je ve svém kódu:
...
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 {
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.
| <<< | COMPUTERWORLD | IDG CZ homepage | |