Programování v jazyce Java

Tomáš Pitner


Obsah

1. Informace o průběhu, hodnocení, apod.
O předmětu...
Složky hodnocení předmětu
Kritéria hodnocení předmětu
Obsah (sylabus) předmětu
Cvičení
O přednášejícím...
Konzultační hodiny
Informační zdroje
2. Úvod do jazyka Java
Java jako programovací jazyk...
Z toho plyne, že...
Další charakteristiky
Java jako běhové prostředí
Java pro programátora (1)
Java pro programátora (2)
Hlavní domény Javy (1)
Hlavní domény Javy (2)
Javová platforma
Java je tedy dána...
Vývoj Javy
Specifikace a implementace Javy
Verze Javy
Aktuální verze
Získání distribuce Javy
Stažení distribuce Sun
Obsah vývojové distribuce Javy
Obsah vývojové distribuce Javy (2)
Nástroje ve vývojové distribuci
Pomocné nástroje ve vývojové distribuci
Základní životní cyklus javového programu
Struktura javového programu
Demo "Ahoj!"
Překlad a spuštění "Ahoj!"
Vytvoření zdrojového textu "Ahoj!" ("for dummies")
Překlad "Ahoj!" ("for dummies")
Spuštění "Ahoj!" ("for dummies")
Co znamená spustit program?
Praktické informace (aneb co je nutné udělat)
Praktické informace (aneb co je vhodné udělat)
Úloha do cvičení 1.
Odkazy
3. Úvod do objektového programování
Co je třída a objekt?
Vlastnosti objektu (1)
Vlastnosti objektu (2)
Příklad - deklarace třídy Clovek
Příklad - použití třídy Clovek (1)
Příklad - použití třídy Clovek (2)
Proměnné - deklarace
Proměnné - datový typ
Proměnné - jmenné konvence
Proměnné - použití
Proměnné - modifikátory přístupu
Proměnné - použití (2)
Vytváření objektů
Metody
Metody - příklad
Volání metod
Volání metod - příklad
Parametry metod
Předávání skutečných parametrů metodám
Příklad předávání parametrů - primitivní typy
Příklad předávání parametrů - objektové typy (1)
Příklad předávání parametrů - objektové typy (2)
Návrat z metody
Konstruktory
Konstruktory (2)
Přetěžování
Přetěžování - příklad
Přetěžování - příklad (2)
Přetěžování - příklad (3)
Shrnutí
Odkazy na objekty
Přiřazování objektových proměnných
Vracení odkazu na sebe
Řetězení volání
Proměnné a metody třídy - statické
Příklad statické proměnné a metody
Úloha do cvičení 2.
4. Objektové modelování v Javě
Kroky řešení reálného problému na počítači
Vývoj software je proces...
Celkový rámec vývoje SW
Metodiky vývoje SW
Metodika typu "vodopád"
Srovnání Java - Pascal
Organizace programových souborů
Organizace zdrojových souborů
Příklad - svět chovatelů a jejich psů
Shromáždění informací o realitě
Jak zachytíme tyto informace
Modelování reality pomocí tříd
Zápis třídy do zdrojového souboru
Organizace tříd do balíků
Balíky
Příslušnost třídy k balíku
Dědičnost
Terminologie dědičnosti
Jak zapisujeme dědění
Deklarace import NázevTřídy
Deklarace import názevbalíku.*
Opakování - vlastnosti tříd
Příklad
Příklad - co tam bylo nového
Přístupová práva
Granularita omezení přístupu
Typy omezení přístupu
Kde jsou která omezení aplikovatelná?
Příklad - public
Příklad - protected
Příklad - přátelský
Příklad - private
Když si nevíte rady
Přístupová práva a umístění deklarací do souborů
Zadání úlohy 3. (zadává se v týdnu od 13. října)
5. Příkazy a řídicí struktury v Javě
Příkazy v Javě
Přiřazení v Javě
Přiřazení primitivní hodnoty
Přiřazení odkazu na objekt
Volání metody
Návrat z metody
Řízení toku programu v těle metody
Cyklus s podmínkou na začátku
Doporučení k psaní cyklů/větvení
Příklad použití "while" cyklu
Cyklus s podmínkou na konci
Příklad použití "do-while" cyklu
Cyklus "for"
Příklad použití "for" cyklu
Doporučení k psaní for cyklů
Vícecestné větvení "switch - case - default"
Vnořené větvení
Vnořené větvení (2)
Řetězené "if - else if - else"
Příkazy "break"
Příkaz "continue"
"break" a "continue" s návěštím
Doporučení k příkazům break a continue
Zadání úlohy 4. (zadává se v týdnu od 20. října)
6. Datové typy v Javě
Primitivní vs. objektové datové typy - opakování
Přiřazení proměnné primitivního typu - opakování
Přiřazení objektové proměnné - opakování
Primitivní datové typy
Integrální typy - celočíselné
Integrální typy - "char"
Typ char - kódování
Čísla s pohyblivou řádovou čárkou
Vestavěné konstanty s pohyblivou řádovou čárkou
Typ logických hodnot - boolean
Typ void
Pole v Javě
Pole (2)
Pole - co když deklarujeme, ale nevytvoříme?
Pole - co když deklarujeme, vytvoříme, ale nenaplníme?
Kopírování polí
7. Objektové modelování v Javě - pokračování
Rozhraní
Co je rozhraní
Deklarace rozhraní
Implementace rozhraní
Využití rozhraní
Implementace více rozhraní současně
Implementace více rozhraní současně - příklad
Rozšiřování rozhraní
Rozšiřování rozhraní - příklad
Rozhraní - poznámky
Abstraktní třídy
Abstraktní třídy (2)
Příklad rozhraní - abstraktní třída - neabstraktní třída
8. Ladění a testování programů
Ladění programů v Javě
Ještě lepší...
Postup při práci s assert
Ukázka použití assert (1)
Ukázka použití assert (2)
Ukázka použití assert (3)
Ukázka použití assert (4)
Postup při práci s JUnit
Ukázka použití JUnit (1)
Ukázka použití JUnit (2)
Ukázka použití JUnit (3)
Ukázka použití JUnit (4)
Postup při práci s jass
Odkazy
Zadání úlohy 5. (zadává se v týdnu od 3. listopadu)
9. Operátory a výrazy, porovnávání objektů
Aritmetické
Logické
Relační (porovnávací)
Porovnávání objektů
Porovnávání objektů - příklad
Metoda hashCode
Metoda hashCode - příklad
Bitové
Operátor podmíněného výrazu ? :
Operátory typové konverze (přetypování)
Operátor zřetězení +
Priority operátorů a vytváření výrazů
10. Kontejnery
Kontejnery
Základní kategorie kontejnerů
Kontejnery - rozhraní, nepovinné metody
Kontejnery - souběžný přístup, výjimky
Iterátory
Kolekce
Seznamy
Seznamy a obyčejné iterátory - příklad
Iterátor po seznamu - příklad
Množiny
Množina - příklad
Uspořádané množiny
Uspořádaná množina - příklad s chybou
Uspořádaná množina - příklad OK
Mapy
Mapa - příklad
Uspořádané mapy
Uspořádaná mapa - příklad
Uspořádaná mapa - příklad s komparátorem
Srovnání implementací kontejnerů
Historie
Odkazy
Zadání úlohy 6. (zadává se v týdnu od 17. listopadu)
11. Dokumentace a distribuce aplikací
Dokumentace javových programů
Typy komentářů
Kde uvádíme dokumentační komentáře
Generování dokumentace
Značky javadoc
Příklad zdrojového textu se značkami javadoc
Spouštění javadoc
Příklady
Distribuce aplikací
Spuštění jar
Volby jar
jar - příklad
Rozšíření .jar archívů
Tvorba spustitelných archívů
Vytvoření spustitelného archívu - příklad
Spuštění archívu - příklad
Další příklad spuštění jar
12. Výjimky
Co a k čemu jsou výjimky
Výjimky technicky
Syntaxe kódu s ošetřením výjimek
Syntaxe metody propouštějící výjimku
Reakce na výjimku
Kaskády výjimek
Kategorizace výjimek a dalších chybových objektů
Vlastní hierarchie výjimek
Klauzule finally
Odkazy
Zadání úlohy 7. (zadává se v týdnu od 1. prosince)
13. Vstupy a výstupy v Javě
Koncepce vstupně/výstupních operací v Javě
Práce se soubory
Třída File
Třída File (2)
Třída File (3)
Práce s adresáři
Práce s binárními proudy
Vstupní binární proudy
Důležité neabstraktní třídy odvozené od InputStream
Další vstupní proudy
Práce se znakovými proudy
Výstupní proudy
Konverze: znakové <-> binární proudy
Serializace objektů
Odkazy
Zadání úlohy 8. (zadává se v týdnu od 15. prosince)
14. Událostmi řízené programování v Javě
Komponenty GUI v Javě
Událostmi řízené programování
Řízení událostmi
Kde to všechno je?
Typy událostí vznikajících v GUI
Přidání posluchače uálosti
Příklad - událost zavření okna
Totéž bez anonymity...
Příklad
Další odkazy
15. Písemky
První písemka
Druhá písemka
Třetí (zkoušková) písemka

