98
Kapitola 4 Základní operátory
Poté jsem se pokusil demonstrovat některé vlastnosti konverze mezi hodnotami číselných datových typů. Na řádku 19 jsem proměnné _long přiřadil hodnotu, kterou budeme následně převádět. Hodnotu jsem zadával v hexadecimálním tvaru, aby bylo názorně vidět, jak taková konverze probíhá. V příkazu na řádcích 23–28 jsem pak požádal o tisk jednotlivých konvertovaných hodnot, vždy nejprve v šestnáctkové a pak v desítkové soustavě. Nezlobte se, že vám nyní nevysvětlím, proč jsem se rozhodl tisknout příslušné hodnoty právě takto. Bylo by to na delší povídání, a kromě toho si myslím, že si mnohé dokážete sami odvodit. Příslušný tisk je na řádcích 29–32. První je uvedena výchozí hodnota proměnné _long a na dalších řádcích jsou jednotlivé převedené hodnoty. Všimněte si hodnoty typu int na řádku 30. Hexadecimální zápis je naštěstí uváděn jako kladné číslo, takže je vidět, že v nejvyšším bajtu je hodnota 8, což je nejmenší šestnáctková číslice, která má nejvyšší bit jedničkový. Proto je také dané číslo vnímáno jako záporné, přestože převáděné číslo bylo kladné. Další čísla již mají ve svých nejvyšších bajtech čísla menší než 8 a ta mají nejvyšší bit nulový, takže tato čísla jsou vnímána jako kladná. Na hexadecimálním zápisu čísla je krásně vidět, jak se při převodu na „menší“ typ vždy smaže horní polovina čísla „většího“ typu. Příkaz na řádku 34 se snaží demonstrovat, že převod reálného čísla na celé pouze uřízne desetinnou část. Předpokládám, že hodnotu čísla π znáte, takže dokážete zkontrolovat, zda JShell správně spočetl hodnotu celé části jeho stonásobku. Na řádku 38 pak demonstruji implicitní převod znaku na číslo (= jeho kód) a vzápětí pak na řádku 42 převod čísla na znak s daným kódem.
Přetypování instancí objektových datových typů Pravidla pro přetypovávání objektových datových typů vycházejí z jejich vzájemných vztahů „podtyp × nadtyp“, který jsem již ve stručnosti zmiňoval v pasáži Implicitní přetypování na straně 92 a které budu podrobněji rozebírat až v druhé části. Prozatím tedy pouze princip. Převod spočívá v tom, že se při kontrole přípustnosti přetypování virtuální stroj podívá na skutečný typ přetypovávané hodnoty a na to, jestli cílový datový typ je jejím mateřským typem nebo některým z jeho nadtypů. Je-li tomu tak, přetypování se realizuje, není-li tomu tak, ohlásí se chyba. Ukázky použití operace implicitního i explicitního přetypování najdete ve výpisu 4.7. Na řádku 1 jsem ukazoval, že při explicitním přetypování počítač akceptuje uložení hodnoty proměnné _Object do proměnné _String. Je to ale možné pouze proto, že při posledním přiřazení do proměnné _Object jsme přiřazovali hodnotu typu String (viz příkaz na řádku 29 ve výpisu 4.5 na straně 94). Když jsem pak na řádku 5 zopakoval přiřazení hodnoty proměnné _byte do proměnné _Object (přesněji vytvoření objektu, který uchovával hodnotu dané proměnné), tak zpětný převod daného objektu na jím reprezentovanou hodnotu při explicitním přetypování (viz příkaz na řádku 9) prošel. 62_Java 14 – ZLOM.doc verze 1.03.8618_2021-01-16_so_10-42
Strana 98 z 578 Ukázka elektronické knihy, UID: KOS286730
4.6 Operátor přetypování (typ)
99
Následující opakované uložení hodnoty proměnné _Object do proměnné _String (řádek 16 s komentářem na řádcích 13–15) způsobilo chybu, protože tentokrát byl v převáděné proměnné odkaz na objekt reprezentující hodnotu typu byte, a ten standardním způsobem na String přetypovat nejde (o možném způsobu se dočtete vzápětí). Výpis 4.7: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
Explicitní přetypování hodnot objektových typů
jshell> _String = (String)_Object; //V proměnné je string => projde _String ==> "i=100, j=130, k=80" | assigned to _String : String jshell> _Object = _byte; //To už jsme dělali, proběhne implicitní konverze _Object ==> 33 | assigned to _Object : Object jshell> _byte = (byte)_Object; //V proměnné je byte => projde _byte ==> 33 | assigned to _byte : byte
jshell> /* Když byl v proměnné _Object string, konverze prošla. ...> Nyní je v ní ale byte, tak projít nemůže. ...> Aby na to ale virtuální stroj přišel, musí kontrolovat za běhu. ...> */ _String = (String)_Object; | Exception java.lang.ClassCastException: class java.lang.Byte cannot be cast to class java.lang.String (java.lang.Byte and java.lang.String are in module java.base of loader 'bootstrap') 18 | at (#48:1) 19 20 jshell> _String = _Object + ""; 21 _String ==> "33" 22 | assigned to _String : String 23 24 jshell>
Univerzální „přetypování“ na String Kromě výše uvedených „řádných“ přetypování bych tu přidal ještě jedno „falešné“. Je jím přetypování čehokoliv na String. „Řádné“ přetypování na String pomocí operátoru (String) vám totiž překladač většinou nepovolí. Co ale projde vždy, je výraz ""+x, kde x může být proměnná libovolného typu, objektového i primitivního (již jsem se o tom zmiňoval v pasáži Sčítání stringů na straně 88). Důvod je jednoduchý – nejedná se o přetypování, ale o sloučení stringů. Protože na levé straně operátoru + stojí string, převede počítač i pravý operand na string (u instancí objektových typů zavolá jejich metodu toString() a dosadí vrácený výsledek) a k výsledku přidá prázdný string, tj. nic. Místo původního x (přitom x nemusí být jen proměnná, ale může to být i výraz, který je nejdříve potřeba spočítat) tak získáme string, který je tou nejlepší textovou reprezentací x. 62_Java 14 – ZLOM.doc verze 1.03.8618_2021-01-16_so_10-42
Strana 99 z 578 Ukázka elektronické knihy, UID: KOS286730
100
Kapitola 4 Základní operátory
Předpokládám, že je vám jasné, že stejného výsledku bychom dosáhli i s výrazem x+"". Jak už jsme si řekli, překladači je totiž jedno, který ze sčítanců je string. Jakmile je to jeden z nich, převede na text i ten druhý. Tuto možnost „přetypování“ jsem demonstroval ve výpisu 4.7 na řádku 20. Když se sečetl obsah proměnné _Object převedený na String s prázdným stringem, vznikl součet typu String, a ten (přesněji odkaz na něj) byl virtuální stroj ochoten do proměnné typu String uložit.
Textový podpis String, který vznikne (mimo jiné) součtem dané proměnné s prázdným stringem, budeme označovat jako textový podpis hodnoty přičítané k prázdnému stringu. Přičteme-li proměnnou k libovolnému stringu, přičte se k němu textový podpis hodnoty uložené v dané proměnné. Textovým podpisem stringů jsou samy stringy. Textovým podpisem hodnot primitivních typů je literál s odpovídající hodnotou. U objektových typů je to trochu složitější. Podrobněji se touto problematikou budeme zabývat v pasáži Třída Object na straně 240. JShell používá textový podpis, když oznamuje, že uložil hodnotu do proměnné. V tomto oznámení je vždy uveden název cílové proměnné následovaný šipkou ==> a textovým podpisem ukládané hodnoty. Prozatím jsme se setkávali pouze s podpisy hodnot primitivních typů, stringů a hodnoty null. Na textový podpis jiného objektu si budete muset počkat do výpisu 6.13 na straně 143.
4.7 Specifika číselných typů Číselné datové typy patří k nejpoužívanějším, ale stále dokáží svým uživatelů přinést různá překvapení. Pojďme se na ně podívat, ať se později nezařadíte k těm překvapeným.
Malé celočíselné typy V pasáži Primitivní datové typy na straně 53 jsme si říkali, že vedle často používaných celočíselných datových typů int a long definuje Java i „malé“ celočíselné typy short a byte. Problém těchto datových typů ale tkví v tom, že operace čtení a ukládání jsou pro ně kupodivu pomalejší, protože současné procesory preferují práci s úseky paměti, jejíž velikost odpovídá velikosti datových typů int a long. Má proto smysl je používat pouze v případech, kdy je dat daného typu opravdu hodně a potřebujeme využít jejich malý rozměr k úspoře paměti. Při práci s hodnotami těchto typů ale musíme mít na paměti, že výsledek jakékoliv aritmetické operace s daty těchto typů je typu int. Nemůžete jej proto uložit do proměnné daného typu bez explicitního přetypování. Ve výpisu 4.8 je to demonstrováno. 62_Java 14 – ZLOM.doc verze 1.03.8618_2021-01-16_so_10-42 Ukázka elektronické knihy
Strana 100 z 578