Obsah
Seznam obrázků
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í písemka.
Celkem 100 bodů
K ukončení zkouškou potřebujete:
70 - 79 - hodnocení "dobře"
80 - 89 - hodnocení "velmi dobře"
90 - 100 - hodnocení "výborně"
K ukončení zápočtem potřebujete:
60 bodů
viz aktuální informace o předmětu PB162 na IS MU
Cvičení se konají pod vedením příslušných kvalifikovaných cvičících v počítačových učebnách. Náplň:
Hlavním obsahem je konzultovaná samostatná práce na bodovaných úlohách.
cvičení jsou jen hodinová, účast na přednáškách je proto žádoucí
předpokládá se i jistý netriviální podíl práce mimo cvičení
Tomáš Pitner
kanc. B307 (3. patro budovy B)
tel. 541512360 (z tlf. mimo budovu), kl. 360 (v budově)
e-mail: [email protected]
Web předmětu:http://www.fi.muni.cz/~tomp/java
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é
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ě
Obsah
Úvod, srovnání s jinými, oblasti použití Javy
Distribuce, instalace, použití Java SDK
Životní cyklus programu v Javě
Základní praktické úkony - velmi podrobně!!!
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)
Javovou platformu tvoří:
Specifikace Javy (tzv. "Editions") - např.: Java 2 Standard Edition, v1.4
Implementace Javy ("Development Kits" nebo "Runtime Environments") - např.: Java 2 Software Development Kit, v1.4.2 - obsahuje vývojové nástroje
Java 2 Runtime Enviroment, v1.4 - obsahuje jen běhové prostředí pro spouštění hotových přeložených pg.
(Stav k září 2003:)
aktuálně vždy na webu java.sun.com
java.sun.com (pro Windows, Solaris, Linux)
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 :-))
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.jar.
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)
javac - překladač (.java -> .class)
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ů
javah - generátor hlavičkových souborů pro C
javap - disassembler bajtkódu (např. pro ruční optimalizace, hledání chyb)
Zdrojový kód každé veřejně přístupné třídy je umístěn v jednom souboru (NazevTridy.java)
vytvoření zdrojového textu (libovolným editorem čistého textu) -> Pokus.java
překlad (nástrojem javac) Pokus.java -> Pokus.class
spuštění, např. java Pokus
překládá se javac název souboru se třídou (včetně přípony .java!!!)
spouští se vždy udáním java a názvu třídy (bez přípony .class!!!)
Zdrojový kód v souboru tomp\ucebnice\Pozdrav.java
package tomp.ucebnice;
public class Pozdrav {
// Program spouštíme aktivací funkce "main"
public static void main(String[] args) {
System.out.println("Ahoj!");
}
}
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 1.4.2
Jsme v adresáři c:\devel\pb162, v něm je podadresář tomp\ucebnice, v něm je soubor Pozdrav.java
Spustíme překladjavac 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 4.2.2 (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!
Systémové proměnné by měly obsahovat:
JAVA_HOME=kořenový adresář instalace Javy, např. JAVA_HOME=c:\j2sdk1.4.2
CLASSPATH=cesty ke třídám (podobně jako v PATH jsou cesty ke spustitelným souborům), např. CLASSPATH=c:\devel\pb162
První cvičení vám pomůže seznámit se s procesem životního cyklus javového programu: zápis a správné umístění zdrojového textu, překlad, spuštění. Proto si jako první krok budete muset vybrat platformu, pod níž budete vaše programy vyvíjet, překládat, spouštět.
Výběr platformy je na vás, ale při odevzdávání hotových úloh se musíte řídit pokyny cvičících, kteří mohou předepsat, v jaké formě zdrojové texty úloh chtějí.
Příklad 2.1.
Vyberte si platformu, na které budete v Javě vyvíjet (Windows, Linux, IRIX...) Na všech uvedených je vývojové prostředí Javy dostupné (na UNIXu např. přes module add java, na Windows bez nastavování přímo z příkazové řádky).
Vyberte si pod zvolenou platformou takový adresář, který bude přístupný ze všech strojů FI stejné platformy, tj. bez ohledu na to, u které konkrétní stanice právě sedíte.
Vytvořte si v něm podadresář pro vývoj v Javě, např. $HOME/pb162.
Cesta k adresáři by měla být přiměřené jednoduchá, neměla by obsahovat mezery a jiné "nepraktické" znaky.
Ve vývojovém podadresáři si vytvořte adresář pro úlohy prvního cvičení (např. $HOME/pb162/cv1). V tomto adresáři budete řešit úlohu prvního cvičení.
Příklad 2.2.
Ve vývojovém adresáři tohoto cvičení si vytvořte podadresář pro balík cz.muni.fi.vášlogin
zkopírujte do něj z předchozích slidů zdrojový kód Pozdrav.java
Upravte v něm deklaraci balíku tak, aby byl zařazen do balíku cz.muni.fi.vášlogin.
Přeložte jej.
Spusťte jej.
Vytvořte ve vývojovém adresáři dávku (compile-cv1.bat, compile-cv1.bash apod.) určenou k překladu zdrojového kódu Pozdrav.java
Vytvořte podobně dávku (run-cv1.bat, run-cv1.bash apod.) pro spuštění třídy vzniklé překladem zdrojového kódu Pozdrav.java.
Vytvořte ve vývojovém adresáři dávku (compile.bat, compile.bash apod.) určenou k překladu libovolného zdrojového kódu v balíku cz.muni.fi.vášlogin. Název zdrojového souboru se bude zadávat jako parametr dávky.
Vytvořte ve vývojovém adresáři dávku (run.bat, run.bash apod.) určenou ke spuštění vybrané třídy z balíku cz.muni.fi.vášlogin. Název třídy se bude zadávat jako parametr dávky.
Celý obsah adresáře cvičení 1 sbalte do souboru pb162-vášlogin-cv1.zip a odevzdejte podle pokynů cvičícího.
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 - jakk 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.
např. všechny objekty třídy Clovek mají vlastnost jmeno,
tato vlastnost má však obecně pro různé lidi různé hodnoty - lidi mají různá jména
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
Třída Clovek má vlastnost jmeno
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);
}
}
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 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();
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.
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:
modifikátoryTypjméno;
např.:
protected int rokNarozeni;
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. Podrobněji viz Datové typy v Javě
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ř.:
protected int rokNarozeni;
je identifikátor se správně (vhodně) utvořeným jménem, zatímco:
protected int RokNarozeni;
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é prvniho
System.out.println(petr.jmeno); // přístup k (čtení) proměnné prvniho
}
}
Existují tady dva objekty, každý má své (obecně různé) hodnoty proměnných - např. jsou různá jména obou lidí.
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).
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.
modifikatorytypVraceneHodnotynazevMetody(
seznamFormalnichParametru) {
tělo (výkonný kód) metody}
seznamFormalnichParametru je tvaru: typParametrunazevFormalnihoParametru, ...
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):
Vrácenou hodnotu musíme uvést za příkazem return. V tomto případě tedy nesmí return chybět!
Typ vrácené hodnoty musíme v hlavičce metody deklarovat.
Nevrací-li metoda nic, pak musíme namísto typu vracené hodnoty psát void.
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ší).
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)
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. Ttakové 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.
Druhé cvičení vás pomůže nacvičit práci s více jednoduchými objekty několika tříd.
Vše, co vytvoříte i překopírujete, budete umisťovat do balíku cz.muni.fi.{vaslogin}.banka a odevzdáte podle pokynů cvičícího. Cvičící si může zadání upravit - toto je jen vzor.
Úkolem bude:
Příklad 3.1.
Ze slidů této přednášky vzít a "přivlastnit" (tj. umístit) do svého balíku třídy Clovek a Ucet.
Třídu Ucet upravit tak, že bude mít další proměnnoumajitel typu Clovek. Tato proměnná ponese odkaz na vlastníka účtu.
Kromě toho bude mít třída Ucet konstruktor se dvěma parametry: majitelem účtu a počátečním stavem/zůstatkem. Konstruktor si odkaz na majitele účtu pochopitelně zapamatuje v příslušné proměnné a zůstatek nastaví.
Do třídy dále přidejte metodu vypisInfo, aby vypisovala informace o zůstatku a o vlastníkovi účtu.
Vytvořte dále třídu Banka s metodou public Ucet vytvorUcet(Clovek maj, double pocatecni). Tato metoda vytvoří pro budoucího majitele maj nový účet a dá do něj počáteční vklad pocatecni. Současně přičte jedničku k celkového počtu zřízených účtů a tento celkový počet vypíše (přes System.out.println). Vytvořený účet vrátí.
Na základě výše uvedených deklarací napište do třídy Banka hlavní metodu programu (main, viz vzor minule) takovou, aby:
vytvořila jednu banku (např. b1)
vytvořila člověka (Petr Novotný, 1949) a (Jan Veselý, 1970)
v metodě main banka b1 vytvořila Petrovi voláním metody vytvorUcet dva účty (pu1: zůstatek 1000, pu2: zůstatek 50000) a Janovi jeden (ju: zůstatek 3000)
z Petrova druhého účtu se převede 1000 na Janův účet
z Janova účtu naopak 500 na Petrův první účet
vypíší se zůstatky na všech účtech
Obsah
Kroky řešení problému na počítači - pár slov o SW inženýrství
Organizace javových pg. - třídy, balíky
„Objektovost“: zapouzření, dědičnost
Modifikátory přístupu k proměnným, metodám, třídám
Deklarace import
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:
Pascal: 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...)
Java (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 zachytit
Zjistíme a zachytíme vztahy mezi objekty našeho zájmu
Zjistíme, které činnosti objekty (aktéři, aktoři) provádějí
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
Určení základních tříd, tj.
skupin (kategorií) objektů, které mají podobné vlastnosti/schopnosti:
Pes
Clovek
...
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
Balíky obvykle organizujeme do hierarchií, např.:
svet
svet.chovatelstvi
svet.chovatelstvi.psi
svet.chovatelstvi.morcata
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!
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 ...
}
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
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 Ucet nazveme KontokorentniUcet
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
public => přístupné odevšad
public class Ucet { ... }
třída Ucet je veřejná = lze např.
vytvořit objekt typu Ucet i v metodě jiné třídy
deklarovat podtřídu třídy Ucet ve stejném i jiném balíku
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é jen ze 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 prvek zneviditelní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
Cílem třetí úlohy je zvládnutí jednoduchého použití dědičnosti v objektovém programu. Hotovým základem je balík tomp.ucebnice.auta, v něm třídy Auto a Demo. Třídy tohoto balíku si stáhněte k sobě a pracujte s nimi lokálně (i když na UNIXech je teoreticky lze namapovat přímo z cesty /home/tomp/public_html/java/ucebnice/javasrc/ a dále.... Auto představuje třídu vozidel - aut, od této třídy zatím nebudeme vytvářet speciálnější typy aut - osobní, nákladní. Demo je třída, která musí fungovat, jakmile jí poskytneme/dopíšeme níže uvedené třídy.
Do balíku tomp.ucebnice.auta.mycky napište třídy Mycka, RucniMycka a MyckaSUdrzbou tak, aby potomkem - podtřídou třídy Mycka byla RucniMycka a jejím potomkem MyckaSUdrzbou.
Třída Mycka musí umět auto umýt (metoda s hlavičkou public double umyj(Auto a) - vrací cenu za umytí jako hodnotu typu double - cenu si zvolte sami dle uvážení...), obdobně navoskovat (metoda navoskovat) a musí nabízet "kompletní program" (metoda kompletniProgram) se slevou 20 % součtu cen jednotlivých služeb v rámci programu.
Třída RucniMycka musí umět totéž jako Mycka a navíc musí umět čištění interiéru (metoda s hlavičkou public double vycistiInterier(Auto a)). Její "kompletní program" (metoda kompletniProgram) bude dostupný za cenu podobně spočtenou jako v předchozí třídě a bude obsahovat navíc čištění interiéru.
Třída MyckaSUdrzbou musí umět totéž jako RucniMycka a navíc musí umět údržbu spodku vozidla (metoda s hlavičkou public double provedUdrzbuSpodku(Auto a)). Její "kompletní program" (metoda kompletniProgram) bude dostupný za cenu podobně spočtenou jako v předchozí třídě a bude obsahovat navíc údržbu spodku.
Výsledkem - Vašim řešením - jsou tedy dopsané třídy umístěné v balíku tomp.ucebnice.auta.mycky.
Pozn: ve třídách balíku tomp.ucebnice.auta.mycky budete v metodách používat třídu Auto - parametr typu Auto. Abyste jej v tomto místě zpřístupnili (je totiž v jiném balíku), musíte napřed deklarací třídy (po package, před class...) uvést import tomp.ucebnice.auta.Auto; - blíže viz slidy.
Obsah
Příkazy v Javě
Řídicí příkazy (větvení, cykly)
Přiřazovací příkaz =
Ří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í = (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é:
Zivocich z1 = new Zivocich();
Co to udělalo?
vytvořilo nový objekt typu Zivocich (new Zivocich())
přiřadilo jej do proměnné z1 typu Zivocich
Nyní můžeme odkaz na tentýž vytvořený objekt znovu přiřadit - do z2:
Zivocich z2 = z1;
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)
identifikace objektu, jehož metodu voláme
. (tečka)
název metody, 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)
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
if (logický výraz) příkaz
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
v těle cyklu je jeden jednoduchý příkaz ...
while (podmínka)
příkaz;
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++;
i
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í operace; vstupní podmínka; příkaz po každém průchodu)
příkaz;
anebo (obvyklejší, bezpečnější)
for (počáteční operace; vstupní podmínka; příkaz po každém průchodu) {
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);
}
Vypíše na obrazovku deset řádků s čísly postupně 0 až 9 Pět pozdravů nebo Výpis prvků pole objektů "for" cyklem
Používejte asymetrické intervaly (ostrá a neostrá nerovnost):
podmínka daná počátečním přiřazením i = 0 a inkrementací i++ je neostrou nerovností, zatímco
opakovací podmínka i < 10 je ostrou nerovností -> i už nesmí hodnoty 10 dosáhnout!
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
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ícecestné větvení
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ěliVí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. Řízení průchodu cyklem pomocí "break" a "continue"
Umožní ještě jemnější řízení průchodu vnořenými cykly
pomocí návěští můžeme naznačit, který cyklus má být příkazem break přerušen nebo
tělo kterého cyklu má být přeskočeno příkazem continue. Návěští
Raději NEPOUŽÍVAT, ale jsou menším zlem než by bylo goto (kdyby v Javě existovalo...), protože nepředávají řízení dále než za konec struktury (cyklu, větvení).
Toto však již neplatí pro break a continue na návěští!
Poměrně často se používá break při sekvenčním vyhledávání prvku
Jednoduché vlastní implementace vyhledávacích a řadicích algoritmů (Lineární a binární vyhledávání)
Úvod
Tato úloha bude používat pole, které se dosud na přednáškách zmiňovalo jen v souvislosti s argumenty metody main, konkrétně v její hlavičce jsme viděli: main(String[] args).
Ani tato úloha ale nebude vyžadovat více znalostí o práci s polem, než jen to, že:
proměnná typu pole čísel float se deklaruje např. float[] poleCisel;
pole samo o sobě (i když obsahuje primitivní hodnoty) je objektového typu, tj. přiřazení poleCisel2 = poleCisel; pouze kopíruje odkaz, neduplikuje celé pole vč. obsahu!
obdobně deklarace proměnné typu pole, např. float[] poleCisel; pole nevytvoří, jen deklaruje odkaz na pole, které musí vzniknout jinak.
Např. voláním new float[10] vytvoříme pole o deseti prvcích typu float, hodnoty jsou všechny 0.
na jednotlivé prvky se, stejně jako ve většině jazyků, odkazujeme přes index prvku, tj. např. poleCisel[1] je druhý prvek pole. V Javě jsou totiž meze indexů polí vždy 0..početprvků-1.
Do takto referencovaného prvku pole lze samozřejmě i zapisovat, tj. poleCisel[1] = 5.2 vloží na druhé místo pole číslo 5.2.
Počet prvků pole najdeme v jeho proměnné length (nezapomínejme, že pole je objekt, může tudíž mít proměnné...),
tj. poleCisel má poleCisel.length prvků, nejvyšší index je poleCisel.length-1.
Část I.Cílem je napsat dvě třídy, jejichž objekty budou umět:
nastavit "do sebe" posloupnost určenou k vyhledávání
zodpovědět, na kterém indexu se nachází určitý prvek
zda se tam hledaný prvek vůbec nachází
Dále pak napsat dvě třídy rozšiřující dvě výše uvedené tak, aby navíc:
umožnily vyhledání maximálního a minimálního prvku
LinearSearcher bude vyhledávat v neuspořádaném poli lineárně - Kostra třídy LinearSearcher: http://www.fi.muni.cz/~tomp/java/ucebnice/javasrc/tomp/searching/LinearSearcher.java
BinarySearcher bude vyhledávat v uspořádaném poli půlením intervalu - algoritmus viz např. kurz (slidy) Úvod do programování(pozn.: nemusíte testovat, zda je zadané pole skutečně uspořádané) - Kostra třídy BinarySearcher: http://www.fi.muni.cz/~tomp/java/ucebnice/javasrc/tomp/searching/BinarySearcher.java
LinearSearcherMinMax bude rozšiřovat LinearSearcher tak, aby uměla vyhledat maximum/minimum - Kostra třídy LinearSearcherMinMax: http://www.fi.muni.cz/~tomp/java/ucebnice/javasrc/tomp/searching/LinearSearcherMinMax.java
BinarySearcherMinMax bude rozšiřovat BinarySearcher tak, aby uměla vyhledat maximum/minimum - Kostra třídy BinarySearcherMinMax: http://www.fi.muni.cz/~tomp/java/ucebnice/javasrc/tomp/searching/BinarySearcherMinMax.java
Kostry tříd uložte do balíku cz.muni.fi.{vaslogin}.searching
Upravte je, aby dělaly, co mají.
Do stejného balíku uložte také třídu Demo z http://www.fi.muni.cz/~tomp/java/ucebnice/javasrc/tomp/searching/Demo.java (je jasné, že uložení do balíku zároveň znamená úpravu deklarace package)
Třídy pro vyhledávání vyzkoušejte spuštěním vaší třídy Demo.
Část II.Řazení probubláváním a quicksort
Cílem je napsat dvě třídy objektů, jejichž objekty budou umět:
nastavit "do sebe" posloupnost určenou k uspořádání (setřídění)
setřídit ji
vrátit ji
BubbleSorter bude pole řadit probubláváním - Kostra třídy BubbleSorter
QuickSorter bude pole řadit metodou rychlého řazení (quicksort) viz např. kurz (slidy) Úvod do programování nebo Návrh algoritmů IKostra třídy QuickSorter
Kostry tříd BubbleSorter a QuickSorter uložte do balíku cz.muni.fi.{vaslogin}.sorting
Upravte je, aby dělaly, co mají.
Do stejného balíku uložte také třídu Demo z http://www.fi.muni.cz/~tomp/java/ucebnice/javasrc/tomp/sorting/Demo.java (je jasné, že uložení do balíku zároveň znamená úpravu deklarace package)
Třídy pro řazení vyzkoušejte spuštěním vaší třídy Demo.
Pozn: Pokud cvičící zadání modifikuje, je to OK. Tohle je vzorové zadání. Za obě části úlohy dohromady získáte opět max. 5 bodů.
Obsah
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 Cislo 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);
}
}
Cislo c1 = new Cislo(1.23456);
Cislo c2 = c1;
c1.zvysO(2);
c1.vypis();
c2.vypis();
3.23456
3.23456
hodnotac1c2
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ů:
integrální typy (obdoba ordinálních typů v Pascalu) - zahrnují typy celočíselné (byte, short, int a long) a typ char;
typy čísel s pohyblivou řádovou čárkou (float a double)
typ logických hodnot (boolean).
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
short (16 bitů), tj. -32768..32767
byte (8 bitů), tj. -128..127
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
float - 32 bitů
double - 64 bitů
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
Přípustné hodnoty jsou false a true.
Na rozdíl od Pascalu na nich není definováno uspořádání.
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
typhodnoty[]jménopole
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ům
jmé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];
Clovek
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();
Nyní jsou v poli lidi naplněny první dva prvky odkazy na objekty.
Zbylé prvky zůstaly naplněny prázdnými odkazy null.
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 pole 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.
Clovek[] lidi2;
lidi2 = lidi1;
V proměnné lidi2 je nyní odkaz na stejné pole jako je v lidi1.
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"
Samozřejmě bychom mohli kopírovat prvky ručně, např. pomocí for cyklu, ale volání System.arraycopy je zaručeně nejrychlejší a přitom stále platformově nezávislou metodou, jak kopírovat pole.
Také arraycopy však do cílového pole zduplikuje jen odkazy na objekty, nevytvoří kopie objektů!
Obsah
Rozhraní
Implementace rozhraní třídou
Implementace více rozhraní jednou třídou
Rozšiřování rozhraní (dědičnost mezi rozhraními)
Rozšiřování více rozhraní (vícenásobná dědičnost mezi rozhraními)
Abstraktní třídy (částečná implementace)
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!
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í implementovat obě 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ř.:
Chovatel ch = new AbstraktniChovatel(...);
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
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...
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):
Spustíme-li bez povolení assert (bez volby -ea nebo naopak s explicitním zákazem: -da), pak program podmínky za assert neověřuje:
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:
Má-li testovaná třída/y chyby a zjistí-li to testovač, vypadá to třeba 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)
Úloha je obdobou té předchozí.
Část Ia. Jde o to zmodifikovat třídy z předchozí úlohy, aby:
třídy LinearSearcher a BinarySearcher byly umístěny v balíku cz.muni.fi.{vaslogin}.searching2
a obě implementovaly rozhraní Searcher v balíku tomp.searching2.
Rozhraní Searcher je zatím v balíku tomp.searching2, přesuňte si je do cz.muni.fi.{vaslogin}.searching2 - nezapomeňte, že s přesunem do jiného balíku musíte změnit deklaraci package.
Jako společného předka těchto tříd v hierarchii dědičnosti použijte třídy AbstractSearcher, která bude (asi neúplně, protože je abstraktní) implementovat rozhraní Searcher.
Část Ib. Dále zmodifikujte třídyXXXMinMax z předchozí úlohy, aby:
byly také umístěny v balíku cz.muni.fi.{vaslogin}.searching2
obě implementovaly rozhraní SearcherMinMax.
Povšimněte si, že rozhraní SearcherMinMax rozšiřuje Searcher.
Upravte třídy z předchozí úlohy, aby dělaly, co mají.
Uložte je do patřičného balíku.
Do stejného balíku uložte také třídu Demo a upravte si ji tak, aby používala vaše třídy XXXSearcher, XXXSearcherMinMax.
Třídy pro vyhledávání vyzkoušejte spuštěním vaší třídy Demo.
Část II. Na závěr reimplementujte (čili použijte a předělejte z úlohy 4.) do balíku cz.muni.fi.{vaslogin}.sorting2 také třídy BubbleSorter a QuickSorter tak, aby obě implementovaly rozhraní Sorter.
Rozhraní Sorter je zatím v tomp.sorting2, přesunete si je jako obvykle do cz.muni.fi.{vaslogin}.sorting2
Zdrojový kód rozhraní Sorter naleznete v balíku tomp.sorting2.
Při reimplementaci využijte společného abstraktního předka - třídu AbstractSorter, kterou si vytvoříte a umístíte do balíku cz.muni.fi.{vaslogin}.sorting2. Tato abstraktní třída bude částečně implementovat rozhraní Sorter - tj. bude implementovat ty metody, jež ve třídách BubbleSorter, QuickSorter vycházejí naprosto stejné.
Z praktických důvodů je vhodné nadeklarovat v této třídě například pomocnou metodu void swap(int i, int j), která prohodí v poli hodnoty na indexech i, j. Kontrolní otázka: Jaký přístupový modifikátor by tato metoda měla/mohla mít?
K reimplementovaným třídám sami vytvořte vhodné demo, které ukáže, že jsou funkční.
Obecné informace
Pozn: Pokud cvičící zadání modifikuje, je to OK. Tohle je vzorové zadání.
Obsah
Operátory v Javě: aritmetické, logické, relační, bitové
Porovnávání primitivních hodnot a objektů, metody equals a hashCode
Ternární operátor podmíněného výrazu
Typové konverze
Operátor zřetězení
logické součiny (AND):
& (nepodmíněný - vždy se vyhodnotí oba operandy),
&& (podmíněný - líné vyhodnocování - druhý operand se vyhodnotí, jen nelze-li o výsledku rozhodnout z hodnoty prvního)
logické součty (OR):
| (nepodmíněný - vždy se vyhodnotí oba operandy),
|| (podmíněný - líné vyhodnocování - druhý operand se vyhodnotí, jen nelze-li o výsledku rozhodnout z hodnoty prvního)
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
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 9.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 9.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();
}
}
Bitové:
součin &
součet |
exkluzivní součet (XOR) ^ (znak "stříška")
negace (bitwise-NOT) ~ (znak "tilda")
Posuny:
vlevo << o stanovený počet bitů
vpravo >> o stanovený počet bitů s respektováním znaménka
vpravo >>> o stanovený počet bitů bez respektování znaménka
Dále viz např. Bitové operátory
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 řetězec null
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)
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)
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()
Kolekce
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é.
Prvky implementují rozhraní Comparable:
Funguje, prvky třídy Clovek jsou porovnatelné, množina je uspořádána podle příjmení lidí.
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 10.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:října
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
Existují tyto starší typy kontejnerů (-> náhrada):
Hashtable -> HashMap, HashSet (podle účelu)
Vector -> List
Stack -> List
Roli iterátoru plnil dříve výčet (enumeration) se dvěma metodami:
boolean hasMoreElements()
Object nextElement()
Demo efektivity práce kontejnerů - Demo kolekcí
Velmi podrobné a kvalitní seznámení s kontejnery najdete na Trail: Collections
V této úloze důkladně procvičíme nejběžnější kontejnery (včetně uspořádaných), iterátory nad nimi a také tvorbu vlastních komparátorů.
Úkolem bude realizovat jednoduchý systémek simulující zasílání zpráv. Vše bude v balíku messaging (cvičící může jméno balíku upřesnit, např. tak, aby obsahovalo vaši identifikaci - login, UČO). Systémek bude obsahovat následují třídy:
osoba, která bude přijímat a odesílat zprávy (Message). K osobě si pamatujeme její jméno a příjmení (řetězce) a odkazy na dvě složky (Folder) na poštu: odeslanou a přijatou. Složky na zprávy se mohou vytvořit ihned s vytvořením osoby. Odeslání zprávy je realizováno jejím vytvořením, naplněním textem (nemusí zadávat přímo uživatel z klávesnice, stačí programově voláním metody), dále pak následuje vložení do složky odeslaných zpráv odesílatele a do složky přijatých zpráv u všech příjemců.
zpráva. Bude obsahovat odkaz na osobu, která ji odeslala (Person) a množinu příjemců (Set), kterým je určena. Dále si při vytvoření zapamatuje aktuální čas (stačí vytvořit a zapamatovat si objekt třídy java.util.Date). Kromě toho ponese pochopitelně také vlastní text zprávy (String).
složka na poštu. Bude si pamatovat svého vlastníka (Person), bude umět přidat do sebe zprávu a vypsat všechny zprávy jednak v pořadí, v jakém byly přijímány, a také podle příjmení odesílatele (vzestupně).
demo třída s metodou main, která vytvoří tři lidi. Poté první a druhý člověk pošlou nějaké zprávy třetímu. Následně vypíšeme zprávy odeslané prvním člověkem (v pořadí odeslání) a zprávy doručené třetímu člověku (v abecedním pořadí podle příjmení odesílatele).
třída implementující rozhraní java.util.Comparator. Objekt této třídy předáváme při vytváření uspořádané množiny, do níž se budou vkládat zprávy v pořadí podle příjmení odesílatele. Metoda compareTo(Object o1, Object o2) tedy musí vracet záporné číslo, je-li příjmení osoby o1 před příjmením o2, kladné číslo v případě, že příjmení osoby o2 předchází příjmení o1. Jsou-li obě příjmení stejná, může vrátit kladné nebo záporné číslo, neměl by však vracet nulu, jsou-li zprávy odlišné. Nulu by měl vrátit pouze a jen tehdy, jsou-li celé zprávy, tj. i text zprávy, její čas odeslání a odesílatel stejné. Kdo by si chtěl pohrát, může při shodnosti příjmení řadit sekundárně podle textu zprávy, data odeslání apod. K implementaci metody compare využijte metodu compareTo třídy String.
Možná realizace výše uvedeného zadání znázorněná jako diagram tříd (navržený v Together 6.0).
Pozn: Pokud cvičící zadání modifikuje, je to OK. Tohle je vzorové zadání. Za úlohu získáte opět max. 5 bodů.
Obsah
Dokumentace javových programů, dokumentace API
Typy komentářů - dokumentační komentáře
Generování dokumentace
Značky javadoc
Distribuční archívy .jar
Vytvoření archívu, metainformace
Spustitelné archívy
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 souborempackage-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]
-help, -verbose
-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
Zdroják s dokumentačními komentáři - Komentáře
Ukázkové spuštení javadoc
javadoc -classpath . -d apidocs svet
Distribucí nemyslíme použití nástroje typu "InstallShield"..., ale spíše něčeho podobného tar/ZIPu
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ý ZIPu (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
c - vytvoří archív
t - vypíše obsah archívu
x - extrahuje archív
u - aktualizuje obsah archívu
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:
pro webové aplikace - .war
pro enterprise (EJB) aplikace - .ear
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
java
-jarNázevBalíku.jar
Nejprve vytvoříme soubor manifestu. Příklad jeho obsahu:
Spuštění aplikace zabalené ve spustitelném archívu je snadné:
java
-jarjardemo.jar
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
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 rámci metody, kde výjimka vznikla - do bloku catch -> výjimka je v bloku catch tzv. zachycena
výjimka "propadne" do nadřazené (volající) metody, kde je buďto v bloku catch zachycena nebo opět propadne atd.
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 catchnenechá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
Klauzule (blok) finally:
Může následovat ihned po bloku try nebo až po blocích catch
Slouží k "úklidu v každém případě", tj.
když je výjimka zachycena blokem catch
i když je výjimka propuštěna do volající metody
Používá se typicky pro uvolnění systémových zdrojů - uzavření souborů, soketů...
Sun Java Tutorial - Lesson: Handling Errors with Exceptions
demo programy z učebnice - Výjimky
V této úloze procvičíme použití kontejnerů, výjimek (vč. definice vlastních tříd výjimek) a také testování s junit.
Úkolem bude realizovat jednoduchou implementaci abstraktního datového typu fronta (queue) a několika souvisejících výjimek. Kromě toho je součástí programu také testovací třída (rozšiřující junit.framework.TestCase) pro junit (používejte aktuální verzi balíku junit dostupnou na http://junit.org).
Program bude obsahovat následují rozhraní a třídy:
Rozhraní fronty. Popisuje operace put (vložení provku do fronty), get (odebrání prvku z fronty), empty (test na prázdnost fronty) a size (počet prvků aktuálně ve frontě).
Obrázek 12.1. Zdrojový text rozhraní Queue
// rozhraní fronty ukládající prvky typu String
public interface Queue {
void put(String s) throws QueueFullException; // vloží prvek do fronty
String get() throws QueueEmptyException; // odebere z fronty prvek a vrátí jej
boolean empty(); // test na prázdnost fronty, vrátí true, je-li prázdná
int size(); // vrátí aktuální počet prvků ve frontě
int freeCapacity(); // vrátí aktuální počet prvků, které ještě lze bez odebírání vložit
void clear(); // vyprázdní frontu
}
Třída implementující rozhraní Queue. Fronta bude fungovat tak, že prvky budou ukládány do vhodného kontejneru (asi List) uvnitř objektu typu QueueImpl. Konstruktor bude mít jeden celočíselný parametr určující celkovou kapacitu fronty (maximální počet současně přítomných prvků ve frontě).
Obecná výjimka při práci s frontou. Nadefinujete si ji sami, v programu se nebudou vytvářet přímo instance tohoto typu, ale až některé ze tříd potomků, viz dále.
Je výjimka, kterou vyhodí operace get (pomocí příkazu throw) v případě, že chceme odebrat prvek z prázdné fronty. Nadefinujete si ji sami, tak, aby byla potomkem QueueException.
Je výjimka, kterou vyhodí operace put v případě, že chceme vložit prvek do fronty, jejíž kapacita je již naplněna. Nadefinujete si ji sami, tak, aby byla potomkem QueueException.
Testovací třída s metodou main, která rovnou spustí junit testovač (spouštěč testů) junit.swingui.TestRunner.
Obrázek 12.2. Zdrojový text metody main třídy QueueTest
// metoda main rovnou aktivující spouštěč testů (TestRunner)
public static void main(String[] args) {
// zjisti jména testovacích tříd (=testů)
String[] testCaseNames = {QueueTest.class.getName()};
// aktivuj spouštěč testů
junit.swingui.TestRunner.main(testCaseNames);
}
Testovací třídu implementujte tak trochu podle svého - nepředepisuji přesně, co a jak důkladě se má testovat. Z povahy třídy QueueImpl však plyne, že by se minimálně mělo otestovat:
že funguje vložení do nenaplněné (např. prázdné fronty);
že naopak nefunguje (tj. vyhodí výjimku) vložení do plné fronty;
taktéž nefunguje (tj. vyhodí výjimku) odebrání z prázdné fronty;
že empty() je true, právě když size() je 0 - a to nastane mj. po zavolání clear();
že freeCapacity() po zkonstruování objektu fronty musí být rovna hodnotě parametru konstruktoru;
a především že fronta se opravdu chová jako fronta, tj. struktura FIFO - prvky jsou odebírány v tom pořadí, v jakém byly vkládány.
Váš cvičící pravděpodobně určí balík, do kterého budete vytvořené třídy (a případně i výchozí rozhraní Queue) ukládat. Stejně tak může upřesnit požadavky na testovací třídu.
Pokud cvičící zadání modifikuje, je to OK. Tohle je vzorové zadání. Za úlohu získáte opět max. 5 bodů.
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
znakové (Reader/Writer) nebo
binární (Stream)
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:
(pro soubor) vrací true, když se podaří soubor vytvořit
(pro adresář) vrací true, když se podaří adresář vytvořit
navíc si dotvoří i příp. neexistující adresáře na cestě
Vytvoření dočasného (temporary) souboru:
vytvoří dočasný soubor ve standardním pro to určeném adresáři (např. c:\temp) s uvedeným prefixem a sufixem názvu
dtto, ale vytvoří dočasný soubor v adr. directory
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:
BufferedReader, CharArrayReader, InputStreamReader, PipedReader, StringReader
LineNumberReader, FileReader, PushbackReader
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
private void readObject(java.io.ObjectInputStream stream) throws IOException, ClassNotFoundException
private void writeObject(java.io.ObjectOutputStream stream) throws IOException
DataOutputStream.writeObject(Object o)
Tutoriály k Java I/O: kapitola z Sun Java Tutorial
Demo programy na serializaci (z učebnice): Serializace objektů
V této úloze procvičíme použití souborů, vstupních a výstupních operací, ale i kontejnerů, výjimek atd.
Úkolem bude realizovat jednoduchý správce osobních kontaktů (adresář), v němž bude uživatel mít možnost uchovávat kontaktní údaje o osobách. O každé osobě bude v adreáři zaznamenáno:
login (jednoznačný) - řetězec
jméno - řetězec
příjmení - řetězec
e-mail - řetězec
telefonní číslo - dlouhé celé číslo
Program by měl určitě obsahovat následují třídy, všechny ve stejném balíku (cvičící určí, jakém):
Třída správce kontaktů.
Bude umět kontakty načíst ze souboru, všechny vypsat a uložit do textového souboru. Soubor, ze kterého se čte a zapisuje, bude určen pro daný správce kontaktů jednou provždy při konstrukci. Zapisuje se do stejného souboru, z něhož se čte. Údaj o jedné osobě bude uložen na právě jednom řádku textového souboru ve formátu: login jméno příjmení e-mail telefon. Jednotlivá pole řádku vstupu jsou oddělena vždy jednou mezerou. Telefon je dlouhé celé číslo, jinak jsou to řetězce. Můžeme předpokládat, že údaje jsou v souboru vždy ve správném formátu, tj. nemusíme chyby vstupu/uživatele detekovat.
Správce kontaktů musí umět zařadit nový kontakt - pozor, login osobu jednoznačně určuje, proto vložením nové osoby s loginem, který už některá stávající osoba má, bude stávající osoba nahrazena.
Dále musí umět vyřadit osobu s daným loginem.
Třída osob.
Každá osoba nese výše uvedené údaje (tj. login, jméno, příjmení, e-mail a telefonní číslo).
Osoba musí umět vypsat nebo vrátit v řetězcové podobě údaje o sobě.
Demo třída. Udělá po řadě následující operace:
Vytvoří správce kontaktů, tj. jeden objekt typu AddressBook, načtením z textového souboru, jehož jméno je zadáno jako první argument na příkazovém řádku při spuštění aplikace. Nad takto inicializovaným správcem provede následující operace:
Načtené kontakty vypíše.
Načte od uživatele, z příkazového řádku (std. vstupu), údaje o jedné osobě (login, jméno, příjmení, e-mail a telefonní číslo), vytvoří osobu a přidá do správce kontaktů.
Načte od uživatele, z příkazového řádku (std. vstupu), login osoby, kterou chce smazat, a osobu s tímto loginem ze správce kontaktů smaže.
Vypíše všechny kontakty.
Uloží všechny kontakty zpět do souboru na disk.
Poznámka | |
---|---|
Pro inspiraci: takto nějak může (ale nemusí) vypadat metoda na načítání kontaktů na osoby ze souboru: |
Poznámka | |
---|---|
Načítání z konzoly (příkazového řádku, standardního vstupu) zrealizujeme takto: |
Poznámka | |
---|---|
Pro parsing (analýzu, "rozebrání") vstupního řádku při načítání kontaktu můžete použít např. metody řetězce indexOf, kterými najdete pozice mezer ve vstupním řádku a metody substring, kterou vyberete část řádku. Rovněž je možné - a je to dokonce méně pracné - využít třídy StringTokenizer - viz dokumentace k Java Core API. Objekt této třídy je schopen pohodlně "rozebrat" řetězec na jednotlivé podřetězce původně oddělené mezerami. |
Poznámka | |
---|---|
Uložení kontaktů do souboru můžete udělat s pomocí FileWriteru. |
Váš cvičící pravděpodobně určí balík, do kterého budete vytvořené třídy ukládat. Stejně tak může upřesnit požadavky na demo třídu.
Pokud cvičící zadání modifikuje, je to OK. Tohle je vzorové zadání. Za úlohu získáte opět max. 5 bodů.
Obsah
Komponenty GUI v Javě
Řízení událostmi a asynchronní programování
Typy událostí
Posluchači událostí, anonymní vnitřní třídy
V Javě lze psát přenositelné aplikace s "okenním" rozhraním - s GUI
při jejich vývoji se s výhodou uplatní prostředí IDE - „builder“, např. JBuilder, Sun Studio ONE, NetBeans...
V tomto kurzu budeme pracovat s moderním Swing GUI, což je součást JFC (Java Foundation Classes). Starší variantou - dosud živou v Javě verzí 1.1.x, je grafické rozhraní nad komponentami AWT (Abstract Windowing Toolkit).
Základním principem tvorby aplikací s GUI je řízení programu událostmi.
Netýká se však pouze GUI, je to obecnější pojem označující typ asynchronního programování, kdy je:
tok programu řízen událostmi;
události nastávají obvykle určitou uživatelskou akcí - klik či pohyb myši, stisk tlačítka...;
událostmi řízené aplikace musí být většinou programovány jako vícevláknové (i když spouštění vláken obvykle explicitně programovat nemusíme).
Postup (životní cyklus události):
Událost vznikne (typicky uživatelskou akcí nad komponentou GUI).
Na komponentu musí být "zavěšen" posluchač dané události (event listener).
Systém vyvolá příslušnou metodu posluchače - my tu metodu obvykle smysluplně implementujeme tak, aby realizovala potřebnou akci.
Viz též příklad - První GUI aplikace.
Technicky jsou dotyčné třídy a rozhraní komponent definovány obvykle v balících:
java.awt - základní komponenty GUI AWT
java.awt.event - události GUI AWT
a v ostatních balících java.awt.*
javax.swing - základní komponenty GUI Swing - rozšíření AWT
javax.swing.event - události GUI Swing
a v mnoha ostatních balících javax.swing.*
Události mohou souviset s uživatelskou akcí nad/s:
oknem - WindowEvent
klávesnicí - KeyEvent
myší (klikání a pohyb) - MouseEvent
získáním nebo ztrátou fokusu - FocusEvent
(obecnou) akcí nad GUI (stisk tlačítka v GUI) - ActionEvent
Aby událost mohla být ošetřena, tj. mohlo se na ni někde reagovat, je třeba k dané komponentně přidat objekt posluchače událostí.
Velmi často - a skoro nikde jinde - se jako objekt posluchače používá objektu anonymní vnitřní třídy:
Třída je (jakoby „on-the-fly“) definována a ihned - jen jedenkrát! - použita.
Ve skutečnosti samozřejmě se daná třída (její bajtkód) vytvoří a přeloží s ostatními hned ve fázi překladu mateřské třídy.
V případě posluchačů události obvykle vnitřní třída má jen jednu metodu.
Proč vůbec pomocí vnitřní třídy?
vnitřní třída má přístup k (i chráněným) prvkům mateřské třídy!
poněkud nepřehledné, třída je skryta v ostatním kódu
navíc: pokud si speciálně nepamatujeme odkaz na jednou vytvořený a zapojený posluchač, pak jej nelze z paměti odstranit - nemáme na něj odkaz
V následujícím úryvku kodu se:
okno.addWindowListener(
new WindowListener() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
}
);
...vytvoří jedna instance anonymní vnitřní třídy a ta se předá/použije jako posluchač událostí.
class MyWindowListener implements WindowListener {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
}
...
okno.addWindowListener(new MyWindowListener());
Tvorba Swing-GUI aplikací - Trail: Creating a GUI with JFC/Swing
Tvorba appletů - Trail: Writing Applets
Vytváření aplikací přístupných i uživatelům s omezeními:
potíže se zrakem
problémy s ovládáním myši...viz JFC-Accessibility
Vynikající článek o GUI v Javě: Ray Toal:Developing Complex User Interface Applications in Java
písemky se píší přímo do počítače
výsledkem je přeložitelný kód: 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í