Seznam obrázků

8.1. Třídy AssertDemo, Zlomek
8.2. Správný postup překladu AssertDemo
8.3. Spuštění AssertDemo s povolením assert
8.4. Spuštění AssertDemo bez povolení assert
8.5. Upravená třída Zlomek s porovnáním a součtem
8.6. Testovací třída JUnitDemo
8.7. Spouštění testovače nad testovací třídou JUnitDemo
8.8. Testovač spouštějící třídu JUnitDemo
8.9. Testovací třída JUnitDemo našla chybu
9.1. Dva lidi jsou stejní, mají-li stejná příjmení
9.2. Třída Clovek s metodami equals a hashCode
10.1. Pohyb po seznamu iterátorem
10.2. Spuštění pg. s iterátorem
10.3. Pohyb seznamovým iterátorem
10.4. Spuštění pg. se seznamovým iterátorem
10.5. Vložení prvků do množiny a dotaz na přítomnost
10.6. Spuštění pg. s množinou
10.7. Vložení neporovnatelných prvků do uspořádané množiny
10.8. Spuštění nefunkčního pg. s uspořádanou množinou
10.9. Vložení porovnatelných prvků do uspořádané množiny
10.10. Spuštění funkčního pg. s uspořádanou množinou
10.11. Vložení lidí do mapy pod klíčem příjmení a vyhledání člověka
10.12. Spuštění funkčního pg. s mapou
10.13. Vložení lidí do mapy pod uspořádaným klíčem příjmení a projde je
10.14. Spuštění funkčního pg. s uspořádanou mapou
10.15. Vložení účtů do mapy pod uspořádaným klíčem člověka - vlastníka
10.16. Spuštění funkčního pg. s uspořádanou mapou
11.1. Volby nástroje JAR
11.2. Třída JarDemo
11.3. Vytvoření archívu se všemi soubory z podadresáře tomp/ucebnice/jar
11.4. .jar archiv v okně PowerArchiveru
11.5. Vybalení všech souborů z archívu
11.6. Soubor manifestu
11.7. Zabalení archívu s manifestem
11.8. Spuštění aplikace z arhcivu
12.1. Zdrojový text rozhraní Queue
12.2. Zdrojový text metody main třídy QueueTest
13.1. Příklad načítání kontaktů ze souboru
13.2. Načtení řetězce ze standardního vstupu

Seznam příkladů

2.1.
2.2.
3.1.

Kapitola 1. Informace o průběhu, hodnocení, apod.

  • Souvislosti, prerekvizity, návaznosti

  • Hodnocení a jeho složky

  • Kontakty a konzultační hodiny

  • Odkazy na informační zdroje

O předmětu...

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

Složky hodnocení předmětu

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ů

[1][2]

Kritéria hodnocení předmětu

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ů

Obsah (sylabus) předmětu

Cvičení

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í

O přednášejícím...

Tomáš Pitner

Konzultační hodiny

  • vždy v B307

    • Čt 14.00 - 15.00 (preferovaná doba)

    • Pá 14.00 - 15.00

  • nebo jindy, dle dohody

Informační zdroje



Kapitola 2. Úvod do jazyka Java

  • Ú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ě!!!

Java jako programovací jazyk...

  • jazyk "3. generace - 3GL" (imperativní jazyk vysoké úrovně)

  • 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í)

  • jednodušší než C++

  • reálným soupeřem je (Microsoft) C# (zatím převážně na platf. Windows)

Z toho plyne, že...

  • co se naučíme v Javě, v C# jako když najdeme...

  • ale teď vážně: Java podporuje vytváření správných návyků v objektovém programování

  • a naopak systematicky brání přenosu některých špatných návyků z jiných jazyků

Další charakteristiky

  • 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

Java jako běhové prostředí

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

Java pro programátora (1)

  • 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++

  • dnes již stejně aktivních programátorů v Javě jako 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

Java pro programátora (2)

Konkrétní možnosti:

  • v Javě se dobře píší vícevláknové aplikace (multithreaded applications)

  • Java má automatické odklizení nepoužitelných objektů (automatic garbage collection)

  • Java je jednodušší než C++ (méně syntaktických konstrukcí, méně nejednoznačností v návrhu)

Hlavní domény Javy (1)

  • Š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)

  • Výukové účely (nahrazuje Pascal jako referenční jazyk)

  • Další přenositelné aplikace - např. klientské/desktopové

Hlavní domény Javy (2)

  • webové aplikace (servlety, JSP - konkurence proprietárním ASP, SSI, ... a pomalým CGI)

  • zpracování semistrukturovaných dat (XML)

  • přenositelné aplikace s GUI

  • aplikace distribuované po síti (applety nebo Java Web Start)

Javová platforma

Javovou platformu tvoří:

  • Java Virtual Machine

  • překladač (přístupný např. příkazem javac) a další vývojové nástroje

  • Java Core API (základní knihovna tříd)

Java je tedy dána...

  • definicí jazyka (Java Language Definition) - syntaxe a sémantika jazyka

  • popisem chování JVM

  • popisem Java Core API

Vývoj Javy

  • nejrychleji se vyvíjí Java Core API

  • chování JVM se mění např. pokud se objeví bezpečnostní "díra" nebo nelze-li dosáhnout požadované změny chování pomocí modifikace Java Core API

  • daleko konzervativnější je samotný jazyk - mění se zřídka, ale přece: např. Java2, v1.4 přidáván nové klíčové slovo "assert", v1.5 přidá enum a další.

