LINUXSOFT.cz Přeskoč levou lištu
Uživatel: Heslo:  
   CZUKPL

> Java (21) - datové typy

Java je jazyk se silnou typovou kontrolou. To sice pomáhá eliminovat mnohé problémy, přesto nás to nezbavuje nutnosti dbát určitých pravidel. Jejich zvládnutí však poskytuje až netušené možnosti, co lze v Javě dělat.

29.11.2005 06:00 | Lukáš Jelínek | Články autora | přečteno 29450×

Jak nenarazit

Jsou dvě skupiny programátorů. Jedna preferuje co největší volnost v práci s datovými typy, druhá naopak co nejpřísnější pravidla. Jazyků bez typové kontroly je málo, stejně tak jazyků se zcela striktní kontrolou. Java se nachází v té přísnější oblasti, pravidla však nejsou až tak tuhá.

Jak známo, v Javě existují dva druhy datových typů: primitivní, a referenční. Primitivních typů je jen několik, nemůžeme od nich odvozovat typy nové (něco jako typedef neexistuje), naopak v typech referenčních spočívá těžiště práce. Teprve u nich se totiž uplatňuje objektové programování, které je v Javě jako doma.

Primitivní typy

Tyto typy nesou pouze svoji hodnotu, podporují různé operátory pro operace nad těmito typy, a jsou do jisté míry vzájemně přetypovatelné. Platí pravidlo, že od typu s menším rozsahem nebo méně přesného k typu s větším rozsahem, resp. přesnějšímu, můžeme provést implicitní přetypování, opačně nikoliv. Explicitně lze přetypovávat i opačně. Pozor - typ boolean není vzájemně přetypovatelný s žádným datovým typem.

Oproti např. jazyku C v Javě nikdy nevíme, jaké množství paměti ten který typ zabere - to je otázka implementace a na použití typu se to neprojeví. Co naopak známe, jsou meze rozsahu dat. Proto se můžeme vždy spolehnout, že určitý typ bude mít daný rozsah, bez ohledu na implementaci. Následující příklad ukazuje, jak s primitivními typy lze a nelze pracovat:

int i = 10;               // přiřazení hodnoty
long l = i;               // implicitní přetypování - lze
byte b1 = l;              // implicitní přetypování - nelze
byte b2 = (byte) i;       // explicitní přetypování - lze
boolean f1 = i;           // impl. přetypování na boolean - nelze
boolean f2 = (boolean) i; // expl. přetypování na boolean - nelze
double d = l;             // implicitní přetypování - lze

Referenční typy

Mezi referenční typy patří rozhraní, objektové třídy a pole. Přistupuje se k nim zásadně pomocí referencí (které ale mají spíše charakter ukazatelů). Při přiřazení hodnoty proměnné referenčního typu proto dojde vždy k přiřazení reference (tzn. nová proměnná bude ukazovat na tentýž objekt jako ta původní), nikoli hodnoty - pro předání hodnoty, tj. zkopírování objektu, se musí použít buď kopírovací konstruktor (pokud ho třída poskytuje), anebo klonování (pokud třída implementuje rozhraní Cloneable a má přístupnou metodu clone(); tento postup se však nedoporučuje). U polí je trochu jiná situace, už jsme se tomu věnovali v kapitole o polích.

Reference na hodnoty referenčních typů lze přetypovávat jen v případech, kdy jsou typy kompatibilní. Striktní typová kontrola vylučuje přetypování na nekompatibilní typy, bez ohledu na vnitřní datovou reprezentaci (oproti situaci v C++, kde si můžeme v tomto ohledu dělat prakticky cokoliv - samozřejmě na vlastní nebezpečí).

Znamená to, že přetypování projde jen v těchto dvou situacích (přetypování na totožný typ vynechávám):

  • Přetypováváme na nadtyp (nadřazená třída nebo rozhraní). Takto lze přetypovávat i implicitně, je to vždy bezpečné.
  • Přetypováváme na podtyp. To lze jen v situaci, kdy se jedná o instanci typu, na který přetypováváme, nebo o instanci jeho podtypu. Tento druh typové konverze je nutno dobře ohlídat, a to i v případech, kdy očekáváme hodnotu určitého typu (implementace se může změnit).

Podívejme se tedy, co je a co není v tomto ohledu legální:

String s1 = "testovací objekt";

// lze - přetypování na nadtyp
CharSequence cs = (CharSequence) s1;

// lze - instance příslušného podtypu
String s2 = (String) cs;              

// nelze - nekompatibilní typy
// ohlásí kompilátor
StringBuffer sb1 = (StringBuffer) s1; 

