Java striktně rozlišuje mezi hodnotami
primitivních datových typů (čísla, logické hodnoty, znaky) a
objektových typů (řetězce a všechny uživatelem definované [tj. vlastní] typy-třídy)
Základní rozdíl je v práci s proměnnými:
proměnné primitivních typů přímo obsahují danou hodnotu, zatímco
proměnné objektových typů obsahují pouze odkaz na příslušný objekt
Důsledek -> dvě objektové proměnné mohou nést odkaz na tentýž objekt
Příklad, deklarujeme třídu Counter
takto:
public class Counter { private double value; public Counter(double v) { value = v; } public void add(double v) { value += v; } public void show() { System.out.println(value); } }
nyní ji použijeme:
Counter c1 = new Counter(1.23456); Counter c2 = c1; c1.add(2); c1.show(); c2.show();
dostaneme:
3.23456 3.23456
Proměnné těchto typů nesou elementární, z hlediska Javy atomické, dále nestrukturované hodnoty.
Deklarace takové proměnné (kdekoli) způsobí:
rezervování příslušného paměťového prostoru (např. pro hodnotu
int
čtyři bajty)
zpřístupnění (pojmenování) tohoto prostoru identifikátorem proměnné
Místo, kde je paměťový prostor pro proměnnou rezervován, závisí na tom, zda se jedná o porměnnou lokální (tzn. buď parametr metody nebo proměnná v metodě deklarovaná), pak se vyhradí na zásobníku, nebo zda jde o proměnnou objektu či třídy -- pak má místo v rámci paměťového prostoru objektu.
V Javě jsou celá čísla vždy interpretována jako znaménková
"Základním" celočíselným typem je 32bitový
int
s rozsahem -2 147 483 648
až 2147483647
větší rozsah (64 bitů) má long
,
cca +/- 9*10^18
Pro celočíselné typy existují (stejně jako pro floating-point
typy) konstanty - minimální a maximální hodnoty
příslušného typu. Tyto konstanty mají název vždy
Typ.MIN_VALUE
, analogicky MAX... Viz např. Minimální
a maximální hodnoty
char
- kódováníJava vnitřně kóduje znaky a řetězce v UNICODE, pro vstup a výstup je třeba použít některou za serializací (převodu) UNICODE na sekvence bajtů:
např. vícebajtová kódování UNICODE: UTF-8 a UTF-16
osmibitová kódování ISO-8859-x, Windows-125x a pod.
Problém může nastat při interpretaci kódování znaků národních abeced uvedených přímo ve zdrojovém textu programu.
Ve zdroj. textu správně napsaného javového vícejazyčného programu by žádné národní znaky VŮBEC neměly vyskytovat.
Je vhodné umístit je do speciálních souborů tzv.
zdrojů (v Javě objekty třídy
java.util.ResourceBundle
).
Kódována podle ANSI/IEEE 754-1985
Možné zápisy literálů typu float
(klasický i
semilogaritmický tvar) - povšimněte si "f" nebo "F" za číslem - je u float
nutné!:
float f = -.777f, g = 0.123f, h = -4e6F,
1.2E-15f;
double
: tentýž zápis, ovšem bez "f" za
konstantou a s větší povolenou přesností a rozsahem
Kladné "nekonečno": Float.POSITIVE_INFINITY
, podobně záporné:
Float.NEGATIVE
...
totéž pro Double
Obdobně existují pro oba typy konstanty uvádějící rozlišení
(nejmenší uložitelnou absolutní hodnotu různou od 0) daného typu
- MIN_VALUE
, podobně pro
MAX_VALUE
...
Konstanta NaN
- Not A
Number
Viz také Minimální a maximální hodnoty
Přípustné hodnoty jsou false
a
true
.
Na rozdíl od Pascalu na nich není definováno uspořádání, nelze je porovnávat pomocí <, >, <=, >=.
void
Není v pravém slova smyslu datovým typem, nemá žádné hodnoty.
Označuje "prázdný" typ pro sdělení, že určitá metoda nevrací žádný výsledek.
Pole v Javě je speciálním objektem
Můžeme mít pole jak primitivních, tak objektových hodnot
Kromě pole v Javě existují i jiné objekty na ukládání více hodnot - tzn. kontejnery, viz dále
Poznámka | |
---|---|
na rozdíl od C/C++ nikdy neuvádíme při deklaraci počet prvků pole - ten je podstatný až při vytvoření objektu pole |
Syntaxe přístupu k prvkůmjménopole[indexprvku]Používáme
jak pro přiřazení prvku do
pole: jménopole[indexprvku] = hodnota;
tak pro čtení hodnoty z pole
proměnná = jménopole[indexprvku];
Syntaxe vytvoření objektu pole: jako u jiného objektu - voláním konstruktoru:
jménopole = new typhodnoty[ početprvků ]; nebo vzniklé pole rovnou naplníme hodnotami/odkazy
Pole je objekt, je třeba ho před použitím nejen deklarovat, ale i vytvořit:
Person[] lidi; lidi = new Person[5];
lidi[0] = new Person("Václav Klaus"); lidi[1] = new Person("Libuše Benešová");
lidi[0].writeInfo(); lidi[1].writeInfo();
Co kdybychom pole pouze deklarovali a nevytvořili:
Person[] lidi; lidi[0] = new Person("Václav Klaus");
Toto by skončilo s běhovou chybou "NullPointerException", pole neexistuje, nelze do něj tudíž vkládat prvky!
Pokud tuto chybu uděláme v rámci metody:
public class Pokus { public static void main(String args[]) { String[] pole; pole[0] = "Neco"; } }
Pokus.java:4: variable pole might not have been initialized pole[0] = "Neco"; ^ 1 error
public class Pokus { static String[] pole; public static void main(String args[]) { pole[0] = "Neco"; } }
Překladač chybu neodhalí a po spuštění se objeví:
Exception in thread "main" java.lang.NullPointerException at Pokus.main(Pokus.java:4)
Co kdybychom pole deklarovali, vytvořili, ale nenaplnili příslušnými prvky:
Person[] lidi; lidi = new Person[5]; lidi[0].writeInfo();
Toto by skončilo také s běhovou chybou NullPointerException:
V Javě obecně přiřazení proměnné objektového typu vede pouze k duplikaci odkazu, nikoli celého odkazovaného objektu.
Person[] lidi2; lidi2 = lidi1;
V proměnnélidi2je nyní odkaz na stejné pole jako je vlidi1.
Zatímco, provedeme-li vytvoření nového pole + arraycopy, pak lidi2 obsahuje duplikát/klon/kopii původního pole.
Person[] lidi2 = new Person[5]; System.arraycopy(lidi, 0, lidi2, 0, lidi.length);
viz též Dokumentace API třídy "System"
Poznámka | |
---|---|
Samozřejmě bychom mohli kopírovat prvky ručně, např. pomocí for cyklu, ale volání System.arraycopy je zaručeně nejrychlejší a přitom stále platformově nezávislou metodou, jak kopírovat pole. |
Také arraycopy však do cílového pole zduplikuje jen odkazy na objekty, nevytvoří kopie objektů!
+
, -
,
*
, /
a %
(zbytek po celočíselném dělení)
Pozn: operátor dělení / je polymorfní, funguje pro celočíselné argumenty jako celočíselný, pro floating-point (float, double) jako "obyčejný".
Pracují nad logickými (booleovskými) hodnotami (samozřejmě vč. výsledků porovnávání <, >, ==, atd.).
Tyto lze použít na porovnávání primitivních hodnot:
Test na rovnost/nerovnost lze použít na porovnávání primitivních hodnot i objektů:
Pozor na porovnávání objektů: == vrací true jen při rovnosti odkazů, tj. jsou-li objekty identické. Rovnost obsahu (tedy "rovnocennost") objektů se zjišťuje voláním metody o1.equals(Object o2)
Pozor na srovnávání floating-point čísel na rovnost: je třeba počítat s chybami zaokrouhlení; místo porovnání na přesnou rovnost raději používejme jistou toleranci: abs(expected-actual) < delta
? :
Jediný ternární operátor, navíc polymorfní, pracuje nad různými typy 2. a 3. argumentu.
Platí-li první operand (má hodnotu true
)
->
Typ prvního operandu musí být boolean
, typy
druhého a třetího musí být přiřaditelné do výsledku.
Podobně jako v C/C++
Píše se (typ)hodnota, např. (Person)o, kde o byla proměnná deklarovaná jako Object.
Pro objektové typy se ve skutečnosti nejedná o žádnou konverzi spojenou se změnou obsahu objektu, nýbrž pouze o potvrzení (tj. typovou kontrolu), že běhový typ objektu je požadovaného typu - např. (viz výše) že o je typu Person.
Naproti tomu u primitivních typů se jedná o úpravu hodnoty - např. int přetypujeme na short a „ořeže“ se tím rozsah.
+
Výsledkem je vždy řetězec, ale argumenty mohou být i jiných typů, např.
sekvence int i = 1; System.out.println("variable
i = " + i);
je v pořádku
s řetězcovou konstantou se spojí řetězcová podoba dalších argumentů (např. čísla).
Pokud je argumentem zřetězení odkaz na objekt o
->
je-li o != null
-> použije se hodnota
vrácená metodou o.toString()
(tu lze překrýt a
dosáhnout tak očekávaného řetězcového výstupu)
Porovnáme-li dva objekty (tzn. odkazy na objekty) prostřednictvím operátoru == dostaneme rovnost jen v případě, jedná-li se o dva odkazy na tentýž objekt - tj. dva totožné objekty.
Jedná-li se o dva obsahově stejné objekty existující samostatně, pak == vrátí false.
Chceme-li (intuitivně) chápat rovnost objektů podle obsahu, tj.
dva objekty jsou rovné (rovnocenné, nikoli totožné), mají-li stejný obsah, pak
musíme pro danou třídu překrýt metodu equals, která musí vrátit true, právě když se obsah výchozího a srovnávaného objektu rovná.
Fungování equals lze srovnat s porovnáváním dvou databázových záznamů podle primárního klíče.
Nepřekryjeme-li equals, funguje původní equals přísným způsobem, tj. rovné si budou jen totožné objekty.
Příklad: objekt třídy Person nese informace o člověku. Dva objekty položíme stejné (rovnocenné), nesou-li stejná příjmení:
Obrázek 1. Dva lidi jsou stejní, mají-li stejná příjmení
public class Person { private String firstname; private String surname; public Person (String j, String p) { firstname = j; surname = p; } public boolean equals(Object o) { if (o instanceof Person) { Person c = (Person)o; // dva lidé se (v našem případě) rovnají, mají-li stejná příjmení return surname.equals(c.surname); } else { // porovnáváme-li osobu s objektem jiného typu, nikdy se nerovnají return false; } } }
Jakmile u třídy překryjeme metodu equals, měli bychom současně překrýt objektů i metodu hashCode:
hashCode vrací celé číslo (int) „co nejlépe“ charakterizující obsah objektu, tj.
pro dva stejné (equals) objekty musí vždy vrátit stejnou hodnotu.
Pro dva obsahově různé objekty by hashCode naopak měl vracet různé hodnoty (ale není to stoprocentně nezbytné a ani nemůže být vždy splněno).
Metoda hashCode totiž nemůže vždy být prostá.
V těle hashCode s oblibou „přehráváme“ (delegujeme) řešení na volání hashCode jednotlivých složek objektu - a to těch, které figurují v equals:
Obrázek 2. Třída Person s metodami equals a hashCode
public class Person { private String firstname; private String surname; public Person (String j, String p) { firstname = j; surname = p; } public boolean equals(Object o) { if (o instanceof Person) { Person c = (Person)o; // dva lidé se (v našem případě) rovnají, mají-li stejná příjmení return surname.equals(c.surname); } else { // porovnáváme-li osobu s objektem jiného typu, nikdy se nerovnají return false; } } public int hashCode() { return surname.hashCode(); } }