Specifikace a implementace Javy

  • 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.

Verze Javy

  • hrubé členění - na verze "Java (před Java 2)" a "Java 2"

  • číslování verzí:

    • tzv. major číslo, např. Java 2, v1.4

    • tzv. minor číslo, např. Java 2, v1.4.2

  • změnu minor (třetího) čísla doprovází jen odstraňování chyb

  • při změně major (druhého) čísla se může měnit Core API a někdy i jazyk

  • ke změně prvního čísla zatím nedošlo...

Aktuální verze

(Stav k září 2003:)

  • Java 2 Standard Edition v1.4.2 pro všechny platformy

  • aktuálně vždy na webu java.sun.com

Získání distribuce Javy

  • používání Javy pro běžný vývoj (i komerční) je zdarma

  • redistribuce javového vývojového prostředí je dovolena pouze s licencí od Sunu

  • redistribuce javového běhového prostředí je možná zdarma

  • distribuce vyvíjí Sun Microsystems Inc. (Javasoft) i další výrobci (např. IBM) a tvůrci Open Source

Stažení distribuce Sun

  • 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 :-))

Obsah vývojové distribuce Javy

  • 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.

Obsah vývojové distribuce Javy (2)

  • 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.

Nástroje ve vývojové distribuci

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ů

Pomocné nástroje ve vývojové distribuci

  • javah - generátor hlavičkových souborů pro C

  • javap - disassembler bajtkódu (např. pro ruční optimalizace, hledání chyb)

Základní životní cyklus javového programu

  • Program sestává z jedné (ale obvykle více) tříd (class)

  • Zdrojový kód každé veřejně přístupné třídy je umístěn v jednom souboru (NazevTridy.java)

  • Postup:

    • vytvoření zdrojového textu (libovolným editorem čistého textu) -> 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!!!)

Struktura javového programu

  • Každý netriviální javový program sestává z více tříd (class)

  • Třídy jsou členěny do balíků (package)

  • Zařazení do balíků znamená mj. umístění zdrojového souboru do příslušného adresáře!!!

  • U běžné "desktopové" aplikace představuje vždy jedna (evt. více) tříd vstupní bod do programu - je to třída/y obsahující metodu main.

Demo "Ahoj!"

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 a spuštění "Ahoj!"

Překlad

  1. Máme nainstalován J2SDK 1.4.2

  2. Jsme v adresáři c:\devel\pb162, v něm je podadresář tomp\ucebnice, v něm je soubor Pozdrav.java

  3. Spustíme překladjavac tomp\ucebnice\Pozdrav.java

  4. Je-li program správně napsán, přeloží se "mlčky"

  5. (výsledný .class soubor bude v témže adresáři jako zdroj)

Spuštění

  1. Poté spustíme program Pozdrav: java -classpath . tomp.ucebnice.Pozdrav

  2. 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ář.

  3. -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 .)

  4. Je-li program správně napsán a přeložen, vypíše se Ahoj!

Vytvoření zdrojového textu "Ahoj!" ("for dummies")

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)

Překlad "Ahoj!" ("for dummies")

Překlad překladačem javac (úspěšný, bez hlášení překladače)

Spuštění "Ahoj!" ("for dummies")

Spuštění voláním java

Co znamená spustit program?

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!

Praktické informace (aneb co je nutné udělat)

Cesty ke spustitelným programům (PATH) musejí obsahovat i adresář JAVA_HOME\bin

Praktické informace (aneb co je vhodné udělat)

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

Úloha do cvičení 1.

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.

  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).

  2. 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.

  3. Vytvořte si v něm podadresář pro vývoj v Javě, např. $HOME/pb162.

  4. Cesta k adresáři by měla být přiměřené jednoduchá, neměla by obsahovat mezery a jiné "nepraktické" znaky.

  5. 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.

  1. Ve vývojovém adresáři tohoto cvičení si vytvořte podadresář pro balík cz.muni.fi.vášlogin

  2. zkopírujte do něj z předchozích slidů zdrojový kód Pozdrav.java

  3. Upravte v něm deklaraci balíku tak, aby byl zařazen do balíku cz.muni.fi.vášlogin.

  4. Přeložte jej.

  5. Spusťte jej.

  6. 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

  7. 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.

  8. 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.

  9. 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.

  10. Celý obsah adresáře cvičení 1 sbalte do souboru pb162-vášlogin-cv1.zip a odevzdejte podle pokynů cvičícího.

Kapitola 3. Úvod do objektového programování

  • 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)

Co je třída a objekt?

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

Příklad:

  • Třída Clovek má vlastnost jmeno

  • Objekt panProfesor typu Clovek má vlastnost jmeno s hodnotou "Václav Klaus".

Vlastnosti objektu (1)

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

Vlastnosti objektu (2)

Proměnné

  1. jsou nositeli "pasivních" vlastností; jakýchsi atributů, charakteristik objektů

  2. de facto jde o datové hodnoty svázané (zapouzdřené) v objektu

Metody

  1. jsou nositeli "výkonných" vlastností; "dovedností" objektů

  2. de facto jde o funkce (procedury) pracující (převážně) nad proměnnými objektu

Příklad - deklarace třídy Clovek

  • deklarujme třídu objektů - lidí

    public class Clovek {
        protected String jmeno;
        protected int rokNarozeni;
        public Clovek(String j, int rN) {
            jmeno = j;
            rokNarozeni = rN;
        }
        public void vypisInfo() {
            System.out.println("Clovek:");
            System.out.println("Jmeno="+jmeno);
            System.out.println("Rok narozeni="+rokNarozeni);
        }
    }
    
  • Použijme ji v programu -

    1. vytvořme instanci - objekt - typu Clovek

    2. vypišme informace o něm

Příklad - použití třídy Clovek (1)

Mějme deklarovánu třídu Clovek

Metoda main v následujícím programu:

  1. vytvoří 2 lidi (pomocí new Clovek)

  2. 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ů.

Příklad - použití třídy Clovek (2)

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.

Proměnné - deklarace

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;

Proměnné - datový typ

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.

Proměnné - jmenné konvence

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é - použití

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é
    }
}       

Proměnné - modifikátory přístupu

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)

Proměnné - použití (2)

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í.

Vytváření objektů

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).

Metody

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).

Metody - příklad

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);
    }
}

Volání metod

  • 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.

Volání metod - příklad

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.

Parametry metod

V deklaraci metody uvádíme v její hlavičce tzv. formální parametry.

Syntaxe:

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.

Předávání skutečných parametrů metodám

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!

Příklad předávání parametrů - primitivní typy

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!

Příklad předávání parametrů - objektové typy (1)

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 předávání parametrů - objektové typy (2)

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();
    }

Návrat z metody

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é...

Konstruktory

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í.

Konstruktory (2)

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

Přetěžování

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.

Přetěžování - příklad

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.

Přetěžování - příklad (2)

  • Často přetížená metoda volá jinou "verzi" metody se stejným názvem:

        public void prevedNa(Ucet u) {
            prevedNa(u, zustatek);
        }
    
  • Toto je jednodušší, přehlednější, udělá se tam potenciálně méně chyb.

    Lze doporučit. Je to přesně postup divide-et-impera, rozděl a panuj, dělba práce mezi metodami!

