Obsah
Seznam obrázků
Seznam příkladů
Obsah
Souvislosti, prerekvizity, návaznosti
Hodnocení a jeho složky
Kontakty a konzultační hodiny
Odkazy na informační zdroje
Prakticky zaměřený bakalářský předmět
Cílem je naučit základním principům objektového programování a algoritmizace
Souvisí s
IB001 - Úvod do programování (předpokládají se znalosti na úrovni IB001)
IB002 - Návrh algoritmů I (v PB162 se prakticky implementují vybrané algoritmy probírané v IB002)
Předpokládají se základní znalosti strukturované algoritmizace a programování (v rozsahu Úvodu do programování), tj. např.:
základní příkazy, sestavování jednoduchých výrazů
základní datové typy (celá a reálná čísla, logické proměnné, řetězce)
základní řídicí struktury - větvení, cykly, procedury/funkce
Hodnocení má tři složky:
40 bodů - hodnocení úloh řešených samostatně v průběhu semestru (na cvičeních, ve volném čase...)
12 bodů - první písemka řešení jednoduché praktické úlohy přímo u počítače, v době cvičení, zhruba po prvním měsíci až 5 týdnech výuky (říjen). Čas na písemku: i se zadáním během hodinového cvičení. Bližsí info viz „První písemka“.
18 bodů - druhá písemka řešení praktické úlohy přímo u počítače, v době cvičení, na konci výuky v semestru (prosinec). Bližsí info viz „Druhá písemka“.
30 bodů - třetí písemka řešení rozsáhlejší praktické úlohy přímo u počítače, ve zkouškovém období. Čas na písemku: delší než u prvních dvou. Bližsí info viz „Třetí (zkoušková) písemka“.
Celkem 100 bodů
![]() | Poznámka |
---|---|
písemky v průběhu semestru (tj. první dvě) i úlohy hodnotí cvičící |
![]() | Poznámka |
---|---|
docházka do cvičení je povinná, ale netvoří součást bodového hodnocení |
Doporučené ukončení je zkouškou. K tomu potřebujete:
A 94 - 100 bodů
B 88 - 93 bodů
C 82 - 87 bodů
D 76 - 81 bodů
E 70 - 75 bodů
F 0 - 69 bodů
K ukončení zápočtem postačí:
60 bodů
viz aktuální informace o předmětu PB162 na IS MU
Cvičení jsou dvouhodinová, konají se pod vedením příslušných kvalifikovaných cvičících v počítačových učebnách, zpravidla B116, B117 - viz váš rozvrh.
Náplň
Hlavním obsahem je konzultovaná samostatná práce na bodovaných úlohách.
cvičení jsou dvouhodinová, účast na přednáškách je však přesto žádoucí
předpokládá se i jistý netriviální podíl práce mimo cvičení
odměnou vám, kromě získaných znalostí a dovedností, budou celkem 4 kr. + za ukončení
Tomáš Pitner
kanc. B307 (3. patro budovy B)
tel. 54949 5940 (z tlf. mimo budovu), kl. 5940 (volání v rámci fakulty i celé MU)
e-mail: tomp@fi.muni.cz
www TP: http://www.fi.muni.cz/~tomp
materiály PB162-Java (IS): https://is.muni.cz/auth/el/1433/podzim2005/PB162/
Primárním konzultačním bodem jsou vaši cvičící. Cvičení jsou vedena mj. právě z důvodu možnosti konzultací.
Konzultace přímo s přednášejícím: vždy v kanc. B307, prosím o dodržování časů:
Studijní materiály předmětu: https://is.muni.cz/auth/el/1433/podzim2005/PB162/ (budou vystavovány postupně, pro celkový přehled o obsahu lze použít materiály loňské)
Další zdroje, materiály z minulých let: http://www.fi.muni.cz/~tomp/java
Knihy
TP: Java - začínáme programovat, Grada Publishing, 2002, http://www.fi.muni.cz/~tomp/java/ucebnice
Pavel Herout: Učebnice jazyka Java, Kopp, 2000
(Pavel Herout: Java - grafické uživatelské rozhraní a čeština, Kopp, 2001) - pro pokročilé
Rudolf Pecinovský: Myslíme v jazyce Java 5.0, Grada Publishing, 2005
Bruce Eckel: Myslíme v jazyce Java - příručka programátora, Grada Publishing, 2000
(Bruce Eckel: Myslíme v jazyce Java - příručka zkušeného programátora, Grada Publishing, 2000) - pro pokročilé
a další, viz např.http://www.vltava.cz
Joshua Bloch: Java efektivně 57 zásad softwarového experta, Grada Publishing
Bogdan Kiszka: 1001 tipů a triků pro programování v jazyce Java, Computer Press, 2003, informace na Vltavě
univerzální (není určen výhradně pro specifickou aplikační oblast)
objektově-orientovaný (výpočet je realizován jako volání metod/zasílání zpráv objektů)
ideovým předchůdcem je C++ (a evt. Delphi) (C++ zbaveno zbytečností a nepříjemností)
reálným soupeřem je (Microsoft) C# (zatím převážně na platf. Windows)
program v Javě je meziplatformně přenositelný na úrovni zdrojového i přeloženého kódu
je to umožněno tím, že přeložený javový program běží v tzv. Java Virtual Machine (JVM)
zdrojový i přeložený kód je tedy přenositelný mezi všemi obvyklými platformami (UNIX, Windows, MAC OS X, ale také sálové počítače, minipočítače typu IBM AS/400 apod.)
tedy všude tam, kde existuje příslušná JVM
Kód je při běhu dobře zabezpečen:
je možné nastavit úrovně přístupu k hostitelskému systému pomocí tzv. Security Manageru
je možné ověřovat před spuštěním elektronický podpis kódu
jazyk vhodný pro efektivní (rychlé) psaní přehledných programů (mj. také díky dokumentačním možnostem)
v průměru vyšší produktivita programátorské práce v Javě než v C++
zdarma dostupné nezměrné množství knihoven pro různorodé aplikační oblasti, např. na SourceForge a tisících dalších místech
k dispozici je řada kvalitních vývojových prostředí (i zdarma) - NetBeans, JBuilder, Visual Age for Java, Eclipse, IDEA
Konkrétní možnosti:
Škálovatelné výkonné aplikace běžící na serverech (Java Enterprise Edition)
Aplikace na přenosných a vestavěných zařízeních (Java Micro Edition)
Přenositelné desktopové i grafické/okénkové (GUI) aplikace
Javovou platformu tvoří:
překladač
(přístupný např. příkazem javac) a další vývojové
nástroje
chování JVM se mění např. pokud se objeví bezpečnostní "díra" nebo nelze-li dosáhnout požadované změny chování pomocí modifikace Java Core API
daleko konzervativnější je samotný jazyk - mění se zřídka, ale přece: např. Java2, v1.4 přidává nové klíčové slovo assert, Java 5.0 (postaru 1.5) obohacuje jazyk o enum a další.
Specifikace Javy (tzv. "Editions") - např.: Java 2 Standard Edition, 1.4 nebo Java 2 Standard Edition 5.0
Implementace Javy ("Development Kits" nebo "Runtime Environments") - např.: Java 2 Software Development Kit, 5.0 - obsahuje vývojové nástroje
Java 2 Runtime Enviroment, 5.0 - obsahuje jen běhové prostředí pro spouštění hotových přeložených pg.
Verze Javy byly až do verze 1.5 (ponovu 5.0) děleny na "Java (před Java 2)" a "Java 2"
číslování verzí:
tzv. major číslo, např. Java 2, v1.4
tzv. minor číslo, např. Java 2, v1.4.2
při změně major (druhého) čísla se může měnit Core API a někdy i jazyk
ke změně prvního čísla dochází až teď, s verzí 5.0 (postaru 1.5) - tj. s celkovou změnou pojmenovávacího schématu
Nové schéma číslování verzí počínaje 5.0 (postaru 1.5):
tzv. major číslo, např. Java 2, 5.0
tzv. minor číslo, např. Java 2, 5.0
Stav k září 2005:
Java 2 Standard Edition v5.0 je stabilní verzí pro všechny platformy
Java 2 Enterprise Edition v1.4 je stabilní verzí pro všechny platformy
Připravována je Java 2 Enterprise Edition v5.0
aktuální informace najdete vždy na webu http://java.sun.com
generika (generické datové typy) technika umožňující silnou typovou kontrolu v době překladu - např. v metodách manipulujících s prvky dynamických datových struktur
umožňuje snadnou a typově bezpečnou iteraci po prvcích kolekcí a polí
zajišťuje automatickou konverzi mezi primitivními typy a příslušnými objektovými "obalujícími" typy
typově bezpečné výčtové typy
umožňují specifikovat proměnný počet parametrů metod
dovoluje odkazovat se na statické prvky bez uvádění jména třídy (rozhraní)
dovolují specifikovat u tříd, rozhraní, metod metadata (anotace) dostupná i za běhu
java.sun.com (pro Windows, Solaris, Linux)
lze stáhnout jak samotné vývojové prostředí (JDK), jen běhové prostředí (JRE) nebo JDK v balíčku s IDE (Integrated Development Environment) NetBeans.
dokumentace se stahuje z téhož místa, ale samostatně (nebo lze číst z WWW)
celkově vývojové prostředí J2SDK 1.4.2 vč. dokumentace zabere cca 220 MB na disku
potřebná velikost operační paměti - min 64 MB, doporučeno 128 MB (i více :-))
Lze stáhnout:
samotné vývojové prostředí (JDK),
jen běhové prostředí (JRE),
JDK v balíčku s grafickým (okénkovým) integrovaným vývojovým prostředím (IDE, Integrated Development Environment) NetBeans.
Vývojové nástroje (Development
Tools) v bin -- určené k vývoji,
spouštění, ladění a dokumentování programů v Javě.
Běhové prostředí Javy (Java Runtime
Environment) se nalézá v jre.
Obsahuje Java Virtual Machine (JVM), knihovnu tříd Java Core API a
další soubory potřebné pro běh programů v Javě.
Přídavné knihovny (Additional
libraries) v podadresáři lib jsou
další knihovny nutné pro běh vývojových nástrojů.
Ukázkové applety a aplikace (Demo
Applets and Applications) v demo.
Příklady zahrnují i zdrojový kód.
Hlavičkové soubory pro C (C header
Files) - v include - představují
podporu pro psaní tzv. nativních metod přímo v jazyce C.
Staré hlavičkové soubory (Old Native Interface Headers) - totéž, ale pro starší verzi rozhraní.
Zdrojový kód (Source Code)
knihoven z Java Core API se nalézá v archivu
src.zip.
Dokumentace (Documentation) -
v podadresáři docs - obsahuje dokumentaci k
dané verzi JDK, k API, nejrůznější průvodce vývojem, dokumentaci k
nástrojům, ukázkové programy a odkazy na související
dokumentaci.
Pod Windows jsou to .exe
soubory umístěné v podadresáři bin
java -
spouštěč (přeloženého bajtkódu)
javadoc
- generátor dokumentace API
jar -
správce archivů JAR (sbalení, rozbalení, výpis)
jdb -
debugger
appletviewer
- referenční prostředí pro spouštění appletů
javaws -
referenční prostředí pro spouštění aplikací typu "Java Web Start"
prostřednictvím Java Network Launching Protocol (JNLP)
Seznámíme se se strukturou programu v Javě,
naučíme se spouštět hotové jednoduché programy ve výukovém prostředí BlueJ
a tyto programy upravovat.
Spuštění programu se zahajuje vstupem do metody main.
Běh programu spočívá ve vytváření objektů a interakci s nimi, tj. volání jejich metod.
Běh končí, jakmile jsou provedeny všechny příkazy aktivované metodou main.
Pro výuku v kurzu PB162 budeme převážně používat jednoduché, pro výuku určené, vývojové prostředí BlueJ.
BlueJ je pro všechny platformy (Win, Linux...) zdarma dostupné na http://bluej.org.
BlueJ je lokalizované pro češtinu vč. základního manuálu.
Pro BlueJ existuje i jednoduchý animátor běhu programů - vizualizuje spouštění a chod programů.
Toto prostředí je již předinstalované v síti FI.
BlueJ je již v síti FI instalováno, pro vlastní potřebu jej lze zdarma stáhnout z http://bluej.org.
Předtím je třeba mít korektně nainstalováno samotné vývojové prostředí Java.
Po stažení příslušného instalačního balíčku postupujte v závislosti na platformě:
poklepat (double-click) na staženém programu
bluejsetup-verze.exe
(např. bluejsetup-205.exe
)
následovat pokyny instalačního programu
instalační program přitom nechá vybrat z předem instalovaných běhových prostředí Javy
na konzole (v prostředí shellu) se přepněte (pomocí
cd) do adresáře se staženým balíčkem, např.
bluej-205.jar
spusťte instalaci příkazem java -jar
bluej-205.jar
při instalaci vyberte vhodný cílový adresář a zvolte z předem instalovaných běhových prostředí Javy
Po úspěšné instalaci na:
najdete v menu Start systému Windows složku BlueJ a zde spouštěcí odkaz BlueJ s ikonou ptáka
v adresáři, kam se BlueJ nainstalovalo, najdete spustitelný
soubor bluej - tím lze prostředí
spustit.
Chcete-li, aby nabídky (menu) a hlášky BlueJ byly v češtině, postupuje podle pokynů v Konfigurační doplněk pro prostředí BlueJ (autor Rudolf Pecinovský).
Takto vypadá počeštěné prostředí:
Každý javový program vytvořený v BlueJ představuje jeden projekt. Toto je specificky vlastnost BlueJ, ne Javy jako takové.
Práce s hotovým programem v BlueJ tedy znamená
otevřít projekt -> menu
Projekt/Otevřít
projekt
.
Každý běžný javový program spouštíme (nejen v BlueJ) aktivací metody main.
V BlueJ to znamená kliknout pravým tlačítkem myši na ikoně příslušné třídy a vybrat void main(String[] args).
Při spuštění uvádíme parametry, které metoda main dostane. Jelikož zatím žádné specifikovat nepotřebujeme, ponecháme dialog, jak je a stiskneme Enter (klikneme OK).
Výsledek spuštění programu se projeví v tzv. okně terminálu (konzole) BlueJ a vypadá takto:
Objektový přístup znamená především to, že program za běhu vytváří objekty a volá jejich metody. To lze v BlueJ snadno realizovat.
V demostračním programu Hello máme jednu třídu (Hello), od níž lze vytvořit objekt (instanci) a na ní volat metody.
Zdrojový kód třídy Hello (dostupný poklepáním na její ikonu) prozradí, které metody zde máme k dispozici.
Dále postupujeme:
Pravým tlačítkem klikneme na ikonu Hello a vybereme new Hello().
do dialogu uvedeme název odkazu na vytvářený objekt typu Hello - můžeme ponechat hello1.
Vytvořený objekt (instance třídy Hello) je k dispozici jako červená krabička vlevo dole.
Vytvořený objekt (instance hello1 třídy Hello) je k dispozici jako červená krabička vlevo dole.
S instancí je nyní možné komunikovat/interagovat - tj. volat metody.
Jelikož v úvahu (viz zdrojový kód Hello) připadá jen metoda void go(), zavoláme právě ji:
Metoda proběhne s výsledkem podobným, jako když jsme program spouštěli metodou main.
Metoda main totiž realizovala postupně stejné kroky, jako jsme teď provedli interaktivně (krok po kroku) sami.
Nejjednodušší úpravou programu je změna těla některé z metod, v našem případě již známé metody go().
Editor zdrojového kódu třídy Hello aktivujeme pravým tlačítkem - položkou Otevřít v editoru.
Kód metody go() změníme přepsáním hlášky "Hello, world" na "Ahoj!".
Po kliknutí na tlačítko Přeložit by se mělo objevit:
Třídu Hello s upravenou metodou spustíme jako v předchozím - např. pravým tlačítkem a metodou main.
Objeví se:
Cílem je porozumět obecné struktuře javového programu
a být schopni vytvořit a spustit jej i mimo prostředí BlueJ.
Program sestává z jedné (ale obvykle více) tříd (class). Ukážeme na příkladu třídy s názvem NazevTridy:
Zdrojový kód každé (veřejně přístupné) třídy je
umístěn v jednom souboru
(NazevTridy.java)
Postup:
vytvoření zdrojového textu (libovolným editorem čistého
textu) ->
NazevTridy.java
překlad (nástrojem javac) NazevTridy.java
-> NazevTridy.class
spuštění, např. java
NazevTridy
překládá se javac NazevTridy.java
(název souboru se třídou včetně přípony
.java
!!!)
spouští se vždy udáním java
NazevTridy (název třídy bez přípony
.class
!!!)
Půjde o podobný jednoduchý demoprogram, jaký jsme spouštěli v BlueJ pod názvem Hello.
Navíc se zde objeví použití balíků - třída bude umístěna do balíku tomp.ucebnice.
Zdrojový kód bude vypadat takto:
package tomp.ucebnice;
public class Pozdrav {
// Program spouštíme aktivací funkce "main"
public static void main(String[] args) {
System.out.println("Ahoj!");
}
}
Pozn: Jelikož třída Pozdrav je umístěna do
balíku tomp.ucebnice
, její
zdrojový soubor musí být uložen v podadresáři
tomp\ucebnice
.
Překlad
Máme nainstalován J2SDK 5.0
Jsme v adresáři c:\devel\pb162, v něm je
podadresář tomp\ucebnice
, v něm je soubor
Pozdrav.java
Spustíme překlad javac
tomp\ucebnice\Pozdrav.java
Je-li program správně napsán, přeloží se "mlčky"
(výsledný .class soubor bude v témže
adresáři jako zdroj)
Spuštění
Poté spustíme program Pozdrav:
java -classpath . tomp.ucebnice.Pozdrav
Volba překladače -classpath adresář zajistí, že (dříve přeložené) třídy používané při spuštění této třídy budou přístupné pod adresářem adresář.
-classpath . tedy značí, že třídy (soubory
.class) se budou hledat v odpovídajících
podadresářích aktuálního adresáře (adresáře
.
)
Je-li program správně napsán a přeložen, vypíše se Ahoj!
Vytvoření a editace zdrojového kódu v editoru PSPad (dostupný zdarma, instalovaný na všech Win strojích v učebnách na FI)
Spuštění javového programu
= spuštění metody main jedné ze tříd tvořících program
Tato funkce může mít parametry:
podobně jako např. v Pascalu nebo v C
jsou typu String (řetězec)
předávají se při spuštění z příkazového řádku do pole
String[] args
Metoda main nevrací žádnou
hodnotu - návratový typ je vždy(!) void
Její hlavička musí vypadat vždy přesně tak, jako ve výše uvedeném příkladu, jinak nebude spuštěna!
Cesty ke spustitelným programům
(PATH) musejí obsahovat i adresář
JAVA_HOME\bin
Systémové proměnné by měly obsahovat:
JAVA_HOME=kořenový adresář instalace
Javy, např. JAVA_HOME=c:\jdk5.0
CLASSPATH=cesty ke třídám (podobně jako v
PATH
jsou cesty ke spustitelným souborům),
např. CLASSPATH=c:\devel\pb162
Odkazy na zdroje (učebnice)http://www.fi.muni.cz/~tomp/java/ucebnice/resources.html
Další tutoriály:http://www.mike-levin.com/learning-java/toc.html
Obsah
Pojmy: třída, objekt
Deklarace a definice tříd, jejich vlastnosti (proměnné, metody)
Vytváření objektů (deklarace sama objekt nevytvoří...), proměnné odkazující na objekt
Jmenné konvence - jak tvořit jména tříd, proměnných, metod
Použití objektů, volání metod, přístupy k proměnným
Modifikátory přístupu/viditelnosti (public, protected...)
Konstruktory (dotvoří/naplní prázdný objekt)
Přetěžování metod (dvě metody se stejným názvem a jinými parametry)
Třída (také poněkud nepřesně zvaná objektový typ) představuje skupinu objektů, které nesou stejné vlastnosti
"stejné" je myšleno kvalitativně, nikoli kvantitativně, tj.
Objekt je jeden konkrétní jedinec (reprezentant, entita) příslušné třídy
pro konkrétní objekt nabývají vlastnosti deklarované třídou konkrétních hodnot
Příklad:
Objekt panProfesor typu
Clovek
má vlastnost jmeno
s hodnotou "Václav Klaus"
.
Vlastnostmi objektů jsou:
proměnné
metody
Vlastnosti objektů - proměnné i metody - je třeba deklarovat.
viz Sun Java Tutorial / Trail: Learning the Java Language: Lesson: Classes and Inheritance
Proměnné
jsou nositeli "pasivních" vlastností; jakýchsi atributů, charakteristik objektů
de facto jde o datové hodnoty svázané (zapouzdřené) v objektu
Metody
jsou nositeli "výkonných" vlastností; "dovedností" objektů
de facto jde o funkce (procedury) pracující (převážně) nad proměnnými objektu
deklarujme třídu objektů - lidí
public class Clovek {
protected String jmeno;
protected int rokNarozeni;
public Clovek(String j, int rN) {
jmeno = j;
rokNarozeni = rN;
}
public void vypisInfo() {
System.out.println("Clovek:");
System.out.println("Jmeno="+jmeno);
System.out.println("Rok narozeni="+rokNarozeni);
}
}
Použijme ji v programu -
vytvořme instanci - objekt - typu
Clovek
vypišme informace o něm
Mějme deklarovánu třídu Clovek
Metoda main v následujícím programu:
vytvoří 2 lidi (pomocí new Clovek)
zavolá jejich metody
public class TestLidi {
public static void main(String[] args) {
Clovek ales = new Clovek("Ales Necas", 1966);
Clovek beata = new Clovek("Beata Novakova", 1970);
ales.vypisInfo();
beata.vypisInfo();
}
}
Tedy: vypíší se informace o obou vytvořených objektech - lidech.
Nyní podrobněji k proměnným objektů.
Ve výše uvedeném programu znamenalo na řádku:
Clovek ales = new Clovek("Ales Necas", 1966);
Clovek ales: pouze deklarace (tj. určení typu)
proměnné ales - bude typu
Clovek.
ales = new Clovek ("Ales Necas", 1966):
vytvoření objektu Clovek se jménem Ales
Necas.
Lze napsat zvlášť do dvou řádků nebo (tak jak jsme to udělali) na řádek jeden.
Každý příkaz i deklaraci ukončujeme středníkem.
Ve výše uvedených příkladech jsme objekty vytvářeli voláními
new Clovek(...) bezděčně jsme tak použili
operátor new, který vytvoří prázdný objekt typu Clovek a
volání konstruktoru, který prázdný objekt naplní počátečními údaji (daty).
Objekty:
jsou instance "své" třídy
vytváříme je operátorem new - voláním
konstruktoru
vytvořené objekty ukládáme do proměnné stejného typu (nebo typu předka či implementovaného rozhraní - o tom až později)
Položky jmeno a
rokNarozeni
v předchozím příkladu jsou proměnné objektu
Clovek
.
Jsou deklarovány v těle deklarace třídy Clovek.
Deklarace proměnné objektu má tvar:
např.:
Výše uvedená proměnná rokNarozeni měla datový
typ int (32bitové celé číslo). Tedy:
proměnná takového typu nese jednu hodnotu typu celé číslo (v rozsahu -2^31.. 2^31-1);
nese-li jednu hodnotu, pak se jedná o tzv. primitivní datový typ.
Kromě celých čísel int nabízí Java celou řadu dalších primitivních datových typů. Primitivní typy jsou dané napevno, programátor je jen používá, nedefinuje.
Tam, kde nestačí diskrétní hodnoty (tj. primitivní typy), musíme použít typy složené, objektové.
Objektovými typy v Javě jsou třídy (class) a rozhraní (interface). Třídy už jsme viděli v příkladu Clovek.
Existují třídy definované přímo v Javě, v knihovně Java Core API.
Nenajdeme-li tam třídu, kterou potřebujeme, můžeme si ji nadefinovat sami - viz Clovek.
Na jméno (identifikátor) proměnné sice Java neklade žádná speciální omezení (tedy mimo omezení platná pro jakýkoli identifikátor), ale přesto bývá velmi dobrým zvykem dodržovat při pojmenovávání následující pravidla (blíže viz podrobný rozbor na FIXME):
jména začínají malým písmenem
nepoužíváme diakritiku (problémy s editory, přenositelností a kódováním znaků)
(raději ani český jazyk, angličtině rozumí "každý")
je-li to složenina více slov, pak je nespojujeme podtržítkem, ale další začne velkým písmenem (tzv. "CamelCase")
např.:
je identifikátor se správně (vhodně) utvořeným jménem, zatímco:
není vhodný identifikátor proměnné (začíná velkým písmenem)
Dodržování těchto jmenných konvencí je základem psaní srozumitelných programů a bude vyžadováno, sledováno a hodnoceno v odevzdávaných úlohách i písemkách.
Proměnné objektu odkazujeme pomocí "tečkové notace":
public class TestLidi2 {
public static void main(String[] args) {
...
Clovek ales = new Clovek("Ales Necas", 1966); // vytvoření objektu ...
System.out.println(ales.jmeno); // přístup k (čtení) jeho proměnné ...
ales.jmeno = "Aleš Novák"; // modifikace (zápis do) jeho proměnné
}
}
Přístup k proměnným (i metodám) může být řízen uvedením tzv. modifikátorů před deklaraci prvku, viz výše:
// protected = přístup pouze z třídy ve stejném balíku nebo z podtřídy:
protected String jmeno;
Modifikátorů je více typů, nejběžnéjší jsou právě zmíněné modifikátory přístupu (přístupových práv)
Objektů (tzv. instancí) stejného typu (tj. stejné třídy) si můžeme postupně vytvořit více:
public class TestLidi3 {
public static void main(String[] args) {
...
Clovek ales = new Clovek("Ales Necas", 1966); // vytvoření prvniho objektu
Clovek petr = new Clovek("Petr Svoboda", 1968); // vytvoření druheho objektu ...
System.out.println(ales.jmeno); // přístup k (čtení) proměnné - prvnímu objektu
System.out.println(petr.jmeno); // přístup k (čtení) proměnné - druhému objektu
}
}
Existují tady dva objekty, každý má své (obecně různé) hodnoty proměnných - např. jsou různá jména obou lidí.
Nad existujícími (vytvořenými) objekty můžeme volat jejich metody. Metoda je:
podporgram (funkce, procedura), který primárně pracuje s proměnnými "mateřského" objektu,
může mít další parametry
může vracet hodnotu podobně jako v Pascalu funkce.
Každá metoda se musí ve své třídě deklarovat.
V Javě neexistují metody deklarované mimo třídy (tj. Java nezná žádné "globální" metody).
Výše uvedená třída Clovek měla metodu na výpis informací o daném objektu/člověku:
public class Clovek {
protected String jmeno;
protected int rokNarozeni;
public Clovek(String j, int rN) {
jmeno = j;
rokNarozeni = rN;
}
// Metoda vypisInfo() na výpis informací o člověku:
public void vypisInfo() {
System.out.println("Clovek:");
System.out.println("Jmeno="+jmeno);
System.out.println("Rok narozeni="+rokNarozeni);
}
}
Samotnou deklarací (napsáním kódu) metody se žádný kód neprovede.
Chceme-li vykonat kód metody, musíme ji zavolat.
Volání se realizuje (tak jako u proměnných) "tečkovou notací", viz dále.
Volání lze provést, jen je-li metoda z místa volání přístupná - "viditelná". Přístupnost regulují pdobně jako u proměnných modifikátory přístupu.
Vracíme se k prvnímu příkladu: vytvoříme dva lidi a zavoláme postupně jejich metodu
vypisInfo
.
public class TestLidi {
public static void main(String[] args) {
Clovek ales = new Clovek("Ales Necas", 1966);
Clovek beata = new Clovek("Beata Novakova", 1970);
ales.vypisInfo(); // volání metody objektu ales
beata.vypisInfo(); // volání metody objektu beata
}
}
Vytvoří se dva objekty Clovek a vypíší se informace o nich.
V deklaraci metody uvádíme v její hlavičce tzv. formální parametry.
Syntaxe:
modifikatorytypVraceneHodnoty
nazevMetody
(
seznamFormalnichParametru) {
tělo (výkonný kód) metody}
seznamFormalnichParametru je tvaru:
typParametru nazevFormalnihoParametru, ...
Podobně jako v jiných jazycích parametr představuje v rámci metody lokální proměnnou.
Při volání metody jsou f. p. nahrazeny skutečnými parametry.
Hodnoty primitivních typů - čísla, logické hodnoty, znaky
se předávají hodnotou, tj. hodnota se nakopíruje do lokální proměnné metody
Hodnoty objektových typů - všechny ostatní (tj. vč. všech uživatelem definovaných typů)
se předávají odkazem, tj. do lokální proměnné metody se nakopíruje odkaz na objekt - skutečný parametr
Pozn: ve skutečnosti se tedy parametry vždy předávají hodnotou, protože v případě objektových parametrů se předává hodnota odkazu na objekt - skutečný parametr.
V Javě tedy nemáme jako programátoři moc na výběr, jak parametry předávat
to je ale spíše výhoda!
Rozšiřme definici třídy
Clovek
o metodu
zakric
s parametry:
...
public void zakric(int kolikrat) {
System.out.println("Kricim " + kolikrat + "krat UAAAA!");
}
...
Při zavolání:
...
zakric(10);
...
tato metoda vypíše
Kricim 10krat UAAAA!
Následující třída Ucet modeluje jednoduchý bankovní účet s možnostmi:
přidávat na účet/odebírat z účtu
vypisovat zůstatek na něm
převádět na jiný účet
public class Ucet {
// stav (zustatek) penez uctu
protected double zustatek;
public void pridej(double castka) {
zustatek += castka;
}
public void vypisZustatek() {
System.out.println(zustatek);
}
public void prevedNa(Ucet kam, double castka) {
zustatek -= castka;
kam.pridej(castka);
}
}
Metoda prevedNa pracovat nejen se svým "mateřským" objektem, ale i s objektem kam předaným do metody... opět přes tečkovou notaci.
Příklad použití třídy
Ucet
:
...
public static void main(String[] args) {
Ucet petruvUcet = new Ucet();
Ucet ivanuvUcet = new Ucet();
petruvUcet.pridej(100);
ivanuvUcet.pridej(220);
petruvUcet.prevedNa(ivanuvUcet, 50);
petruvUcet.vypisZustatek();
ivanuvUcet.vypisZustatek();
}
Kód metody skončí, tj. předá řízení zpět volající metodě (nebo
systému - v případě startovní metody main),
jakmile
dokončí poslední příkaz v těle metody nebo
dospěje k příkazu return
Metoda může při návratu vrátit hodnotu - tj. chovat se jako funkce (ve pascalském smyslu):
Pozn.: I když metoda něco vrátí, my to nemusíme použít, ale je to trochu divné...
Co a k čemu jsou konstruktory?
Konstruktury jsou speciální metody volané při vytváření nových instancí dané třídy.
Typicky se v konstruktoru naplní (inicializují) proměnné objektu.
Konstruktory lze volat jen ve spojení s operátorem
new k vytvoření nové instance třídy - nového
objektu, evt. volat z jiného konstruktoru
Syntaxe (viz výše):
public class Clovek {
protected String jmeno;
protected int rokNarozeni;
// konstruktor se dvěma parametry
// - inicializuje hodnoty proměnných ve vytvořeném objektu
public Clovek(String j, int rN) {
jmeno = j;
rokNarozeni = rn;
}
...
}
Příklad využití tohoto konstruktoru:
...
Clovek pepa = new Clovek("Pepa z Hongkongu", 1899);
...
Toto volání vytvoří objekt pepa a naplní ho jménem a rokem narození.
Jak je psát a co s nimi lze dělat?
nemají návratový typ (ani void -
to už vůbec ne!!!)
mohou mít parametry
mohou volat konstruktor rodičovské třídy - ale jen jako svůj první příkaz
Jedna třída může mít:
Více metod se stejnými názvy, ale různými parametry.
Pak hovoříme o tzv. přetížené (overloaded) metodě.
Nelze přetížit metodu pouze změnou typu návratové hodnoty.
Ve třídě Ucet přetížíme metodu
prevedNa
.
Přetížená metoda převede na účet příjemce celý zůstatek z účtu odesílatele:
public void prevedNa(Ucet u) {
u.pridej(zustatek);
zustatek = 0;
}
Ve třídě
Ucet
koexistují dvě různé metody se stejným názvem, ale jinými parametry.
Pozn: I když jsou to teoreticky dvě úplně různé metody, pak když už se jmenují stejně, měly by dělat něco podobného.
Je ale otázka, zdali převod celého zůstatku raději nenapsat jako nepřetíženou, samostatnou metodu, např.:
public void prevedVseNa(Ucet u) {
prevedNa(u, zustatek);
}
Je to o něco instruktivnější, ale přibude další identifikátor - název metody - k zapamatování.
Což může být výhoda (je to výstižné) i nevýhoda (musíme si pamatovat další).
Deklarace proměnné objektového typu ještě žádný objekt nevytváří.
To se děje až příkazem - operátorem -
new.
V následující ukázce vytvoříme dva účty.
Odkazy na ně budou primárně v proměnných petruvUcet a ivanuvUcet.
V proměnné u nebude primárně odkaz na žádný účet.
Pak do ní přiřadíme (u = petruvUcet;) odkaz
na objekt skrývající se pod odkazem
petruvUcet.
Od této chvíle můžeme s účtem petruvUcet manipulovat přes odkaz (proměnnou) u.
Což se také děje: u.prevedNa(ivanuvUcet,
50);
...
public static void main(String[] args) {
Ucet petruvUcet = new Ucet();
Ucet ivanuvUcet = new Ucet();
Ucet u;
petruvUcet.pridej(100);
ivanuvUcet.pridej(220);
u = petruvUcet;
u.prevedNa(ivanuvUcet, 50); // odečte se z Petrova účtu
petruvUcet.vypisZustatek(); // vypíše 50
ivanuvUcet.vypisZustatek();
}
Metoda může vracet odkaz na objekt, nad nímž je volána pomocí
Příklad - upravený Ucet s metodou
prevedNa
vracející odkaz na sebe
public class Ucet {
float zustatek;
public void pridej(float castka) {
zustatek += castka;
}
public void vypisZustatek() {
System.out.println(zustatek);
}
public Ucet prevedNa(Ucet u, float castka) {
zustatek -= castka; // nebo také vhodné je: pridej(-castka);
u.pridej(castka);
return this;
}
}
Vracení odkazu na sebe (tj. na objekt, na němž se metoda volala) lze s výhodou využít k "řetězení" volání:
...
public static void main(String[] args) {
Ucet petruvUcet = new Ucet();
Ucet ivanuvUcet = new Ucet();
Ucet igoruvUcet = new Ucet();
petruvUcet.pridej(100);
ivanuvUcet.pridej(100);
igoruvUcet.pridej(100);
// budeme řetězit volání:
petruvUcet.prevedNa(ivanuvUcet, 50).prevedNa(igoruvUcet, 20);
petruvUcet.vypisZustatek(); // vypíše 30
ivanuvUcet.vypisZustatek(); // vypíše 150
igoruvUcet.vypisZustatek(); // vypíše 120
}
Dosud jsme zmiňovali proměnné a metody (tj. souhrnně prvky - members) objektu.
Lze deklarovat také metody a proměnné patřící celé
třídě, tj. skupině všech objektů daného typu. Takové metody a
proměnné nazýváme statické a označujeme
v deklaraci modifikátorem static
Představme si, že si budeme pamatovat, kolik lidí se nám během chodu programu vytvořilo a vypisovat tento počet.
Budeme tedy potřebovat do třídy Clovek doplnit:
jednu proměnnou pocetLidi společnou pro celou třídu Clovek - každý člověk ji při svém vzniku zvýší o jedna.
jednu metodu kolikMamLidi, která vrátí počet dosud vytvořených lidí.
public class Clovek {
protected String jmeno;
protected int rokNarozeni;
protected static int pocetLidi = 0;
public Clovek(String j, int rN) {
jmeno = j;
rokNarozeni = rn;
pocetLidi++;
}
...
public static int kolikMamLidi() {
return pocetLidi;
}
...
}
Pozn: Všimněte si v obou případech modifikátoru/klíčového slova static.
Obsah
Generický (univerzální, obecný...) model postupu:
Zadání problému
Shromáždění informací o realitě a jejich analýza
Modelování reality na počítači a implementace požadovaných operací nad modelovanou realitou
(podle JS, SW inženýrství):
při němž jsou uživatelovy potřeby
transformovány na požadavky na software,
tyto jsou transformovány na návrh,
návrh je implementován pomocí kódu,
kód je testován, dokumentován a certifikován pro operační použití.
V tomto předmětu nás z toho bude zajímat jen něco a jen částečně:
Specifikace (tj. zadání a jeho formalizace)
Vývoj (tj. návrh a vlastní programování)
částečně Validace (z ní především testování)
Specifikace SW: Je třeba definovat funkcionalitu SW a operační omezení.
Vývoj SW: Je třeba vytvořit SW, který splňuje požadavky kladené ve specifikaci.
Validace SW: SW musí být validován („kolaudován“), aby bylo potvrzeno, že řeší právě to, co požaduje uživatel.
Evoluce SW: SW musí být dále rozvíjen, aby vyhověl měnícím se požadavkům zákazníka.
Tyto generické modely jsou dále rozpracovávány do podoby konkrétních metodik.
Metodika (tvorby SW) je ucelený soubor inženýrských postupů, jak řízeným způsobem, s odhadnutelnou spotřebou zdrojů dospět k použitelnému SW produktu.
Některé skupiny metodik:
strukturovaná
objektová
...
Nevracím se nikdy o více jak jednu úroveň zpět:
Analýza (Analysis)
Návrh (Design)
Implementace (Implementation)
Testování (Testing)
Nasazení (Deployment)
Nyní zpět k Javě a jednoduchým programům...
Co bude odlišné oproti dosavadním programátorským zkušenostem?
Struktura a rozsah programu:
program měl jeden nebo více zdrojových souborů (soubor = modul) tvořenými jednotlivými procedurami/fcemi, definicemi a deklaracemi (typů, proměnných...)
(a některé další OO jazyky): program je obvykle tvořen více soubory (soubor = popis jedné třídy) tvořenými deklaracemi metod a proměnných (případně dalších prvků) těchto tříd.
v Pascalu nebyla (nutná)
v Javě je nezbytná - zdrojové soubory organizujeme podle toho, ve kterých balících jsou třídy zařazeny
(přeložené soubory se implicitně ukládájí vedle zdrojových)
Zkusme naznačit, jak bychom realizovali jednoduchý systém, který bude
shromažďovat, ukládat a na požádání zpřístupňovat informace o psech (+ jejich výcviku, očkování...)
o jejich chovatelích
a dalších souvisejících entitách (např. chovatelských sdruženích, veterinářích,...)
Zjistíme, jaké typy objektů se ve zkoumaném výseku reality vyskytují a které potřebujeme
člověk, pes, veterinář
Zjistíme a zachytíme vztahy mezi objekty našeho zájmu
člověk-chovatel vlastní psa
Zjistíme, které činnosti objekty (aktéři, aktoři) provádějí
veterinář psa očkuje, pes štěká, kousne člověka...
Jak zachytíme tyto informace:
neformálními prostředky - tužkou na papíře vlastními slovy v přirozeném jazyce
formálně pomocí nějakého vyjadřovacího aparátu - např. grafického jazyka
pomocí CASE nástroje přímo na počítači
Zatím se přidržíme neformálního způsobu...
V Javě, na rozdíl od C++ neexistuje vícenásobná dědičnost -
to nám ušetří řadu komplikací
ale je třeba to něčím nahradit
Pokud po třídě chceme, aby disponovala vlastnostmi z několika různých množin (skupin), můžeme ji deklarovat tak, že
implementuje více rozhraní
Rozhraní je vlastně popis (specifikace) množiny vlastností, aniž bychom tyto vlastnosti ihned implementovali Vlastnostmi zde rozumíme především metody.
Říkáme, že určitá třída implementuje rozhraní, pokud implementuje (tedy má - přímo sama nebo podědí) všechny vlastnosti (tj. metody), které jsou daným rozhraním předepsány.
Javové rozhraní je tedy množina hlaviček metod označená identifikátorem - názvem rozhraní. (a celých specifikací - tj. popisem, co přesně má metoda dělat - vstupy/výstupy metody, její vedlešjí efekty...)
Vypadá i umisťuje se do souborů podobně jako deklarace třídy
Všechny metody v rozhraní musí být public
a v hlavičce se to ani nemusí uvádět.
Těla metod v deklaraci rozhraní se nepíší. (Metody v rozhraní tudíž vypadají velmi podobně jako abstraktní metody ve třídách, ale nemusím psát abstract.)
Příklad deklarace rozhraní
public interface Informujici {
void vypisInfo();
}
Příklad
public class Clovek implements Informujici {
...
public void vypisInfo() {
...
}
}
Čteme: Třída Clovek implementuje rozhraní
Informujici
.
Třída v hlavičce uvede implements NázevRozhraní
Třída implementuje všechny metody předepsané rozhraním
Potřebujeme-li u jisté proměnné právě jen funkcionalitu popsanou určitým rozhraním,
tuto proměnnou můžeme pak deklarovat jako typu rozhraní - ne přímo objektu, který rozhraní implementuje.
Příklad
Informujici petr = new Clovek("Petr Novák", 1945);
petr.vypisInfo(); // "petr" stačí deklarovat jen jako Informujici
// jiné metody než předepsané tímto intf.
// nepotřebujeme!
V realitě jsme často svědci toho, že třídy jsou podtřídami jiných:
tj. všechny objekty podtřídy jsou zároveň objekty nadtřídy,
např. každý objekt typu (třídy) ChovatelPsu je
současně typu Clovek
nebo
např. každý objekt typu (třídy) Pes je
současně typu DomaciZvire
(alespoň v našem
výseku reality - existují i psi "nedomácí"...)
Podtřída je tedy "zjemněním" nadtřídy:
přebírá její vlastnosti a zpravidla přidává další, rozšiřuje svou nadtřídu/předka
V Javě je každá uživatelem
definovaná třída potomkem nějaké jiné - neuvedeme-li předka explicitně,
je předkem vestavěná třída Object
Terminologie:
Nadtřídě (superclass) se také říká "(bezprostřední) předek", "rodičovská třída"
Podtřídě (subclass) se také říká "(bezprostřední) potomek", "dceřinná třída"
Dědění může mít i více "generací", např.
Zivocich <-
Clovek
<- Chovatel
(živočich je rodičovskou třídou člověka, ten je rodičovskou třídou
chovatele)
Přeneseně tedy předkem (nikoli bezprostředním) chovatele je živočich.
Klíčovým slovem extends :
public class Clovek extends Zivocich {
... popis vlastností (proměnných, metod...) člověka navíc oproti živočichovi ...
}
Jak víme, třídy popisují skupiny objektů podobných vlastností
Třídy mohou mít tyto skupiny vlastností:
Metody - procedury/funkce, které pracují (především) s objekty této třídy
Proměnné - pojmenované datové prvky (hodnoty) uchovávané v každém objektu této třídy
Vlastnosti jsou ve třídě "schované", tzv. zapouzdřené (encapsulated)
Třída připomíná pascalský záznam (record), ten však zapouzdřuje jen proměnné, nikoli metody.
Dědičnost (alespoň v javovém smyslu) znamená, že dceřinná třída (podtřída, potomek)
má všechny vlastnosti (metody, proměnné) nadtřídy
+ vlastnosti uvedené přímo v deklaraci podtřídy
Cíl: vylepšit třídu Ucet
Postup:
Zdokonalíme náš příklad s účtem tak, aby si účet "hlídal", kolik se z něj převádí peněz
Zdokonalenou verzi třídy Ucetnazveme KontokorentniUcet
Příklad 3.1. Příklad kompletního zdrojového kódu třídy
public class KontokorentniUcet extends Ucet {
// double zustatek; znovu neuvádíme
// ... zdědí se z nadtřídy/předka "Ucet"
// kolik mohu "jít do mínusu"
double povolenyKontokorent;
public void pridej(double castka) {
if (zustatek + povolenyKontokorent + castka >= 0) {
// zavoláme původní "neopatrnou" metodu
super.pridej(castka);
} else {
System.err.println("Nelze odebrat částku " + (-castka));
}
}
// public void vypisZustatek() ... zdědí se
// public void prevedNa(Ucet u, double castka) ... zdědí se
// ... předpokládejme, že v třídě "Ucet" používáme variantu:
// pridej(-castka);
// u.pridej(castka);
// } }
Vzorový zdroják sám o sobě nepůjde přeložit, protože nemáme třídu, na níž závisí. Celý kód vystavím až po kontrole příslušných úloh.
Klíčové slovo extends -
značí, že třída KontokorentniUcet
je
potomkem/podtřídou/rozšířením/dceřinnou třídou
(subclass) třídy
Ucet
.
Konstrukce
super.metoda(...); značí, že je volána metoda
rodičovské třídy/předka/nadtřídy (superclass).
Kdyby se nevolala překrytá metoda,
super
by se neuvádělo.
Větvení if() {...} else
{...} - složené závorky se používají k uzavření příkazů
do sekvence - ve smyslu pascalského begin/end.
Přístup ke třídám i jejim prvkům lze (podobně jako např. v C++) regulovat:
Přístupem se rozumí jakékoli použití dané třídy, prvku...
Omezení přístupu je kontrolováno hned při překladu -> není-li přístup povolen, nelze program ani přeložit.
Tímto způsobem lze regulovat přístup staticky, mezi celými třídami, nikoli pro jednotlivé objekty
Jiný způsob zabezpečení představuje tzv. security manager, který lze aktivovat při spuštění JVM.
Přístup je v Javě regulován jednotlivě po prvcích
Omezení přístupu je určeno uvedením jednoho z tzv. modifikátoru přístupu (access modifier) nebo naopak neuvedením žádného.
Pro třídy:
veřejné - public
neveřejné - lokální v balíku
Pro vlastnosti tříd = proměnné/metody:
veřejné - public
chráněné - protected
neveřejné - lokální v balíku
soukromé - private
protected => přístupné jen z
podtříd a ze tříd stejného balíku
public class Ucet {
// chráněná proměnná
protected float povolenyKontokorent;
}
používá se jak pro metody (velmi často), tak pro proměnné (méně často)
lokální v balíku = přátelský=> přístupné jenze tříd stejného balíku,už ale ne z podtříd, jsou-li v jiném balíku
public class Ucet {
Date created; // přátelská proměnná
}
používá se spíše u proměnných než metod, ale dost často se
vyskytuje z lenosti programátora, kterému se nechce psát
protected
osobně moc nedoporučuji, protože svazuje přístupová práva s
organizací do balíků (-> a ta se může přece jen měnit častěji než
např. vztah nadtřída-podtřída.)
Mohlo by mít význam, je-li práce rozdělena na více lidí na jednom balíku pracuje jen jeden člověk - pak si může přátelským přístupem chránit své neveřejné prvky/třídy -> nesmí ovšem nikdo jiný chtít mé třídy rozšiřovat a používat přitom přátelské prvky.
Používá se relativně často pro neveřejné třídy definované v jednom zdrojovém souboru se třídou veřejnou.
private => přístupné jen v rámci třídy,
ani v podtřídách - používá se častěji pro proměnné než metody
označením private prvekzneviditelníme i případným podtřídám!
public class Ucet {
private String majitel;
...
}
proměnná majitel je soukromá = nelze k ní
přímo přistoupit ani v podtřídě - je tedy třeba zpřístupnit
proměnnou pro "vnější" potřeby jinak, např.
přístupovými metodami setMajitel(String
m) a String getMajitel()
Nastavení přístupových práv k třídě pomocí modifikátorů se děje na úrovni tříd, tj. vztahuje se pak na všechny objekty příslušné třídy i na její statické vlastnosti (proměnné, metody) atd.
Nastavení musí vycházet z povahy dotyčné proměnné či metody.
Nevíme-li si rady, jaká práva přidělit, řídíme se následujícím:
metoda by měla být public, je-li užitečná
i mimo třídu či balík - "navenek"
jinak protected
máme-li záruku, že metoda bude v případných podtřídách
nepotřebná, může být private - ale
kdy tu záruku máme???
proměnná by měla být private, nebo
protected
, je-li potřeba přímý přístup v
podtřídě
téměř nikdy bychom neměli deklarovat proměnné jako
public!
Třídy deklarované jako veřejné
(public) musí být umístěné do souborů s názvem
totožným s názvem třídy (+přípona .java
) i na
systémech Windows (vč. velikosti písmen)
kromě takové třídy však může být v tomtéž souboru i libovolný počet deklarací neveřejných tříd
private nemají význam, ale
přátelské ano
Soubor Zivocich.java bude obsahovat (pozor na
velká/malá písmena - v obsahu i názvu souboru):
public class Zivocich {
... popis vlastností (proměnných, metod...) živočicha ...
}
public značí, že třída je "veřejně"
použitelná, tj. i mimo balík
Třídy zorganizujeme do balíků.
V balíku jsou vždy umístěny související třídy.
Co znamená související?
třídy, jejichž objekty spolupracují - člověk, úřad
třídy na podobné úrovni abstrakce - chovatel, domácí zvíře
třídy ze stejné části reality - chovatel psů, pes
Balíky obvykle organizujeme do hierarchií, např.:
Neplatí však, že by
třídy "dceřinného" balíku (např.
svet.chovatelstvi.psi)
byly zároveň třídami balíku "rodičovského"
(svet.chovatelstvi)!!!
Hierarchie balíků má tedy význam spíše pro srozumitelnost a logické členění.
Deklarujeme ji syntaxí: package
názevbalíku;
Uvádíme obvykle jako první deklaraci v zdrojovém souboru;
Příslušnost k balíku musíme současně potvrdit správným umístěním zdrojového souboru do adresářové struktury;
např. zdrojový soubor třídy Pes umístíme
do podadresáře svet\chovatelstvi\psi
Neuvedeme-li příslušnost k balíku, stane se třída součástí implicitního balíku - to však nelze pro jakékoli větší a/nebo znovupoužívané třídy či dokonce programy doporučit a zde nebude tolerováno!
Deklarace import nesouvisí s děděním, ale s organizací tříd programu do balíků:
Umožní odkazovat se v rámci kódu jedné třídy na ostatní třídy
Syntaxe: import názevtřídy;
kde názevtřídy je uveden včetně názvu balíku
Píšeme obvykle ihned po deklaraci příslušnosti k balíku
(package názevbalíku;)
Import není nutné deklarovat mezi třídami téhož balíku!
Pak lze používat všechny třídy z uvedeného balíku
Doporučuje se "import s hvězdičkou" nepoužívat:
jinak nevíme nikdy s jistotou, ze kterého balíku se daná třída použila;
i profesionálové to však někdy používají :-)
lze tolerovat tam, kde používáme z určitého balíku většinu tříd;
v tomto úvodním kurzu většinou tolerovat nebudeme!
"Hvězdičkou" nezpřístupníme třídy z podbalíků, např.
import svet.* nezpřístupní třídu
svet.chovatelstvi.Chovatel
Primitivní vs. objektové typy
Kategorie primitivních typů: integrální, boolean, čísla s pohyblivou řádovou čárkou
Pole: deklarace, vytvoření, naplnění, přístup k prvkům, rozsah indexů
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
takto:
public class Cislo {
private float hodnota;
public Cislo(float h) {
hodnota = h;
}
public void zvysO(float kolik) {
hodnota += kolik;
}
public void vypis() {
System.out.println(hodnota);
}
}
nyní ji použijeme:
Cislo c1 = new Cislo(1.23456);
Cislo c2 = c1;
c1.zvysO(2);
c1.vypis();
c2.vypis();
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é
V Javě existují tyto skupiny primitivních typů:
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
menší rozsah mají
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ř. Minmální
a maximální hodnoty
char představuje jeden 16bitový znak v
kódování UNICODE
Konstanty typu char zapisujeme
v apostrofech - 'a', 'Ř'
pomocí escape-sekvencí - \n (konec řádku) \t (tabulátor)
hexadecimálně - \u0040 (totéž, co 'a')
oktalově - \127
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 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" 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!, s větší povolenou přesností a rozsahem
Kladné a záporné nekonečno:
Float.POSITIVE_INFINITY , totéž s
NEGATIVE
...
totéž pro Double
obdobně existují pro oba typy konstanty uvádějící rozlišení
daného typu - MIN_VALUE , podobně s
MAX
...
Konstanta NaN - Not A
Number
Viz také Minimální a maximální hodnoty
Význam podobný jako v C/C++.
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
pole primitivních hodnot tyto hodnoty obsahuje
pole objektů obsahuje odkazy na objekty
Kromě pole v Javě existují i jiné objekty na ukládání více hodnot - tzn. kontejnery, viz dále
Syntaxe deklarace
![]() | 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
jménopole = new typhodnoty[] {prvek1, prvek2, ...};
Pole je objekt, je třeba ho před použitím nejen deklarovat, ale i vytvořit:
Clovek[] lidi;
lidi = new Clovek[5];
Nyní můžeme pole naplnit:
lidi[0] = new Clovek("Václav Klaus", Clovek.MUZ);
lidi[1] = new Clovek("Libuše Benešová", Clovek.ZENA);
lidi[0].vypisInfo(); lidi[1].vypisInfo();
Co kdybychom pole pouze deklarovali a nevytvořili:
Clovek[] lidi;
lidi[0] = new Clovek("Václav Klaus", Clovek.MUZ);
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";
}
}
překladač nás varuje:
Pokus.java:4: variable pole might not have been
initialized pole[0] = "Neco"; ^ 1 error
Pokud ovšem
bude proměnnou objektu/třídy:
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:
Clovek[] lidi;
lidi = new Clovek[5];
lidi[0].vypisInfo();
Toto by skončilo také s běhovou chybou NullPointerException:
pole existuje, má pět prvků, ale první z nich je prázdný, nelze tudíž volat jeho metody (resp. vůbec používat jeho vlastnosti)!
V Javě obecně přiřazení proměnné objektového typu vede pouze k duplikaci odkazu, nikoli celého odkazovaného objektu.
Nejinak je tomu u polí, tj.:
Clovek[] 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.
Clovek[] lidi2 = new Clovek[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 |
Také arraycopy však do cílového pole zduplikuje jen odkazy na objekty, nevytvoří kopie objektů!
Operátory v Javě: aritmetické, logické, relační, bitové
Ternární operátor podmíněného výrazu
Typové konverze
Operátor zřetězení
logické součiny (AND):
logické součty (OR):
negace (NOT):
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ů:
Upozornění:
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-points čí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
Platí-li první operand (má hodnotu true)
->
výsledkem je hodnota druhého operandu
jinak je výsledkem hodnota třetího operandu
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ř. (Clovek)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í, že běhový typ objektu je požadovaného typu - např. (viz výše) že o je typu Clovek.
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("promenna
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)
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ů:
Upozornění:
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-points čí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
Použití ==
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 Clovek nese informace o člověku. Dva objekty položíme stejné (rovnocenné), nesou-li stejná příjmení:
Obrázek 4.1. Dva lidi jsou stejní, mají-li stejná příjmení
public class Clovek implements Comparable {
String jmeno, prijmeni;
public Clovek (String j, String p) {
jmeno = j;
prijmeni = p;
}
public boolean equals(Object o) {
if (o instanceof Clovek) {
Clovek c = (Clovek)o;
return prijmeni.equals(c.prijmeni);
} else
throw new IllegalArgumentException(
"Nelze porovnat objekt typu Clovek s objektem jineho typu");
}
}
Méně agresivní verze by nemusela při porovnávání s jiným objektem než Clovek vyhodit výjimku, pouze vrátit 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 4.2. Třída Clovek s metodami equals a hashCode
public class Clovek implements Comparable {
String jmeno, prijmeni;
public Clovek (String j, String p) {
jmeno = j;
prijmeni = p;
}
public boolean equals(Object o) {
if (o instanceof Clovek) {
Clovek c = (Clovek)o;
return prijmeni.equals(c.prijmeni);
} else
throw new IllegalArgumentException(
"Nelze porovnat objekt typu Clovek s objektem jineho typu");
}
public int hashCode() {
return prijmeni.hashCode();
}
}
Obsah
V Javě máme následující příkazy:
Přiřazovací příkaz = a jeho modifikace (kombinované operátory jako je += apod.)
Řízení toku programu (větvení, cykly)
Volání metody
Návrat z metody - příkaz return
Příkaz je ukončen středníkem ;
v Pascalu středník příkazy odděluje, v Javě (C/C++) ukončuje
Operátor přiřazení:
Operátor přiřazení = (assignment)
na levé straně musí být proměnná
na pravé straně výraz přiřaditelný (assignable) do této proměnné
Rozlišujeme přiřazení primitivních hodnot a odkazů na objekty
Na pravé straně výraz vracející hodnotu primitivního typu
číslo, logická hodnotu, znak (ale ne např. řetězec)
Na levé straně proměnná téhož typu jako přiřazovaná hodnota nebo typu širšího
např. int lze přiřadit do
long
Při zužujícím přiřazení se také provede konverze, ale může dojít ke ztrátě informace
Přiřazením primitivní hodnoty se hodnota zduplikuje ("opíše") do proměnné na levé straně.
Konstrukci = lze použít i pro přiřazení do
objektové proměnné:
Co to udělalo?
vytvořilo nový objekt typu Zivocich
( new Zivocich()
)
Nyní můžeme odkaz na tentýž vytvořený objekt
znovu přiřadit - do z2:
Proměnné z1 a z2
ukazují
nyní na stejný objekt typu živočich!!!
Proměnné objektového typu obsahují odkazy (reference) na objekty, ne objekty samotné!!!
Metoda objektu je vlastně procedura/funkce, která realizuje svou činnost primárně s proměnnými objektu.
Volání metody určitého objektu realizujeme:
identifikaceObjektu.názevMetody(skutečné parametry)
identifikaceObjektu, jehož metodu voláme
. (tečka)
názevMetody, jíž nad daným objektem voláme
v závorách uvedeme skutečné parametry volání (záv. může být prázdná, nejsou-li parametry)
Návrat z metody se děje:
Buďto automaticky posledním příkazem v těle metody
nebo explicitně příkazem return
návratováhodnota
způsobí ukončení provádění těla metody a návrat, přičemž může být specifikována návratová hodnota
typ skutečné návratové hodnoty musí korespondovat s deklarovaným typem návratové hodnoty
Příkaz (neúplného) větvení if
platí-li logický výraz (má hodnoty true), provede se příkaz
Příkaz úplného větvení if -
else
if (logický výraz)
příkaz1
else
příkaz2
platí-li logický výraz (má
hodnoty true
), provede se příkaz1
neplatí-li, provede se příkaz2
Větev else se nemusí
uvádět
Tělo cyklu se provádí tak dlouho, dokud platí podmínka
obdoba while v Pascalu
v těle cyklu je jeden jednoduchý příkaz ...
while (podmínka)
příkaz;
... nebo příkaz složený
while (podmínka) {
příkaz1;
příkaz2;
příkaz3;
...
}
Tělo cyklu se nemusí provést ani jednou - pokud už hned na začátku podmínka neplatí
Větvení, cykly: doporučuji vždy psát se složeným příkazem v těle (tj. se složenými závorkami)!!!
jinak hrozí, že se v těle větvení/cyklu z neopatrnosti při editaci objeví něco jiného, než chceme, např.:
while (i < a.length)
System.out.println(a[i]); i++;
Provede v cyklu jen ten výpis, inkrementaci ne a program se zacyklí.
Pišme proto vždy takto:
while (i < a.length) {
System.out.println(a[i]); i++;
}
Dokud nejsou přečteny všechny vstupní argumenty:
int i = 0;
while (i < args.length) {
"přečti argument args[i]"
i++;
}
Dalším příkladem je použití while pro realizaci
celočíselného dělení se zbytkem:
Tělo se provádí dokud platí podmínka (vždy aspoň jednou)
obdoba repeat v Pascalu (podmínka
je ovšem interpretována opačně)
Relativně málo používaný - je méně přehledný než
while
Syntaxe:
do {
příkaz1;
příkaz2;
příkaz3;
...
} while (podmínka);
Dokud není z klávesnice načtena požadovaná hodnota:
String vstup = "";
float cislo;
boolean nacteno; // vytvoř reader ze standardního vstupu
BufferedReader in = new BufferReader(new InputStream(System.in));
// dokud není zadáno číslo, čti
do {
vstup = in.readLine();
try {
cislo = Float.parseFloat(vstup);
nacteno = true;
} catch (NumberFormatException nfe) {
nacteno = false;
}
} while(!nacteno);
System.out.println("Nacteno cislo "+cislo);
obecnější než for v Pascalu, podobně jako v
C/C++
De-facto jde o rozšíření while, lze jím snadno
nahradit
Syntaxe:
for(počáteční op.; vstupní podm.; příkaz po každém průch.)
příkaz;
anebo (obvyklejší, bezpečnější)
for (počáteční op.; vstupní podm.; příkaz po každém průch.) {
příkaz1;
příkaz2;
příkaz3;
...
}
Provedení určité sekvence určitý počet krát
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
Používejte asymetrické intervaly (ostrá a neostrá nerovnost):
Vytvarujte se složitých příkazů v hlavičce (kulatých
závorkách) for cyklu -
je lepší to napsat podle situace před cyklus nebo až do jeho těla
Někteří autoři nedoporučují psát deklaraci řídicí proměnné přímo do závorek cyklu
ale rozepsat takto:
int i;
for (i = 0; ...
potom je proměnná i přístupná
("viditelná") i mimo cyklus - za cyklem, což se však ne vždy
hodí.
Obdoba pascalského select - case - else
Větvení do více možností na základě ordinální hodnoty
Syntaxe:
switch(výraz) {
case hodnota1: prikaz1a;
prikaz1b;
prikaz1c;
...
break;
case hodnota2: prikaz2a;
prikaz2b;
...
break;
default: prikazDa;
prikazDb;
...
}
Je-li výraz roven některé z
hodnot, provede se sekvence uvedená za příslušným
case.
Sekvenci obvykle ukončujeme příkazem break,
který předá řízení ("skočí") na první příkaz za ukončovací závorkou
příkazu switch
.
Větvení if - else můžeme samozřejmě vnořovat do
sebe:
if(podmínka_vnější) {
if(podmínka_vnitřní_1) {
...
} else {
...
}
} else {
if(podmínka_vnitřní_2) {
...
} else {
...
}
}
Je možné "šetřit" a neuvádět složené závorky, v takovém případě se
else vztahuje vždy k nejbližšímu neuzavřenému
if
, např. znovu předchozí příklad:
if(podmínka_vnější)
if(podmínka_vnitřní1)
...
else // vztahuje se k nejbližšímu if
// s if (podmínka_vnitřní_1)
...
else // vztahuje se k prvnímu if,
// protože je v tuto chvíli
// nejbližší neuzavřené
if (podmínka_vnitřní_2)
...
else // vztahuje se k if (podmínka_vnitřní_2)
...
Tak jako u cyklů - tento způsob zápisu nelze v žádném případě doporučit!!!
Někdy rozvíjíme pouze druhou (negativní) větev:
if (podmínka1) {
...
} else if (podmínka2) {
...
} else if (podmínka3) {
...
} else {
...
}
Neplatí-li podmínka1, testuje se podmínka2, neplatí-li, pak podmínka3...
neplatí-li žádná, provede se příkaz za posledním -
samostatným - else.
Opět je dobré všude psát složené závorky!!!
Realizuje "násilné" ukončení průchodu cyklem nebo větvením
switch
Syntaxe použití break v cyklu:
for (int i = 0; i < a.length; i++) {
if(a[i] == 0) {
break; // skoci se za konec cyklu
}
}
if (a[i] == 0) {
System.out.println("Nasli jsme 0 na pozici "+i);
} else {
System.out.println("0 v poli neni");
}
použití u switch jsme již
viděli„Vícecestné větvení "switch - case - default"“
Používá se v těle cyklu.
Způsobí přeskočení zbylé části průchodu tělem cyklu
for (int i = 0; i < a.length; i++) {
if (a[i] == 5)
continue;
System.out.println(i);
}
Výše uvedený příklad vypíše čísla
1, 2
, 3
,
4
, 6
, 7
,
8
, 9
, nevypíše hodnotu
5
.
Umožní ještě jemnější řízení průchodu vnořenými cykly:
Obsah
Ladění programů s debuggerem jdb
Nástroje ověřování podmínek za běhu - klíčové slovo assert
Nástroje testování jednotek (tříd, balíků) - junit
Pokročilé systémy dynamického ověřování podmínek - jass
Je mnoho způsobů...
kontrolní tisky -
System.err.println(...)
řádkovým debuggerem jdb
integrovaným debuggerem v IDE
pomocí speciálních nástrojů na záznam běhu pg.:
nejrůznější "loggery" - standardní poskytuje od JDK1.4 balík
java.util.logging nebo alternativní a
zdařilejší log4j
je používat systémy pro běhovou kontrolu platnosti podmínek:
vstupní podmínka metody (zda je volána s přípustnými parametry)
výstupní podmínka metody (zda jsou dosažené výstupy správné)
a podmínka kdekoli jinde - např. invariant cyklu...
K tomuto slouží jednak
standardní klíčové slovo (od JDK1.4) assert
booleovský výraz
testovací nástroje typu JUnit (a
varianty - HttpUnit
,...) - s metodami
assertEquals()
apod.
pokročilé nástroje na běhovou kontrolu platnosti
invariantů, vstupních, výstupních a dalších podmínek - např.
jass (Java with ASSertions), http://csd.informatik.uni-oldenburg.de/~jass/.
Ukázka testu jednotky:Test
třídy ChovatelPsu
Postup:
Napsat zdrojový program užívající klíčové slovo
assert (pouze od verze Java2 v1.4 výše).
Nepotřebujeme žádné speciální běhové knihovny, vše je součástí Javy;
musíme ovšem mít překladové i běhové prostředí v1.4 a vyšší.
Přeložit jej s volbou -source 1.4
Spustit jej s volbou -ea (-enableassertions).
Aktivovat aserce lze i selektivně pro některé třídy (-ea název_třídy nebo -ea název_balíku... - tři tečky na konci!!!).
Dojde-li za běhu programu k porušení podmínky stanovené za assert, vznikne běhová chyba (AssertionError) a program skončí.
Třída Zlomek používá assert k ověření, že zlomek není (chybou uživatele) vytvářen s nulovým jmenovatelem.
Za assert uvedeme, co musí v daném místě za
běhu programu platit.
Program přeložíme (s volbou -source 1.4):
Program spustíme (s volbou -ea nebo selektivním -ea:NázevTřídy):
Uvědomit si, že žádný nástroj za nás nevymyslí, JAK máme své třídy testovat. Pouze nám napomůže ke snadnějšímu sestavení a spuštění testu.
Postup:
Stáhnout si z http://junit.org poslední (stačí binární) distribuci testovacího prostředí.
Nainstalovat JUnit (stačí rozbalit do
samostatného adresáře).
Napsat testovací třídu/třídy - buďto implementují rozhraní
junit.framework.Test nebo obvykleji rovnou
rozšiřují třídu junit.framework.TestCase
Testovací třída obsahuje metodu na nastavení testu (setUp), testovací metody (testNeco) a úklidovou metodu (tearDown).
Testovací třídu spustit v prostředí (řádkovém nebo GUI) -
junit.textui.TestRunner,
junit.swingui.TestRunner
...
Testovač zobrazí, které testovací metody případně selhaly.
Třída Zlomek zůstává zhruba jako v předchozím příkladu, přibývají však metody equals (porovnává dva zlomky, zda je jejich číselná hodnota stejná) a soucet (sečítá dva zlomky, součet vrací jako výsledek).
Testovací třída JUnitDemo má „přípravnou“ metodu setUp, tearDown a testovací metody.
Spuštění testovače v prostředí GUI Swing nad testovací třídou JUnitDemo.
Pokud testovací třída prověří, že testovaná třída/y je/jsou OK, vypadá to přibližně takto:
jass je preprocesor javového zdrojového textu. Umožňuje ve zdrojovém textu programu vyznačit podmínky, jejichž splnění je za běhu kontrolováno.
Podmínkami se rozumí:
pre- a postconditions u metod (vstupní a výstupní podmínky metod)
invarianty objektů - podmínky, které zůstávají pro objekt v platnosti mezi jednotlivými operacemi nad objektem
Postup práce s jass:
stažení a instalace balíku z http://csd.informatik.uni-oldenburg.de/~jass/
vytvoření zdrojového textu s příponou
.jass, javovou syntaxí s použitím speciálních
komentářových značek
takový zdrojový text je přeložitelný i normálním překladačem
javac, ale v takovém případě ztrácíme možnosti
jass
proto nejprve .jass souboru převedeme
preprocesorem jass na javový (.java
)
soubor
ten již přeložíme javac a spustíme
java
, tedy jako každý jiný zdrojový soubor v
Javě
z .jass zdrojů je možné vytvořit také
dokumentaci API obsahující jass značky, tj. informace, co kde musí
platit za podmínky atd. - vynikající možnost!
úvodní materiálek k použití junit (v němčině, jako PDF)
Třída sice smí dědit maximálně z jedné nadtřídy (předka), ale
zato může současně implementovat libovolný počet rozhraní!
Podmínkou ovšem je, aby se metody ze všech implementovaných rozhraní „snesly“ v jedné třídě.
Které že se nesnesou? Např. dvě metody se skoro stejnou hlavičkou, lišící se „jen“ návratovým typem...
Příklad - kromě výše uvedeného intf. Informujici mějme ještě:
public interface Kricici {
void zakric();
}
Třída Clovek implementuje dvě rozhraní:
public class Clovek
implements Informujici, Kricici {
...
public void vypisInfo() {
...
}
public void zakric() {
...
}
}
Podobně jako u tříd, i rozhraní mohou být rozšiřována/specializována. Mohou dědit.
Na rozdíl od třídy, která dědí maximálně z jedné nadtřídy (předka) -
z rozhraní můžeme odvozovat potomky (podrozhraní - subinterfaces)
dokonce i vícenásobně - z více rozhraní odvodíme společného potomka slučujícího a rozšiřujícího vlastnosti všech předků.
Přesto to nepřináší problémy jako u klasické plné vícenásobné dědičnosti např. v C++, protože rozhraní samo
nemá proměnné
metody neimplementuje
nedochází tedy k nejednoznačnostem a konfliktům při podědění neprázdných, implementovaných metod a proměnných
Příklad - Informujici informuje „jen trochu“, DobreInformujici je schopen ke standardním informacím (vypisInfo) přidat dodatečné informace (vypisDodatecneInfo).
public interface Informujici {
void vypisInfo();
}
public interface DobreInformujici extends Informujici {
void vypisDodatecneInfo();
}
Třída, která chce implementovat intf.DobreInformujici, musí implementovatoběmetody předepsané tímto rozhraním. Např.:
public class Informator implements DobreInformujici {
public void vypisInfo() {
... // kód metody
}
public void vypisDodatecneInfo() {
... // kód metody
}
}
Používají se i prázdná rozhraní - nepředepisující žádnou metodu
deklarace, že třída implementuje také rozhraní, ji "k ničemu nezavazuje", ale poskytuje typovou informaci o dané třídě
i v Java Core API jsou taková rozhraní - např.
java.lang.Cloneable
I když Java disponuje rozhraními, někdy je vhodné určitou specifikaci implementovat pouzečástečně:
Rozhraní = Specifikace
Abstraktní třída = Částečná implementace
Třída = Implementace
Abstraktní třída je tak označena v hlavičce, např.:
public abstract class AbstraktniChovatel
...
Obvykle má alespoň jednu abstraktní metodu, deklarovanou např.:
public abstract void vypisInfo() ...
Od a.t. nelze vytvořit instanci, nelze napsat např.:
Viz Svět chovatelství z učebnice:
rozhraní svet.chovatelstvi.Chovatel -
specifikace, co má chovatel umět
svet.chovatelstvi.AbstraktniChovatel -
částečná implementace chovatele
svet.chovatelstvi.psi.ChovatelPsu - úplná
implementace chovatele psů
Pozn.: Obecný chovatel se ihned úplně implementovat nedá (ještě to neumíme), proto je definován jako abstraktní třída AbstraktniChovatel a teprve až ChovatelPsu je neabstraktní třída.
Obsah
Kontejnery jako základní dynamické struktury v Javě
Kolekce, iterátory (Collection, Iterator)
Seznamy (rozhraní List, třídy ArrayList, LinkedList)
Množiny (rozhraní Set, třída HashSet), uspořádané množiny (rozhraní SortedSet, třída TreeSet), rozhraní Comparable, Comparator
Mapy (rozhraní Map, třída HashMap), uspořádané mapy (rozhraní SortedMap, třída TreeMap)
Klasické netypové vs. nové typové kontejnery - generické datové typy
Iterace cyklem foreach
Starší typy kontejnerů (Vector, Stack, Hashtable)
Kontejnery (containers) v Javě
slouží k ukládání objektů (ne hodnot primitivních typů!)
v Javě koncipovány dosud jako beztypové - to se ve verzi 1.5 částečně změní!
tím se liší od např. Standard Template Library v C++
Většinou se používají kontejnery hotové, vestavěné, tj. ty, jež jsou součastí Java Core API:
vestavěné kontejnerové třídy jsou definovány v balíku
java.util
je možné vytvořit si vlastní implementace, obvykle ale zachovávající/implementující „standardní“ rozhraní
jsou dynamickými alternativami k poli a mají daleko širší použití
k uchování proměnného počtu objektů -
počet prvků se v průběhu existence kontejneru může měnit
oproti polím nabízejí časově efektivnější algoritmy přístupu k prvkům
seznam (List) - lineární struktura
množina (Set) - struktura bez uspořádání, rychlé dotazování na přítomnost prvku
asociativní pole, mapa (Map) - struktura uchovávající dvojice klíč->hodnota, rychlý přístup přes klíč
Funkcionalita vestavěných kontejnerů je obvykle předepsána výhradně rozhraním, jenž implementují.
Rozhraní však připouštějí, že některé metody jsou nepovinné, třídy jej nemusí implementovat!
V praxi se totiž někdy nehodí implementovat jak čtecí, tak i zápisové operace - některé kontejnery jsou „read-only“
Moderní kontejnery jsou nesynchronizované, nepřipouštějí souběžný přístup z více vláken.
Standardní, nesynchronizovaný, kontejner lze však „zabalit“ synchronizovanou obálkou.
Při práci s kontejnery může vzniknout řada výjimek, např. IllegalStateException apod.
Většina má charakter výjimek běhových, není povinností je odchytávat - pokud věříme, že nevzniknout.
Iterátory jsou prostředkem, jak "chodit" po prvcích kolekce buďto
v neurčeném pořadí nebo
v uspořádání (u uspořádaných kolekcí)
Každý iterátor musí implementovat velmi jednoduché rozhraní Iterator se třemi metodami:
boolean hasNext()
Object next()
void remove()
jsou kontejnery implementující rozhraní Collection - API doc k rozhr. Collection
Rozhraní kolekce popisuje velmi obecný kontejner, disponující operacemi: přidávání, rušení prvku, získání iterátoru, zjišťování prázdnosti atd.
Mezi kolekce patří mimo Mapy všechny ostatní vestavěné kontejnery - List, Set
Prvky kolekce nemusí mít svou pozici danou indexem - viz např. Set
lineární struktury
implementují rozhraní List (rozšíření Collection) API doc k rozhr. List
prvky lze adresovat indexem (typu int)
poskytují možnost získat dopředný i zpětný iterátor
lze pracovat i s podseznamy
Vytvoříme seznam, naplníme jej a chodíme po položkách iterátorem.
Vytvoříme seznam, naplníme jej a chodíme po položkách seznamovým iterátorem, vytvořeným na určité pozici (indexu) v seznamu.
K procházení seznamovým iterátorem lze použít metody next, previous.
Množiny
jsou struktury standardně bez uspořádání prvků (ale existují i uspořádané, viz dále)
implementují rozhraní Set (což je rozšíření Collection)
Cílem množin je mít možnost rychle (se složitostí O(log(n))) provádět atomické operace:
vkládání prvku (add)
odebírání prvku (remove)
dotaz na přítomnost prvku (contains)
lze testovat i relaci „je podmnožinou“
Standardní implementace množiny:
hašovací tabulka (HashSet) nebo
vyhledávací strom (černobílý strom, Red-Black Tree - TreeSet)
Vložíme prvky do množiny a ptáme se, zda tam jsou:
Uspořádané množiny:
Implementují rozhraní SortedSet -API doc k rozhraní SortedSet
Jednotlivé prvky lze tedy iterátorem procházet v přesně definovaném pořadí - uspořádání podle hodnot prvků.
Existuje vestavěná impl. TreeSet - černobílé stromy (Red-Black Trees) API doc ke třídě TreeSet
Uspořádání je dáno buďto:
standardním chováním metody compareTo vkládaných objektů - pokud implementují rozhraní Comparable
nebo je možné uspořádání definovat pomocí tzv. komparátoru (objektu impl. rozhraní Comparator) poskytnutých při vytvoření množiny.
Vložíme prvky do uspořádané množiny. Prvky musejí implementovat rozhraní Comparable, nebo musíme poskytnout komparátor. Když neuděláme ani jedno:
Nefunguje, prvky třídy Clovek nebyly porovnatelné.
Mapy (asociativní pole, nepřesně také hašovací tabulky nebo haše) fungují v podstatě na stejných principech a požadavcích jako Set:
Ukládají ovšem dvojice (klíč, hodnota) a umožnují rychlé vyhledání dvojice podle hodnoty klíče.
Základními metodami jsou: dotazy na přítomnost klíče v mapě (containsKey),
výběr hodnoty odpovídající zadanému klíči (get),
možnost získat zvlášt množiny klíčů, hodnot nebo dvojic (klíč, hodnota).
Mapy mají:
podobné implementace jako množiny (tj. hašovací tabulky nebo stromy).
logaritmickou složitost základních operací (put, remove, containsKey)
Lidi se do mapy vkládají s klíčem = příjmení člověka, pak se přes příjmení mohou vyhledat:
Uspořádané mapy:
Implementují rozhraní SortedMap - API doc k rozhraní SortedMap
Dvojice (klíč, hodnota) jsou v nich uspořádané podle hodnot klíče.
Existuje vestavěná impl. TreeMap - černobílé stromy (Red-Black Trees) - API doc ke třídě TreeMap
Uspořádání lze ovlivnit naprosto stejným postupem jako u uspořádané množiny.
Jsou-li klíče uspořádané (pomocí implementace Comparable nebo komparátorem), mohou se prvky procházet podle uspořádání klíčů:
Příklad, kdy jsou klíče uspořádané komparátorem:
Obrázek 8.15. Vložení účtů do mapy pod uspořádaným klíčem člověka - vlastníka
package tomp.ucebnice.kolekce;
import java.util.*;
public class SortedMapComparatorDemo {
public static void main(String[] args) {
// vytvoříme mapu
SortedMap sm = new TreeMap(new ClovekComparator());
// naplníme ji třemi lidmi
Clovek c1 = new Clovek("Josef", "Vykoukal");
Ucet u1 = new Ucet(100);
sm.put(c1, u1);
Clovek c2 = new Clovek("Dalimil", "Brabec");
Ucet u2 = new Ucet(50);
sm.put(c2, u2);
Clovek c3 = new Clovek("Viktor", "Kotrba");
Ucet u3 = new Ucet(2000);
sm.put(c3, u3);
// projdi abecedně všechny vlastníky účtů v mapě
// proto je třeba získat iterátor nad množinou klíčů
for(Iterator i = sm.keySet().iterator(); i.hasNext(); ) {
Clovek majitel = (Clovek)i.next();
Ucet ucet = (Ucet)sm.get(majitel);
majitel.vypisInfo();
System.out.println(" je majitelem uctu se zustatkem "
+ ucet.zustatek + " Kc");
}
}
static class Ucet {
double zustatek;
public Ucet(double z) {
zustatek = z;
}
}
static class Clovek { // nemusí být Comparable
String jmeno, prijmeni;
Clovek (String j, String p) {
jmeno = j;
prijmeni = p;
}
public void vypisInfo() {
System.out.print("Clovek "+jmeno+" "+prijmeni);
}
}
static class ClovekComparator implements Comparator {
public int compare(Object o1, Object o2) {
// porovnává jen lidi a to podle příjmení
if (o1 instanceof Clovek && o2 instanceof Clovek) {
Clovek c1 = (Clovek)o1;
Clovek c2 = (Clovek)o2;
return c1.prijmeni.compareTo(c2.prijmeni);
} else
throw new IllegalArgumentException(
"Nelze porovnat objekt typu Clovek s objektem jineho typu");
}
}
}
Seznamy:
na bázi pole (ArrayList) - rychlý přímý přístup (přes index)
na bázi lineárního zřetězeného seznamu (LinkedList) - rychlý sekvenční přístup (přes iterátor)
téměř vždy se používá ArrayList - stejně rychlý a paměťově efektivnější
Množiny a mapy:
na bázi hašovacích tabulek (HashMap, HashSet) - rychlejší, ale neuspořádané (lze získat iterátor procházející klíče uspořádaně)
na bázi vyhledávacích stromů (TreeMap, TreeSet) - pomalejší, ale uspořádané
spojení výhod obou - LinkedHashSet, LinkedHashMap - novinka v Javě 2, v1.4
Demo efektivity práce kontejnerů - Demo kolekcí
Velmi podrobné a kvalitní seznámení s kontejnery najdete na Trail: Collections
Obsah
Výjimky - proč a jak, co to vlastně je výjimka
Syntaxe bloku s ošetřením (zachycením) výjimky
„Únik“ výjimky z metody - deklarace metody propouštějící výjimku
Reakce na výjimku
Kaskády výjimek
Kategorizace výjimek (hlídané, běhové, vážné chyby)
Vlastní typy výjimek, objektová hierarchie výjimek
Klauzule finally
výjimky jsou mechanizmem, jak psát robustní, spolehlivé programy odolné proti chybám "okolí" - uživatele, systému...
v Javě jsou výjimky ještě lépe implementovány než
v C++ (navíc klauzule finally)
není dobré výjimkami "pokrývat" chyby programu samotného - to je hrubé zneužití
Výjimka
(Exception) je objekt třídy
java.lang.Exception
Objekty -výjimky- jsou vytvářeny (vyvolávány) buďto
automaticky běhovým systémem Javy, nastane-li nějaká běhová chyba, např. dělení nulou, nebo
jsou vytvořeny samotným programem, zdetekuje-li nějaký chybový stav, na nějž je třeba reagovat - např. do metody je předán špatný argument
Vzniklý objekt výjimky je předán buďto:
Výjimka tedy "putuje programem" tak dlouho, než je zachycena
Základní syntaxe:
try {
//zde může vzniknout výjimka
} catch (TypVýjimky proměnnáVýjimky) {
// zde je výjimka ošetřena
// je možné zde přistupovat k proměnnéVýjimky
}
Příklad - Otevření souboru může vyvolat výjimku
Bloku try se říká hlídaný
blok, protože výjimky (příslušného hlídaného typu) zde
vzniklé jsou zachyceny.
Pokud výjimka nikde v těle numůže vzniknout, překladač to zdetekuje a vypíše:
... Exception XXX is never thrown in YYY
...
Příklad s propouštěnou výjimkou -Otevření souboru s propouštěnou výjimkou
modifikatory návratovýTyp nazevMetody(argumenty) throws TypPropouštěnéVýjimky {
... tělo metody, kde může výjimka vzniknout ...
}
Jak můžeme reagovat?
Napravit příčiny vzniku chybového stavu - např. znovu nechat načíst vstup
Poskytnout za chybný vstup náhradu - např. implicitní hodnotu
Operaci neprovést („vzdát“) a sdělit chybu výše tím, že výjimku „propustíme“ z metody
Výjimková pravidla:
Vždy nějak reagujme! Neignorujme, nepotlačujme, tj.
blok catch nenechávejme
prázdný, přinejmenším vypišme
e.printStackTrace()
Nelze-li reagovat na místě, propusťme výjimku výše (a popišme to v dokumentaci...) - Příklad komplexní reakce na výjimku
V některých blocích try mohou vzniknout
výjimky více typů:
pak můžeme bloky catch řetězit, viz
přechozí příklad: Příklad
komplexní reakce na výjimku
Pokud catch řetězíme, musíme respektovat,
že výjimka je zachycena nejbližším příhodným
catch
Pozor na řetězení catch s výjimkami typů
z jedné hierarchie tříd: pak musí být výjimka z podtřídy (tj.
speciálnější) uvedena - zachycována - dříve než výjimka obecnější -
Takto
ne!
Všechny objekty výjimek a chybových stavů implementují rozhraní java.lang.Throwable - „vyhoditelný“
Nejčastěji se používají tzv. hlídané výjimky (checked exceptions) - to jsou potomci/instance třídy java.lang.Exception
Tzv. běhové (runtime, nebo též nehlídané, unchecked) výjimky jsou typu/typu potomka --> java.lang.RuntimeException - takové výjimky nemusejí být zachytávány
Vážné chyby JVM (potomci/instance java.lang.Error) - obvykle signalizují těžce napravitelné chyby v JVM - např. Out Of Memory, Stack Overflow..., ale též např. chybu programátora: AssertionError
Typy (=třídy) výjimek si můžeme definovat sami, např. viz - Výjimky ve světě chovatelství
bývá zvykem končit názvy tříd - výjimek - na Exception
Sun Java Tutorial - Lesson: Handling Errors with Exceptions
demo programy z učebnice - Výjimky
Obsah
Koncepce I/O proudů v Javě, skládání (obalování vlastnostmi)
Práce se soubory a adresáři, třída File
Binární proudy, třídy InputStream, OutputStream
Znakové proudy, třídy Reader, Writer
Serializace objektů
založeny na v/v proudech
plně platformově nezávislé
V/V proudy jsou
koncipovány jako "stavebnice" - lze vkládat do sebe a tím přidávat vlastnosti, např.
is = new InputStream(...);
bis = new BufferedInputStream(is);
Téměř vše ze vstupních/výstupních tříd a rozhraní je v balíku java.io.
počínaje J2SDK1.4 se rozvíjí alternativní balík -
java.nio (New I/O)
vše je opět v balíku java.io
základem je třída java.io.File - nositel
jména souboru, jakási "brána" k fyzickým souborům na disku.
používá se jak pro soubory, tak adresáře, linky i soubory identifikované UNC jmény (\\počítač\adresář...)
opět plně platformově nezávislé
na odstínění odlišností jednotlivých systémů souborů lze použít vlastností (uvádíme jejich hodnoty pro JVM pod systémem MS Windows):
File.separatorChar \ - jako
char
File.separator \ - jako
String
File.pathSeparatorChar ; - jako
char
File.pathSeparator ; - jako
String
System.getProperty("user.dir") - adresář
uživatele, pod jehož UID je proces JVM spuštěn
Vytvoření konstruktorem - máme několik možností:
vytvoří v aktuálním adresáři soubor s názvem filename
vytvoří v adresáři baseDir soubor s názvem filename
vytvoří v adresáři se jménem baseDirName soubor s názvem filename
vytvoří soubor se (souborovým - file:) URL url
Testy existence a povahy souboru:
test na existenci souboru (nebo adresáře)
test, zda jde o soubor (tj. ne adresář)
test, zda jde o adresář
Test práv ke čtení/zápisu:
test, zda lze soubor číst
test, zda lze do souboru zapisovat
Vytvoření souboru nebo adresáře:
Vytvoření dočasného (temporary) souboru:
Zrušení:
zrušení souboru nebo adresáře
Přejmenování (ne přesun mezi adresáři!):
přejmenuje soubor nebo adresář
Další vlastnosti:
délka (velikost) souboru v bajtech
čas poslední modifikace v ms od začátku éry - podobně jako systémový čas vracený System.currentTimeMillis().
jen jméno souboru (tj. poslední část cesty)
celá cesta k souboru i se jménem
absolutní cesta k souboru i se jménem
adresář, v němž je soubor nebo adresář obsažen
Blíže viz dokumentace API třídy File.
Klíčem je opět třída File - použitelná i pro
adresáře
Jak např. získat (filtrovaný) seznam souborů v adresáři?
pomocí metody File[] listFiles(FileFilter
ff) nebo podobné
File[] listFiles(FilenameFilter fnf):
FileFilter je rozhraní s jedinou metodou boolean accept(File pathname), obdobně FilenameFilter, viz Popis API java.io.FilenameFilter
Vstupní jsou odvozeny od abstraktní třídy
InputStream
Výstupní jsou odvozeny od abstraktní třídy
OutputStream
Uvedené metody, kromě abstract byte read(), nemusejí být nutně v neabstraktní podtřídě překryty.
uzavře proud a uvolní příslušné zdroje (systémové "file handles" apod.)
poznačí si aktuální pozici (později se lze vrátit zpět pomocí reset())...
...ale jen když platí tohle
přečte bajt (0-255 pokud OK; jinak -1, když už není možné přečíst)
přečte pole bajtů
přečte pole bajtů se specifikací délky a pozice plnění pole b
vrátí se ke značce nastavené metodou mark(int)
přeskočí zadaný počte bajtů
java.io.FilterInputStream - je bázová třída k odvozování všech vstupních proudů přidávajících vlastnost/schopnost filtrovat poskytnutý vstupní proud.
Příklady filtrů (ne všechny jsou v
java.io!):
proud s vyrovnávací pamětí (je možno specifikovat její optimální velikost)
proud s kontrolním součtem (např. CRC32)
proud dešifrující data ze vstupu
má metody pro čtení hodnot primitivních typů, např.
float readFloat()
počítá současně i haš (digest) čtených dat, použitý algoritmus lze nastavit
dekomprimuje (např. GZIPem) zabalený vstupní proud (má ještě specializované podtřídy)
doplňuje informaci o tom, ze kterého řádku vstupu čteme (zavrhovaná - deprecated - třída)
přidává schopnost informovat o průběhu čtení z proudu
do proudu lze data vracet zpět
Příklad rekonstrukce objektů ze souborů
FileInputStream istream = new FileInputStream("t.tmp");
ObjectInputStream p = new ObjectInputStream(istream);
int i = p.readInt();
String today = (String)p.readObject();
Date date = (Date)p.readObject();
istream.close();
vstupní proud zvukových dat
proud dat čtených z pole bajtů
roura napojená na "protilehlý" PipedOutputStream
proud vzniklý spojením více podřízených proudů do jednoho virtuálního
proud na čtení serializovaných objektů
základem je abstraktní třída Reader,
konkrétními implementacemi jsou:
nebudeme důkladně probírat všechny typy
principy:
jedná se o protějšky k vstupním proudům, názvy jsou
konstruovány analogicky (např. FileReader ->
FileWriter
)
místo generických metod read mají
write(...)
Příklady:
poskytuje metody pro pohodlný zápis hodnot primitivních typů
a řetězců - příkladem jsou System.out a
System.err
poskytuje metody pro pohodlný zápis hodnot primitivních typů a řetězců
Ze vstupního binárního proudu InputStream
(čili každého) je možné vytvořit znakový Reader
pomocí
// nejprve binární vstupní proud - toho kódování znaků nezajímá InputStream is = ... // znakový proud isr // použije pro dekódování standardní znakovou sadu Reader isr = new InputStreamReader(is); // sady jsou definovány v balíku
java.nioCharset chrs = java.nio.Charset.forName("ISO-8859-2"); // znakový proud isr2 // použije pro dekódování jinou znakovou sadu Reader isr2 = new InputStreamReader(is, chrs);
Podporované názvy znakových sad naleznete na webu IANA Charsets.
Obdobně pro výstupní proudy - lze vytvořit Writer z OutputStream.
nebudeme podrobně studovat, zatím stačí vědět, že:
serializace objektů je postup, jak z objektu vytvořit sekvenci bajtů persistentně uložitelnou na paměťové médium (disk) a později restaurovatelnou do podoby výchozího javového objektu.
deserializace je právě zpětná rekonstrukce objektu
aby objekt bylo možno serializovat, musí
implementovat (prázdné) rozhraní
java.io.Serializable
proměnné objektu, které nemají být serializovány,
musí být označeny modifikátorem - klíčovým slovem -
transient
pokud požaduje "speciální chování" při de/serializaci, musí objekt definovat metody
metody:
Tutoriály k Java I/O: kapitola z Sun Java Tutorial
Demo programy na serializaci (z učebnice): Serializace objektů
Dokumentace javových programů, dokumentace API
Typy komentářů - dokumentační komentáře
Generování dokumentace
Značky javadoc
Základním a standardním prostředkem je tzv. dokumentace API
Dokumentace je naprosto nezbytnou součástí javových programů.
Rozlišujeme dokumentaci např. instalační, systémovou, uživatelskou, programátorskou...
Zde se budeme věnovat především dokumentaci programátorské, určené těm, kdo budou náš kód využívat ve svých programech, rozšiřovat jej, udržovat jej. Programátorské dokumentaci se říká dokumentace API (apidoc, apidocs).
Při jejím psaní dodržujeme tato pravidla:
Dokumentujeme především veřejné (public) a chráněné (protected) prvky (metody, proměnné). Ostatní dle potřeby.
Dokumentaci píšeme přímo do zdrojového kódu programu ve speciálních dokumentačních komentářích vpisovaných před příslušné prvky (metody, proměnné).
Dovnitř metod píšeme jen pomocné komentáře pro programátory (nebo pro nás samotné).
Podobně jako např. v C/C++:
od značky // do konce řádku, nepromítnou se do dokumentace API
začínají /* pak je text komentáře,
končí */ na libovolném počtu řádků
od značky /** po značku */ může být opět na libovolném počtu řádků
Každý další řádek může začínat mezerami či *, hvězdička se v komentáři neprojeví.
Dokumentační komentáře uvádíme:
Před hlavičkou třídy - pak komentuje třídu jako celek.
Před hlavičkou metody nebo proměnné - pak komentuje příslušnou metodu nebo proměnnou.
Celý balík (package) je možné komentovat speciálním
samostatným HTML souborem
package-summary.html uloženým v adresáři
balíku.
Dokumentace má standardně podobu HTML stránek (s rámy i bez)
Dokumentace je generována nástrojem javadoc
z
dokumentačních komentářů
i ze samotného zdrojového textu
Lze tedy (základním způsobem) dokumentovat i program bez vložených komentářů!
Chování javadoc můžeme změnit
volbami (options) při spuštění,
použitím jiného tzv. docletu, což je
třída implementující potřebné metody pro generování
komentářů.
Princip generování ze zdrojových textů pomocí
speciálních docletů se dnes používá i po jiné než
dokumentační účely - např. pro generátory zdrojových kódu aplikací EJB
apod.
javadoc můžeme podrobněji instruovat pomocí
značek vkládaných do dokumentačních komentářů, např.:
specifikuje autora API/programu
označuje verzi API, např. "1.0"
informuje, že prvek je zavrhovaný
popisuje informace o výjimce, kterou metoda propouští ("vyhazuje")
popisuje jeden parametr metody
uvedeme, od kdy (od které verze pg.) je věc podporována/přítomna
uvedeme odkaz, kam je také doporučeno nahlédnout (související věci)
Zdrojový text třídy Window:
/**
* Klasse, die ein Fenster auf dem Bildschirm repräsentiert
* Konstruktor zum Beispiel:
* <pre>
* Window win = new Window(parent);
* win.show();
* </pre>
*
* @see awt.BaseWindow
* @see awt.Button
* @version 1.2 31 Jan 1995
* @author Bozo the Clown
**/
class Window extends BaseWindow {
...
}
Příklad dokumentačního komentáře k proměnné:
/**
* enthält die aktuelle Anzahl der Elemente.
* muss positiv und kleiner oder gleich der Kapazität sein
**/
protected int count;
Tyto a další příklady a odkazy lze vidět v původním materiálu JavaStyleGuide des IGE, odkud byly ukázky převzaty.
javadoc [options] [packagenames] [sourcefiles]
[classnames] [@files]
možné volby:
-public,
-protected
, -package
,
-private
- specifikuje, které prvky mají
být v dokumentaci zahrnuty (implicitně:
-protected
)
-d destinationdirectory - kam se má
dok. uložit
-doctitle title - titul celé
dokumentace
Distribucí nemyslíme použití nástroje typu "InstallShield"..., ale
spíše něčeho podobného
tar/ZIP
u
Java na sbalení množiny souborů zdrojových i přeložených
(.class) nabízí nástroj
jar
.
Sbalením vznikne soubor (archív) .jar
formátově podobný ZIP
u (obvykle
je to ZIP formát), ale nemusí být
komprimován.
Kromě souborů obsahuje i metainformace (tzv. MANIFEST)
Součástí archívu nejsou jen .class soubory, ale i další zdroje, např. obrázky, soubory s národními variantami řetězců, zdrojové texty programu, dokumentace...
jar {ctxu} [vfm0M] [jar-file] [manifest-file] [-C
dir] files
volby:
v - verbose
0 - soubory nekomprimuje
f - pracuje se se souborem, ne se
"stdio"
m - přibalí metainformace z
manifest-file
parametr files uvádí, které
soubory se sbalí - i nejavové (např. typicky dokumentace API - HTML,
datové soubory)
Vezměme následující zdrojový text třídy
JarDemo v balíku tomp.ucebnice.jar, tj. v adresáři
c:\tomp\pb162\java\tomp\ucebnice\jar:
Vytvoříme archív se všemi soubory z podadresáře
tomp/ucebnice/jar (s volbou c - create, v -
verbose, f - do souboru):
Vzniklý .jar soubor lze prohlédnout/rozbalit
také běžným nástrojem typu unzip, gunzip, WinZip, PowerArchiver nebo
souborovým managerem typu Servant Salamander...
Tento archív rozbalíme v adresáři /temp
následujícím způsobem:
Formáty vycházející z JAR:
liší se podrobnějším předepsáním adresářové struktury a dalšími povinnými metainformacemi
Vytvoříme jar s manifestem obsahujícím tento
řádek:
Main-Class: NázevSpouštěnéTřídy
poté zadáme:
java
-jarNázevBalíku
.jar
a spustí se metoda main třídy
NázevSpouštěnéTřídy.
Nejprve vytvoříme soubor manifestu. Příklad jeho obsahu:
Spuštění aplikace zabalené ve spustitelném archívu je snadné:
java
-jarjardemo.jar
Pokud si vezmeme anglicko-český slovník, zjistíme, že v překladu to znamená něco obecně použitelného, tedy mohli bychom použít termínu zobecnění. A přesně tím generics jsou -- zobecněním.
Jak již víme, struktura tříd v Javě má společného předka, třídu
Object. Tato skutečnost zjednodušuje implementaci
nejednoho programu -- potřebujeme-li pracovat s nějakými objekty,
o kterých tak úplně nevíme, co jsou zač, můžeme využít společného předka
a pracovat s ním. Každý objekt v programu je totiž i instancí třídy
Object
. To v praxi umožňuje například snadnou
implementaci spojových seznamů, hashovacích tabulek, ale například i
využití reflexe.
Jakkoliv je společný předek jistě výhodou, přináší i některé obtíže.
Uvažujme opět (spojový) seznam. Pracujeme-li v programu s nějakým,
víme jenom, že je to seznam objektů a nic více. Samozřejmě si můžeme
bokem pamatovat, že tam ukládáme jenom řetězce, ale to nic nemění na tom,
že runtime systému je srdečně jedno, jaké objekty do seznamu vkládáme.
Stejně tak, chceme-li z tohoto seznamu číst, získáváme zase jenom obecné
objekty, které musíme explicitně přetypovat na řetězec (tj. třídu
String). A opět -- ačkoliv jako programátoři
víme, co v seznamu má být, obecně si nemůžeme být
jisti, zda to tam skutečně je. Což vede buď k
tomu, že se budeme ptát, zda získávaný objekt je skutečně řetězcem a až
poté jej přetypujeme nebo budeme „riskovat“ runtime
výjimku ClassCastException
. Jak vidíme, nevýhody
jsou a to docela významné.
Řešením v této situaci je příchod Javy verze 1.5, která mimojiné přináší i zmiňovaná generics. Opět využijeme našeho spojového seznamu. Vraťme se zpět k jeho definici. Co od něj vlastně požadujeme?
Aby byl seznamem čehokoliv a tak byl universální (což je stav popsaný výše)?
Nebo aby to byl seznam nějakého obecného, předem nedefinovaného typu a umožnil tento typ při vytvoření instance určit?
Jelikož předchozí otázky jsou řečnické, odpověď následuje ihned.
Samozřejmě, že druhá uvedená možnost je lepší (už proto, že má veškerou
sílu první možnosti, stačí nadefinovat, aby tím obecným typem byl
Object). Ve zbytku textu si tedy ukážeme, jak
toho generics docilují a jak je používat.
V úvodu jsme se lehce zmínili o spojovém seznamu. Nyní si budeme
ukazovat příklady na obecném seznamu
(rozhraní java.util.List).
Takhle vypadá deklarace List
bez použití generics:
public interface List {
...
}
A takhle s nimi:
public interface List <E> {
...
}
Jak vidíme, úvod je velmi jednoduchý. Do špičatých závorek pouze umístíme symbol, kterým říkáme, že seznam bude obsahovat prvky E (předem neznámého) typu.
Zde uděláme malou odbočku -- je doporučováno používat velké, jednopísmenné deklarace generics. Toto písmeno by zároveň mělo vystihovat použití resp. význam takového zobecnění. Tedy T je typ, E je prvek (element) a tak podobně
Pouhá deklarace u jména třídy resp. rozhraní samozřejmě nemůže stačit.
Zjednodušeně řečeno, zdrojový kód využívající generics musí
typ E použít všude tam, kde by dříve použil
obecný Object. To jest například místo
Object get(int index);
se použije
E get(int index);
Co jsme nyní udělali? Touto definicí jsme řekli, že metoda get vrací pouze objekty, které jsou typu E na místo libovolného objektu, což je přesně to, co od generics vyžadujeme. Všimněte si, že nyní už s E pracujeme jako s jakoukoliv jinou třídou nebo rozhraním.
Totožně postupujeme i u metod, které do seznamu prvky typu E přidávají. Viz
boolean add(E o);
Dovolím si další malou poznámku na okraj -- výše zmíněné metody
by samozřejmě mohly pracovat s typem Object.
Překladač by proti tomu nic nenamítal, nicméně očekávaná funkcionalita
by byla pryč.
Nyní tedy máme seznam, který při použití bude obsahovat nějaké prvky typu E. Nyní chceme takový seznam použít někde v našem kódu a užívat si výhod generics. Vytvoříme jej následovně:
List<String> = new ArrayList<String>();
Použití je opět jednoduché a velmi intuitivní. Nyní následují dva příklady demonstrující výhody generics (první je napsán „postaru“).
Object number = new Integer(2);
List numbers = new ArrayList();
numbers.add(new Integer(1));
numbers.add(number);
Number n = (Number)numbers.get(0);
Number o = (Number)numbers.get(1);
Object number = 2;
List<Number> numbers = new ArrayList<Number>();
numbers.add(1);
numbers.add((Number)number);
Number n = numbers.get(0);
Number o = numbers.get(1);
Jak vidíme v horním příkladu, do seznamu lze vložit libovolný objekt
(byť zde jsme měli „štěstí“ a bylo to číslo) a při
získávání objektů se spoléháme na to, že se jedná o číslo. Níže naopak
nelze obecný objekt vložit, je nutné jej explicitně přetypovat na číslo,
teprve poté překladač kód zkompiluje. Podotkněme, že pokud bychom
na Number přetypovali například
String
, program se také přeloží, ale v okamžiku
zavolání takového příkazu se logicky vyvolá výjimka
ClassCastException
. Získání čísel ze seznamu je
ovšem přímočaré -- stačí pouze zavolat metodu
get, která má správný návratový typ.
Povšimněte si rovněž použití další vlastnosti nové Javy, tzv. autoboxingu, kdy primitivní typ je automaticky převeden na odpovídající objekt (a vice versa).
Přestože konstrukce cyklu for patří svou povahou jinam, zmiňujeme se o nich zde, u dynamických struktur - kontejnerů, neboť se převážně používá k iterování (procházení) prvků seznamů, množin a dalších struktur. Obecný tvar cyklu "foreach" je syntaktickou variantou běžného "for":
for (TypRidiciPromenne ridici_promenna : dyn_struktura) {
// co se dela pro kazdou hodnotu ze struktury...
}
V následujícím příkladu jsou v jednotlivých průchodech cyklem for postupně ze seznamu vybírány a do řídicí proměnné e přiřazovány všechny jeho prvky (objekty).
for (Object e : seznam) {
System.out.println(e);
}
V předchozích částech jsme se seznámili s hlavní myšlenkou generics a její
realizací, a sice nahrazení konkrétního nadtypu (většinou
Object) typem obecným. Nicméně tohle samo o sobě je velmi
omezující a nedostačující. Nyní se tedy ponoříme hlouběji do tajů
generics.
Představme si následující situaci. V programu chceme mít seznam, kde budou jako prvky různé jiné seznamy. První nápad, jak jej nadeklarovat může být třeba tento:
List<List<Object>> seznamSeznamu;
Na první pohled se to zdá být bez chyby. Máme seznam, kam budeme vkládat
jiné seznamy a jelikož každý seznam musí obsahovat instance třídy
Object, můžeme tam vložit libovolný seznam, tedy
třeba i náš List<Number>. Nicméně tato
úvaha je chybná. Uvažujme následující kód:
List<Number> cisla = new ArrayList<Number>();
List<Object> obecny = cisla;
obecny.add("Ja nejsem cislo");
Jak vidíme, „něco je špatně.“ To, že se pokoušíme přiřadit do seznamu objektů obecny řetězec "Ja nejsem cislo" je přece naprosto v pořádku, do seznamu objektů můžeme skutečně vložit cokoliv. V tom případě ale musí být špatně přiřazení na druhém řádku. To znamená, že seznam čísel není seznamem objektů! Zde je vidět rozdíl oproti „klasickému“ uvažování v mezích dědičnosti. Přečtěte si pozorně následující větu a pokuste se pochopit její význam.
Do seznamu, který obsahuje nejvýše čísla lze vkládat pouze objekty, které jsou alespoň čísly.
Z toho vyplývá, že je nelegální přiřazovat objekt „seznam čísel“ do objektu „seznam objektů.“ Tedy, vrátíme-li se k našemu příkladu se seznamem seznamů, vidíme, proč byla naše úvaha chybná. Do námi definovaného seznamu totiž lze ukládat pouze seznamy objektů a ne libovolné seznamy. Jak tedy docílíme kýženého jevu? K tomuto účelu nám generics poskytují nástroj zvaný žolík, anglicky wildcard, který se zapisuje jako ?. Vraťme se nyní k předchozímu příkladu:
List<Number> cisla = new ArrayList<Number>();
List<?> obecny = cisla; // tohle je OK
obecny.add("Ja nejsem cislo"); // tohle nelze prelozit
Jak je již v komentáři kódu naznačeno, poslední řádek neprojde překladačem.
Proč? Protože pomocí List<?> říkáme, že
obecny je seznamem neznámých
prvků. A jelikož nevíme, jaké prvky v seznamu jsou, nemůžeme do něj
ani žádné prvky přidávat. Jedinou výjimkou je
„žádný“ prvek, totiž null, který lze
přidat kamkoliv. Mírně filosoficky řečeno, null není ničím
a tak je zároveň vším.
Naopak, ze seznamu neznámých objektů můžeme samozřejmě prvky číst, neboť
každý prvek je určitě alespoň instancí třídy Object.
Ukážeme si praktické použití žolíku.
public static void tiskniSeznam(List<?> seznam) {
for (Object e : seznam) {
System.out.println(e);
}
}
Nyní si představme, že chceme metodu, která udělá z nějakého seznamu čísel jeho sumu. Uvažujme tedy následující (a pomiňme možné přetečení nebo podtečení rozsahu double):
public static double suma(List<Number> cisla) {
double result = 0;
for (Number e : cisla) {
result += e.doubleValue()
}
return result;
}
Opět, metoda se jeví jako bezproblémová. Nic ale není tak jednoduché,
jak by se mohlo zdát. Nyní zkusíme uvažovat bez příkladu. Představme si,
že máme seznam celých čísel, u kterého chceme provést sumu. Jistě není
sporu o tom, že celá čísla jsou zároveň obecná čísla a přesto seznam
List<Integer> nelze použít jako
parametr výše deklarované metody z naprosto stejného důvodu, kvůli
kterému nešlo říci, že seznam objeků je seznam čísel.
Samozřejmě je tu opět řešení. Zkusme nejdříve uvažovat selským rozumem. Výše jsme říkali, že místo seznamu objektů chceme seznam neznámých prvků. Nyní jsme v podobné situaci, pouze se nacházíme na jiném místě v hierarchii tříd. Zkusme tedy obdobnou úvahu použít i zde. Nechceme seznam čísel nýbrž seznam neznámých prvků, které jsou nejvýše čísly. Nyní je již pouze třeba ozřejmit syntaxi takové „úvahy“.
public static double suma(List<? extends Number> cisla) {
...
}
Toto použití žolíku má uplatnění i v samotném rozhraní List<E> a sice v metodě „přidej vše“. Zamyslete se nad tím, proč tomu tak je.
boolean addAll(Collection<? extends E> c);
Uvědomte si prosím následující -- prostý žolík je vlastně „zkratka“
pro „neznámý prvek rozšiřující Object“.
Ač by se tak mohlo zdát, možnosti wildcards jsme
ještě nevyčerpali. Představme si situaci, kdy potřebujeme, aby možnou
hodnotou byla instance třídy, která je v hierarchii mezi třídou
specifikovanou naším obecným prvkem E a třídou
Object. Pokud přemýšlíte, k čemu je něco takového
dobré, představte si, že máte množinu celých čísel, které chcete setřídit.
Jak lze taková čísla třídit? Například obecně podle hodnoty metody
hashCode(), tedy na úrovni třídy
Object
. Nebo jako obecné číslo, tj. na úrovni třídy
Number
. A konečně i jako celé číslo na úrovni třídy
Integer
. Skutečně, níže již jít nemůžeme, protože
libovolné zjemnění této třídy například na celá kladná čísla by nemohlo
třídit obecná celá čísla.
Následující příklad demonstruje syntaxi a použití popsané konstrukce
public TreeMap(Comparator<? super K> c);
Jedná se o konstruktor stromové mapy, tj. mapy klíč/hodnota, která je navíc setříděna podle klíče. Nyní opět trochu odbočíme a podíváme se, jak vypadá deklarace obecného rozhraní setříděné mapy.
public interface SortedMap<K,V> extends Map<K,V> {...
Máme zde nový prvek -- je-li třeba použít více nezávislých obecných typů, zapíšeme je opět do „zobáčků“ jako seznam hodnot oddělených čárkou. Povšimněte si opět mnemotechniky -- K je key (klíč), V je value (hodnota). Je-li to třeba, je možné použít i žolíků. Viz následující příklad konstruktorů naší staré známé stromové mapy.
public TreeMap(Map<? extends K, ? extends V> m);
public TreeMap(SortedMap<K, ? extends V> m);
Tato část bude relativně krátká a stručná, poněvadž pro používání generics a žolíků platí stále stejná pravidla. Generickou metodou rozumíme takovou, která je parametrizována alespoň jedním obecným typem, který nějakým způsobem „váže“ typy proměnných a/nebo návratové hodnoty metody.
Představme si například, že chceme statickou metodu, která přenese prvky z pole nějakého typu přidá hodnoty do seznamu s prvky téhož typu.
static <T> void arrayToList(T[] array, List<T> list) {
for (T o : array) {
list.add(o);
}
}
Zde narážíme na malou záludnost. Ve skutečnosti nemusí být seznam
list téhož typu, stačí, aby jeho typ byl
nadtřídou typu pole array. To se může jevit
jako velmi matoucí, ovšem pouze do té chvíle, dokud si neuvědomíme,
že pokud máme např. pole celých čísel, tj. Integer
a seznam obecných čísel Number
, pak
platí, že pole prvků typu Integer
JE polem prvků typu Number
!
Skutečně, zde se dostáváme zpět ke klasické dědičnosti a nesmí nás mást
pravidla, která platí pro obecné typy ve třídách.
Jak již bylo zmíněno, je žádoucí, aby typ použitý u generické metody spojoval alespoň dva parametry nebo parametr a návratovou hodnotu. Následující příklad demonstruje nesprávné použití generické metody.
public static <T, S extends T> void copy(List<T> destination, List<S> source);
Příklad je syntakticky bezproblémový, dokonce jej lze i přeložit a bude fungovat dle očekávání. Nicméně správný zápis by měl být následující.
public static <T> void copy(List<T> destination, List<? extends T> source);
Zde je již vidět požadovaná vlastnost -- T spojuje dva parametry metody a přebytečné S je nahrazené žolíkem. V prvním příkladu si všimněte zápisu S extends T . Ukazuje další možnou deklaraci generics.
Při deklaraci pole nelze použít parametrizovanou třídu, pouze třídu s žolíkem, který není vázaný (nebo bez použítí žolíku). Tj. jediná správná deklarace je následující:
List<?>[] pole = new List<?>[10];
Parametrizovanou třídu v seznamu nelze použít z toho důvodu, že při vkládání prvků do nich runtime systém kontroluje pouze typ vkládaného prvku, nikoliv už to, zda využívá generics a zda tento odpovídá deklarovanému typu. To znamená, že měli bychom například pole seznamů, které obsahují pouze řetězce, mohli bychom do něj bez problémů vložit pole čísel. To by samo o sobě nic nezpůsobilo, ovšem mohlo by dojít k „přeměně“ typu generics, čímž by se seznam čísel „proměnil“ na seznam řetězců, což by bylo špatně.
Uvažujme následující metodu (bez použití generics), která vyhledává
maximální prvek nějaké kolekce. Navíc platí, že prvky kolekce musí
implementovat rozhraní Comparable,
což, jak lze snadno nahlédnout, není syntaxí vůbec podchyceno a tudíž
zavolání této metody může vyvolat výjimku ClassCastException
.
public static Object max(Collection c);
Nyní se pokusíme vymyslet, jak zapsat tuto metodu za použití generics. Chceme, aby prvky kolekce implementovali rozhraní Comparable. Podíváme-li se na toto rozhraní, zjistíme, že je též parametrizované generics. Potřebujeme tedy takovou instanci, která je schopná porovnat libovolné třídy v hierarchii nad třídou, která bude prvkem vstupní kolekce. První pokus, jak zapsat požadované.
public static <T extends Comparable<? super T>> T max(Collection<T> c);
Tento zápis je relativně OK. Metoda správně vrátí proměnnou stejného
typu, jaký je prvkem v kolekci, dokonce i použití Comparable
je správné. Nicméně, pokud bychom se zajímali o signaturu
metody po „výmazu“ generics, dostaneme následující.
public static Comparable max(Collection c);
To neodpovídá signatuře metody výše. Využijeme tedy vícenásobné vazby.
[1]
public static <T extends Object & Comparable<? super T>> T max (Collection<T> c);
Nyní, po „výmazu“ má již metoda správnou signaturu, protože v úvahu se bere první zmíněná třída. Obecně lze použít více vazeb pro generics, například chceme-li, aby obecný prvek byl implementací více rozhraní.
V článku jsme se seznámili se základními i některými pokročilými technikami použití generics. Tato technologie má i další využití, například u reflexe. Tohle však již překračuje rámec začátečnického seznamování s Javou.
Celý článek vychází z materiálů, které jsou volně k disposici na oficiálních stránkách Javy firmy Sun, zejména pak z Generics in the Java Programming Language od Gilada Brachy. Některé příklady v této stati jsou převzaty ze zmíněného článku.
![]() | Poznámka |
---|---|
Psáno se svolením autora podle článku P. Adámka "Co se děje v Javě" (publikováno ve sborníku SLT2004) |
Mezi pravidelné změny v každé nové verzi jazyka Java patří zvýšení rychlosti; v tomto směru Java 1.5 nikterak nevybočuje. Kromě několika změn v garbage collectoru a virtuálním stroji je asi nejvýznamnějším prvkem vlastnost nazvaná class data sharing. Tento mechanismus urychluje start virtuálního stroje a šetří paměť v případě několika souběžně spuštěných virtuálních strojů. Princip spočívá v tom, že se při instalaci JRE (nebo Java SDK) spustí virtuální stroj a pak je paměťová oblast vyhrazená pro read-only data uložena do souboru na disk. Při každém dalším spuštění JVM se tato oblast namapuje do paměti, což start výrazně urychlí. Pokud běží více virtuálních strojů naráz, tato oblast je sdílená.
Mechanismus funguje pouze pro klientskou verzi JVM; serverová verze podporována není, což je ale pochopitelné. Serverové aplikace jsou většinou vytvářeny jako vícevláknové v rámci jednoho virtuálního stroje a běží nepřetržitě.
Další věc, která doposud v Javě chybí, je typ {\em enum}. To se doposud řešilo několika způsoby, od definování konstant typu int (což postrádá jakoukoliv typovou kontrolu) až po užívání typově bezpečného řešení založeného na definici třídy pro každý výčtový typ, navrženého v publikaci J. Blocha. Toto řešení ve své nejjednodušší variantě vypadá takto:
public class Color {
private Color() {};
public static final Color red = new Color();
public static final Color green = new Color();
public static final Color blue = new Color();
public static final Color yellow = new Color();
}
Právě na tomto principu je založená implementace výčtového typu, která je od verze 5 přímou součástí jazyka Java. Výčtový typ se zde ale definuje mnohem snadněji:
public enum Color { red, green, blue, yellow };
Pro úplnost je nutné poznamenat, že takto definovaný výčtový typ disponuje mnohem širšími možnostmi než předchozí varianta. Dále viz dokumentace API k Java 5.
Tento nový prvek Jazyka Java umožňuje zdrojový kód rozšířit o doplňkové informace deklarativního charakteru; uplatnění nalezne zejména v J2EE, kde eliminuje nutnost vytváření spousty zbytečného kódu a psaní deployment deskriptorů.
Nyní bude v Javě konečně možné používat mocné formátování výstupu funkcí fprint, známou z jazyků C/C++. Kromě toho jsou rozšířeny možnosti zpracování vstupu.
Toto je další prvek známý z jazyků C/C++; osobně jej považuji za nepříliš důležitý a někteří lidé tvrdí, že byl zaveden jenom kvůli metodě printf (viz předchozí bod).
Ve Windows přibyl nový vzhled pro knihovnu Swing, který vypadá jako Windows XP, v Linuxu pak vzhled, který vypadá jako GTK.
Za tímto téměř marketingovým pojmem se skrývá mnoho drobných ale velice užitečných vylepšení. Patří mezi ně plná podpora XML 1.1 a jmenných prostorů, XML Schémat, SAX 2.0.1, XSLT a XSLTC, DOM level 3, XPath, a další.
Umožní monitorovat a ovládat virtuální stroj pomocí rozhraní JMX.
[1] V materiálu, ze kterého čerpám, je navíc Collection<? extends T> Domnívám se ovšem, že metoda zmíněná v tomto článku má stejnou funkcionalitu. Pokud se někomu podaří nalézt protipříklad, budu rád.
písemky se píší přímo do počítače
výsledkem je přeložitelný program; nepřeložitelný program může být hodnocen 0 body
odevzdává se zdrojový text
podkladem pro písemku může být výsledek předchozí(ch) úloh(y)
12 bodů
píše se během cvičení, čas tedy max 1 hod (spíše 50 min).
odevzdá se dle pokynů cvičícího
předmětem bude využít předem známé API (existující třídy)
v případě omluvené nepřítomnosti je možné psát v náhradním termínu, který je následující cvičení (neurčí-li cvičící jinak)
písemku zadává a hodnotí cvičící
18 bodů
píše se během cvičení, čas tedy max 1 hod (spíše 50 min).
odevzdá se dle pokynů cvičícího
v případě omluvené nepřítomnosti je možné psát v náhradním termínu, který je následující cvičení (neurčí-li cvičící jinak)
písemku zadává a hodnotí cvičící
![]() | Poznámka |
---|---|
Psáno se svolením autora podle článku P. Adámka "Co se děje v Javě" (publikováno ve sborníku SLT2004) |
Mezi pravidelné změny v každé nové verzi jazyka Java patří zvýšení rychlosti; v tomto směru Java 1.5 nikterak nevybočuje. Kromě několika změn v garbage collectoru a virtuálním stroji je asi nejvýznamnějším prvkem vlastnost nazvaná class data sharing. Tento mechanismus urychluje start virtuálního stroje a šetří paměť v případě několika souběžně spuštěných virtuálních strojů. Princip spočívá v tom, že se při instalaci JRE (nebo Java SDK) spustí virtuální stroj a pak je paměťová oblast vyhrazená pro read-only data uložena do souboru na disk. Při každém dalším spuštění JVM se tato oblast namapuje do paměti, což start výrazně urychlí. Pokud běží více virtuálních strojů naráz, tato oblast je sdílená.
Mechanismus funguje pouze pro klientskou verzi JVM; serverová verze podporována není, což je ale pochopitelné. Serverové aplikace jsou většinou vytvářeny jako vícevláknové v rámci jednoho virtuálního stroje a běží nepřetržitě.
Další věc, která doposud v Javě chybí, je typ {\em enum}. To se doposud řešilo několika způsoby, od definování konstant typu int (což postrádá jakoukoliv typovou kontrolu) až po užívání typově bezpečného řešení založeného na definici třídy pro každý výčtový typ, navrženého v publikaci J. Blocha. Toto řešení ve své nejjednodušší variantě vypadá takto:
public class Color {
private Color() {};
public static final Color red = new Color();
public static final Color green = new Color();
public static final Color blue = new Color();
public static final Color yellow = new Color();
}
Právě na tomto principu je založená implementace výčtového typu, která je od verze 5 přímou součástí jazyka Java. Výčtový typ se zde ale definuje mnohem snadněji:
public enum Color { red, green, blue, yellow };
Pro úplnost je nutné poznamenat, že takto definovaný výčtový typ disponuje mnohem širšími možnostmi než předchozí varianta. Dále viz dokumentace API k Java 5.
Tento nový prvek Jazyka Java umožňuje zdrojový kód rozšířit o doplňkové informace deklarativního charakteru; uplatnění nalezne zejména v J2EE, kde eliminuje nutnost vytváření spousty zbytečného kódu a psaní deployment deskriptorů.
Nyní bude v Javě konečně možné používat mocné formátování výstupu funkcí fprint, známou z jazyků C/C++. Kromě toho jsou rozšířeny možnosti zpracování vstupu.
Toto je další prvek známý z jazyků C/C++; osobně jej považuji za nepříliš důležitý a někteří lidé tvrdí, že byl zaveden jenom kvůli metodě printf (viz předchozí bod).
Ve Windows přibyl nový vzhled pro knihovnu Swing, který vypadá jako Windows XP, v Linuxu pak vzhled, který vypadá jako GTK.
Za tímto téměř marketingovým pojmem se skrývá mnoho drobných ale velice užitečných vylepšení. Patří mezi ně plná podpora XML 1.1 a jmenných prostorů, XML Schémat, SAX 2.0.1, XSLT a XSLTC, DOM level 3, XPath, a další.
Umožní monitorovat a ovládat virtuální stroj pomocí rozhraní JMX.