// nelze - instance nekompatibilního podtypu
// vyhodí výjimku (viz dále)
StringBuffer sb2 = (StringBuffer) cs; 

Výjimka ClassCastException

Již mnohokrát v tomto seriálu jsem upozorňoval na nutnost použití správného typu dat - s tím, že nedodržení bude "potrestáno" výjimkou ClassCastException. Je to asynchronní výjimka (běžně k ní nedochází), proto by se v normálních případech ani neměla ošetřovat. Naopak, mělo by se jí předcházet, takže její vyhození pak vždy signalizuje, že je někde něco (naprogramováno) špatně.

Výjimky ClassCastException se dočkáme vždy, když se pokusíme o nelegální přetypování (takové, které nelze odhalit již při kompilaci). Musím varovat hlavně před záludnými případy s přetypováním polí. Více ukáže následující příklad:

ArrayList<String> al = new ArrayList<String>();
al.add("text");

String s1 = (String) al.toArray()[0];   // funguje
String sa1[] = (String[]) al.toArray(); // nelze!

String s2 = (String) al.toArray(new String[0])[0];  // funguje
String sa2[] = al.toArray(new String[0]);           // správně

První možnost (pro někoho možná překvapivě) vyvolá výjimku ClassCastException. V čem je problém? Obě funkce vrátí pole, jehož prvky jsou řetězce. Prvky těchto polí můžeme tedy zcela bezpečně přetypovat na řetězce. Toto už ale v žádném případě nelze říci o příslušných polích. Bez ohledu na to, jaké prvky v poli jsou, se prostě nejedná kompatibilní typy (typ Object[] není přetypovatelný na String[]) a proto nemůžeme tuto konverzi provést.

Mám správnou instanci?

Kdo provádí operaci na datech, která získal odněkud "zvenku", a potřebuje tato data přetypovat, musí si bezpodmínečně zjistit, že má správný typ. Spoléhání na to, že "to vyjde", není dobré. A možná ještě horší je řešení pomocí zachycování výjimky ClassCastException. To je přímo cesta do pekel, protože kromě velké režie na zpracování výjimky se může také stát, že někdo později změní hierarchii tříd, a příslušné typy najednou budou kompatibilní - výjimka se tedy nevyvolá, ale výsledkem bude nějaké nedefinované (potenciálně chybné) chování.

Základním prostředkem pro zjištění správného typu je operátor instanceof. Ten použijeme v případech, kdy požadujeme instanci konkrétní třídy. Například takto (úprava kusu příkladu z kapitoly o síťové komunikaci):

URL url = new URL(urlString);             // urlString máme odjinud
URLConnection con = url.openConnection(); // otevře se spojení

if (con instanceof HttpURLConnection) {   // test typu instance

  // bezpečné přetypování
  HttpURLConnection hcon = (HttpURLConnection) con; 
  ...
}
else {
  con.disconnect();
  System.err.println("Toto není adresa protokolu HTTP");
  ...
}

Tehdy jsem uvedl, že mlčky předpokládáme správný typ. Při adrese udané "natvrdo" v kódu to mohlo být použitelné (pro zkušební účely), ale obecně takové předpoklady dělat nesmíme. Zvlášť tehdy, má-li vliv na typ někdo jiný (např. uživatel, který někde zadá vstupní data). Kontrolu zkrátka nelze vynechávat.

Třída Class

Každý referenční typ má v Javě má svoji instanci třídy Class. Tato instance se vytvoří automaticky při načtení příslušného typu do JVM, a lze ji kdykoli získat zavoláním getClass() na instanci objektu. Stejná instance třídy Class je k dispozici také jako proměnná třídy/rozhraní. Přiblížíme si to na příkladě (zjednodušená verze výše uvedeného příkladu - s úpravou pro použití této cesty):

URL url = new URL(urlString);             // urlString máme odjinud
URLConnection con = url.openConnection(); // otevře se spojení

if (HttpURLConnection.class.isInstance(con)) {   // test typu instance
  ...
}

if (con.getClass() == HttpURLConnection.class) { // test typu instance
  ...
}

Zbývá ještě vyřešit, co kdy použít. Operátor instanceof se hodí v případech, kdy požadovanou třídu známe již při psaní programu. Naopak metodu isInstance() použijeme spíš tam, kde tuto třídu získáme až za běhu. Třetí způsob uvádím proto, že to "také jde", ale moc se ve skutečnosti nepoužívá (je také poměrně omezený, protože porovnává naprosto striktně, přímo instance třídy Class). Třída Class toho ale umí mnohem víc. Pusťme se tedy do toho - nestihneme sice všechno, ale i tak to stojí za to.

Zjišťování informací