Přetěžování - příklad (3)

  • 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ší).

Shrnutí

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)

Odkazy na objekty

Deklarace proměnné objektového typu ještě žádný objekt nevytváří.

To se děje až příkazem - operátorem - new.

  • Proměnné objektového typu jsou vlastně odkazyna dynamicky vytvořené objekty.

  • Přiřazením takové proměnné zkopírujeme pouze odkaz, nikoli celý objekt.

Přiřazování objektových proměnných

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();
    }

Vracení odkazu na sebe

Metoda může vracet odkaz na objekt, nad nímž je volána pomocí

return this;

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;
    }
}

Řetězení volání

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
    }

Proměnné a metody třídy - statické

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říklad statické proměnné a metody

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.

Úloha do cvičení 2.

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.

  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.

  2. 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.

  3. 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í.

  4. 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

Kapitola 4. Objektové modelování v Javě

  • 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

Kroky řešení reálného problému na počítači

Generický (univerzální, obecný...) model postupu:

  1. Zadání problému

  2. Shromáždění informací o realitě a jejich analýza

  3. Modelování reality na počítači a implementace požadovaných operací nad modelovanou realitou

Vývoj software je proces...

(podle JS, SW inženýrství):

  1. při němž jsou uživatelovy potřeby

  2. transformovány na požadavky na software,

  3. tyto jsou transformovány na návrh,

  4. návrh je implementován pomocí kódu,

  5. kód je testován, dokumentován a certifikován pro operační použití.

Celkový rámec vývoje SW

V tomto předmětu nás z toho bude zajímat jen něco a jen částečně:

  1. Specifikace (tj. zadání a jeho formalizace)

  2. Vývoj (tj. návrh a vlastní programování)

  3. částečně Validace (z ní především testování)

    1. Specifikace SW: Je třeba definovat funkcionalitu SW a operační omezení.

    2. Vývoj SW: Je třeba vytvořit SW, který splňuje požadavky kladené ve specifikaci.

    3. Validace SW: SW musí být validován („kolaudován“), aby bylo potvrzeno, že řeší právě to, co požaduje uživatel.

    4. Evoluce SW: SW musí být dále rozvíjen, aby vyhověl měnícím se požadavkům zákazníka.

Metodiky vývoje SW

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á

  • ...

Metodika typu "vodopád"

Nevracím se nikdy o více jak jednu úroveň zpět:

  1. Analýza (Analysis)

  2. Návrh (Design)

  3. Implementace (Implementation)

  4. Testování (Testing)

  5. Nasazení (Deployment)

Nyní zpět k Javě a jednoduchým programům...

Srovnání Java - Pascal

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.

Organizace programových souborů

  • v Pascalu: zdrojové (.pas) soubory, výsledný (jeden) spustitelný soubor (.exe), resp. přeložené kódy jednotek (.tpu)

  • v Javě: zdrojové (.java) soubory, přeložené soubory v bajtkódu (.class) - jeden z nich spouštíme

Organizace zdrojových souborů

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)

Příklad - svět chovatelů a jejich psů

Zkusme naznačit, jak bychom realizovali jednoduchý systém, který bude

  1. shromažďovat, ukládat a na požádání zpřístupňovat informace o psech (+ jejich výcviku, očkování...)

  2. o jejich chovatelích

  3. a dalších souvisejících entitách (např. chovatelských sdruženích, veterinářích,...)

Shromáždění informací o realitě

Zjistíme, jaké typy objektů se ve zkoumaném výseku reality vyskytují a které potřebujeme zachytit

člověk, pes, veterinář

Zjistíme a zachytíme vztahy mezi objekty našeho zájmu

člověk-chovatel vlastní psa

Zjistíme, které činnosti objekty (aktéři, aktoři) provádějí

veterinář psa očkuje, pes štěká, kousne člověka...

Jak zachytíme tyto informace

neformálními prostředky - tužkou na papíře vlastními slovy v přirozeném jazyce

formálně pomocí nějakého vyjadřovacího aparátu - např. grafického jazyka

pomocí CASE nástroje přímo na počítači

Zatím se přidržíme neformálního způsobu...

Modelování reality pomocí tříd

Určení základních tříd, tj.

skupin (kategorií) objektů, které mají podobné vlastnosti/schopnosti:

  • Pes

  • Clovek

  • ...

Zápis třídy do zdrojového souboru

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

Organizace tříd do balíků

Třídy zorganizujeme do balíků.

V balíku jsou vždy umístěny související třídy.

Co znamená související?

třídy, jejichž objekty spolupracují - člověk, úřad

třídy na podobné úrovni abstrakce - chovatel, domácí zvíře

třídy ze stejné části reality - chovatel psů, pes

Balíky

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í.

Příslušnost třídy k balíku

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!

Dědičnost

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 dědičnosti

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.

Jak zapisujeme dědění

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 NázevTřídy

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!

Deklarace import názevbalí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

Opakování - vlastnosti tříd

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)

  • všechny vlastnosti (metody, proměnné) nadtřídy

  • + vlastnosti uvedené přímo v deklaraci podtřídy

Příklad

Cíl: vylepšit třídu Ucet

Postup:

  1. Zdokonalíme náš příklad s účtem tak, aby si účet "hlídal", kolik se z něj převádí peněz

  2. 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); 
            // }
    }
    

    Příklad kompletního zdrojového kódu třídy

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.

Příklad - co tam bylo nového

  • 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řístupová práva

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.

Granularita omezení přístupu

Přístup je v Javě regulován jednotlivě po prvcích

ne jako v C++ po blocích

Omezení přístupu je určeno uvedením jednoho z tzv. modifikátoru přístupu (access modifier) nebo naopak neuvedením žádného.

Typy omezení přístupu

  • Existují čtyři možnosti:

    • public = veřejný

    • protected = chráněný

    • modifikátor neuveden = říká se lokální v balíku nebo chráněný v balíku nebo "přátelský"

    • private = soukromý

Kde jsou která omezení aplikovatelná?

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

Příklad - public

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

Příklad - protected

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)

Příklad - přátelský

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.

Příklad - private

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()

Když si nevíte rady

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!

Přístupová práva a umístění deklarací do souborů

  • 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

Zadání úlohy 3. (zadává se v týdnu od 13. října)

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.

Kapitola 5. Příkazy a řídicí struktury v Javě

  • Příkazy v Javě

  • Řídicí příkazy (větvení, cykly)

Příkazy v Javě

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

Přiřazení v Javě

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

Přiřazení primitivní hodnoty

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

např. int -> short

Přiřazením primitivní hodnoty se hodnota zduplikuje ("opíše") do proměnné na levé straně.

Přiřazení odkazu na objekt

Konstrukci = lze použít i pro přiřazení do objektové proměnné:

Zivocich z1 = new Zivocich();

Co to udělalo?

  1. vytvořilo nový objekt typu Zivocich (new Zivocich())

  2. 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é!!!

Volání metody

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)

Návrat z metody

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

Řízení toku programu v těle metody

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

Cyklus s podmínkou na začátku

Tělo cyklu se provádí tak dlouho, dokud platí podmínka

