Soubory, proudy, serializace

Download Report

Transcript Soubory, proudy, serializace

Vaše jistota na trhu IT
Soubory a proudy
Rudolf Pecinovský
[email protected]
Copyright © 2008, Rudolf Pecinovský
1
Vaše jistota na trhu IT
Koncepce čtení a ukládání dat
Copyright © 2008, Rudolf Pecinovský
2
Terminologie
► Soubor
Entita (objekt) operačního systému
sloužící jako obálka pro úschovu dat
► Datový proud
Objekt programu sloužící ke zprostředkování
přenosu dat mezi zdrojem a cílem
● Program může být zdrojem, cílem, či obojím
► Serializovatelnost
Schopnost objektu být odeslán datovým proudem
a zrekonstruovat se po přijetí zpět na
plnohodnotný objekt
► Persistence
Schopnost objektu uchovat svůj stav mezi dvěma seancemi
Copyright © 2008, Rudolf Pecinovský
3
Koncepce
► Java odděluje práci se soubory od práce s jejich obsahem
a do jisté míry i od vlastní serializace
► Práce se soubory:
● Vytvoření, přesun, přejmenování, odstranění, …
● Zjištění názvu, velikosti, přístupových práv, …
► Práce s obsahem
● Otevření proudu
● Čtení a zápis dat
● Spláchnutí (flush) a zavření proudu
► Serializace
● Ukládání objektů a jejich rekonstrukce po opětném načtení
včetně případných odkazů
● Řešení problémů s citlivými či zbytečně ukládanými daty
● Řešení problémů verzemi tříd
● Řešení problémů s objekty kontrolujícími vytváření svých instanci
Copyright © 2008, Rudolf Pecinovský
4
Historie
► Na počátku byla v balíčku java.io
definována práce se soubory a s proudy bajtů
► Koncepce proudů založena na vzoru Dekorátor
► Při práci s proudy znaků v národních abecedách problémy =>
ve verzi 1.2 zavedeny znakové proudy
► Pro zvýšení efektivity nízkoúrovňových blokových operací byl ve
verzi 1.4 přidán balíček java.nio
● Po přidání toho balíčku byly původní proudy z java.io upraveny tak,
aby jej využívaly => nevyžadujeme-li zvýšenou efektivitu a škálovatelnost,
není třeba měnit kód
Copyright © 2008, Rudolf Pecinovský
5
Vaše jistota na trhu IT
Práce se soubory –
třída java.io.File
Copyright © 2008, Rudolf Pecinovský
6
Charakteristika třídy File
1/2
► Neměnný hodnotový typ
► Abstraktní reprezentace cesty k souboru či složce
● Nereprezentuje soubor, ale jeho název
► Díky abstrakci umožňuje definovat práci se soubory a složkami
nezávisle na platformě a jejích specifikách
● Je schopna se přizpůsobit různým oddělovačům souborů na cestě
(/ versus \) a cest v seznamu (: versus ;)
● Je schopna jednotně zacházet s různými přístupy
ke kořenovým složkám
● Je schopna se vypořádat s různými interpretacemi vlivu
velikosti znaků na vlastní cestu
● Umí správně reagovat na zadání relativní i absolutní cesty
Copyright © 2008, Rudolf Pecinovský
7
Charakteristika třídy File
2/2
► Instance třídy File reprezentuje jak soubor, tak složku
● O koho se konkrétně jedná zjistíme až dotazem
► Instance může reprezentovat i neexistující soubor
● Zda existuje zjistíme dotazem
► Dotazy na reálné soubory a jejich vlastnosti jsou hlídány
bezpečnostním správcem (security manager)
Copyright © 2008, Rudolf Pecinovský
8
Konstruktory a tovární metody
► Konstruktory
● File(String pathname)
● File(String parent, String child)
● File(File parent, String child)
● File(URI uri)
► Statické tovární metody
● createTempFile(String prefix, String suffix)
● createTempFile(String prefix, String suffix,
File directory)
● File[] listRoots()
► Převody
● URI
● URL
toURI()
toURL() Deprecated – NEPOUŽÍVAT,
Místo toho vytvořit URI a ten pak převést metodou toURL()
Copyright © 2008, Rudolf Pecinovský
9
Relativní – absolutní – kanonická cesta
► Cesty (String)
● String getName()
● String getParent()
● String getPath()
● String toString()
● String getAbsolutePath()
● String
► Soubory
● File
● File
● File
getParentFile()
getAbsoluteFile()
getCanonicalFile()
► Dotazy
● boolean
isAbsolute()
getCanonicalPath()
► Kanonická cesta zaručuje,
že instance reprezentující shodnou kanonickou cestu
odkazují na týž soubor
Copyright © 2008, Rudolf Pecinovský
10
Soubory × složky
► Dotazy
● boolean
● boolean
● boolean
isDirectory()
isFile()
isHidden()
► Obsah složky – názvy
● String[]
● String[]
list()
list(FilenameFilter filter)
► Obsah složky – soubory
● File[]
● File[]
● File[]
listFiles()
listFiles(FileFilter filter)
listFiles(FilenameFilter filter)
► Rozhraní FileFilter
● boolean
accept(File pathname)
► Rozhraní FileNameFilter
● boolean
accept(File dir, String name)
Copyright © 2008, Rudolf Pecinovský
11
Vlastnosti souboru
► Povolené čtení
● boolean canRead()
● boolean setReadable(boolean readable)
● boolean setReadable(boolean readable, boolean ownerOnly)
► Povolený zápis
● boolean
● boolean
● boolean
● boolean
canWrite()
setReadOnly()
setWritable(boolean writable)
setWritable(boolean writable, boolean ownerOnly)
► Povolené spuštění programu v souboru
● boolean canExecute()
● boolean setExecutable(boolean executable)
● boolean setExecutable(boolean execut, boolean ownerOnly)
► Čas poslední modifikace
● long lastModified()
● boolean setLastModified(long time)
► Další vlastnosti
● long length()
● boolean isHidden()
Copyright © 2008, Rudolf Pecinovský
12
Práce se soubory na disku
► Zjištění existence souboru
● boolean
exists()
► Vytvoření souboru
● boolean
mkdir()
Vytvoří novou podsložku v existující rodičovské složce
● boolean mkdirs()
Vytvoří novou podsložku včetně potřebných rodičovských složek
● boolean createNewFile()
► Mazání souboru
● boolean
delete()
Smaže soubor ihned
● void deleteOnExit()
Smaže soubor při ukončení programu
► Přejmenování souboru
● boolean
renameTo(File dest)
Copyright © 2008, Rudolf Pecinovský
13
Zjišťování souboru ve složce třídy
► Je-li soubor ve stejné složce jako class-soubor dané třídy, mohu
jej vyhledat prostřednictvím class-objektu
● URL getResource(String name)
● InputStream getResourceAsStream(String
name)
► Odvozujeme-li umístění souboru od složky
s kořenovým balíčkem, využijeme ClassLoader –
ten nabízí stejně metody, jen začíná v kořenovém balíčku
► Zadáním prázdného řetězce obdržíme URL složky
s danou třídou, resp. s kořenovým balíčkem
► Tímto způsobem můžeme přistupovat i k obsahu souborů,
které jsou součástí JAR
● V JARu nemůžeme pracovat se soubory, ale pouze s jejich obsahem
● Soubory v JARu můžeme číst, ale nemůžeme do nich zapisovat
● U názvů souborů v JARu VŽDY ZÁLEŽÍ na velikosti písmen
Copyright © 2008, Rudolf Pecinovský
14
Třída Soubor
import
import
import
import
1/5
java.io.File;
java.net.URL;
java.net.URI;
java.net.URISyntaxException;
/********************************************************************
* Knihovní třída {@code Soubor} definuje metody usnadňující získání
* instancí třídy {@link File} reprezentujících požadovaný soubor.
*
* @author
Rudolf PECINOVSKÝ
* @version
0.00.000
*/
public class Soubor
{
//== KONSTRUKTORY A TOVÁRNÍ METODY ==================================
/** Soukromý konstruktor bránící vytvoření instancí. */
private Soubor() {}
Copyright © 2008, Rudolf Pecinovský
15
Soubor.getFile(String, Object)
2/5
/*********************************************************************
* Vrátí soubor se zadaným názvem zadaným vůči složce,
* v níž je umístěn class-soubor zadané instance.
*
* @param název
Název požadovaného souboru
* @param instance Instance třídy, v jejíž složce se soubor nachází,
*
resp. odkud začíná jeho relativní adresa
* @return Instance třídy {@link File}
*
reprezentující požadovaný soubor
*/
public static File getFile(String název, Object instance)
{
Class<?> classObjekt = instance.getClass();
return getFile(název, classObjekt);
}
Copyright © 2008, Rudolf Pecinovský
16
Soubor.getFile(String, Class<?>)
3/5
/*********************************************************************
* Vrátí soubor se zadaným názvem zadaným vůči složce,
* v níž je umístěn zadaný class-soubor.
*
* @param název Název požadovaného souboru
* @param cls
Class-objekt třídy, v jejíž složce se soubor nachází,
*
resp. odkud začíná jeho relativní adresa
* @return Instance třídy {@link File}
*
reprezentující požadovaný soubor
*/
public static File getFile(String název, Class<?> cls)
{
URL url = cls.getResource(název);
if (url == null) {
throw new IllegalArgumentException( String.format(
"\nNebyl nalezen soubor \"%s\" ve složce \"%s\"",
název, cls.getResource("") ) );
}
return url2file(url);
}
Copyright © 2008, Rudolf Pecinovský
17
Soubor.getFile(String)
4/5
/*********************************************************************
* Vrátí soubor se zadaným názvem
* zadaným vůči složce s kořenovým balíčkem.
*
* @param název Název požadovaného souboru
* @return Instance třídy {@link File}
*
reprezentující požadovaný soubor
*/
public static File getFile(String název)
{
ClassLoader clsLdr = Soubor.class.getClassLoader();
URL url = clsLdr.getResource(název);
if (url == null) {
throw new IllegalArgumentException( String.format(
"\nNebyl nalezen soubor \"%s\" ve složce \"%s\"",
název, clsLdr.getResource("") ) );
}
return url2file(url);
}
Copyright © 2008, Rudolf Pecinovský
18
Soubor.url2file(URL)
5/5
public static File url2file(URL url)
{
URI uri;
try {
uri = url.toURI();
}catch( URISyntaxException use ) {
throw new RuntimeException(
"\nZadané URL nelze konvertovat na URI", use);
}
File file;
try {
file = new File(uri);
}catch( Exception e ) {
String jar = uri.toString().startsWith("jar:")
? "\nHledaný soubor se nachází v souboru JAR"
: "";
String s = "\nURI: " + uri +
"\nnelze konvertovat na File; " + jar;
throw new RuntimeException( s, e);
}
return file;
}
Copyright © 2008, Rudolf Pecinovský
19
Vaše jistota na trhu IT
Třída javax.swing.JFilechooser
Copyright © 2008, Rudolf Pecinovský
20
javax.swing.JFileChooser
► Umožňuje
● Vybrat více souborů současně
● Zadat, zda se má vybírat soubor, složka, či oboje
● Definovat filtry pro zobrazení vybraných typů souborů
● Zabudovat do dialogového okna náhled či jinou komponentu
● Začlenit okno jako komponentu do větších celků
Copyright © 2008, Rudolf Pecinovský
21
Postup pro otevření souboru
1/4
1. Vytvoření instance třídy JFileChooser
●
●
Vytvořenou instanci je výhodné používat vícekrát,
neboť její vytvoření trvá relativně dlouho
(zjišťují se dostupné diskové jednotky)
Konstruktory:
JFileChooser()
JFileChooser(File currentDirectory)
JFileChooser(String currentDirectoryPath)
● Bez parametru nebo s parametrem null začáíná v uživatelově implicitním adresáři
2. Nastavení počáteční složky pro vyhledávání pomocí
setCurrentDirectory(File)
3. Přednastavení jména souboru pomocí metody
setSelectedFile(File)
4. Potřebujeme-li umožnit vybrat více souborů současně, zavoláme
setMultipleSelectionEnabled(boolean)
Copyright © 2008, Rudolf Pecinovský
22
Postup pro otevření souboru
2/4
5. Nastavit filtry (masky) pro výběr souborů
(např. pouze soubory *.txt) jako instance třídy
javax.swing.filechooser.FileFilter
●
Třída vyžaduje implementaci abstraktních metod
●
Java 6.0 zavedla předdefinovanou třídu
FileNameExtensionFilter
●
● boolean accept(File f)
Vrací true, má-li se daný soubor nabízet
● String getDescription()
Vrací popis filtru, podle nějž jej uživatel vybírá
● Té se zadá popis následovaný jednotlivými příponami
FileNameExtensionFilter(String desc, String... ext)
Filtrů může být více, přidávají se voláním
void addChoosableFileFilter(FileFilter)
Copyright © 2008, Rudolf Pecinovský
23
Postup pro otevření souboru
3/4
6. Zavoláním setFileSelectionMode(int)
zadat, zda se mají vybírat adresáře a/nebo soubory
● JFileChooser.FILES_ONLY
● JFileChooser.DIRECTORIES_ONLY
● JFileChooser.FILES_AND_DIRECTORIES
7. Zobrazit dialogové okno pro výběr souborů či složek
zavoláním metody
●
●
●
showOpenDialog(Component parent)
showSaveDialog(Component parent)
showDialog(Component parent,
String approveButtonText)
Chceme-li sami zadat text na potvrzovacím tlačítku
Copyright © 2008, Rudolf Pecinovský
24
Postup pro otevření souboru
4/4
8. Předchozí metody vracejí jednu ze dvou konstant
●
●
JFileChooser.APPROVE_OPTION
JFileChooser.CANCEL_OPTION
9. Vybraný(-né) soubor(y) získáme zavoláním
●
●
File getSelectedFile()
File[] getSelectedFiles()
► Existuje ještě plejáda dalších možností použití
Copyright © 2008, Rudolf Pecinovský
25
Vaše jistota na trhu IT
Návrhový vzor Adaptér
Copyright © 2008, Rudolf Pecinovský
26
Motivace
► Občas potřebujeme, aby třída měla jiné rozhraní než to, které má
● Třída neimplementuje požadované rozhraní,
nicméně poskytuje požadovanou funkčnost
● Příklad:
používáme třídu z jedné knihovny,
jejíž instance bychom mohli použít jako parametry metod jiné knihovny,
ale tato jiná knihovna vyžaduje parametry
implementující nějaké specifické rozhraní, které naše třída nezná
► Dopředu víme, že z hlediska požadované funkčnosti stačí
implementovat pouze část daného rozhraní
nicméně překladač vyžaduje kompletní implementaci
● Iterátor neumožňující odstraňovat prvky z kontejneru
● Kontejnery s předem zadaným, nezměnitelným obsahem
● Posluchači některých událostí při tvorbě GUI
Copyright © 2008, Rudolf Pecinovský
27
Adaptér jako rodič adaptované třídy
► Třída Adaptér definuje implicitní implementace všech metod
požadovaných rozhraním IPožadované
přičemž implicitní verze typicky:
● Vyhazuje UnsupportedOperationException
● Nedělá nic
► Potomci pak mohou
definovat pouze ty
metody, které se jim
„hodí do krámu“
► Pro klienta potomek
implementuje vše
Copyright © 2008, Rudolf Pecinovský
28
Příklad – adaptér jako předek
public interface IPosuvný extends IKreslený
{
//== DEKLAROVANÉ METODY =================================
Adaptér je definován jako
public Pozice getPozice();
třída vnořená do rozhraní,
public void setPozice( Pozice pozice );
na něž bude své potomky
public void setPozice( int x, int y );
adaptovat
//== VNOŘENÉ TŘÍDY ======================================
public static class Adaptér extends IKreslený.Adaptér
implements IPosuvný
{
public Pozice getPozice(){
throw new UnsupportedOperationException();
}
public void setPozice( int x, int y ) {
throw new UnsupportedOperationException();
}
public void setPozice( Pozice pozice ) {
setPozice( pozice.x, pozice.y );
}
}
Metody, o jejichž
implementaci se uživatel
může rozhodnout podle
potřeby
Metoda s definovatelnou
implementací používající
vzor Šablonová metoda
}
Copyright © 2008, Rudolf Pecinovský
29
Adaptovaný objekt jako atribut
1/2
► Adaptér definuje
atribut s odkazem
na adaptovaný
objekt
public class Adaptér implements IPožadované {
Existující adaptovaný;
public Adaptér(Existující exist) {
adaptovaný = exist;
}
Copyright © 2008, Rudolf Pecinovský
30
Adaptovaný objekt jako atribut
2/2
► Všechna volání metod „přehrává“
na volání ekvivalentních metod
adaptovaného objektu
public class Adaptér implements IPožadované {
Existující adaptovaný;
public Adaptér(Existující exist) {
adaptovaný = exist;
}
public void metoda(Parametr parametr) {
Požadovaný požadovaný = uprav(parametr);
adaptovaný.jehoMetoda(požadovaný);
}
}
Copyright © 2008, Rudolf Pecinovský
31
Vaše jistota na trhu IT
Návrhový vzor Dekorátor
Copyright © 2008, Rudolf Pecinovský
32
Motivace
► V knihovně potřebujeme nabízet třídy‘
poskytující různé kombinace funkcionalit
► Při nutnosti nabízet téměř všechny možné kombinace
roste počet potřebných tříd kombinatoricky = neúnosně
► S každou další přidanou
funkcionalitou musíme
přidat násobek dosud
existujících tříd
► S každou přidanou
základní třídou musíme
přidat i třídy pro možné
kombinace přidaných
funkcionalit
Copyright © 2008, Rudolf Pecinovský
33
Řešení
► Využít postupu aplikovaného u návrhového vzoru Adaptér
a zabalit objekt do objektu poskytujícího novou funkcionalitu,
který tak původní objekt ozdobí (dekoruje) novou funkcionalitou
► Pro každou novou funkcionalitu pak bude stačit
přidat pouze jedinou obalovou (dekorující) třídu
► Přidávané funkcionality je možné kumulovat,
tj. zabalený objekt lze znovu zabalit do objektu
přidávajícího další funkcionalitu
● Výsledný objekt lze vytvářet postupně:
AAuto benzin
AAuto abs
AAuto auto
● Nebo najednou
AAuto auto =
Copyright © 2008, Rudolf Pecinovský
= new Benzin();
= new ABS( benzin );
= new Automat(abs);
new Automat( new ABS(new Benzin()) );
34
Využití adaptéru
► Pro všechny dekorující třídy lze definovat společného rodiče,
který volání neupravených metod převede na volání
odpovídajících metod dekorovaného objektu
► Každá nově přidaná základní třída automaticky získává
všechny v úvahu přicházející funkcionality
Copyright © 2008, Rudolf Pecinovský
35
Společný rodič dekorujících tříd
public class Dekorovaný extends AAuto
{
AAuto dekorovaný;
public Dekorovaný(AAuto auto) {
dekorovaný = auto;
}
public void zrychliNa(int rychlost, int doba) {
dekorovaný.zrychliNa( rychlost, doba );
}
public void zpomalNa(int rychlost, int dráha) {
dekorovaný.zpomalNa( rychlost, dráha );
}
public void zabrzdiNa(int dráha) {
dekorovaný.zabrzdiNa( dráha );
}
}
Copyright © 2008, Rudolf Pecinovský
36
Vaše jistota na trhu IT
Čtení a zápis dat pomocí proudů
Copyright © 2008, Rudolf Pecinovský
37
Charakteristika
► Koncepce převzatá z C/C++ (1984)
► Datový proud je objekt zabezpečující tok dat
mezi zdrojem a cílem
► Alespoň na jednom konci proudu musí být program
► Proudy umožňují jednotnou práci s daty
nezávisle na jejich zdroji, cíli a a doprovodných akcích
(používání vyrovnávací paměti, filtrování nezajímavých dat,
šifrování, bezpečnostní kontroly, formátování …)
Copyright © 2008, Rudolf Pecinovský
38
Skupiny proudů
java.io
Bajtové
Znakové
Vstupní
InputStream
Reader
Výstupní
OutputStream
Writer
► V každé skupině jsou pak proudy
lišící se podle zdroje/cíle
● Z/do souboru
● Z/do pole/objektu
● Z/do programu
●…
Copyright © 2008, Rudolf Pecinovský
39
Další možnosti
► Proudy mohou poskytovat dodatečnou funkčnost
● Používání vyrovnávací paměti (buffer)
● Čtení s možností vrácení přečtených znaků
● Převod dat do binárního tvaru a zpět
● Ukládání a načítání objektů (serializace)
● Komprimace a dekomprimace dat
● Šifrování a dešifrování
●…
► Proudy poskytující další funkčnost jsou definovány
aplikací návrhového vzoru Dekorátor
Copyright © 2008, Rudolf Pecinovský
40
Přehled vstupních bajtových proudů
► InputStream
● ByteArrayInputStream – proud pro čtení z pole bajtů
● FileInputStream – proud pro čtení ze souboru
● FilterInputStream – univerzální rodič pro vstupní bajtové proudy
s dodatečnou funkčností (společných rodič některých dekorátorů)
●
●
●
●
BufferedInputStream – proud pro čtení s vyrovnávací pamětí
DataInputStream – proud pro čtení hodnot primitivních typů a řetězců
LineNumberInputStream
PushBackInputStream – vstupní proud umožňující návrat a opětovné čtení
● ObjectInputStream – proud pro čtení serializovaných objektů
● PipedInputStream – proud pro čtení z „datovodu“ (trubky – pipe),
do nějž zapisuje jiná část programu nebo jiný program.
● SequenceInputStream – zřetězení vstupních proudů
● StringBufferInputStream
Copyright © 2008, Rudolf Pecinovský
41
Přehled vstupních znakových proudů
► Reader
● BufferedReader – vstupní znakový proud využívající vyrovnávací paměť
● LineNumberReader – proud, který navíc umí pracovat s čísly řádků
● CharArrayReader – proud pro čtení z pole znaků
● FilterReader – univerzální rodič pro vstupní znakové proudy
s dodatečnou funkčností (společných rodič některých dekorátorů)
● PushBackReader –proud umožňující návrat a opětovné čtení
● InputStreamReader – převaděč bajtového proudu na znakový
● FileReader – proud pro čtení ze souboru
● PipedReader – proud pro čtení z „datovodu“ (trubky – pipe),
do nějž zapsal jiný modul či program
● StringReader – proud pro čtení z textového řetězce (stringu)
Copyright © 2008, Rudolf Pecinovský
42
Přehled výstupních bajtových proudů
► OutputStream
● ByteArrayOutputStream – proud pro zápis do pole bajtů
● FileOutputStream – proud pro zápis do souboru
● FilterOutputStream – univerzální rodič pro výstupní bajtové proudy
s dodatečnou funkčností (společných rodič některých dekorátorů)
● BufferedOutputStream – proud využívající vyrovnávací paměť
● DataOutputStream – proud pro zápis hodnot primitivních typů a řetězců
● PrintStream – výstupní bajtový proud určený k tisku na výstupní zařízení
● ObjectOutputStream – výstupní bajtový proud schopný serializovat
(uložit, odeslat, …) instance objektových typů
● PipedOutputStream – výstupní bajtový proud odesílající data
prostřednictvím „datovodu“ (trubky – pipe) do jiného modulu či programu
Copyright © 2008, Rudolf Pecinovský
43
Přehled výstupních znakových proudů
► Writer
● BufferedWriter – výstupní znakový proud
využívající vyrovnávací paměť
● CharArrayWriter – znakový proud zapisující do pole znaků
● FilterWriter – univerzální rodič pro výstupní znakové proudy
s dodatečnou funkčností (společných rodič budoucích dekorátorů)
● OutputStreamWriter – proud převádějící bajtový proud na znakový
● FileWriter – výstupní znakový proud zapisující do souboru
● PipedWriter – výstupní znakový proud odesílající data
prostřednictvím „datovodu“ (trubky – pipe) do jiného modulu či programu
● PrintWriter – znakový proud určený k tisku na výstupní zařízení
● StringWriter – znakový proud zapisující do textového řetězce
Copyright © 2008, Rudolf Pecinovský
44
Pomocná rozhraní
► FileFilter
Filtrování souborů podle vlastností
(název, délka, soubor/složka, …)
► FilenameFilter
Filtrování souborů podle názvu
► DataInput
Proudy schopné číst řetězce
a hodnoty primitivních typů
► ObjectInput
Proudy schopné číst (deserializovat)
instance objektových typů
► Serializable
Značkovací rozhraní implementované
třídami, jejichž instance lze
serializovat a deserializovat
Copyright © 2008, Rudolf Pecinovský
► Flushable
Objekty, které je možné
„spláchnout“ příkazem flush()
► Closeable
Objekty, které je možné zavřít
příkazem close()
► DataOutput
Proudy shopné odesílat řetězce
a hodnoty primitivních typů
► ObjectOutput
Proudy schopné odesílat
(serializovat) instance
objektových typů
► Externalizable
Rozhraní pro alternativní způsob
serializace/deserializace
45
Společné charakteristiky
► Konstruktor proud současně také otevírá =>
instance proudu má smysl vytvářet až v okamžiku,
kdy daný proud doopravdy potřebuji
(jinak proud zbytečně blokuje zdroje)
► Při ukončení práce musím proud zavřít
● Všechny proudy implementují rozhraní Closeable,
takže je pro ně možné vytvořit společnou zavírací metodu,
které bude jednotným způsobem řešit případné problémy
► Chceme-li mít při použití výstupních proudů zaručeno,
že data doopravdy odešla na výstupní zařízení
(a neflákají se ve vyrovnávací paměti),
musíme data spláchnout zavoláním metody flush()
● Všechny výstupní proudy implementují rozhraní Flushable,
takže je pro ně možné vytvořit společnou zavírací metodu,
které bude jednotným způsobem řešit případné problémy
Copyright © 2008, Rudolf Pecinovský
46
Metody vstupních proudů
1/3
► Stream: int available()
● Vrátí počet bajtů, které ještě mohou být přečteny nebo přeskočeny.
► Reader: boolean ready()
● Je-li možno přečíst další znaky bez čekání, vrátí true.
Vrátí-li false, ještě to neznamená, že se při dalším čtení zablokuje,
protože se mezi tím mohou nějaké znaky na vstupu objevit.
Nicméně v okamžiku testování na vstupu zrovna nic není.
► Stream: int read()
Reader: int read()
● Přečte ze vstupu další bajt, resp. znak a vrátí jej jako celé číslo v rozsahu
0 – 255 (bajt), resp. 0 – 0xFFFF (char).
● Metoda čeká na to, dokud
● není k dispozici čtený bajt, resp. znak – pak jej vrátí,
● není dosaženo konce souboru (pak v obou případech vrátí -1) nebo
● není detekována nějaká chyba (pak vyvolá výjimku IOException)
Copyright © 2008, Rudolf Pecinovský
47
Metody vstupních proudů
2/3
► Stream: int read(byte[] b)
Reader: int read(char[] cbuf)
● Přečte ze vstupu další bajty, resp. znaky do zadaného vektoru a vrátí počet
přečtených bajtů, resp. znaků.
● Metoda čeká na to, dokud
● nejsou nějaké bajty (znaky) k dispozici nebo
● dokud není dosaženo konce souboru nebo
● dokud není detekována nějaká chyba (pak vyvolá IOException).
● Pokud nic nepřečte, protože bylo dosaženo konce souboru vrátí -1.
► Stream: int read(byte[] b,
int off, int len)
Reader: int read(char[] cbuf, int off, int len)
● Obdobně jako předchozí,
jenom čte do předem definované části zadaného pole
► void close()
● Uzavře proud a uvolní alokované zdroje.
Uzavření dříve uzavřeného proudu neudělá nic.
Copyright © 2008, Rudolf Pecinovský
48
Metody vstupních proudů
3/3
► long skip(long n)
● Přeskočí na vstupu zadaný počet bajtů (znaků).
Vrátí skutečný počet přeskočených bajtů (znaků).
► boolean markSupported()
● Vrátí informaci o tom, zda daný proud podporuje umísťování značek.
► void mark(int readlimit)
● Označí zadanou pozici v proudu, aby se k ní mohl později vrátit.
Parametr readlimit označuje, kolik bajtů (znaků) chce program mít
možnost přečíst, než se k této zarážce vrátí a začne od ní číst znovu.
► void reset()
● Vrátí se v proudu k nastavené značce,
aby odtud při příštím volání read začal znovu číst.
Některé proudy umožňují volat reset bez předchozího volání mark
a začínají pak číst celý proud znovu od počátku.
Copyright © 2008, Rudolf Pecinovský
49
Metody výstupních proudů
1/2
► void write(int b)
● Zapíše zadaný bajt, resp. znak na výstup. Z celého čísla,
které je předáváno jako parametr, se na vstup pošle pouze příslušný
počet nižších bajtů (stream 1, writer 2). Ostatní bajty jsou ignorovány.
► Stream: void write(byte[] b)
Writer: void write(char[] cbuf)
● Pošle na výstup obsah zadaného vektoru.
► Stream: void write(byte[] b,
int off, int len)
Writer: void write(char[] cbuf, int off, int len)
● Pošle na výstup obsah len bajtů, resp. znaků ze zadaného vektoru
od pozice off.
► Writer: void write(String s)
Writer: void write(String s, int off, int len)
● Pošle na výstup zadaný řetězec, resp. jeho zadanou část.
Copyright © 2008, Rudolf Pecinovský
50
Metody výstupních proudů
2/2
► flush()
● Spláchne zadaný proud, tj. zabezpečí,
aby se zapsaná data fyzicky dostala k požadovanému cíli.
● Je-li proud napojen na další proud, zabezpečí,
aby se i tento proud spláchnul.
● Po úspěšně provedené operaci flush máme jistotu,
že data jsou tam, kde mají být
(pro případ následného zhroucení aplikace).
► close()
● Zavře daný proud a uvolní všechny alokované zdroje.
Copyright © 2008, Rudolf Pecinovský
51
Společné zásady
► Při čtení z disku a zápisu na disk je vhodné použít proudy
s vyrovnávací pamětí, které práci velmi výrazně zrychlí
► Proudy by neměly zůstávat dlouho zbytečně otevřené,
protože blokují zdroje OS
► Řada operací s proudy vyhazuje kontrolované výjimky.
Tyto výjimky by neměly zůstat neošetřené.
Neočekáváme-li, že k nim někdy dojde,
měli bychom je ošetřit převodem na nekontrolované.
► Při práci s konzolou nastávají občas problémy
s jazykovým nastavením – je třeba s nimi počítat
Copyright © 2008, Rudolf Pecinovský
53
Příklad: Převod kódování
public class Převod_2
{
//== KONSTANTNÍ ATRIBUTY
private static final
private static final
private static final
TŘÍDY =======================================
Charset ASCII_SET = Charset.forName("ASCII");
PrintStream out = System.out;
PrintStream err = System.err;
//== PROMĚNNÉ ATRIBUTY TŘÍDY =========================================
/** Kódová stránka zdrojových souborů. */
public static Charset sourceCP;
/** Kódová stránka cílových souborů. */
public static Charset destCP;
/** Kódová stránka výpisů na standardní výstup. */
public static Charset consoleCP;
/** Složka se zdrojovými soubory. */
public static File sourceDir;
/** Složka s cílovými soubory. */
public static File destDir;
Copyright © 2008, Rudolf Pecinovský
54
Příklad: převeďSoubor(File, File)
private boolean převeďSoubor( File input, File output ) {
println( out, "Převádíme soubor: " + input +
"\n
na soubor: " + output);
LineNumberReader lnrd = otevřiSoubor( input );
OutputStream os = null;
try {
os = new BufferedOutputStream(
new FileOutputStream( output ) );
}catch( IOException e ) {
problém(e, "Nepodařilo se otevřít výstupní soubor " + output);
}//try writer
boolean odháčkovat = destCP.equals(ASCII_SET);
try {
String řádek;
while( (řádek = lnrd.readLine()) != null ) {
os.write( řádek.getBytes( destCP ) );
os.write( '\n' );
}
}catch( IOException e ) {
problém( e, "Při převodu ze souboru " + input +
" do souboru " + output + " došlo k chybě" );
}finally {
zavřiSoubory(lnrd, input, os, output);
}
return true;
}
Copyright © 2008, Rudolf Pecinovský
55
Příklad: zavřiSoubory
/*********************************************************************
* Zavře zadaný vstupní a výstupní proud a v případě problémů
* vypíše příslušnou zprávu.
* Souborové parametry slouží pouze k vypsání případné chybové zprávy.
* @param is
Zavíraný vstupní proud
* @param input
Soubor z nějž čte vstupní proud
* @param os
Zavíraný výstupní proud
* @param output
Soubor, kam zapisuje výstupní proud
*/
private void zavřiSoubory( Closeable is, File input,
Closeable os, File output) {
try {
is.close();
} catch (IOException e) {
problém(e, "Nepodařilo se zavřít vstupní soubor " + input);
}
try {
os.close();
} catch (IOException e) {
problém(e, "Nepodařilo se zavřít výstupní soubor " + output);
}
}
Copyright © 2008, Rudolf Pecinovský
56
Příklad: Převod kódování
1/5
/*********************************************************************
* Otevře zadaný soubor jako vstupní proud v potřebném kódování.
*
* @param file soubor s parametry (měl by mít příponu .args).
*/
private LineNumberReader otevřiSoubor(File input)
{
LineNumberReader lnrd = null;
try {
FileInputStream fis = new FileInputStream( input );
Reader reader = new InputStreamReader( fis, sourceCP );
lnrd = new LineNumberReader( reader );
}catch( IOException e ) {
problém( e, "Nepodařilo se otevřít vstupní soubor " + "\n" +
input );
}
return lnrd;
}
Copyright © 2008, Rudolf Pecinovský
57
Příklad: kopírujSoubor(File, File)
1/2
private void kopírujSoubor( File input, File output )
{
println( out, "Kopíruju soubor: " + input +
"\n
na soubor: " + output );
InputStream is = null;
try {
is = new BufferedInputStream(
new FileInputStream( input ) );
}catch( IOException e ) {
problém( e, "Nepodařilo se otevřít vstupní soubor " + "\n" +
input );
}//try reader
OutputStream os = null;
try {
os = new BufferedOutputStream(
new FileOutputStream( output ) );
}catch( IOException e ) {
problém( e, "Nepodařilo se otevřít výstupní soubor " + output )
}//try writer
//... Pokračování na dalším snímku
Copyright © 2008, Rudolf Pecinovský
58
Příklad: kopírujSoubor(File, File)
2/2
try {
int bajt;
while( (bajt = is.read()) >= 0 ) {
os.write( bajt );
}
}catch( IOException e ) {
problém( e, "Při kopírování ze souboru " + input +
" do souboru " + output + " došlo k chybě" );
} finally {
zavřiSoubory(is, input, os, output);
}
}
Copyright © 2008, Rudolf Pecinovský
59
Příklad: převeďSložku( File, File)
private boolean převeďSložku( File inputDir, File outputDir ) {
if( !outputDir.exists() ) {
outputDir.mkdirs();
}
String[] seznam = inputDir.list();
//Převede jednotlivé soubory ve složce
for( String název : seznam )
{
File inFile = new File( inputDir, název );
File outFile = new File( outputDir, název );
if( inFile.isFile() ) {
převeďSoubor( inFile, outFile );
}
else if( inFile.isDirectory() ) {
převeďSložku( inFile, outFile);
} else {
problém( new IllegalStateException(), "Převod selhal" );
}
}
return true;
}
Copyright © 2008, Rudolf Pecinovský
60
Vaše jistota na trhu IT
Zásady správného programování
►Maximalizovat přehlednost, dodržovat konvence
►Programovat proti rozhraní, ne proti implementaci
►Důsledné zapouzdřování a skrývání implementace
►Upřednostňovat skládání před dědičností
►Jedna entita  jeden úkol
►Návrh řízený odpovědnostmi
►Minimalizovat vzájemnou provázanost
►Vyhýbat se duplicitám v kódu
►Nepodřizovat návrh snahám o maximální efektivitu
Copyright © 2008, Rudolf Pecinovský
61
Maximalizovat přehlednost, dodržovat konvence
► Zákaznící si na programech vysoce cení celkové náklady
(TCO = Total Cost od Ownership)
► TCO bývá pro mnohé důležitější než souhrn dostupných funkcí;
funkce je možné doplnit, ale program s drahým provozem a
drahými modifikacemi je prostě drahý
► Při návrhu programu je třeba dbát na jeho snadnou
modifikovatelnost a spravovatelnost
► Vynikající programátor, v jehož kódu se nikdo jiný nevyzná,
bývá pro tým většinou spíše ztrátou
► Pozor na programátory z MFF; jsou sice často skvělí,
ale stejně často jsou to příliš velcí sólisté =>
potřebují schopného a znalého manažera
Copyright © 2008, Rudolf Pecinovský
62
Obecné konvence
► Identifikátory
● Smysluplné názvy
● Velikost písmen – při dodržování zásad poskytujete čtenáři informaci
navíc
● Dodržovat konvence pro přístupové metody vlastností
► Odsazování kódu
● Vnořovat vnitřky bloků
● Zarovnávat závorky
● Odsazovat pokračovací řádky
► Celková úprava
● Dodržovat max. 80 znaků na řádek
● Nezhušťovat program, přehledný je lepší než stručný
● Vhodný překladač optimalizuje lépe než programátor
► Dokumentační komentáře psát poctivě
Copyright © 2008, Rudolf Pecinovský
63
Programovat proti rozhraní, ne proti implementaci
► Rozhraním se v tuto chvíli myslí jak interface,
tak obecné rozhraní dané třídy/metody
► Proměnné, prostřednictvím nichž komunikuje modul s okolím,
nemají být instancemi konkrétní třídy, ale instancemi rozhraní
● Příklady: Kalkulačka, kontejnery,
● Návrhový vzor Most, Zástupce, Šablonová metoda, …
► I u objektů, které jsou instancemi třídy,
je třeba nezneužívat znalost implementace
► Komunikace prostřednictvím rozhraní uvolňuje ruce
při příštích modifikacích
► Rozhraní by mělo být optimalizováno
vzhledem k budoucím uživatelům
Copyright © 2008, Rudolf Pecinovský
64
Zapouzdřování
► Zapouzdřením se rozumí sdružení dat a je obhospodařujícího
kódu na jedno místo
► Třída by neměla obsahovat metody, které pracují s cizími daty,
ani atributy, s nimiž pracují cizí metody
► Výjimkou jsou třídy, jejichž hlavním účelem je poskytnout
data sdílená celou skupinou tříd
► Jednotlivé zprávy posílané objektu by měly být pokud možno na
sobě nezávislé, metody by měly požadovat jen nezbytně nutné
parametry
Copyright © 2008, Rudolf Pecinovský
65
Skrývání implementace
► Zapouzdřování úzce souvisí se skrýváním implementace
► Autor třídy by si měl důkladně rozmyslet,
které atributy a metody budou určeny k veřejnému použití,
a které budou pouze služební, a proto soukromé
► Atributy by měly být zásadně soukromé
a okolí by s nimi mělo komunikovat pouze
prostřednictvím přístupových metod
► Jedinou výjimkou z předchozího pravidla mohou být
nezměnitelné konstanty, a i u těch to musí být odůvodněné
► Na skrývání implementace je třeba dbát nejen uvnitř třídy,
ale i mezi balíčky
Copyright © 2008, Rudolf Pecinovský
66
Oddělovat částí kódu, jež se asi budou měnit
► Aby změna jedné části kódu minimalizoval potřebu změn
v ostatních částech, je třeba tu část kódu,
která se bude pravděpodobně měnit, oddělit od zbytku
► Nejlepší způsob je vložit mezi měněný kód a zbytek světa
rozhraní (interface), které bude daný kód implementovat
► Jakákoliv změna (kromě změny rozhraní)
se pak daleko snáze provádí, protože není třeba
mít neustále na paměti zbytek programu.
Copyright © 2008, Rudolf Pecinovský
67
Upřednostňovat skládání před dědičností
► Dědičnost narušuje zapouzdření i skrývání implementace
► Dědičnost bychom měli použít opravdu jen tehdy,
kdy je její použití opodstatněné
► Kdykoliv je to jen trochu možné,
měli bychom dát před dědičností přednost skládání
● Potenciální rodič je definován jako atribut potenciálního potomka
● Toto řešení bychom měli použít vždy, když by potomek nebyl schopen
v jakékoliv situaci zastoupit svého potenciálního rodiče
► Špatné použití dědičnosti
● Letadlo – cyklista – čísla – geometrické tvary
Copyright © 2008, Rudolf Pecinovský
68
Jedna entita  jeden úkol
► Programy by měly být maximálně soudržné,
tj. žádná entita (balíček – třída – metoda – atribut)
by neměla mít na starosti několik věcí současně
► Příklad: jízdenkový automat
► Metoda, která má více než 10 příkazů, „smrdí“ tím,
že dělá několik věcí současně (Kent Beck: TDD)
● Student, který přijde ke zkoušce s programem,
jehož metody mají více než 35 příkazů, vyletí od zkoušky,
aniž bych se díval na jeho program (Cay Horstmann: Big Java)
► Velké entity vedou k nestabilitě programu a duplikaci kódu
Copyright © 2008, Rudolf Pecinovský
69
Návrh řízený odpovědnostmi
► RDD (Responsibility Driven Design) doporučuje,
aby každý úkol měla na starosti pouze jedna entita
► Tento přístup velmi usnadňuje modifikaci programu –
při změně či vylepšení funkce stačí upravit entitu,
která je za danou funkci zodpovědná
Copyright © 2008, Rudolf Pecinovský
70
Minimalizovat vzájemnou provázanost
► Každá entita si má při plnění svých úloh vystačit
pokud možno sama a minimálně se obracet na jiné entity =>
je třeba minimalizovat počet vazeb mezi entitami
► Začneme-li upravovat funkcionalitu entity,
na které někdo závisí, je třeba zkontrolovat,
nakolik je třeba upravit i závislou entitu
► Provázanosti se nelze zbavit,
ale je třeba ji maximálně minimalizovat
Copyright © 2008, Rudolf Pecinovský
71
Vyhýbat se duplicitám v kódu
► Řeší-li se v programu několik věcí na různých místech
velmi podobně nebo dokonce stejně,
bylo by vhodné všechna řešení sloučit
a na toto sloučené řešení se pak jenom odvolávat
► Stejné pravidlo platí jak pro data (konstanty), tak pro kód
► Řešení
● Společný rodič
● Služebník
● Metoda řešící společné části kódu
● Pojmenované konstanty místo literálů
► Výhody:
● Menší náchylnost k chybám, protože nevznikají chyby kopírováním
● Snazší modifikovatelnost, protože změnu je třeba provést
jen na jednom místě
Copyright © 2008, Rudolf Pecinovský
72
Nepodřizovat zbytečně návrh efektivitě
► Nejčastější příčinou zkrachování projektu
je předčasná snahy po optimalizaci (C.A.Hoare)
► Ve většině případů dokáže optimalizující překladač a virtuální
stroj dosáhnout lepších výsledků než programátor
► „Ručně optimalizované“ programy mívají často takovou podobu,
že už je překladač a VM nemohou dále optimalizovat,
takže běží pomaleji, než kdyby optimalizovány nebyly
► Daleko větší přínos rychlosti většinou přinese
zásadní změna algoritmu
► Zrychlení programu koupí rychlejšího stroje
přijde často levněji než optimalizace programu
Copyright © 2008, Rudolf Pecinovský
73
Zkratky pro důležité zásady
► KISS – Keep It Small, Simple (Stupid)
● Nepokoušet se hned o maximální funkčnost,
naprogramovat v prvním kole jenom nezbytně nutnou funkčnost,
kterou budeme v dalších kolech doplňovat a vylepšovat
► TDD – Test Driven Development
● Nejdříve je třeba navrhnout testy,
teprve pak můžeme navrhovat program, který bude tyto testy plnit
► POJO – Plain Old Java Object
► LSP – Liskov Substitution Principle
● Potomek musí být vždy schopen plnohodnotně
vystupovat v roli předka
► DRY – Don‘t Repeat Yourself
● Maximálně se vyhýbat
Copyright © 2008, Rudolf Pecinovský
74
Vaše jistota na trhu IT
Děkuji za pozornost
Rudolf Pecinovský
mail: [email protected]
ICQ: 158 156 600
Copyright © 2008, Rudolf Pecinovský
75