Třída Class poskytuje základní aparát pro zjišťování informací o daném typu. Podívejme se na některé z nabízených metod:

  • isInstance(Object o) - Zjišťuje, zda je objekt předaný v argumentu instancí dané třídy (viz výše). Je to vlastně dynamický ekvivalent operátoru instanceof
  • isAssignableFrom(Class c) - Zjišťuje, zda je objekt předávaný v argumentu přiřaditelný tomuto typu.
  • isArray() - Zjišťuje, zda se jedná o pole.
  • isInterface() - Zjišťuje, zda se jedná o rozhraní.
  • isPrimitive() - Zjišťuje, zda se jedná o primitivní typ. Ono totiž úplně neplatí, že Class se váže jen k referenčním objektům. Zapouzdřující třídy (např. Integer pro int) obsahují konstantu TYPE, která nese právě objekt třídy Class pro příslušný primitivní typ.
  • getName() - Vrací název typu. Tento název je zvláštní tím, že v sobě obsahuje "zakódovanou" dimenzi pole.
  • getSimpleName() - Vrací název typu tak, jak je definován ve zdrojovém kódu. Metoda je k dispozici od Javy 5.0.

Vytváření tříd a instancí

Nejen zjišťovat informace můžeme pomocí třídy Class. Máme tu totiž k dispozici nástroje mnohem silnějšího kalibru.

Za normálních okolností vytváříme objekty zavoláním jejich konstruktoru nebo nějaké metody, která instanci vytvoří (zavoláním neveřejného konstruktoru). Existuje ale ještě další cesta, a tou je metoda newInstance() třídy Class. Ta se pokusí zavolat bezparametrický konstruktor - což samozřejmě nemusí dopadnout dobře (konstruktor nemusí existovat apod.), a v takovém případě se vyvolá příslušná výjimka (např. InstantiationException). Metodu běžně nevyužijeme, ale hodí se v kombinaci s druhou podobnou věcí - a tou je vytvoření třídy.

Za normálních okolností lze již při startu rozhodnout, které třídy se mají načíst a inicializovat. Jsou to jednoduše ty, které se někde v programu používají. Někdy bychom ale potřebovali za běhu načíst nějakou třídu, která třeba při spuštění ani neexistovala. Typicky to může být třeba nějaký plugin (zásuvný modul). Ale i to je možné.

Třída Class má dvojici statických metod forName(). My se nyní zaměříme jen na tu jednodušší z nich, ale princip je stejný. Metodě předhodíme řetězec s názvem třídy, a příslušná třída je načtena a inicializována. Pochopitelně jen tehdy, je-li k dispozici. O načtení se postará systémový zavaděč tříd (classloader), který soubor s třídou hledá v místech určených proměnnou CLASSPATH. Nenajde-li ho, způsobí to výjimku ClassNotFoundException. Podívejme se na příklad:

try {
  Class c = Class.forName("MyDynamicClass");  // načtení třídy
  
  // lze přetypovat na Runnable?
  if (Runnable.class.isAssignableFrom(c)) {
  
    Runnable r = (Runnable) c.newInstance();  // vytvoření instance
    
    Thread t = new Thread(r);                 // použití
    t.start();
  }
  
} catch (Exception e) {
    System.err.println("Třída nebyla nalezena");
}

Příklad ukazuje, jak lze takto získaný objekt použít. Načteme třídu, zjistíme, zda implementuje rozhraní Runnable, a pokud ano, vytvoříme nové vlákno, které začne vykonávat metodu run() instance třídy MyDynamicClass. Podobnými mechanismy lze dělat ještě větší "psí kusy" (třeba načíst třídu odněkud ze sítě nebo z databáze a provést s ní totéž). Podobně lze také používat metody takových objektů, aniž bychom je předem znali - k tomu se používá postup zvaný reflexe, a o něm bude řeč někdy později (pro běžné použití se z řady důvodů nehodí).

Přístup odepřen

Načítání tříd odněkud zvenku (ať už při startu nebo za běhu) skýtá poměrně velké nebezpečí. Do třídy totiž někdo může podstrčit nějaký nebezpečný kód, který snadno udělá v běžícím programu paseku - samozřejmě ale jen tehdy, pokud mu to dovolíme. Jak mu to nedovolit, tedy jak omezit oprávnění pro provádění různých potenciálně nebezpečných operací, si vyzkoušíme příště. Jen naťuknu, že takové mechanismy se používají např. ve webových prohlížečích, aby applety ze sítě nemohly sahat na lokální disk nebo dělat jiné zapovězené věci.

Verze pro tisk

pridej.cz

 

DISKUZE

forName 29.11.2005 21:15 Petr Zajíc