obdoba while v Pascalu

v těle cyklu je jeden jednoduchý příkaz ...

while (podmínka) 
    příkaz;      

... nebo příkaz složený

while (podmínka) { 
    příkaz1; 
    příkaz2; 
    příkaz3;
    ... 
} 

Tělo cyklu se nemusí provést ani jednou - pokud už hned na začátku podmínka neplatí

Doporučení k psaní cyklů/větvení

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

Pišme proto vždy takto:

while (i < a.length) {
    System.out.println(a[i]); i++; 
} 

Příklad použití "while" cyklu

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:

Celočíselné dělení se zbytkem

Cyklus s podmínkou na konci

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);

Příklad použití "do-while" cyklu

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); 
        

Načítej, dokud není zadáno číslo

Cyklus "for"

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; 
    ... 
} 

Příklad použití "for" cyklu

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

Doporučení k psaní for cyklů

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

for (int i = 0; ...

ale rozepsat takto:

int i; 
for (i = 0; ... 

potom je proměnná i přístupná ("viditelná") i mimo cyklus - za cyklem, což se však ne vždy hodí.

Vícecestné větvení "switch - case - default"

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í

Vnořené 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 {
        ...
    }
}

Vnořené větvení (2)

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!!!

Vnořené větvení

Řetězené "if - else if - else"

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!!!

Řetězené if

Příkazy "break"

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"

Příkaz "continue"

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"

"break" a "continue" s návěštím

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í

Doporučení k příkazům break a continue

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

Zadání úlohy 4. (zadává se v týdnu od 20. října)

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

Jak se budou třídy lišit:

  1. 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

  2. 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

  3. 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

  4. 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

Jak postupovat:

  1. Kostry tříd uložte do balíku cz.muni.fi.{vaslogin}.searching

  2. Upravte je, aby dělaly, co mají.

  3. 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)

  4. 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

Jak se budou dvě třídy lišit:

Jak postupovat:

  1. Kostry tříd BubbleSorter a QuickSorter uložte do balíku cz.muni.fi.{vaslogin}.sorting

  2. Upravte je, aby dělaly, co mají.

  3. 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)

  4. 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ů.

Kapitola 6. Datové typy v Javě

  • 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ů

Primitivní vs. objektové datové typy - opakování

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řiřazení proměnné primitivního typu - opakování

  • Příklad:

    float a = 1.23456;
    float b = a;
    a += 2;
    

    a = 1.23456b2a3.23456b1.23456

Přiřazení objektové proměnné - opakování

  • 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);
        }
    }
    
  • nyní ji použijeme:

    Cislo c1 = new Cislo(1.23456);
    Cislo c2 = c1;
    c1.zvysO(2);
    c1.vypis();
    c2.vypis();
    
    3.23456
    3.23456
    

    hodnotac1c2

Primitivní datové typy

Proměnné těchto typů nesou elementární, z hlediska Javy atomické, dále nestrukturované hodnoty.

Deklarace takové proměnné (kdekoli) způsobí:

  1. rezervování příslušného paměťového prostoru (např. pro hodnotu int čtyři bajty)

  2. zpřístupnění (pojmenování) tohoto prostoru identifikátorem proměnné

V Javě existují tyto skupiny primitivních typů:

  1. integrální typy (obdoba ordinálních typů v Pascalu) - zahrnují typy celočíselné (byte, short, int a long) a typ char;

  2. typy čísel s pohyblivou řádovou čárkou (float a double)

  3. typ logických hodnot (boolean).

Integrální typy - celočíselné

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 6482147483647

větší rozsah (64 bitů) má long, cca +/- 9*10^18

menší rozsah mají

  • 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

Integrální typy - "char"

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

Typ char - kódování

Java vnitřně kóduje znaky a řetězce v UNICODE, pro vstup a výstup je třeba použít některou za serializací (převodu) UNICODE na sekvence bajtů:

  • např. vícebajtová kódování UNICODE: UTF-8 a UTF-16

  • osmibitová kódování ISO-8859-x, Windows-125x a pod.

Problém může nastat při interpretaci kódování znaků národních abeced 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).

Čísla s pohyblivou řádovou čárkou

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

Vestavěné konstanty s pohyblivou řádovou čárkou

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

Typ logických hodnot - boolean

Přípustné hodnoty jsou false a true.

Na rozdíl od Pascalu na nich není definováno uspořádání.

Typ void

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ě

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 (2)

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.

Pole - co když deklarujeme, ale nevytvoříme?

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)

Pole - co když deklarujeme, vytvoříme, ale nenaplníme?

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)!

Kopírování polí

V Javě obecně přiřazení proměnné objektového typu vede pouze k duplikaci odkazu, nikoli celého odkazovaného objektu.

Nejinak je tomu u polí, tj.:

Clovek[] lidi2; 
lidi2 = lidi1; 

V proměnné 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ů!

Kapitola 7. Objektové modelování v Javě - pokračování

  • 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)

Rozhraní

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í

Co je 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 - 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...)

Deklarace rozhraní

  • 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();
}

Implementace rozhraní

Příklad

public class Clovek implements Informujici {
    ...
    public void vypisInfo() {
        ...
    }
}

Čteme: Třída Clovek implementuje rozhraní Informujici.

  1. Třída v hlavičce uvede implements NázevRozhraní

  2. Třída implementuje všechny metody předepsané rozhraním

Využití rozhraní

  1. Potřebujeme-li u jisté proměnné právě jen funkcionalitu popsanou určitým rozhraním,

  2. 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!

Implementace více rozhraní současně

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...

Implementace více rozhraní současně - příklad

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() {
        ... 
    }
} 

Rozšiřování rozhraní

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

Rozšiřování rozhraní - příklad

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
    }
}

Rozhraní - poznámky

  • 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

Abstraktní třídy

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řídy (2)

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(...);

Příklad rozhraní - abstraktní třída - neabstraktní třída

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.

Kapitola 8. Ladění a testování programů

  • 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

Ladění programů v Javě

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ště lepší...

  • je používat systémy pro běhovou kontrolu platnosti podmínek:

    • vstupní podmínka metody (zda je volána s přípustnými parametry)

    • výstupní podmínka metody (zda jsou dosažené výstupy správné)

    • a podmínka kdekoli jinde - např. invariant cyklu...

  • K tomuto slouží jednak

    • standardní klíčové slovo (od JDK1.4) assert booleovský výraz

    • testovací nástroje typu JUnit (a varianty - HttpUnit,...) - s metodami assertEquals() apod.

    • pokročilé nástroje na běhovou kontrolu platnosti invariantů, vstupních, výstupních a dalších podmínek - např. jass (Java with ASSertions), http://csd.informatik.uni-oldenburg.de/~jass/.

  • Ukázka testu jednotky:Test třídy ChovatelPsu

Postup při práci s assert

Postup:

  1. 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šší.

  2. Přeložit jej s volbou -source 1.4

  3. 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!!!).

  4. Dojde-li za běhu programu k porušení podmínky stanovené za assert, vznikne běhová chyba (AssertionError) a program skončí.

Ukázka použití assert (1)

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.

Obrázek 8.1. Třídy AssertDemo, Zlomek

Třídy AssertDemo, Zlomek

Ukázka použití assert (2)

Program přeložíme (s volbou -source 1.4):

Obrázek 8.2. Správný postup překladu AssertDemo

Správný postup překladu AssertDemo

Ukázka použití assert (3)

Program spustíme (s volbou -ea nebo selektivním -ea:NázevTřídy):

Obrázek 8.3. Spuštění AssertDemo s povolením assert

Spuštění AssertDemo s povolením assert

Ukázka použití assert (4)

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:

Obrázek 8.4. Spuštění AssertDemo bez povolení assert

Spuštění AssertDemo bez povolení assert

Postup při práci s JUnit

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:

  1. Stáhnout si z http://junit.org poslední (stačí binární) distribuci testovacího prostředí.

  2. Nainstalovat JUnit (stačí rozbalit do samostatného adresáře).

  3. Napsat testovací třídu/třídy - buďto implementují rozhraní junit.framework.Test nebo obvykleji rovnou rozšiřují třídu junit.framework.TestCase

  4. Testovací třída obsahuje metodu na nastavení testu (setUp), testovací metody (testNeco) a úklidovou metodu (tearDown).

  5. Testovací třídu spustit v prostředí (řádkovém nebo GUI) - junit.textui.TestRunner, junit.swingui.TestRunner...

  6. Testovač zobrazí, které testovací metody případně selhaly.

Ukázka použití JUnit (1)

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).

Obrázek 8.5. Upravená třída Zlomek s porovnáním a součtem

Upravená třída Zlomek s porovnáním a součtem

Ukázka použití JUnit (2)

Testovací třída JUnitDemo má „přípravnou“ metodu setUp, tearDown a testovací metody.

Obrázek 8.6. Testovací třída JUnitDemo

Testovací třída JUnitDemo

Ukázka použití JUnit (3)

Spuštění testovače v prostředí GUI Swing nad testovací třídou JUnitDemo.

Obrázek 8.7. Spouštění testovače nad testovací třídou JUnitDemo

Spouštění testovače nad testovací třídou JUnitDemo

Pokud testovací třída prověří, že testovaná třída/y je/jsou OK, vypadá to přibližně takto:

Obrázek 8.8. Testovač spouštějící třídu JUnitDemo

Testovač spouštějící třídu JUnitDemo

Ukázka použití JUnit (4)

Má-li testovaná třída/y chyby a zjistí-li to testovač, vypadá to třeba takto:

Obrázek 8.9. Testovací třída JUnitDemo našla chybu

Testovací třída JUnitDemo našla chybu

Postup při práci s jass

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!

Zadání úlohy 5. (zadává se v týdnu od 3. listopadu)

Ú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.

Jak postupovat:

  1. Upravte třídy z předchozí úlohy, aby dělaly, co mají.

  2. Uložte je do patřičného balíku.

  3. 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.

  4. 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í.

Za tyto úlohy dohromady získáte max. 5 bodů.

Kapitola 9. Operátory a výrazy, porovnávání objektů

  • 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í

Aritmetické

+, -, *, / a % (zbytek po celočíselném dělení)

Logické

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):

  • !

Relační (porovnávací)

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

Porovnávání objektů

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.

Porovnávání objektů - příklad

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.

Metoda hashCode

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á.

Metoda hashCode - příklad

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é

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

Operátor podmíněného výrazu ? :

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.

Operátory typové konverze (přetypování)

  • 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.

Operátor zřetězení +

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)

Priority operátorů a vytváření výrazů

nejvyšší prioritu má násobení, dělení, nejnižší přiřazení

Kapitola 10. Kontejnery

  • 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

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í

K čemu slouží?

  • 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

Základní kategorie kontejnerů

  • 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íč

Kontejnery - rozhraní, nepovinné metody

  • 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

Kontejnery - souběžný přístup, výjimky

  • 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

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

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

Seznamy

  • 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

Seznamy a obyčejné iterátory - příklad

Vytvoříme seznam, naplníme jej a chodíme po položkách iterátorem.

Obrázek 10.1. Pohyb po seznamu iterátorem

Pohyb po seznamu iterátorem

Obrázek 10.2. Spuštění pg. s iterátorem

Spuštění pg. s iterátorem

Iterátor po seznamu - příklad

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.

Obrázek 10.3. Pohyb seznamovým iterátorem

Pohyb seznamovým iterátorem

K procházení seznamovým iterátorem lze použít metody next, previous.

Obrázek 10.4. Spuštění pg. se seznamovým iterátorem

Spuštění pg. se seznamovým iterátorem

Množiny

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)

Množina - příklad

Vložíme prvky do množiny a ptáme se, zda tam jsou:

Obrázek 10.5. Vložení prvků do množiny a dotaz na přítomnost

Vložení prvků do množiny a dotaz na přítomnost

Obrázek 10.6. Spuštění pg. s množinou

Spuštění pg. s množinou

Uspořádané množiny

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.

Uspořádaná množina - příklad s chybou

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:

Obrázek 10.7. Vložení neporovnatelných prvků do uspořádané množiny

Vložení neporovnatelných prvků do uspořádané množiny

Obrázek 10.8. Spuštění nefunkčního pg. s uspořádanou množinou

Spuštění nefunkčního pg. s uspořádanou množinou

Nefunguje, prvky třídy Clovek nebyly porovnatelné.

Uspořádaná množina - příklad OK

Prvky implementují rozhraní Comparable:

Obrázek 10.9. Vložení porovnatelných prvků do uspořádané množiny

Vložení porovnatelných prvků do uspořádané množiny

Obrázek 10.10. Spuštění funkčního pg. s uspořádanou množinou

Spuštění funkčního pg. s uspořádanou množinou

Funguje, prvky třídy Clovek jsou porovnatelné, množina je uspořádána podle příjmení lidí.

Mapy

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)

Mapa - příklad

Lidi se do mapy vkládají s klíčem = příjmení člověka, pak se přes příjmení mohou vyhledat:

Obrázek 10.11. Vložení lidí do mapy pod klíčem příjmení a vyhledání člověka

Vložení lidí do mapy pod klíčem příjmení a vyhledání člověka

Obrázek 10.12. Spuštění funkčního pg. s mapou

Spuštění funkčního pg. s mapou

Uspořádané mapy

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.

Uspořádaná mapa - příklad

Jsou-li klíče uspořádané (pomocí implementace Comparable nebo komparátorem), mohou se prvky procházet podle uspořádání klíčů:

Obrázek 10.13. Vložení lidí do mapy pod uspořádaným klíčem příjmení a projde je

Vložení lidí do mapy pod uspořádaným klíčem příjmení a projde je

Obrázek 10.14. Spuštění funkčního pg. s uspořádanou mapou

Spuštění funkčního pg. s uspořádanou mapou

Uspořádaná mapa - příklad s komparátorem

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");
        }
    }
}

Obrázek 10.16. Spuštění funkčního pg. s uspořádanou mapou

Spuštění funkčního pg. s uspořádanou mapou

Srovnání implementací kontejnerů

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

Historie

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()

Odkazy

Demo efektivity práce kontejnerů - Demo kolekcí

Velmi podrobné a kvalitní seznámení s kontejnery najdete na Trail: Collections

Zadání úlohy 6. (zadává se v týdnu od 17. listopadu)

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:

Person

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ů.

Message

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).