Příspívat do diskuze mohou pouze registrovaní uživatelé.
> Vyhledávání software
> Vyhledávání článků

28.11.2018 23:56 /František Kučera

Prosincový sraz spolku OpenAlt se koná ve středu 5.12.2018 od 16:00 na adrese Zikova 1903/4, Praha 6. Tentokrát navštívíme organizaci CESNET. Na programu jsou dvě přednášky: Distribuované úložiště Ceph (Michal Strnad) a Plně šifrovaný disk na moderním systému (Ondřej Caletka). Následně se přesuneme do některé z nedalekých restaurací, kde budeme pokračovat v diskusi.


Komentářů: 1

12.11.2018 21:28 /Redakce Linuxsoft.cz
22. listopadu 2018 se koná v Praze na Karlově náměstí již pátý ročník konference s tématem Datová centra pro business, která nabídne odpovědi na aktuální a často řešené otázky: Jaké jsou aktuální trendy v oblasti datových center a jak je optimálně využít pro vlastní prospěch? Jak si zajistit odpovídající služby datových center? Podle jakých kritérií vybírat dodavatele služeb? Jak volit vhodné součásti infrastruktury při budování či rozšiřování vlastního datového centra? Jak efektivně datové centrum spravovat? Jak co nejlépe eliminovat možná rizika? apod. Příznivci LinuxSoftu mohou při registraci uplatnit kód LIN350, který jim přinese zvýhodněné vstupné s 50% slevou.
Přidat komentář

6.11.2018 2:04 /František Kučera
Říjnový pražský sraz spolku OpenAlt se koná v listopadu – již tento čtvrtek – 8. 11. 2018 od 18:00 v Radegastovně Perón (Stroupežnického 20, Praha 5). Tentokrát bez oficiální přednášky, ale zato s dobrým jídlem a pivem – volná diskuse na téma umění a technologie, IoT, CNC, svobodný software, hardware a další hračky.
Přidat komentář

4.10.2018 21:30 /Ondřej Čečák
LinuxDays 2018 již tento víkend, registrace je otevřená.
Přidat komentář

18.9.2018 23:30 /František Kučera
Zářijový pražský sraz spolku OpenAlt se koná již tento čtvrtek – 20. 9. 2018 od 18:00 v Radegastovně Perón (Stroupežnického 20, Praha 5). Tentokrát bez oficiální přednášky, ale zato s dobrým jídlem a pivem – volná diskuse na téma IoT, CNC, svobodný software, hardware a další hračky.
Přidat komentář

9.9.2018 14:15 /Redakce Linuxsoft.cz
20.9.2018 proběhne v pražském Kongresovém centru Vavruška konference Mobilní řešení pro business. Návštěvníci si vyslechnou mimo jiné přednášky na témata: Nejdůležitější aktuální trendy v oblasti mobilních technologií, správa a zabezpečení mobilních zařízení ve firmách, jak mobilně přistupovat k informačnímu systému firmy, kdy se vyplatí používat odolná mobilní zařízení nebo jak zabezpečit mobilní komunikaci.
Přidat komentář

12.8.2018 16:58 /František Kučera
Srpnový pražský sraz spolku OpenAlt se koná ve čtvrtek – 16. 8. 2018 od 19:00 v Kavárně Ideál (Sázavská 30, Praha), kde máme rezervovaný salonek. Tentokrát jsou tématem srazu databáze prezentaci svého projektu si pro nás připravil Standa Dzik. Dále bude prostor, abychom probrali nápady na využití IoT a sítě The Things Network, případně další témata.
Přidat komentář

16.7.2018 1:05 /František Kučera
Červencový pražský sraz spolku OpenAlt se koná již tento čtvrtek – 19. 7. 2018 od 18:00 v Kavárně Ideál (Sázavská 30, Praha), kde máme rezervovaný salonek. Tentokrát bude přednáška na téma: automatizační nástroj Ansible, kterou si připravil Martin Vicián.
Přidat komentář

   Více ...   Přidat zprávičku

> Poslední diskuze

2.12.2018 23:56 / František Kučera
Sraz

5.10.2018 17:12 / Jakub Kuljovsky
Re: Jaký kurz a software by jste doporučili pro začínajcího kodéra?

20.9.2018 10:04 / Jan Ober
Jaký kurz a software by jste doporučili pro začínajcího kodéra?

20.9.2018 10:00 / Jan Ober
Re: Gimp

20.2.2018 18:48 / Ivan Majer
portal

Více ...

ISSN 1801-3805 | Provozovatel: Pavel Kysilka, IČ: 72868490 (2003-2018) | mail at linuxsoft dot cz | Design: www.megadesign.cz | Textová verze