Folder

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

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).

MessagesBySenderComparator

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ů.

Kapitola 11. Dokumentace a distribuce aplikací

  • 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

Dokumentace javových programů

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é).

Typy komentářů

Podobně jako např. v C/C++:

řádkové

od značky // do konce řádku, nepromítnou se do dokumentace API

blokové

začínají /* pak je text komentáře, končí */ na libovolném počtu řádků

dokumentační

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í.

Kde uvádíme dokumentační komentáře

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.

Generování dokumentace

Dokumentace má standardně podobu HTML stránek (s rámy i bez)

Dokumentace je generována nástrojem javadoc z

  1. dokumentačních komentářů

  2. 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

  1. volbami (options) při spuštění,

  2. 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.

Značky javadoc

javadoc můžeme podrobněji instruovat pomocí značek vkládaných do dokumentačních komentářů, např.:

@author

specifikuje autora API/programu

@version

označuje verzi API, např. "1.0"

@deprecated

informuje, že prvek je zavrhovaný

@exception

popisuje informace o výjimce, kterou metoda propouští ("vyhazuje")

@param

popisuje jeden parametr metody

@since

uvedeme, od kdy (od které verze pg.) je věc podporována/přítomna

@see

uvedeme odkaz, kam je také doporučeno nahlédnout (související věci)

Příklad zdrojového textu se značkami javadoc

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.

Spouštění javadoc

  • javadoc [options] [packagenames] [sourcefiles] [classnames] [@files]

  • možné volby:

    • -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

Příklady

Zdroják s dokumentačními komentáři - Komentáře

Ukázkové spuštení javadoc

javadoc -classpath . -d apidocs svet

vytvoří dokumentaci tříd z balíku svet do adresáře apidocs

Distribuce aplikací

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...

Spuštění jar

  • 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

  • volby:

    • v - verbose

    • 0 - soubory nekomprimuje

    • f - pracuje se se souborem, ne se "stdio"

    • m - přibalí metainformace z manifest-file

  • parametr files uvádí, které soubory se sbalí - i nejavové (např. typicky dokumentace API - HTML, datové soubory)

Volby jar

Volby JAR lze vypsat i spuštěním jar bez parametrů:

Obrázek 11.1. Volby nástroje JAR

Volby nástroje JAR

jar - příklad

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:

Obrázek 11.2. Třída JarDemo

Třída JarDemo

Vytvoříme archív se všemi soubory z podadresáře tomp/ucebnice/jar (s volbou c - create, v - verbose, f - do souboru):

Obrázek 11.3. Vytvoření archívu se všemi soubory z podadresáře tomp/ucebnice/jar

Vytvoření archívu se všemi soubory z podadresáře tomp/ucebnice/jar

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...

Obrázek 11.4. .jar archiv v okně PowerArchiveru

.jar archiv v okně PowerArchiveru

Tento archív rozbalíme v adresáři /temp následujícím způsobem:

Obrázek 11.5. Vybalení všech souborů z archívu

Vybalení všech souborů z archívu

Rozšíření .jar archívů

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

Tvorba spustitelných archívů

Vytvoříme jar s manifestem obsahujícím tento řádek:

Main-Class:NázevSpouštěnéTřídy

poté zadáme:

java 
-jarNázevBalíku
.jar

a spustí se metoda main třídy NázevSpouštěnéTřídy.

Vytvoření spustitelného archívu - příklad

Nejprve vytvoříme soubor manifestu. Příklad jeho obsahu:

Obrázek 11.6. Soubor manifestu

Soubor manifestu

Následně zabalíme archív s manifestem:

Obrázek 11.7. Zabalení archívu s manifestem

Zabalení archívu s manifestem

Spuštění archívu - příklad

Spuštění aplikace zabalené ve spustitelném archívu je snadné:

java 
-jar
 jardemo.jar

a spustí se metoda main třídy tomp.ucebnice.jar.JarDemo:

Obrázek 11.8. Spuštění aplikace z arhcivu

Spuštění aplikace z arhcivu

Další příklad spuštění jar

  • jar tfv svet.jar | more

  • vypíše po obrazovkách obsah (listing) archívu svet.jar

Kapitola 12. Výjimky

  • 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

Co a k čemu jsou výjimky

  • podobně jako v C/C++, Delphi

  • výjimky jsou mechanizmem, jak psát robustní, spolehlivé programy odolné proti chybám "okolí" - uživatele, systému...

  • v Javě jsou výjimky ještě lépe implementovány než v C++ (navíc klauzule finally)

  • není dobré výjimkami "pokrývat" chyby programu samotného - to je hrubé zneužití

Výjimky technicky

  • Výjimka (Exception) je objekt třídy java.lang.Exception

  • Objekty - výjimky - jsou vytvářeny (vyvolávány) buďto

    • automaticky běhovým systémem Javy, nastane-li nějaká běhová chyba, např. dělení nulou, nebo

    • jsou vytvořeny samotným programem, zdetekuje-li nějaký chybový stav, na nějž je třeba reagovat - např. do metody je předán špatný argument

  • Vzniklý objekt výjimky je předán buďto:

    1. v rámci metody, kde výjimka vznikla - do bloku catch -> výjimka je v bloku catch tzv. zachycena

    2. 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

  • -> pokud není, běh JVM skončí s hlášením o výjimce

Syntaxe kódu s ošetřením výjimek

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.

Syntaxe metody propouštějící výjimku

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 ... 
}

Reakce na výjimku

Jak můžeme reagovat?

  1. Napravit příčiny vzniku chybového stavu - např. znovu nechat načíst vstup

  2. Poskytnout za chybný vstup náhradu - např. implicitní hodnotu

  3. Operaci neprovést („vzdát“) a sdělit chybu výše tím, že výjimku „propustíme“ z metody

Výjimková pravidla:

  1. Vždy nějak reagujme! Neignorujme, nepotlačujme, tj.

  2. blok catchnenechávejme prázdný, přinejmenším vypišme e.printStackTrace()

  3. 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

Kaskády výjimek

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!

Kategorizace výjimek a dalších chybových objektů

  • 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

Vlastní hierarchie výjimek

Klauzule finally

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ů...

Příklad: Úklid se musí provést v každém případě...

Odkazy

Zadání úlohy 7. (zadává se v týdnu od 1. prosince)

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:

Queue

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
}  
QueueImpl

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ě).

QueueException

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.

QueueEmptyException

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.

QueueFullException

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.

QueueTest

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ů.

Kapitola 13. Vstupy a výstupy v Javě

  • 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ů

Koncepce vstupně/výstupních operací v Javě

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)

Blíže viz dokumentace API balíků java.io, java.nio.

Práce se soubory

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

Třída File

Vytvoření konstruktorem - máme několik možností:

new File(String filename)

vytvoří v aktuálním adresáři soubor s názvem filename

new File(File baseDir, String filename)

vytvoří v adresáři baseDir soubor s názvem filename

new File(String baseDirName, String filename)

vytvoří v adresáři se jménem baseDirName soubor s názvem filename

new File(URL url)

vytvoří soubor se (souborovým - file:) URL url

Testy existence a povahy souboru:

boolean exists()

test na existenci souboru (nebo adresáře)

boolean isFile()

test, zda jde o soubor (tj. ne adresář)

boolean isDirectory()

test, zda jde o adresář

Test práv ke čtení/zápisu:

boolean canRead()

test, zda lze soubor číst

boolean canWrite()

test, zda lze do souboru zapisovat

Třída File (2)

Vytvoření souboru nebo adresáře:

boolean createNewFile()

(pro soubor) vrací true, když se podaří soubor vytvořit

boolean mkdir()

(pro adresář) vrací true, když se podaří adresář vytvořit

boolean mkdirs()

navíc si dotvoří i příp. neexistující adresáře na cestě

Vytvoření dočasného (temporary) souboru:

static File createTempFile(String prefix, String suffix)

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

static File createTempFile(String prefix, String suffix, File directory)

dtto, ale vytvoří dočasný soubor v adr. directory

Zrušení:

boolean delete()

zrušení souboru nebo adresáře

Přejmenování (ne přesun mezi adresáři!):

boolean renameTo(File dest)

přejmenuje soubor nebo adresář

Třída File (3)

Další vlastnosti:

long length()

délka (velikost) souboru v bajtech

long lastModified()

čas poslední modifikace v ms od začátku éry - podobně jako systémový čas vracený System.currentTimeMillis().

String getName()

jen jméno souboru (tj. poslední část cesty)

String getPath()

celá cesta k souboru i se jménem

String getAbsolutePath()

absolutní cesta k souboru i se jménem

String getParent()

adresář, v němž je soubor nebo adresář obsažen

Blíže viz dokumentace API třídy File.

Práce s adresáři

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

Práce s binárními proudy

Vstupní jsou odvozeny od abstraktní třídy InputStream

Výstupní jsou odvozeny od abstraktní třídy OutputStream

Vstupní binární proudy

Uvedené metody, kromě abstract byte read(), nemusejí být nutně v neabstraktní podtřídě překryty.

void close()

uzavře proud a uvolní příslušné zdroje (systémové "file handles" apod.)

void mark(int readlimit)

poznačí si aktuální pozici (později se lze vrátit zpět pomocí reset())...

boolean markSupported()

...ale jen když platí tohle

abstract int read()

přečte bajt (0-255 pokud OK; jinak -1, když už není možné přečíst)

int read(byte[] b)

přečte pole bajtů

int read(byte[] b, int off, int len)

přečte pole bajtů se specifikací délky a pozice plnění pole b

void reset()

vrátí se ke značce nastavené metodou mark(int)

long skip(long n)

přeskočí zadaný počte bajtů

Důležité neabstraktní třídy odvozené od InputStream

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!):

BufferedInputStream

proud s vyrovnávací pamětí (je možno specifikovat její optimální velikost)

java.util.zip.CheckedInputStream

proud s kontrolním součtem (např. CRC32)

javax.crypto.CipherInputStream

proud dešifrující data ze vstupu

DataInputStream

má metody pro čtení hodnot primitivních typů, např. float readFloat()

java.security.DigestInputStream

počítá současně i haš (digest) čtených dat, použitý algoritmus lze nastavit

java.util.zip.InflaterInputStream

dekomprimuje (např. GZIPem) zabalený vstupní proud (má ještě specializované podtřídy)

LineNumberInputStream

doplňuje informaci o tom, ze kterého řádku vstupu čteme (zavrhovaná - deprecated - třída)

ProgressMonitorInputStream

přidává schopnost informovat o průběhu čtení z proudu

PushbackInputStream

do proudu lze data vracet zpět

Další vstupní proudy

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();
javax.sound.sampled.AudioInputStream

vstupní proud zvukových dat

ByteArrayInputStream

proud dat čtených z pole bajtů

PipedInputStream

roura napojená na "protilehlý" PipedOutputStream

SequenceInputStream

proud vzniklý spojením více podřízených proudů do jednoho virtuálního

ObjectInputStream

proud na čtení serializovaných objektů

Práce se znakovými proudy

základem je abstraktní třída Reader, konkrétními implementacemi jsou:

  • BufferedReader, CharArrayReader, InputStreamReader, PipedReader, StringReader

  • LineNumberReader, FileReader, PushbackReader

Výstupní proudy

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:

PrintStream

poskytuje metody pro pohodlný zápis hodnot primitivních typů a řetězců - příkladem jsou System.out a System.err

PrintWriter

poskytuje metody pro pohodlný zápis hodnot primitivních typů a řetězců

Konverze: znakové <-> binární proudy

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.nio
 
Charset 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.

Serializace objektů

  • 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

  • metody:

    • DataOutputStream.writeObject(Object o)

Odkazy

Tutoriály k Java I/O: kapitola z Sun Java Tutorial

Demo programy na serializaci (z učebnice): Serializace objektů

Zadání úlohy 8. (zadává se v týdnu od 15. prosince)

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):

AddressBook

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.

Person

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

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.

[Note]Poznámka

Pro inspiraci: takto nějak může (ale nemusí) vypadat metoda na načítání kontaktů na osoby ze souboru:

Obrázek 13.1. Příklad načítání kontaktů ze souboru

BufferedReader br = new BufferedReader(new FileReader(f));
String line = br.readLine();
while (line != null) {
    Person p = Person.readPerson(line);
    people.put(p.login, p);
    line = br.readLine();
}
br.close();
[Note]Poznámka

Načítání z konzoly (příkazového řádku, standardního vstupu) zrealizujeme takto:

Obrázek 13.2. Načtení řetězce ze standardního vstupu

BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
...
br.readLine(); // vlastní načtení řádku
...
br.readLine(); // a další načtení řádku
[Note]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.

[Note]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ů.

Kapitola 14. Událostmi řízené programování v Javě

  • Komponenty GUI v Javě

  • Řízení událostmi a asynchronní programování

  • Typy událostí

  • Posluchači událostí, anonymní vnitřní třídy

Komponenty GUI v Javě

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).

Událostmi řízené programování

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).

Řízení událostmi

Postup (životní cyklus události):

  1. Událost vznikne (typicky uživatelskou akcí nad komponentou GUI).

  2. Na komponentu musí být "zavěšen" posluchač dané události (event listener).

  3. 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.

Kde to všechno je?

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.*

Typy událostí vznikajících v GUI

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

Přidání posluchače uálosti

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.

Příklad - událost zavření okna

Proč vůbec pomocí vnitřní třídy?

výhody

vnitřní třída má přístup k (i chráněným) prvkům mateřské třídy!

nevýhody

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í.

Totéž bez anonymity...

class MyWindowListener implements WindowListener {
    public void windowClosing(WindowEvent e) {
        System.exit(0);
    }
}
    ...
okno.addWindowListener(new MyWindowListener());

Další odkazy

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:

Vynikající článek o GUI v Javě: Ray Toal:Developing Complex User Interface Applications in Java

Kapitola 15. Písemky

Obecné komentáře ke všem písemkám

  • 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)

První písemka

  • 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í

Druhá písemka

  • 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í

Třetí (zkoušková) písemka

  • 30 bodů

  • píše se ve zkouškovém období, čas i s kontrolou max 1:45 hod.

  • přihlašuje se na ni jako na zkouškový termín

  • odevzdá se dle pokynů zadávajícího

  • písemku zadává a hodnotí vyučující předmětu nebo osoba pověřená - ne nutně váš cvičící