Curs practic de Java Cristian Fr˘asinaru
Cuprins 1 Introducere ˆın Java 1.1 Ce este Java ? . . . . . . . . . . . . . . . . . . 1.1.1 Limbajul de programare Java . . . . . 1.1.2 Platforme de lucru Java . . . . . . . . 1.1.3 Java: un limbaj compilat ¸si interpretat 1.2 Primul program . . . . . . . . . . . . . . . . . 1.3 Structura lexical˘a a limbajului Java . . . . . . 1.3.1 Setul de caractere . . . . . . . . . . . . 1.3.2 Cuvinte cheie . . . . . . . . . . . . . . 1.3.3 Identificatori . . . . . . . . . . . . . . . 1.3.4 Literali . . . . . . . . . . . . . . . . . . 1.3.5 Separatori . . . . . . . . . . . . . . . . 1.3.6 Operatori . . . . . . . . . . . . . . . . 1.3.7 Comentarii . . . . . . . . . . . . . . . 1.4 Tipuri de date ¸si variabile . . . . . . . . . . . 1.4.1 Tipuri de date . . . . . . . . . . . . . . 1.4.2 Variabile . . . . . . . . . . . . . . . . . 1.5 Controlul execut¸iei . . . . . . . . . . . . . . . 1.5.1 Instruct¸iuni de decizie . . . . . . . . . 1.5.2 Instruct¸iuni de salt . . . . . . . . . . . 1.5.3 Instruct¸iuni pentru tratarea except¸iilor 1.5.4 Alte instruct¸iuni . . . . . . . . . . . . 1.6 Vectori . . . . . . . . . . . . . . . . . . . . . . 1.6.1 Crearea unui vector . . . . . . . . . . . 1.6.2 Tablouri multidimensionale . . . . . . 1.6.3 Dimensiunea unui vector . . . . . . . . 1.6.4 Copierea vectorilor . . . . . . . . . . . 1
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
11 11 11 12 13 14 16 16 16 17 17 19 19 20 21 21 22 24 24 25 26 26 26 26 28 28 29
2
CUPRINS 1.6.5 Sortarea vectorilor - clasa Arrays . . . . . . 1.6.6 Vectori cu dimensiune variabil˘a ¸si eterogeni S¸iruri de caractere . . . . . . . . . . . . . . . . . . Folosirea argumentelor de la linia de comand˘a . . . 1.8.1 Transmiterea argumentelor . . . . . . . . . . 1.8.2 Primirea argumentelor . . . . . . . . . . . . 1.8.3 Argumente numerice . . . . . . . . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
29 30 30 31 31 32 34
2 Obiecte ¸si clase 2.1 Ciclul de viat¸˘a al unui obiect . . . . . . . . . . . . . 2.1.1 Crearea obiectelor . . . . . . . . . . . . . . . 2.1.2 Folosirea obiectelor . . . . . . . . . . . . . . 2.1.3 Distrugerea obiectelor . . . . . . . . . . . . 2.2 Crearea claselor . . . . . . . . . . . . . . . . . . . . 2.2.1 Declararea claselor . . . . . . . . . . . . . . 2.2.2 Extinderea claselor . . . . . . . . . . . . . . 2.2.3 Corpul unei clase . . . . . . . . . . . . . . . 2.2.4 Constructorii unei clase . . . . . . . . . . . . 2.2.5 Declararea variabilelor . . . . . . . . . . . . 2.2.6 this ¸si super . . . . . . . . . . . . . . . . . . 2.3 Implementarea metodelor . . . . . . . . . . . . . . 2.3.1 Declararea metodelor . . . . . . . . . . . . . 2.3.2 Tipul returnat de o metod˘a . . . . . . . . . 2.3.3 Trimiterea parametrilor c˘atre o metod˘a . . . 2.3.4 Metode cu num˘ar variabil de argumente . . 2.3.5 Supraˆınc˘arcarea ¸si supradefinirea metodelor 2.4 Modificatori de acces . . . . . . . . . . . . . . . . . 2.5 Membri de instant¸˘a ¸si membri de clas˘a . . . . . . . 2.5.1 Variabile de instant¸˘a ¸si de clas˘a . . . . . . . 2.5.2 Metode de instant¸˘a ¸si de clas˘a . . . . . . . . 2.5.3 Utilitatea membrilor de clas˘a . . . . . . . . 2.5.4 Blocuri statice de init¸ializare . . . . . . . . . 2.6 Clase imbricate . . . . . . . . . . . . . . . . . . . . 2.6.1 Definirea claselor imbricate . . . . . . . . . . 2.6.2 Clase interne . . . . . . . . . . . . . . . . . 2.6.3 Identificare claselor imbricate . . . . . . . . 2.6.4 Clase anonime . . . . . . . . . . . . . . . . . 2.7 Clase ¸si metode abstracte . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . .
35 35 35 37 38 39 39 40 41 42 46 49 50 50 52 53 56 57 58 59 59 61 62 63 64 64 66 66 67 67
1.7 1.8
CUPRINS 2.7.1 Declararea unei clase abstracte 2.7.2 Metode abstracte . . . . . . . . 2.8 Clasa Object . . . . . . . . . . . . . . 2.8.1 Orice clas˘a are o superclas˘a . . 2.8.2 Clasa Object . . . . . . . . . . 2.9 Conversii automate ˆıntre tipuri . . . . 2.10 Tipul de date enumerare . . . . . . . .
3 . . . . . . .
3 Except¸ii 3.1 Ce sunt except¸iile ? . . . . . . . . . . . . 3.2 ”Prinderea” ¸si tratarea except¸iilor . . . . 3.3 ”Aruncarea” except¸iilor . . . . . . . . . . 3.4 Avantajele trat˘arii except¸iilor . . . . . . 3.4.1 Separarea codului pentru tratarea 3.4.2 Propagarea erorilor . . . . . . . . 3.4.3 Gruparea erorilor dup˘a tipul lor . 3.5 Ierarhia claselor ce descriu except¸ii . . . 3.6 Except¸ii la execut¸ie . . . . . . . . . . . . 3.7 Crearea propriilor except¸ii . . . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . . . . . . . . . . . . . . . erorilor . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . .
. . . . . . . . . .
. . . . . . .
. . . . . . . . . .
. . . . . . .
. . . . . . . . . .
4 Intr˘ ari ¸si ie¸siri 4.1 Introducere . . . . . . . . . . . . . . . . . . . . . . . . 4.1.1 Ce sunt fluxurile? . . . . . . . . . . . . . . . . . 4.1.2 Clasificarea fluxurilor . . . . . . . . . . . . . . . 4.1.3 Ierarhia claselor pentru lucrul cu fluxuri . . . . 4.1.4 Metode comune fluxurilor . . . . . . . . . . . . 4.2 Folosirea fluxurilor . . . . . . . . . . . . . . . . . . . . 4.2.1 Fluxuri primitive . . . . . . . . . . . . . . . . . 4.2.2 Fluxuri de procesare . . . . . . . . . . . . . . . 4.2.3 Crearea unui flux . . . . . . . . . . . . . . . . . 4.2.4 Fluxuri pentru lucrul cu fi¸siere . . . . . . . . . . 4.2.5 Citirea ¸si scrierea cu buffer . . . . . . . . . . . . 4.2.6 Concatenarea fluxurilor . . . . . . . . . . . . . . 4.2.7 Fluxuri pentru filtrarea datelor . . . . . . . . . 4.2.8 Clasele DataInputStream ¸si DataOutputStream 4.3 Intr˘ari ¸si ie¸siri formatate . . . . . . . . . . . . . . . . . 4.3.1 Intr˘ari formatate . . . . . . . . . . . . . . . . . 4.3.2 Ie¸siri formatate . . . . . . . . . . . . . . . . . .
. . . . . . .
. . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . .
. . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . .
. . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . .
68 68 71 71 71 74 75
. . . . . . . . . .
77 77 78 82 85 85 87 89 90 91 92
. . . . . . . . . . . . . . . . .
95 95 95 96 97 98 99 99 100 101 103 105 107 108 109 110 110 111
4
CUPRINS 4.4
4.5 4.6
Fluxuri standard de intrare ¸si ie¸sire . . . . . . . . . . . . . 4.4.1 Afisarea informat¸iilor pe ecran . . . . . . . . . . . . 4.4.2 Citirea datelor de la tastatur˘a . . . . . . . . . . . . 4.4.3 Redirectarea fluxurilor standard . . . . . . . . . . . 4.4.4 Analiza lexical˘a pe fluxuri (clasa StreamTokenizer) Clasa RandomAccesFile (fi¸siere cu acces direct) . . . . . . Clasa File . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . .
. . . . . . .
111 112 112 113 115 117 119
5 Interfet¸e 5.1 Introducere . . . . . . . . . . . . . . . . . . 5.1.1 Ce este o interfat¸˘a ? . . . . . . . . . 5.2 Folosirea interfet¸elor . . . . . . . . . . . . . 5.2.1 Definirea unei interfet¸e . . . . . . . . 5.2.2 Implementarea unei interfet¸e . . . . . 5.2.3 Exemplu: implementarea unei stive . 5.3 Interfet¸e ¸si clase abstracte . . . . . . . . . . 5.4 Mo¸stenire multipl˘a prin interfet¸e . . . . . . 5.5 Utilitatea interfet¸elor . . . . . . . . . . . . . 5.5.1 Crearea grupurilor de constante . . . 5.5.2 Transmiterea metodelor ca parametri 5.6 Interfat¸a FilenameFilter . . . . . . . . . . 5.6.1 Folosirea claselor anonime . . . . . . 5.7 Compararea obiectelor . . . . . . . . . . . . 5.7.1 Interfat¸a Comparable . . . . . . . . . 5.7.2 Interfat¸a Comparator . . . . . . . . . 5.8 Adaptori . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
121 . 121 . 121 . 122 . 122 . 123 . 124 . 129 . 130 . 132 . 132 . 133 . 134 . 137 . 138 . 139 . 141 . 142
6 Organizarea claselor 6.1 Pachete . . . . . . . . . . . . . . . . . . 6.1.1 Pachetele standard (J2SDK) . . . 6.1.2 Folosirea membrilor unui pachet . 6.1.3 Importul unei clase sau interfet¸e . 6.1.4 Importul la cerere dintr-un pachet 6.1.5 Importul static . . . . . . . . . . 6.1.6 Crearea unui pachet . . . . . . . 6.1.7 Denumirea unui pachet . . . . . . 6.2 Organizarea fi¸sierelor . . . . . . . . . . . 6.2.1 Organizarea fi¸sierelor surs˘a . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
145 145 145 146 147 148 149 150 151 152 152
CUPRINS
6.3
5
6.2.2 Organizarea unit˘a¸tilor de compilare (.class) 6.2.3 Necesitatea organiz˘arii fi¸sierelor . . . . . . . . 6.2.4 Setarea c˘aii de c˘autare (CLASSPATH) . . . . Arhive JAR . . . . . . . . . . . . . . . . . . . . . . . 6.3.1 Folosirea utilitarului jar . . . . . . . . . . . . 6.3.2 Executarea aplicat¸iilor arhivate . . . . . . . .
7 Colect¸ii 7.1 Introducere . . . . . . . . . . 7.2 Interfet¸e ce descriu colect¸ii . . 7.3 Implement˘ari ale colect¸iilor . . 7.4 Folosirea eficient˘a a colect¸iilor 7.5 Algoritmi polimorfici . . . . . 7.6 Tipuri generice . . . . . . . . 7.7 Iteratori ¸si enumer˘ari . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
154 155 156 157 158 159
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
161 . 161 . 162 . 166 . 168 . 170 . 171 . 172
8 Serializarea obiectelor 8.1 Folosirea serializ˘arii . . . . . . . . . . . . . . . 8.1.1 Serializarea tipurilor primitive . . . . . 8.1.2 Serializarea obiectelor . . . . . . . . . . 8.1.3 Clasa ObjectOutputStream . . . . . . 8.1.4 Clasa ObjectInputStream . . . . . . . 8.2 Obiecte serializabile . . . . . . . . . . . . . . . 8.2.1 Implementarea interfet¸ei Serializable . 8.2.2 Controlul serializ˘arii . . . . . . . . . . 8.3 Personalizarea serializ˘arii obiectelor . . . . . . 8.3.1 Controlul versiunilor claselor . . . . . . 8.3.2 Securizarea datelor . . . . . . . . . . . 8.3.3 Implementarea interfet¸ei Externalizable 8.4 Clonarea obiectelor . . . . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . .
199 . 199 . 200 . 202 . 204 . 206 . 207
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
9 Interfat¸a grafic˘ a cu utilizatorul 9.1 Introducere . . . . . . . . . . . . . . . . . . . 9.2 Modelul AWT . . . . . . . . . . . . . . . . . . 9.2.1 Componentele AWT . . . . . . . . . . 9.2.2 Suprafet¸e de afi¸sare (Clasa Container) 9.3 Gestionarea pozit¸ion˘arii . . . . . . . . . . . . 9.3.1 Folosirea gestionarilor de pozit¸ionare .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
177 177 179 180 180 181 183 183 184 187 188 193 194 196
6
CUPRINS
9.4
9.5
9.6
9.7
9.3.2 Gestionarul FlowLayout . . . . . . . . . . . 9.3.3 Gestionarul BorderLayout . . . . . . . . . . 9.3.4 Gestionarul GridLayout . . . . . . . . . . . 9.3.5 Gestionarul CardLayout . . . . . . . . . . . 9.3.6 Gestionarul GridBagLayout . . . . . . . . . 9.3.7 Gruparea componentelor (Clasa Panel) . . . Tratarea evenimentelor . . . . . . . . . . . . . . . . 9.4.1 Exemplu de tratare a evenimentelor . . . . . 9.4.2 Tipuri de evenimente . . . . . . . . . . . . . 9.4.3 Folosirea adaptorilor ¸si a claselor anonime . Folosirea ferestrelor . . . . . . . . . . . . . . . . . . 9.5.1 Clasa Window . . . . . . . . . . . . . . . . . 9.5.2 Clasa Frame . . . . . . . . . . . . . . . . . . 9.5.3 Clasa Dialog . . . . . . . . . . . . . . . . . . 9.5.4 Clasa FileDialog . . . . . . . . . . . . . . . Folosirea meniurilor . . . . . . . . . . . . . . . . . . 9.6.1 Ierarhia claselor ce descriu meniuri . . . . . 9.6.2 Tratarea evenimentelor generate de meniuri 9.6.3 Meniuri de context (popup) . . . . . . . . . 9.6.4 Acceleratori (Clasa MenuShortcut) . . . . . Folosirea componentelor AWT . . . . . . . . . . . . 9.7.1 Clasa Label . . . . . . . . . . . . . . . . . . 9.7.2 Clasa Button . . . . . . . . . . . . . . . . . 9.7.3 Clasa Checkbox . . . . . . . . . . . . . . . . 9.7.4 Clasa CheckboxGroup . . . . . . . . . . . . 9.7.5 Clasa Choice . . . . . . . . . . . . . . . . . 9.7.6 Clasa List . . . . . . . . . . . . . . . . . . . 9.7.7 Clasa ScrollBar . . . . . . . . . . . . . . . . 9.7.8 Clasa ScrollPane . . . . . . . . . . . . . . . 9.7.9 Clasa TextField . . . . . . . . . . . . . . . . 9.7.10 Clasa TextArea . . . . . . . . . . . . . . . .
10 Desenarea 10.1 Conceptul de desenare . . . . . . . . . . . . 10.1.1 Metoda paint . . . . . . . . . . . . . 10.1.2 Suprafet¸e de desenare - clasa Canvas 10.2 Contextul grafic de desenare . . . . . . . . . 10.2.1 Propriet˘a¸tile contextului grafic . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
209 210 211 212 214 218 219 221 224 227 232 232 233 236 239 242 243 246 247 250 250 251 252 253 255 257 259 261 262 263 265
. . . . .
269 . 269 . 270 . 271 . 274 . 275
CUPRINS
10.3
10.4 10.5
10.6
10.2.2 Primitive grafice . . . . . . . . . . Folosirea fonturilor . . . . . . . . . . . . . 10.3.1 Clasa Font . . . . . . . . . . . . . . 10.3.2 Clasa FontMetrics . . . . . . . . . . Folosirea culorilor . . . . . . . . . . . . . . Folosirea imaginilor . . . . . . . . . . . . . 10.5.1 Afi¸sarea imaginilor . . . . . . . . . 10.5.2 Monitorizarea ˆınc˘arc˘arii imaginilor 10.5.3 Mecanismul de ”double-buffering” . 10.5.4 Salvarea desenelor ˆın format JPEG 10.5.5 Crearea imaginilor ˆın memorie . . Tip˘arirea . . . . . . . . . . . . . . . . . . .
11 Swing 11.1 Introducere . . . . . . . . . . . . . . . . . 11.1.1 JFC . . . . . . . . . . . . . . . . . 11.1.2 Swing API . . . . . . . . . . . . . . 11.1.3 Asem˘an˘ari ¸si deosebiri cu AWT . . 11.2 Folosirea ferestrelor . . . . . . . . . . . . . 11.2.1 Ferestre interne . . . . . . . . . . . 11.3 Clasa JComponent . . . . . . . . . . . . . 11.4 Arhitectura modelului Swing . . . . . . . . 11.5 Folosirea modelelor . . . . . . . . . . . . . 11.5.1 Tratarea evenimentelor . . . . . . . 11.6 Folosirea componentelor . . . . . . . . . . 11.6.1 Componente atomice . . . . . . . . 11.6.2 Componente pentru editare de text 11.6.3 Componente pentru selectarea unor 11.6.4 Tabele . . . . . . . . . . . . . . . . 11.6.5 Arbori . . . . . . . . . . . . . . . . 11.6.6 Containere . . . . . . . . . . . . . . 11.6.7 Dialoguri . . . . . . . . . . . . . . 11.7 Desenarea . . . . . . . . . . . . . . . . . . 11.7.1 Metode specifice . . . . . . . . . . 11.7.2 Considerat¸ii generale . . . . . . . . 11.8 Look and Feel . . . . . . . . . . . . . . . .
7 . . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
275 276 277 279 282 286 287 289 291 291 292 293
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . elemente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
299 299 299 300 301 304 305 307 310 310 314 316 316 316 319 324 329 332 335 336 336 338 340
8
CUPRINS
12 Fire de execut¸ie 12.1 Introducere . . . . . . . . . . . . . . . . . 12.2 Crearea unui fir de execut¸ie . . . . . . . . 12.2.1 Extinderea clasei Thread . . . . . . 12.2.2 Implementarea interfet¸ei Runnable 12.3 Ciclul de viat¸˘a al unui fir de execut¸ie . . . 12.3.1 Terminarea unui fir de execut¸ie . . 12.3.2 Fire de execut¸ie de tip ”daemon” . 12.3.3 Stabilirea priorit˘a¸tilor de execut¸ie . 12.3.4 Sincronizarea firelor de execut¸ie . . 12.3.5 Scenariul produc˘ator / consumator 12.3.6 Monitoare . . . . . . . . . . . . . . 12.3.7 Semafoare . . . . . . . . . . . . . . 12.3.8 Probleme legate de sincronizare . . 12.4 Gruparea firelor de execut¸ie . . . . . . . . 12.5 Comunicarea prin fluxuri de tip ”pipe” . . 12.6 Clasele Timer ¸si TimerTask . . . . . . . .
. . . . . . . . . . . . . . . .
13 Programare ˆın ret¸ea 13.1 Introducere . . . . . . . . . . . . . . . . . . 13.2 Lucrul cu URL-uri . . . . . . . . . . . . . . 13.3 Socket-uri . . . . . . . . . . . . . . . . . . . 13.4 Comunicarea prin conexiuni . . . . . . . . . 13.5 Comunicarea prin datagrame . . . . . . . . . 13.6 Trimiterea de mesaje c˘atre mai mult¸i client¸i 14 Appleturi 14.1 Introducere . . . . . . . . . . . . . . . 14.2 Crearea unui applet simplu . . . . . . . 14.3 Ciclul de viat¸˘a al unui applet . . . . . 14.4 Interfat¸a grafic˘a cu utilizatorul . . . . . 14.5 Definirea ¸si folosirea parametrilor . . . 14.6 Tag-ul APPLET . . . . . . . . . . . . 14.7 Folosirea firelor de execut¸ie ˆın appleturi 14.8 Alte metode oferite de clasa Applet . . 14.9 Arhivarea appleturilor . . . . . . . . . 14.10Restrict¸ii de securitate . . . . . . . . . 14.11Appleturi care sunt ¸si aplicat¸ii . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . .
. . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . .
. . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . .
. . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . .
. . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . .
. . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . .
. . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . .
. . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . .
. . . . . . . . . . .
. . . . . . . . . . . . . . . .
343 . 343 . 344 . 345 . 347 . 352 . 355 . 357 . 358 . 362 . 362 . 367 . 369 . 371 . 373 . 376 . 378
. . . . . .
383 . 383 . 385 . 387 . 388 . 393 . 397
. . . . . . . . . . .
401 . 401 . 402 . 404 . 406 . 408 . 410 . 412 . 416 . 420 . 421 . 421
CUPRINS
9
15 Lucrul cu baze de date 15.1 Introducere . . . . . . . . . . . . . . . . . . 15.1.1 Generalit˘a¸ti despre baze de date . . . 15.1.2 JDBC . . . . . . . . . . . . . . . . . 15.2 Conectarea la o baz˘a de date . . . . . . . . . 15.2.1 Inregistrarea unui driver . . . . . . . 15.2.2 Specificarea unei baze de date . . . . 15.2.3 Tipuri de drivere . . . . . . . . . . . 15.2.4 Realizarea unei conexiuni . . . . . . 15.3 Efectuarea de secvent¸e SQL . . . . . . . . . 15.3.1 Interfat¸a Statement . . . . . . . . . . 15.3.2 Interfat¸a PreparedStatement . . . . . 15.3.3 Interfat¸a CallableStatement . . . . . 15.3.4 Obt¸inerea ¸si prelucrarea rezultatelor 15.3.5 Interfat¸a ResultSet . . . . . . . . . . 15.3.6 Exemplu simplu . . . . . . . . . . . . 15.4 Lucrul cu meta-date . . . . . . . . . . . . . 15.4.1 Interfat¸a DatabaseMetaData . . . . . 15.4.2 Interfat¸a ResultSetMetaData . . . . 16 Lucrul dinamic cu clase 16.1 Inc˘arcarea claselor ˆın memorie . . . . . . 16.2 Mecanismul reflect˘arii . . . . . . . . . . 16.2.1 Examinarea claselor ¸si interfet¸elor 16.2.2 Manipularea obiectelor . . . . . . 16.2.3 Lucrul dinamic cu vectori . . . .
. . . . .
. . . . .
. . . . . . . . . . . . . . . . . .
. . . . .
. . . . . . . . . . . . . . . . . .
. . . . .
. . . . . . . . . . . . . . . . . .
. . . . .
. . . . . . . . . . . . . . . . . .
. . . . .
. . . . . . . . . . . . . . . . . .
. . . . .
. . . . . . . . . . . . . . . . . .
. . . . .
. . . . . . . . . . . . . . . . . .
. . . . .
. . . . . . . . . . . . . . . . . .
. . . . .
. . . . . . . . . . . . . . . . . .
423 . 423 . 423 . 424 . 425 . 426 . 427 . 428 . 430 . 431 . 432 . 434 . 437 . 438 . 438 . 440 . 442 . 442 . 443
. . . . .
445 . 445 . 452 . 453 . 456 . 460
10
CUPRINS
Capitolul 1 Introducere ˆın Java 1.1
Ce este Java ?
Java este o tehnologie inovatoare lansat˘a de compania Sun Microsystems ˆın 1995, care a avut un impact remarcabil asupra ˆıntregii comunit˘a¸ti a dezvoltatorilor de software, impunˆandu-se prin calit˘a¸ti deosebite cum ar fi simplitate, robustet¸e ¸si nu ˆın ultimul rˆand portabilitate. Denumit˘a init¸ial OAK, tehnologia Java este format˘a dintr-un limbaj de programare de nivel ˆınalt pe baza c˘aruia sunt construite o serie de platforme destinate implement˘arii de aplicat¸ii pentru toate segmentele industriei software.
1.1.1
Limbajul de programare Java
Inainte de a prezenta ˆın detaliu aspectele tehnice ale limbajului Java, s˘a amintim caracteristicile sale principale, care l-au transformat ˆıntr-un interval de timp atˆat de scurt ˆıntr-una din cele mai pupulare opt¸iuni pentru dezvoltarea de aplicat¸ii, indiferent de domeniu sau de complexitatea lor. • Simplitate - elimin˘a supraˆınc˘arcarea operatorilor, mo¸stenirea multipl˘a ¸si toate ”facilit˘a¸tile” ce pot provoca scrierea unui cod confuz. • U¸surint¸˘ a ˆın crearea de aplicat¸ii complexe ce folosesc programarea ˆın ret¸ea, fire de execut¸ie, interfat¸˘a grafic˘a, baze de date, etc. • Robustet¸e - elimin˘a sursele frecvente de erori ce apar ˆın programare prin renunt¸area la pointeri, administrarea automat˘a a memoriei ¸si elim11
CAPITOLUL 1. INTRODUCERE ˆIN JAVA
12
inarea pierderilor de memorie printr-o procedur˘a de colectare a obiectelor care nu mai sunt referite, ce ruleaz˘a ˆın fundal (”garbage collector”). • Complet orientat pe obiecte - elimin˘a complet stilul de programare procedural. • Securitate - este un limbaj de programare foarte sigur, furnizˆand mecanisme stricte de securitate a programelor concretizate prin: verificarea dinamic˘a a codului pentru detectarea secvent¸elor periculoase, impunerea unor reguli stricte pentru rularea proceselor la distant¸˘a, etc. • Neutralitate arhitectural˘ a - comportamentul unei aplicat¸ii Java nu depinde de arhitectura fizic˘a a ma¸sinii pe care ruleaz˘a. • Portabililtate - Java este un limbaj independent de platforma de lucru, aceea¸si aplicat¸ie rulˆand f˘ar˘a nici o modificare ¸si f˘ar˘a a necesita recompilarea ei pe sisteme de operare diferite cum ar fi Windows, Linux, Mac OS, Solaris, etc. lucru care aduce economii substant¸iale firmelor dezvoltatoare de aplicat¸ii. • Este compilat ¸si interpretat, aceasta fiind solut¸ia eficient˘a pentru obt¸inerea portabilit˘a¸tii. • Performant¸˘ a - de¸si mai lent decˆat limbajele de programare care genereaz˘a executabile native pentru o anumit˘a platform˘a de lucru, compilatorul Java asigur˘a o performant¸˘a ridicat˘a a codului de octet¸i, astfel ˆıncˆat viteza de lucru put¸in mai sc˘azut˘a nu va fi un impediment ˆın dezvoltarea de aplicat¸ii oricˆat de complexe, inclusiv grafic˘a 3D, animat¸ie, etc. • Este modelat dup˘ a C ¸si C++, trecerea de la C, C++ la Java f˘acˆandu-se foarte u¸sor.
1.1.2
Platforme de lucru Java
Limbajul de programare Java a fost folosit la dezvoltarea unor tehnologii dedicate rezolv˘arii unor probleme din cele mai diverse domenii. Aceste tehnologii au fost grupate ˆın a¸sa numitele platforme de lucru, ce reprezint˘a seturi de libr˘arii scrise ˆın limbajul Java, precum ¸si diverse programe utilitare, folosite pentru dezvoltarea de aplicat¸ii sau componente destinate unei anume categorii de utilizatori.
1.1. CE ESTE JAVA ?
13
• J2SE (Standard Edition) Este platforma standard de lucru ce ofer˘a suport pentru crearea de aplicat¸ii independente ¸si appleturi. De asemenea, aici este inclus˘a ¸si tehnologia Java Web Start ce furnizeaz˘a o modalitate extrem de facil˘a pentru lansarea ¸si instalarea local˘a a programelor scrise ˆın Java direct de pe Web, oferind cea mai comod˘a solut¸ie pentru distribut¸ia ¸si actualizarea aplicat¸iilor Java. • J2ME (Micro Edition) Folosind Java, programarea dispozitivelor mobile este extrem de simpl˘a, platforma de lucru J2ME oferind suportul necesar scrierii de programe dedicate acestui scop. • J2EE (Enterprise Edition) Aceast˘a platform˘a ofer˘a API-ul necesar dezvolt˘arii de aplicat¸ii complexe, formate din componente ce trebuie s˘a ruleze ˆın sisteme eterogene, cu informat¸iile memorate ˆın baze de date distribuite, etc. Tot aici g˘asim ¸si suportul necesar pentru crearea de aplicat¸ii ¸si servicii Web, bazate pe componente cum ar fi servleturi, pagini JSP, etc. Toate distribut¸iile Java sunt oferite gratuit ¸si pot fi desc˘arcate de pe Internet de la adresa ”http://java.sun.com”. In continuare, vom folosi termenul J2SDK pentru a ne referi la distribut¸ia standard J2SE 1.5 SDK (Tiger).
1.1.3
Java: un limbaj compilat ¸si interpretat
In funct¸ie de modul de execut¸ie a aplicat¸iilor, limbajele de programare se ˆımpart ˆın dou˘a categorii: • Interpretate: instruct¸iunile sunt citite linie cu linie de un program numit interpretor ¸si traduse ˆın instruct¸iuni ma¸sin˘a. Avantajul acestei solut¸ii este simplitatea ¸si faptul c˘a fiind interpretat˘a direct sursa programului obt¸inem portabilitatea. Dezavantajul evident este viteza de execut¸ie redus˘a. Probabil cel mai cunoscute limbaj interpretat este limbajul Basic. • Compilate: codul surs˘a al programelor este transformat de compilator ˆıntr-un cod ce poate fi executat direct de procesor, numit cod
CAPITOLUL 1. INTRODUCERE ˆIN JAVA
14
ma¸sin˘a. Avantajul este execut¸ia extrem de rapid˘a, dezavantajul fiind lipsa portabilit˘a¸tii, codul compilat ˆıntr-un format de nivel sc˘azut nu poate fi rulat decˆat pe platforma de lucru pe care a fost compilat. Limbajul Java combin˘a solut¸iile amintite mai sus, programele Java fiind atˆat interpretate cˆat ¸si compilate. A¸sadar vom avea la dispozit¸ie un compilator responsabil cu transformarea surselor programului ˆın a¸sa numitul cod de octet¸i, precum ¸si un interpretor ce va executa respectivul cod de octet¸i. Codul de octet¸i este diferit de codul ma¸sin˘a. Codul ma¸sin˘a este reprezentat de o succesiune de instruct¸iuni specifice unui anumit procesor ¸si unei anumite platforme de lucru reprezentate ˆın format binar astfel ˆıncˆat s˘a poat˘a fi executate f˘ar˘a a mai necesita nici o prelucrare. Codurile de octet¸i sunt seturi de instruct¸iuni care seam˘an˘a cu codul scris ˆın limbaj de asamblare ¸si sunt generate de compilator independent de mediul de lucru. In timp ce codul ma¸sin˘a este executat direct de c˘atre procesor ¸si poate fi folosit numai pe platforma pe care a fost creat, codul de octet¸i este interpretat de mediul Java ¸si de aceea poate fi rulat pe orice platform˘a pe care este instalat˘a mediul de execut¸ie Java. Prin ma¸sina virtual˘a Java (JVM) vom ˆınt¸elege mediul de execut¸ie al aplicat¸iilor Java. Pentru ca un cod de octet¸i s˘a poat˘a fi executat pe un anumit calculator, pe acesta trebuie s˘a fie instalat˘a o ma¸sin˘a virtual˘a Java. Acest lucru este realizat automat de c˘atre distribut¸ia J2SDK.
1.2
Primul program
Crearea oric˘arei aplicat¸ii Java presupune efectuarea urm˘atorilor pa¸si:
1. Scriererea codului surs˘ a
class FirstApp { public static void main( String args[]) { System.out.println("Hello world!"); } }
1.2. PRIMUL PROGRAM
15
Toate aplicat¸iile Java cont¸in o clas˘a principal˘a(primar˘a) ˆın care trebuie s˘a se gaseasc˘a metoda main. Clasele aplicat¸iei se pot gasi fie ˆıntr-un singur fi¸sier, fie ˆın mai multe.
2. Salvarea fi¸sierelor surs˘ a Se va face ˆın fi¸siere care au obligatoriu extensia java, nici o alt˘a extensie nefiind acceptat˘a. Este recomandat ca fi¸sierul care cont¸ine codul surs˘a al clasei primare s˘a aib˘a acela¸si nume cu cel al clasei, de¸si acest lucru nu este obligatoriu. S˘a presupunem c˘a am salvat exemplul de mai sus ˆın fi¸sierul C:\intro\FirstApp.java.
3. Compilarea aplicat¸iei Pentru compilare vom folosi compilatorul javac din distribut¸ia J2SDK. Apelul compilatorului se face pentru fi¸sierul ce cont¸ine clasa principal˘a a aplicat¸iei sau pentru orice fi¸sier/fi¸siere cu extensia java. Compilatorul creeaz˘a cˆate un fi¸sier separat pentru fiecare clas˘a a programului. Acestea au extensia .class ¸si implicit sunt plasate ˆın acela¸si director cu fi¸sierele surs˘a. javac FirstApp.java In cazul ˆın care compilarea a reu¸sit va fi generat fi¸sierul FirstApp.class.
4. Rularea aplicat¸iei Se face cu interpretorul java, apelat pentru unitatea de compilare corespunz˘atoare clasei principale. Deoarece interpretorul are ca argument de intrare numele clasei principale ¸si nu numele unui fi¸sier, ne vom pozit¸iona ˆın directorul ce cont¸ine fi¸sierul FirstApp.class ¸si vom apela interpretorul astfel: java FirstApp Rularea unei aplicat¸ii care nu folose¸ste interfat¸˘a grafic˘a, se va face ˆıntr-o fereastr˘a sistem.
CAPITOLUL 1. INTRODUCERE ˆIN JAVA
16
Atent¸ie Un apel de genul java c:\intro\FirstApp.class este gre¸sit!
1.3 1.3.1
Structura lexical˘ a a limbajului Java Setul de caractere
Limbajului Java lucreaz˘a ˆın mod nativ folosind setul de caractere Unicode. Acesta este un standard internat¸ional care ˆınlocuie¸ste vechiul set de caractere ASCII ¸si care folose¸ste pentru reprezentarea caracterelor 2 octet¸i, ceea ce ˆınseamn˘a c˘a se pot reprezenta 65536 de semne, spre deosebire de ASCII, unde era posibil˘a reprezentarea a doar 256 de caractere. Primele 256 caractere Unicode corespund celor ASCII, referirea la celelalte f˘acˆandu-se prin \uxxxx, unde xxxx reprezint˘a codul caracterului. O alt˘a caracteristic˘a a setului de caractere Unicode este faptul c˘a ˆıntreg intervalul de reprezentare a simbolurilor este divizat ˆın subintervale numite blocuri, cˆateva exemple de blocuri fiind: Basic Latin, Greek, Arabic, Gothic, Currency, Mathematical, Arrows, Musical, etc. Mai jos sunt oferite cˆateva exemple de caractere Unicode. • \u0030 - \u0039 : cifre ISO-Latin 0 - 9 • \u0660 - \u0669 : cifre arabic-indic 0 - 9 • \u03B1 - \u03C9 : simboluri grece¸sti α − ω • \u2200 - \u22FF : simboluri matematice (∀, ∃, ∅, etc.) • \u4e00 - \u9fff : litere din alfabetul Han (Chinez, Japonez, Coreean) Mai multe informat¸ii legate de reprezentarea Unicode pot fi obt¸inute la adresa ”http://www.unicode.org”.
1.3.2
Cuvinte cheie
Cuvintele rezervate ˆın Java sunt, cu cˆateva except¸ii, cele din C++ ¸si au fost enumerate ˆın tabelul de mai jos. Acestea nu pot fi folosite ca nume de clase,
˘ A LIMBAJULUI JAVA 1.3. STRUCTURA LEXICALA
17
interfet¸e, variabile sau metode. true, false, null nu sunt cuvinte cheie, dar nu pot fi nici ele folosite ca nume ˆın aplicat¸ii. Cuvintele marcate prin ∗ sunt rezervate, dar nu sunt folosite. abstract boolean break byte case catch char class const* continue default do
double else extends final finally float for goto* if implements import instanceof
int interface long native new package private protected public return short static
strictfp super switch synchronized this throw throws transient try void volatile while
Incepˆand cu versiunea 1.5, mai exist˘a ¸si cuvˆantul cheie enum.
1.3.3
Identificatori
Sunt secvent¸e nelimitate de litere ¸si cifre Unicode, ˆıncepˆand cu o liter˘a. Dup˘a cum am mai spus, identificatorii nu au voie s˘a fie identici cu cuvintele rezervate.
1.3.4
Literali
Literalii pot fi de urm˘atoarele tipuri: • Intregi Sunt acceptate 3 baze de numerat¸ie : baza 10, baza 16 (ˆıncep cu caracterele 0x) ¸si baza 8 (ˆıncep cu cifra 0) ¸si pot fi de dou˘a tipuri: – normali - se reprezint˘a pe 4 octet¸i (32 bit¸i) – lungi - se reprezint˘a pe 8 octet¸i (64 bit¸i) ¸si se termin˘a cu caracterul L (sau l).
CAPITOLUL 1. INTRODUCERE ˆIN JAVA
18
• Flotant¸i Pentru ca un literal s˘a fie considerat flotant el trebuie s˘a aib˘a cel put¸in o zecimal˘a dup˘a virgul˘a, s˘a fie ˆın notat¸ie exponent¸ial˘a sau s˘a aib˘a sufixul F sau f pentru valorile normale - reprezentate pe 32 bit¸i, respectiv D sau d pentru valorile duble - reprezentate pe 64 bit¸i. Exemple: 1.0, 2e2, 3f, 4D. • Logici Sunt reprezentat¸i de true - valoarea logic˘a de adev˘ar, respectiv false - valoarea logic˘a de fals.
Atent¸ie Spre deosebire de C++, literalii ˆıntregi 1 ¸si 0 nu mai au semnificat¸ia de adev˘arat, respectiv fals.
• Caracter Un literal de tip caracter este utilizat pentru a exprima caracterele codului Unicode. Reprezentarea se face fie folosind o liter˘a, fie o secvent¸˘a escape scris˘a ˆıntre apostrofuri. Secvent¸ele escape permit specificarea caracterelor care nu au reprezentare grafic˘a ¸si reprezentarea unor caractere speciale precum backslash, apostrof, etc. Secvent¸ele escape predefinite ˆın Java sunt: – ’\b’ : Backspace (BS) – ’\t’ : Tab orizontal (HT) – ’\n’ : Linie nou˘a (LF) – ’\f’ : Pagin˘a nou˘a (FF) – ’\r’ : Inceput de rˆand (CR) – ’\"’ : Ghilimele – ’\’’ : Apostrof – ’\\’ : Backslash
˘ A LIMBAJULUI JAVA 1.3. STRUCTURA LEXICALA
19
• S ¸ iruri de caractere Un literal ¸sir de caractere este format din zero sau mai multe caractere ˆıntre ghilimele. Caracterele care formeaz˘a ¸sirul pot fi caractere grafice sau secvent¸e escape. Dac˘a ¸sirul este prea lung el poate fi scris ca o concatenare de sub¸siruri de dimensiune mai mic˘a, concatenarea ¸sirurilor realizˆandu-se cu operatorul +, ca ˆın exemplul: "Ana " + " are " + " mere ". Sirul vid este "". Dup˘a cum vom vedea, orice ¸sir este de fapt o instant¸˘a a clasei String, definit˘a ˆın pachetul java.lang.
1.3.5
Separatori
Un separator este un caracter care indic˘a sfˆar¸situl unei unit˘a¸ti lexicale ¸si ınceputul alteia. In Java separatorii sunt urm˘atorii: ( ) [ ] ; , . . Instruct¸iunile unui program se separ˘a cu punct ¸si virgul˘a.
1.3.6
Operatori
Operatorii Java sunt, cu mici deosebiri, cei din C++: • atribuirea: = • operatori matematici: +, -, *, /, %, ++, -- . Este permis˘a notat¸ia prescurtat˘a de forma lval op= rval: x += 2 n -= 3 Exist˘a operatori pentru autoincrementare ¸si autodecrementare (post ¸si pre): x++, ++x, n--, --n Evaluarea expresiilor logice se face prin metoda scurtcircuitului: evaluarea se opre¸ste ˆın momentul ˆın care valoarea de adev˘ar a expresiei este sigur determinat˘a. • operatori logici: &&(and), ||(or), !(not) • operatori relat¸ionali: <, <=, >, <=, ==, != • operatori pe bit¸i: &(and), |(or), ^ (xor), ~ (not) • operatori de translat¸ie: <<, >>, >>> (shift la dreapta f˘ ar˘ a semn)
CAPITOLUL 1. INTRODUCERE ˆIN JAVA
20
• operatorul if-else: expresie-logica ?
val-true :
val-false
• operatorul , (virgul˘a) folosit pentru evaluarea secvent¸ial˘a a operat¸iilor: int x=0, y=1, z=2; • operatorul + pentru concatenarea ¸sirurilor: String s1="Ana"; String s2="mere"; int x=10; System.out.println(s1 + " are " + x + " " + s2); • operatori pentru conversii (cast) : (tip-de-data) int a = (int)’a’; char c = (char)96; int i = 200; long l = (long)i; //widening conversion long l2 = (long)200; int i2 = (int)l2; //narrowing conversion
1.3.7
Comentarii
In Java exist˘a trei feluri de comentarii: • Comentarii pe mai multe linii, ˆınchise ˆıntre /* ¸si */. • Comentarii pe mai multe linii care ¸tin de documentat¸ie, ˆınchise ˆıntre /** ¸si */. Textul dintre cele dou˘a secvent¸e este automat mutat ˆın documentat¸ia aplicat¸iei de c˘atre generatorul automat de documentat¸ie javadoc. • Comentarii pe o singur˘a linie, care incep cu //. Observat¸ii: • Nu putem scrie comentarii ˆın interiorul altor comentarii. • Nu putem introduce comentarii ˆın interiorul literalilor caracter sau ¸sir de caractere. • Secvent¸ele /* ¸si */ pot s˘a apar˘a pe o linie dup˘a secvent¸a // dar ˆı¸si pierd semnificat¸ia. La fel se ˆıntampl˘a cu secvent¸a // ˆın comentarii care incep cu /* sau */.
1.4. TIPURI DE DATE S¸I VARIABILE
1.4 1.4.1
21
Tipuri de date ¸si variabile Tipuri de date
In Java tipurile de date se impart ˆın dou˘a categorii: tipuri primitive ¸si tipuri referint¸˘ a. Java porne¸ste de la premiza c˘a ”orice este un obiect”, prin urmare tipurile de date ar trebui s˘a fie de fapt definite de clase ¸si toate variabilele ar trebui s˘a memoreze instant¸e ale acestor clase (obiecte). In principiu acest lucru este adev˘arat, ˆıns˘a, pentru usurint¸a program˘arii, mai exist˘a ¸si a¸sa numitele tipurile primitive de date, care sunt cele uzuale : • aritmetice – ˆıntregi: byte (1 octet), short (2), int (4), long (8) – reale: float (4 octeti), double (8) • caracter: char (2 octet¸i) • logic: boolean (true ¸si false) In alte limbaje de programare formatul ¸si dimensiunea tipurilor primitive de date pot depinde de platforma pe care ruleaz˘a programul. In Java acest lucru nu mai este valabil, orice dependent¸˘a de o anumit˘a platform˘a specific˘a fiind eliminat˘a.
Vectorii, clasele ¸si interfet¸ele sunt tipuri referint¸˘a. Valoarea unei variabile de acest tip este, spre deosebire de tipurile primitive, o referint¸˘a (adres˘a de memorie) c˘atre valoarea sau mult¸imea de valori reprezentat˘a de variabila respectiv˘a.
Exist˘a trei tipuri de date din limbajul C care nu sunt suportate de limbajul Java. Acestea sunt: pointer, struct ¸si union. Pointerii au fost eliminat¸i din cauz˘a c˘a erau o surs˘a constant˘a de erori, locul lor fiind luat de tipul referint¸˘a, iar struct ¸si union nu ˆı¸si mai au rostul atˆat timp cˆat tipurile compuse de date sunt formate ˆın Java prin intermediul claselor.
CAPITOLUL 1. INTRODUCERE ˆIN JAVA
22
1.4.2
Variabile
Variabilele pot fi de tip primitiv sau referint¸e la obiecte (tip referint¸˘a). Indiferent de tipul lor, pentru a putea fi folosite variabilele trebuie declarate ¸si, eventual, init¸ializate. • Declararea variabilelor: Tip numeVariabila; • Init¸ializarea variabilelor: Tip numeVariabila = valoare; • Declararea constantelor: final Tip numeVariabila; Evident, exist˘a posibilitatea de a declara ¸si init¸ializa mai multe variabile sau constante de acela¸si tip ˆıntr-o singur˘a instruct¸iune astfel: Tip variabila1[=valoare1], variabila2[=valoare2],...; Convent¸ia de numire a variabilelor ˆın Java include, printre altele, urm˘atoarele criterii: • variabilele finale (constante) se scriu cu majuscule; • variabilele care nu sunt constante se scriu astfel: prima liter˘a mic˘a iar dac˘a numele variabilei este format din mai mult¸i atomi lexicali, atunci primele litere ale celorlalt¸i atomi se scriu cu majuscule. Exemple: final double PI = 3.14; final int MINIM=0, MAXIM = 10; int valoare = 100; char c1=’j’, c2=’a’, c3=’v’, c4=’a’; long numarElemente = 12345678L; String bauturaMeaPreferata = "apa"; In funct¸ie de locul ˆın care sunt declarate variabilele se ˆımpart ˆın urm˘atoatele categorii: a. Variabile membre, declarate ˆın interiorul unei clase, vizibile pentru toate metodele clasei respective cˆat ¸si pentru alte clase ˆın funct¸ie de nivelul lor de acces (vezi ”Declararea variabilelor membre”).
1.4. TIPURI DE DATE S¸I VARIABILE
23
b. Parametri metodelor, vizibili doar ˆın metoda respectiv˘a. c. Variabile locale, declarate ˆıntr-o metod˘a, vizibile doar ˆın metoda respectiv˘a. d. Variabile locale, declarate ˆıntr-un bloc de cod, vizibile doar ˆın blocul respectiv. e. Parametrii de la tratarea except¸iilor (vezi ”Tratarea except¸iilor”). class Exemplu { //Fiecare variabila corespunde situatiei data de numele ei //din enumerarea de mai sus int a; public void metoda(int b) { a = b; int c = 10; for(int d=0; d < 10; d++) { c --; } try { a = b/c; } catch(ArithmeticException e) { System.err.println(e.getMessage()); } } } Observatii: • Variabilele declarate ˆıntr-un for, r˘amˆan locale corpului ciclului: for(int i=0; i<100; i++) { //domeniul de vizibilitate al lui i } i = 101;//incorect • Nu este permis˘a ascunderea unei variabile:
CAPITOLUL 1. INTRODUCERE ˆIN JAVA
24
int x=1; { int x=2; //incorect }
1.5
Controlul execut¸iei
Instruct¸iunile Java pentru controlul execut¸iei sunt foarte asem˘an˘atoare celor din limbajul C ¸si pot fi ˆımp˘art¸ite ˆın urm˘atoarele categorii: • Instruct¸iuni de decizie: if-else, switch-case • Instruct¸iuni de salt: for, while, do-while • Instruct¸iuni pentru tratarea except¸iilor: try-catch-finally, throw • Alte instruct¸iuni: break, continue, return, label:
1.5.1
Instruct¸iuni de decizie
if-else if (expresie-logica) { ... } if (expresie-logica) { ... } else { ... } switch-case switch (variabila) { case valoare1: ... break; case valoare2:
1.5. CONTROLUL EXECUT ¸ IEI
25
... break; ... default: ... } Variabilele care pot fi testate folosind instruct¸iunea switch nu pot fi decˆat de tipuri primitive.
1.5.2
Instruct¸iuni de salt
for for(initializare; expresie-logica; pas-iteratie) { //Corpul buclei } for(int i=0, j=100 ; i < 100 && j > 0; i++, j--) { ... } Atˆat la init¸ializare cˆat ¸si ˆın pasul de iterat¸ie pot fi mai multe instruct¸iuni desp˘art¸ite prin virgul˘a.
while while (expresie-logica) { ... }
do-while do { ... } while (expresie-logica);
CAPITOLUL 1. INTRODUCERE ˆIN JAVA
26
1.5.3
Instruct¸iuni pentru tratarea except¸iilor
Instruct¸iunile pentru tratarea except¸iilor sunt try-catch-finally, respectiv throw ¸si vor fi tratate ˆın capitolul ”Except¸ii”.
1.5.4
Alte instruct¸iuni
• break: p˘ar˘ase¸ste fort¸at corpul unei structuri repetitive. • continue: termina fort¸at iterat¸ia curent˘a a unui ciclu ¸si trece la urm˘atoarea iterat¸ie. • return [valoare]: termin˘a o metod˘a ¸si, eventual, returneaz˘a o valorare. • numeEticheta: : Define¸ste o etichet˘a. De¸si ˆın Java nu exist˘a goto, se pot defini totu¸si etichete folosite ˆın expresii de genul: break numeEticheata sau continue numeEticheta, utile pentru a controla punctul de ie¸sire dintr-o structur˘a repetitiv˘a, ca ˆınexemplul de mai jos: i=0; eticheta: while (i < 10) { System.out.println("i="+i); j=0; while (j < 10) { j++; if (j==5) continue eticheta; if (j==7) break eticheta; System.out.println("j="+j); } i++; }
1.6 1.6.1
Vectori Crearea unui vector
Crearea unui vector presupune realizarea urm˘atoarelor etape:
1.6. VECTORI
27
• Declararea vectorului - Pentru a putea utiliza un vector trebuie, ˆınainte de toate, sa-l declar˘am. Acest lucru se face prin expresii de forma: Tip[] numeVector; sau Tip numeVector[]; ca ˆın exemplele de mai jos: int[] intregi; String adrese[]; • Instant¸ierea Declararea unui vector nu implic˘a ¸si alocarea memoriei necesare pentru ret¸inerea elementelor. Operat¸iunea de alocare a memoriei, numit˘a ¸si instant¸ierea vectorului, se realizeaz˘a ˆıntotdeauna prin intermediul operatorului new. Instant¸ierea unui vector se va face printr-o expresie de genul: numeVector = new Tip[nrElemente]; unde nrElemente reprezint˘a num˘arul maxim de elemente pe care le poate avea vectorul. In urma instant¸ierii vor fi alocat¸i: nrElemente ∗ dimensiune(T ip) octet¸i necesari memor˘arii elementelor din vector, unde prin dimensiune(T ip) am notat num˘arul de octet¸i pe care se reprezint˘a tipul respectiv. v = new int[10]; //aloca spatiu pentru 10 intregi: 40 octeti c = new char[10]; //aloca spatiu pentru 10 caractere: 20 octeti Declararea ¸si instant¸ierea unui vector pot fi f˘acute simultan astfel: Tip[] numeVector = new Tip[nrElemente];
CAPITOLUL 1. INTRODUCERE ˆIN JAVA
28
• Init¸ializarea (opt¸ional) Dup˘a declararea unui vector, acesta poate fi init¸ializat, adic˘a elementele sale pot primi ni¸ste valori init¸iale, evident dac˘a este cazul pentru a¸sa ceva. In acest caz instant¸ierea nu mai trebuie facut˘a explicit, alocarea memoriei f˘acˆandu-se automat ˆın funct¸ie de num˘a rul de elemente cu care se init¸ializeaz˘a vectorul. String culori[] = {"Rosu", "Galben", "Verde"}; int []factorial = {1, 1, 2, 6, 24, 120}; Primul indice al unui vector este 0, deci pozit¸iile unui vector cu n elemente vor fi cuprinse ˆıntre 0 ¸si n − 1. Nu sunt permise construct¸ii de genul Tip numeVector[nrElemente], alocarea memoriei f˘acˆandu-se doar prin intermediul opearatorului new. int v[10]; int v[] = new int[10];
1.6.2
//ilegal //corect
Tablouri multidimensionale
In Java tablourile multidimensionale sunt de fapt vectori de vectori. De exemplu, crearea ¸si instant¸ierea unei matrici vor fi realizate astfel: Tip matrice[][] = new Tip[nrLinii][nrColoane]; matrice[i] este linia i a matricii ¸si reprezint˘a un vector cu nrColoane elemente iar matrice[i][j] este elementul de pe linia i ¸si coloana j.
1.6.3
Dimensiunea unui vector
Cu ajutorul variabilei length se poate afla num˘arul de elemente al unui vector. int []a = new int[5]; // a.length are valoarea 5 int m[][] = new int[5][10]; // m[0].length are valoarea 10 Pentru a ˆınt¸elege modalitatea de folosire a lui length trebuie ment¸ionat c˘a fiecare vector este de fapt o instant¸˘a a unei clase iar length este o variabil˘a public˘a a acelei clase, ˆın care este ret¸inut num˘arul maxim de elemente al vectorului.
1.6. VECTORI
1.6.4
29
Copierea vectorilor
Copierea elementelor unui vector a ˆıntr-un alt vector b se poate face, fie element cu element, fie cu ajutorul metodei System.arraycopy, ca ˆın exemplele de mai jos. Dup˘a cum vom vedea, o atribuire de genul b = a are alt˘a semnificat¸ie decˆat copierea elementelor lui a ˆın b ¸si nu poate fi folosit˘a ˆın acest scop. int a[] = {1, 2, 3, 4}; int b[] = new int[4]; // Varianta 1 for(int i=0; i
1.6.5
Sortarea vectorilor - clasa Arrays
In Java s-a pus un accent deosebit pe implementarea unor structuri de date ¸si algoritmi care s˘a simplifice proceseul de crearea a unui algoritm, programatorul trebuind s˘a se concentreze pe aspectele specifice problemei abordate. Clasa java.util.Arrays ofer˘a diverse metode foarte utile ˆın lucrul cu vectori cum ar fi: • sort - sorteaz˘a ascendent un vector, folosind un algoritm de tip QuickSort performant, de complexitate O(n log(n)). int v[]={3, 1, 4, 2}; java.util.Arrays.sort(v); // Sorteaza vectorul v // Acesta va deveni {1, 2, 3, 4} • binarySearch - c˘autarea binar˘a a unei anumite valori ˆıntr-un vector sortat;
CAPITOLUL 1. INTRODUCERE ˆIN JAVA
30
• equals - testarea egalit˘a¸tii valorilor a doi vectori (au acelea¸si num˘ar de elemente ¸si pentru fiecare indice valorile corespunz˘atoare din cei doi vectori sunt egale) • fill - atribuie fiec˘arui element din vector o valoare specificat˘a.
1.6.6
Vectori cu dimensiune variabil˘ a ¸si eterogeni
Implement˘ari ale vectorilor cu num˘ar variabil de elemente sunt oferite de clase specializate cum ar fi Vector sau ArrayList din pachetul java.util. Astfel de obiecte descriu vectori eterogeni, ale c˘aror elemente au tipul Object, ¸si vor fi studiat¸i ˆın capitolul ”Colect¸ii”.
1.7
S ¸ iruri de caractere
In Java, un ¸sir de caractere poate fi reprezentat printr-un vector format din elemente de tip char, un obiect de tip String sau un obiect de tip StringBuffer. Dac˘a un ¸sir de caractere este constant (nu se dore¸ste schimbarea cont¸inutului s˘a pe parcursul execut¸iei programului) atunci el va fi declarat de tipul String, altfel va fi declarat de tip StringBuffer. Diferent¸a principal˘a ˆıntre aceste clase este c˘a StringBuffer pune la dispozit¸ie metode pentru modificarea cont¸inutului ¸sirului, cum ar fi: append, insert, delete, reverse. Uzual, cea mai folosit˘a modalitate de a lucra cu ¸siruri este prin intermediul clasei String, care are ¸si unele particularit˘a¸ti fat¸˘a de restul claselor menite s˘a simplifice cˆat mai mult folosirea ¸sirurilor de caractere. Clasa StringBuffer va fi utilizat˘a predominant ˆın aplicat¸ii dedicate proces˘arii textelor cum ar fi editoarele de texte. Exemple echivalente de declarare a unui ¸sir: String s = "abc"; String s = new String("abc"); char data[] = {’a’, ’b’, ’c’}; String s = new String(data); Observat¸i prima variant˘a de declarare a ¸sirului s din exemplul de mai sus - de altfel, cea mai folosit˘a - care prezint˘a o particularitate a clasei String fat¸a de restul claselor Java referitoare la instant¸ierea obiectelor sale.
˘ 31 1.8. FOLOSIREA ARGUMENTELOR DE LA LINIA DE COMANDA Concatenarea ¸sirurilor de caractere se face prin intermediul operatorului + sau, ˆın cazul ¸sirurilor de tip StringBuffer, folosind metoda append. String s1 = "abc" + "xyz"; String s2 = "123"; String s3 = s1 + s2; In Java, operatorul de concatenare + este extrem de flexibil, ˆın sensul c˘a permite concatenarea ¸sirurilor cu obiecte de orice tip care au o reprezentare de tip ¸sir de caractere. Mai jos, sunt cˆateva exemple: System.out.print("Vectorul v are" + v.length + " elemente"); String x = "a" + 1 + "b" Pentru a l˘amuri put¸in lucrurile, ceea ce execut˘a compilatorul atunci cˆand ˆıntˆalne¸ste o secvent¸˘a de genul String x = "a" + 1 + "b" este: String x = new StringBuffer().append("a").append(1). append("b").toString() Atent¸ie ˆıns˘a la ordinea de efectuare a operat¸iilor. S¸irul s=1+2+"a"+1+2 va avea valoarea "3a12", primul + fiind operatorul matematic de adunare iar al doilea +, cel de concatenare a ¸sirurilor.
1.8 1.8.1
Folosirea argumentelor de la linia de comand˘ a Transmiterea argumentelor
O aplicat¸ie Java poate primi oricˆate argumente de la linia de comanda ˆın momentul lans˘arii ei. Aceste argumente sunt utile pentru a permite utilizatorului s˘a specifice diverse opt¸iuni legate de funct¸ionarea aplicat¸iei sau s˘a furnizeze anumite date init¸iale programului.
Atent¸ie Programele care folosesc argumente de la linia de comand˘a nu sunt 100% pure Java, deoarece unele sisteme de operare, cum ar fi Mac OS, nu au ˆın mod normal linie de comand˘a.
CAPITOLUL 1. INTRODUCERE ˆIN JAVA
32
Argumentele de la linia de comand˘a sunt introduse la lansarea unei aplicat¸ii, fiind specificate dup˘a numele aplicat¸iei ¸si separate prin spat¸iu. De exemplu, s˘a presupunem c˘a aplicat¸ia Sortare ordoneaz˘a lexicografic (alfabetic) liniile unui fi¸sier ¸si prime¸ste ca argument de intrare numele fi¸sierului pe care s˘a ˆıl sorteze. Pentru a ordona fi¸sierul "persoane.txt", aplicat¸ia va fi lansat˘a astfel: java Sortare persoane.txt A¸sadar, formatul general pentru lansarea unei aplicat¸ii care prime¸ste argumente de la linia de comand˘a este: java NumeAplicatie [arg0 arg1 . . . argn] In cazul ˆın care sunt mai multe, argumentele trebuie separate prin spat¸ii iar dac˘a unul dintre argumente cont¸ine spat¸ii, atunci el trebuie pus ˆıntre ghilimele. Evident, o aplicat¸ie poate s˘a nu primeasc˘a nici un argument sau poate s˘a ignore argumentele primite de la linia de comand˘a.
1.8.2
Primirea argumentelor
In momentul lans˘arii unei aplicat¸ii interpretorul parcurge linia de comand˘a cu care a fost lansat˘a aplicat¸tia ¸si, ˆın cazul ˆın care exist˘a, transmite programului argumentele specificate sub forma unui vector de ¸siruri. Acesta este primit de aplicat¸ie ca parametru al metodei main. Reamintim c˘a formatul metodei main din clasa principal˘a este: public static void main (String args[]) Vectorul args primit ca parametru de metoda main va cont¸ine toate argumentele transmise programului de la linia de comand˘a. In cazul apelului java Sortare persoane.txt vectorul args va cont¸ine un singur element pe prima s˘a pozit¸ie: args[0]="persoane.txt".
Vectoru args este instant¸iat cu un num˘ar de elemente egal cu num˘arul argumentelor primite de la linia de comand˘a. A¸sadar, pentru a afla num˘arul de argumente primite de program este suficient s˘a afl˘am dimensiunea vectorului args prin intermediul atributului length:
˘ 33 1.8. FOLOSIREA ARGUMENTELOR DE LA LINIA DE COMANDA public static void main (String args[]) { int numarArgumente = args.length ; } In cazul ˆın care aplicat¸ia presupune existent¸a unor argumente de la linia de comand˘a, ˆıns˘a acestea nu au fost transmise programului la lansarea sa, vor ap˘area except¸ii (erori) de tipul ArrayIndexOutOfBoundsException. Tratarea acestor except¸ii este prezentat˘a ˆın capitolul ”Except¸ii”. Din acest motiv, este necesar s˘a test˘am dac˘a programul a primit argumentele de la linia de comand˘a necesare pentru funct¸ionarea sa ¸si, ˆın caz contrar, s˘a afi¸seze un mesaj de avertizare sau s˘a foloseasc˘a ni¸ste valori implicite, ca ˆın exemplul de mai jos: public class Salut { public static void main (String args[]) { if (args.length == 0) { System.out.println("Numar insuficient de argumente!"); System.exit(-1); //termina aplicatia } String nume = args[0]; //exista sigur String prenume; if (args.length rel="nofollow">= 1) prenume = args[1]; else prenume = ""; //valoare implicita System.out.println("Salut " + nume + " " + prenume); } } Spre deosebire de limbajul C, vectorul primit de metoda main nu cont¸ine pe prima pozit¸ie numele aplicat¸iei, ˆıntrucˆat ˆın Java numele aplicat¸iei este chiar numele clasei principale, adic˘a a clasei ˆın care se gase¸ste metoda main. S˘a consider˘a ˆın continuare un exemplu simplu ˆın care se dore¸ste afi¸sarea pe ecran a argumentelor primite de la linia de comand˘a: public class Afisare { public static void main (String[] args) { for (int i = 0; i < args.length; i++) System.out.println(args[i]);
CAPITOLUL 1. INTRODUCERE ˆIN JAVA
34 } }
Un apel de genul java Afisare Hello Java va produce urm˘atorul rezultat (aplicat¸ia a primit 2 argumente): Hello Java Apelul java Afisare "Hello Java" va produce ˆıns˘a alt rezultat (aplicat¸ia a primit un singur argument): Hello Java
1.8.3
Argumente numerice
Argumentele de la linia de comand˘a sunt primite sub forma unui vector de ¸siruri (obiecte de tip String). In cazul ˆın care unele dintre acestea reprezint˘a valori numerice ele vor trebui convertite din ¸siruri ˆın numere. Acest lucru se realizeaz˘a cu metode de tipul parseTipNumeric aflate ˆın clasa corespunzatoare tipului ˆın care vrem s˘a facem conversia: Integer, Float, Double, etc. S˘a consider˘am, de exemplu, c˘a aplicat¸ia Power ridic˘a un numar real la o putere ˆıntreag˘a, argumentele fiind trimise de la linia de comand˘a sub forma: java Power "1.5" "2" //ridica 1.5 la puterea 2 Conversia celor dou˘a argumente ˆın numere se va face astfel: public class Power { public static void main(String args[]) { double numar = Double.parseDouble(args[0]); int putere = Integer.parseInt(args[1]); System.out.println("Rezultat=" + Math.pow(numar, putere)); } } Metodele de tipul parseTipNumeric pot produce except¸ii (erori) de tipul NumberFormatException ˆın cazul ˆın care ¸sirul primit ca parametru nu reprezint˘a un numar de tipul respectiv. Tratarea acestor except¸ii este prezentat˘a ˆın capitolul ”Except¸ii”.
Capitolul 2 Obiecte ¸si clase 2.1 2.1.1
Ciclul de viat¸˘ a al unui obiect Crearea obiectelor
In Java, ca ˆın orice limbaj de programare orientat-obiect, crearea obiectelor se realizeaz˘a prin instant¸ierea unei clase ¸si implic˘a urm˘atoarele lucruri: • Declararea Presupune specificarea tipului acelui obiect, cu alte cuvinte specificarea clasei acestuia (vom vedea c˘a tipul unui obiect poate fi ¸si o interfat¸˘a). NumeClasa numeObiect; • Instant¸ierea Se realizeaz˘a prin intermediul operatorului new ¸si are ca efect crearea efectiv˘a a obiectului cu alocarea spat¸iului de memorie corespunz˘ator. numeObiect = new NumeClasa(); • Init¸ializarea Se realizeaz˘a prin intermediul constructorilor clasei respective. Init¸ializarea este de fapt parte integrant˘a a procesului de instant¸iere, ˆın sensul c˘a imediat dup˘a alocarea memoriei ca efect al operatorului new este apelat constructorul specificat. Parantezele rotunde de dup˘a numele clasei indic˘a faptul c˘a acolo este de fapt un apel la unul din constructorii clasei ¸si nu simpla specificare a numelui clasei. Mai general, instant¸ierea ¸si init¸ializarea apar sub forma: 35
36
CAPITOLUL 2. OBIECTE S¸I CLASE numeObiect = new NumeClasa([argumente constructor]);
S˘a consider˘am urm˘atorul exemplu, ˆın care declar˘am ¸si instant¸iem dou˘a obiecte din clasa Rectangle, clas˘a ce descrie suprafet¸e grafice rectangulare, definite de coordonatele colt¸ului stˆanga sus (originea) ¸si l˘a¸timea, respectiv ˆın˘alt¸imea. Rectangle r1, r2; r1 = new Rectangle(); r2 = new Rectangle(0, 0, 100, 200); In primul caz Rectangle() este un apel c˘atre constructorul clasei Rectangle care este responsabil cu init¸ializarea obiectului cu valorile implicite. Dup˘a cum observ˘am ˆın al doilea caz, init¸ializarea se poate face ¸si cu anumit¸i parametri, cu condit¸ia s˘a existe un constructor al clasei respective care s˘a accepte parametrii respectivi. Fiecare clas˘a are un set de constructori care se ocup˘a cu init¸ializare obiectelor nou create. De exemplu, clasa Rectangle are urm˘atorii constructori: public public public public public public
Rectangle() Rectangle(int latime, int inaltime) Rectangle(int x, int y, int latime, int inaltime) Rectangle(Point origine) Rectangle(Point origine, int latime, int inaltime) Rectangle(Point origine, Dimension dimensiune)
Declararea, instant¸ierea ¸si init¸ializarea obiectului pot ap˘area pe aceea¸si linie (cazul cel mai uzual): Rectangle patrat = new Rectangle(0, 0, 100, 100); Obiecte anonime Este posibil˘a ¸si crearea unor obiecte anonime care servesc doar pentru init¸ializarea altor obiecte, caz ˆın care etapa de declarare a referint¸ei obiectului nu mai este prezent˘a: Rectangle patrat = new Rectangle(new Point(0,0), new Dimension(100, 100));
˘ AL UNUI OBIECT 2.1. CICLUL DE VIAT ¸A
37
Spat¸iul de memorie nu este pre-alocat Declararea unui obiect nu implic˘a sub nici o form˘a alocarea de spat¸iu de memorie pentru acel obiect. Alocarea memoriei se face doar la apelul operatorului new. Rectangle patrat; patrat.x = 10; //Eroare - lipseste instantierea
2.1.2
Folosirea obiectelor
Odat˘a un obiect creat, el poate fi folosit ˆın urm˘atoarele sensuri: aflarea unor informat¸ii despre obiect, schimbarea st˘arii sale sau executarea unor act¸iuni. Aceste lucruri se realizeaza prin aflarea sau schimbarea valorilor variabilelor sale, respectiv prin apelarea metodelor sale. Referirea valorii unei variabile se face prin obiect.variabila De exemplu clasa Rectangle are variabilele publice x, y, width, height, origin. Aflarea valorilor acestor variabile sau schimbarea lor se face prin construct¸ii de genul: Rectangle patrat = new Rectangle(0, 0, 100, 200); System.out.println(patrat.width); //afiseaza 100 patrat.x = 10; patrat.y = 20; //schimba originea patrat.origin = new Point(10, 20); //schimba originea Accesul la variabilele unui obiect se face ˆın conformitate cu drepturile de acces pe care le ofer˘a variabilele respective celorlalte clase. (vezi ”Modificatori de acces pentru membrii unei clase”) Apelul unei metode se face prin obiect.metoda([parametri]). Rectangle patrat = new Rectangle(0, 0, 100, 200); patrat.setLocation(10, 20); //schimba originea patrat.setSize(200, 300); //schimba dimensiunea Se observ˘a c˘a valorile variabilelor pot fi modificate indirect prin intermediul metodelor sale. Programarea orientat˘a obiect descurajeaz˘a folosirea direct˘a a variabilelor unui obiect deoarece acesta poate fi adus ˆın st˘ari inconsistente (ireale). In schimb, pentru fiecare variabil˘a care descrie starea
38
CAPITOLUL 2. OBIECTE S¸I CLASE
obiectului trebuie s˘a existe metode care s˘a permit˘a schimbarea/aflarea valorilor variabilelor sale. Acestea se numesc metode de accesare, sau metode setter - getter ¸si au numele de forma setVariabila, respectiv getVariabila. patrat.width = -100; //stare inconsistenta patrat.setSize(-100, -200); //metoda setter //metoda setSize poate sa testeze daca noile valori sunt //corecte si sa valideze sau nu schimbarea lor
2.1.3
Distrugerea obiectelor
Multe limbaje de programare impun ca programatorul s˘a ¸tin˘a evident¸a obiectelor create ¸si s˘a le distrug˘a ˆın mod explicit atunci cˆand nu mai este nevoie de ele, cu alte cuvinte s˘a administreze singur memoria ocupat˘a de obiectele sale. Practica a demonstrat c˘a aceast˘a tehnic˘a este una din principalele furnizoare de erori ce duc la funct¸ionarea defectuoas˘a a programelor. In Java programatorul nu mai are responsabilitatea distrugerii obiectelor sale ˆıntrucˆat, ˆın momentul rul˘arii unui program, simultan cu interpretorul Java, ruleaz˘a ¸si un proces care se ocup˘a cu distrugerea obiectelor care nu mai sunt folosite. Acest proces pus la dispozit¸ie de platforma Java de lucru se nume¸ste garbage collector (colector de gunoi), prescurtat gc. Un obiect este eliminat din memorie de procesul de colectare atunci cˆand nu mai exist˘a nici o referint¸˘a la acesta. Referint¸ele (care sunt de fapt variabile) sunt distruse dou˘a moduri: • natural, atunci cˆand variabila respectiv˘a iese din domeniul s˘au de vizibilitate, de exemplu la terminarea metodei ˆın care ea a fost declarat˘a; • explicit, dac˘a atribuim variabilei respective valoare null.
Cum funct¸ioneaz˘ a colectorul de gunoaie ? Colectorul de gunoaie este un proces de prioritate scazut˘a care se execut˘a periodic, scaneaz˘a dinamic memoria ocupat˘a de programul Java aflat ˆın execut¸ie ¸si marcheaz˘a acele obiecte care au referint¸e directe sau indirecte. Dup˘a ce toate obiectele au fost parcurse, cele care au r˘amas nemarcate sunt eliminate automat din memorie.
2.2. CREAREA CLASELOR
39
Apelul metodei gc din clasa System sugereaz˘a ma¸sinii virtuale Java s˘a ”depun˘a eforturi” ˆın recuperarea memoriei ocupate de obiecte care nu mai sunt folosite, f˘ar˘a a fort¸a ˆıns˘a pornirea procesului.
Finalizare Inainte ca un obiect s˘a fie eliminat din memorie, procesul gc d˘a acelui obiect posibilitatea ”s˘a curet¸e dup˘a el”, apelˆand metoda de finalizare a obiectului respectiv. Uzual, ˆın timpul finaliz˘arii un obiect ˆı¸si inchide fisierele ¸si socket-urile folosite, distruge referint¸ele c˘atre alte obiecte (pentru a u¸ssura sarcina colectorului de gunoaie), etc. Codul pentru finalizarea unui obiect trebuie scris ˆıntr-o metod˘a special˘a numita finalize a clasei ce descrie obiectul respectiv. (vezi ”Clasa Object”)
Atent¸ie Nu confundati metoda finalize din Java cu destructorii din C++. Metoda finalize nu are rolul de a distruge obiectul ci este apelat˘a automat ˆınainte de eliminarea obiectului respectiv din memorie.
2.2
Crearea claselor
2.2.1
Declararea claselor
Clasele reprezint˘a o modalitate de a introduce noi tipuri de date ˆıntr-o aplicat¸ie Java, cealalt˘a modalitate fiind prin intermediul interfet¸elor. Declararea unei clase respect˘a urm˘atorul format general: [public][abstract][final]class NumeClasa [extends NumeSuperclasa] [implements Interfata1 [, Interfata2 ... ]] { // Corpul clasei } A¸sadar, prima parte a declarat¸iei o ocup˘a modificatorii clasei. Ace¸stia sunt:
40
CAPITOLUL 2. OBIECTE S¸I CLASE • public Implicit, o clas˘a poate fi folosit˘a doar de clasele aflate ˆın acela¸si pachet(libr˘arie) cu clasa respectiv˘a (dac˘a nu se specific˘a un anume pachet, toate clasele din directorul curent sunt considerate a fi ˆın acela¸si pachet). O clas˘a declarat˘a cu public poate fi folosit˘a din orice alt˘a clas˘a, indiferent de pachetul ˆın care se g˘ase¸ste. • abstract Declar˘a o clas˘a abstract˘a (¸sablon). O clas˘a abstract˘a nu poate fi instant¸iat˘a, fiind folosit˘a doar pentru a crea un model comun pentru o serie de subclase. (vezi ”Clase ¸si metode abstracte”) • final Declar˘a c˘a respectiva clas˘a nu poate avea subclase. Declarare claselor finale are dou˘a scopuri: – securitate: unele metode pot a¸stepta ca parametru un obiect al unei anumite clase ¸si nu al unei subclase, dar tipul exact al unui obiect nu poate fi aflat cu exactitate decat ˆın momentul executiei; ˆın felul acesta nu s-ar mai putea realiza obiectivul limbajului Java ca un program care a trecut compilarea s˘a nu mai fie susceptibil de nici o eroare. – programare ˆın spririt orientat-obiect: O clasa ”perfect˘a” nu trebuie s˘a mai aib˘a subclase.
Dup˘a numele clasei putem specifica, dac˘a este cazul, faptul c˘a respectiva clas˘a este subclas˘a a unei alte clase cu numele NumeSuperclasa sau/¸si c˘a implementeaz˘a una sau mai multe interfet¸e, ale c˘aror nume trebuie separate prin virgul˘a.
2.2.2
Extinderea claselor
Spre deosebire de alte limbaje de programare orientate-obiect, Java permite doar mo¸stenirea simpl˘ a, ceea ce ˆıneamn˘a c˘a o clas˘a poate avea un singur p˘arinte (superclas˘a). Evident, o clas˘a poate avea oricˆati mo¸stenitori (subclase), de unde rezult˘a c˘a mult¸imea tuturor claselor definite ˆın Java poate fi vazut˘a ca un arbore, r˘ad˘acina acestuia fiind clasa Object. A¸sadar, Object este singura clas˘a care nu are p˘arinte, fiind foarte important˘a ˆın modul de lucru cu obiecte si structuri de date ˆın Java.
2.2. CREAREA CLASELOR
41
Extinderea unei clase se realizeaz˘a folosind cuvˆantul cheie extends: class B extends A {...} // A este superclasa clasei B // B este o subclasa a clasei A O subclas˘a mo¸stene¸ste de la p˘arintele s˘au toate variabilele ¸si metodele care nu sunt private.
2.2.3
Corpul unei clase
Corpul unei clase urmeaz˘a imediat dup˘a declararea clasei ¸si este cuprins ˆıntre acolade. Cont¸inutul acestuia este format din: • Declararea ¸si, eventual, init¸ializarea variabilelor de instant¸˘a ¸si de clas˘a (cunoscute ˆımpreun˘a ca variabile membre). • Declararea ¸si implementarea constructorilor. • Declararea ¸si implementarea metodelor de instant¸a ¸si de clas˘a (cunoscute ˆımpreun˘a ca metode membre). • Declararea unor clase imbricate (interne). Spre deosebire de C++, nu este permis˘a doar declararea metodei ˆın corpul clasei, urmˆand ca implementare s˘a fie facut˘a ˆın afara ei. Implementarea metodelor unei clase trebuie s˘a se fac˘a obligatoriu ˆın corpul clasei. // C++ class A { void metoda1(); int metoda2() { // Implementare } } A::metoda1() { // Implementare }
42
CAPITOLUL 2. OBIECTE S¸I CLASE
// Java class A { void metoda1(){ // Implementare } void metoda2(){ // Implementare } } Variabilele unei clase pot avea acela¸si nume cu metodele clasei, care poate fi chiar numele clasei, f˘ar˘a a exista posibilitatea aparit¸iei vreunei ambiguit˘a¸ti din punctul de vedere al compilatorului. Acest lucru este ˆıns˘a total nerecomandat dac˘a ne gˆandim din perspectiva lizibilit˘a¸tii (clarit˘a¸tii) codului, dovedind un stil ineficient de progamare. class A { int A; void A() {}; // Corect pentru compilator // Nerecomandat ca stil de programare }
Atent¸ie Variabilele ¸si metodele nu pot avea ca nume un cuvˆant cheie Java.
2.2.4
Constructorii unei clase
Constructorii unei clase sunt metode speciale care au acela¸si nume cu cel al clasei, nu returneaz˘a nici o valoare ¸si sunt folosit¸i pentru init¸ializarea obiectelor acelei clase ˆın momentul instant¸ierii lor. class NumeClasa { [modificatori] NumeClasa([argumente]) { // Constructor
2.2. CREAREA CLASELOR
43
} } O clas˘a poate avea unul sau mai mult¸i constructori care trebuie ˆıns˘a s˘a difere prin lista de argumente primite. In felul acesta sunt permise diverse tipuri de init¸ializ˘ari ale obiectelor la crearea lor, ˆın funct¸ie de num˘arul parametrilor cu care este apelat constructorul. S˘a consider˘am ca exemplu declararea unei clase care descrie not¸iunea de dreptunghi ¸si trei posibili constructori pentru aceasta clas˘a. class Dreptunghi { double x, y, w, h; Dreptunghi(double x1, double y1, double w1, double h1) { // Cel mai general constructor x=x1; y=y1; w=w1; h=h1; System.out.println("Instantiere dreptunghi"); } Dreptunghi(double w1, double h1) { // Constructor cu doua argumente x=0; y=0; w=w1; h=h1; System.out.println("Instantiere dreptunghi"); } Dreptunghi() { // Constructor fara argumente x=0; y=0; w=0; h=0; System.out.println("Instantiere dreptunghi"); } } Constructorii sunt apelat¸i automat la instant¸ierea unui obiect. In cazul ˆın care dorim s˘a apel˘am explicit constructorul unei clase folosim expresia this( argumente ), care apeleaz˘a constructorul corespunz˘ator (ca argumente) al clasei respective. Aceast˘a metod˘a este folosit˘a atunci cˆand sunt implementat¸i mai mult¸i constructori pentru o clas˘a, pentru a nu repeta secvent¸ele de cod scrise deja la constructorii cu mai multe argumente (mai generali). Mai eficient, f˘ar˘a
44
CAPITOLUL 2. OBIECTE S¸I CLASE
a repeta acelea¸si secvent¸e de cod ˆın tot¸i constructorii (cum ar fi afi¸sarea mesajului ”Instantiere dreptunghi”), clasa de mai sus poate fi rescris˘a astfel: class Dreptunghi { double x, y, w, h; Dreptunghi(double x1, double y1, double w1, double h1) { // Implementam doar constructorul cel mai general x=x1; y=y1; w=w1; h=h1; System.out.println("Instantiere dreptunghi"); } Dreptunghi(double w1, double h1) { this(0, 0, w1, h1); // Apelam constructorul cu 4 argumente } Dreptunghi() { this(0, 0); // Apelam constructorul cu 2 argumente } } Dintr-o subclas˘a putem apela explicit constructorii superclasei cu expresia super( argumente ). S˘a presupunem c˘a dorim s˘a cre˘am clasa Patrat, derivat˘a din clasa Dreptunghi: class Patrat extends Dreptunghi { Patrat(double x, double y, double d) { super(x, y, d, d); // Apelam constructorul superclasei } }
Atent¸ie Apelul explcit al unui constructor nu poate ap˘area decˆat ˆıntr-un alt constructor si trebuie s˘a fie prima instruct¸iune din constructorul respectiv.
2.2. CREAREA CLASELOR
45
Constructorul implicit Constructorii sunt apelat¸i automat la instant¸ierea unui obiect. In cazul ˆın care scriem o clas˘a care nu are declarat nici un constructor, sistemul ˆıi creeaz˘a automat un constructor implicit, care nu prime¸ste nici un argument ¸si care nu face nimic. Deci prezent¸a constructorilor ˆın corpul unei clase nu este obligatorie. Dac˘a ˆıns˘a scriem un constructor pentru o clas˘a, care are mai mult de un argument, atunci constructorul implicit (f˘ar˘a nici un argument) nu va mai fi furnizat implicit de c˘atre sistem. S˘a consider˘am, ca exemplu, urm˘atoarele declarat¸ii de clase: class Dreptunghi { double x, y, w, h; // Nici un constructor } class Cerc { double x, y, r; // Constructor cu 3 argumente Cerc(double x, double y, double r) { ... }; } S˘a consider˘am acum dou˘a instant¸ieri ale claselor de mai sus: Dreptunghi d = new Dreptunghi(); // Corect (a fost generat constructorul implicit) Cerc c; c = new Cerc(); // Eroare la compilare ! c = new Cerc(0, 0, 100); // Varianta corecta In cazul mo¸stenirii unei clase, instant¸ierea unui obiect din clasa extins˘a implic˘a instant¸ierea unui obiect din clasa p˘arinte. Din acest motiv, fiecare constructor al clasei fiu va trebui s˘a aib˘a un constructor cu aceea¸si signatur˘a ˆın p˘arinte sau s˘a apeleze explicit un constructor al clasei extinse folosind expresia super([argumente]), ˆın caz contrar fiind semnalat˘a o eroare la compilare.
46
CAPITOLUL 2. OBIECTE S¸I CLASE
class A { int x=1; A(int x) { this.x = x;} } class B extends A { // Corect B() {super(2);} B(int x) {super.x = x;} } class C extends A { // Eroare la compilare ! C() {super.x = 2;} C(int x) {super.x = x;} } Constructorii unei clase pot avea urm˘atorii modificatori de acces: public, protected, private ¸si cel implicit. • public In orice alt˘a clas˘a se pot crea instant¸e ale clasei respective. • protected Doar ˆın subclase pot fi create obiecte de tipul clasei respective. • private In nici o alt˘a clas˘a nu se pot instant¸ia obiecte ale acestei clase. O astfel de clas˘a poate cont¸ine metode publice (numite ”factory methods”) care s˘a fie responsabile cu crearea obiectelor, controlˆand ˆın felul acesta diverse aspecte legate de instant¸ierea clasei respective. • implicit Doar ˆın clasele din acela¸si pachet se pot crea instant¸e ale clasei respective.
2.2.5
Declararea variabilelor
Variabilele membre ale unei clase se declar˘a de obicei ˆınaintea metodelor, de¸si acest lucru nu este impus de c˘atre compilator.
2.2. CREAREA CLASELOR
47
class NumeClasa { // Declararea variabilelor // Declararea metodelor } Variabilele membre ale unei clase se declar˘a ˆın corpul clasei ¸si nu ˆın corpul unei metode, fiind vizibile ˆın toate metodele respectivei clase. Variabilele declarate ˆın cadrul unei metode sunt locale metodei respective. Declararea unei variabile presupune specificarea urm˘atoarelor lucruri: • numele variabilei • tipul de date al acesteia • nivelul de acces la acea variabila din alte clase • dac˘a este constant˘a sau nu • dac˘a este variabil˘a de instant¸˘a sau de clas˘a • alt¸i modificatori Generic, o variabil˘a se declar˘a astfel: [modificatori] Tip numeVariabila [ = valoareInitiala ]; unde un modificator poate fi : • un modificator de acces : public, protected, private (vezi ”Modificatori de acces pentru membrii unei clase”) • unul din cuvintele rezervate: static, final, transient, volatile Exemple de declarat¸ii de variabile membre: class Exemplu { double x; protected static int n; public String s = "abcd"; private Point p = new Point(10, 10); final static long MAX = 100000L; }
48
CAPITOLUL 2. OBIECTE S¸I CLASE
S˘a analiz˘am modificatorii care pot fi specificat¸i pentru o variabil˘a, alt¸ii decˆat cei de acces care sunt tratati ˆıntr-o sect¸iune separata: ”Specificatori de acces pentru membrii unei clase”. • static Prezent¸a lui declar˘a c˘a o variabil˘a este variabil˘a de clas˘a ¸si nu de instant¸˘a. (vezi ”Membri de instant¸a ¸si membri de clas˘a”) int variabilaInstanta ; static int variabilaClasa; • final Indic˘a faptul c˘a valoarea variabilei nu mai poate fi schimbat˘a, cu alte cuvinte este folosit pentru declararea constantelor. final double PI = 3.14 ; ... PI = 3.141; // Eroare la compilare ! Prin convent¸ie, numele variabilelor finale se scriu cu litere mari. Folosirea lui final aduce o flexibilitate sporit˘a ˆın lucrul cu constante, ˆın sensul c˘a valoarea unei variabile nu trebuie specificat˘a neap˘arat la declararea ei (ca ˆın exemplul de mai sus), ci poate fi specificat˘a ¸si ulterior ˆıntr-un constructor, dup˘a care ea nu va mai putea fi modificat˘a. class Test { final int MAX; Test() { MAX = 100; // Corect MAX = 200; // Eroare la compilare ! } } • transient Este folosit la serializarea obiectelor, pentru a specifica ce variabile membre ale unui obiect nu particip˘a la serializare. (vezi ”Serializarea obiectelor”)
2.2. CREAREA CLASELOR
49
• volatile Este folosit pentru a semnala compilatorului s˘a nu execute anumite optimiz˘ari asupra membrilor unei clase. Este o facilitate avansat˘a a limbajului Java.
2.2.6
this ¸si super
Sunt variabile predefinite care fac referint¸a, ˆın cadrul unui obiect, la obiectul propriu-zis (this), respectiv la instant¸a p˘arintelui (super). Sunt folosite ˆın general pentru a rezolva conflicte de nume prin referirea explicit˘a a unei variabile sau metode membre. Dup˘a cum am v˘azut, utilizate sub form˘a de metode au rolul de a apela constructorii corespunz˘atori ca argumente ai clasei curente, respectiv ai superclasei class A { int x; A() { this(0); } A(int x) { this.x = x; } void metoda() { x ++; } } class B extends A { B() { this(0); } B(int x) { super(x); System.out.println(x); } void metoda() { super.metoda();
50
CAPITOLUL 2. OBIECTE S¸I CLASE System.out.println(x); }
}
2.3 2.3.1
Implementarea metodelor Declararea metodelor
Metodele sunt responsabile cu descrierea comportamentului unui obiect. Intrucˆat Java este un limbaj de programare complet orientat-obiect, metodele se pot g˘asi doar ˆın cadrul claselor. Generic, o metod˘a se declar˘a astfel: [modificatori] TipReturnat numeMetoda ( [argumente] ) [throws TipExceptie1, TipExceptie2, ...] { // Corpul metodei } unde un modificator poate fi : • un specificator de acces : public, protected, private (vezi ”Specificatori de acces pentru membrii unei clase”) • unul din cuvintele rezervate: static, abstract, final, native, synchronized S˘a analiz˘am modificatorii care pot fi specificat¸i pentru o metod˘a, alt¸ii decˆat cei de acces care sunt tratat¸i ˆıntr-o sect¸iune separat˘a. • static Prezent¸a lui declar˘a c˘a o metod˘a este de clas˘a ¸si nu de instant¸˘a. (vezi ”Membri de instant¸a ¸si membri de clas˘a”) void metodaInstanta(); static void metodaClasa(); • abstract Permite declararea metodelor abstracte. O metod˘a abstract˘a este o metod˘a care nu are implementare ¸si trebuie obligatoriu s˘a fac˘a parte dintr-o clas˘a abstract˘a. (vezi ”Clase ¸si metode abstracte”)
2.3. IMPLEMENTAREA METODELOR
51
• final Specific˘a faptul c˘a acea metoda nu mai poate fi supradefinit˘a ˆın subclasele clasei ˆın care ea este definit˘a ca fiind final˘a. Acest lucru este util dac˘a respectiva metod˘a are o implementare care nu trebuie schimbat˘a sub nici o form˘a ˆın subclasele ei, fiind critic˘a pentru consistent¸a st˘arii unui obiect. De exemplu, student¸ilor unei universit˘a¸ti trebuie s˘a li se calculeze media finala, ˆın funct¸ie de notele obt¸inute la examene, ˆın aceea¸si manier˘a, indiferent de facultatea la care sunt.
class Student { ... final float calcMedie(float note[], float ponderi[]) { ... } ... } class StudentInformatica extends Student { float calcMedie(float note[], float ponderi[]) { return 10.00; } }// Eroare la compilare !
• native In cazul ˆın care avem o libr˘arie important˘a de funct¸ii scrise ˆın alt limbaj de programare, cum ar fi C, C++ ¸si limbajul de asamblare, acestea pot fi refolosite din programele Java. Tehnologia care permite acest lucru se nume¸ste JNI (Java Native Interface) ¸si permite asocierea dintre metode Java declarate cu native ¸si metode native scrise ˆın limbajele de programare ment¸ionate. • synchronized Este folosit ˆın cazul ˆın care se lucreaz˘a cu mai multe fire de execut¸ie iar metoda respectiv˘a gestioneaz˘a resurse comune. Are ca efect construirea unui monitor care nu permite executarea metodei, la un moment dat, decˆat unui singur fir de execut¸ie. (vezi ”Fire de executie”)
52
CAPITOLUL 2. OBIECTE S¸I CLASE
2.3.2
Tipul returnat de o metod˘ a
Metodele pot sau nu s˘a returneze o valoare la terminarea lor. Tipul returnat poate fi atˆat un tip primitiv de date sau o referint¸˘a la un obiect al unei clase. In cazul ˆın care o metod˘a nu returneaz˘a nimic atunci trebuie obligatoriu specificat cuvˆantul cheie void ca tip returnat: public void afisareRezultat() { System.out.println("rezultat"); } private void deseneaza(Shape s) { ... return; } Dac˘a o metod˘a trebuie s˘a returneze o valoare acest lucru se realizeaz˘a prin intermediul instruct¸iunii return, care trebuie s˘a apar˘a ˆın toate situat¸iile de terminare a funct¸iei. double radical(double x) { if (x >= 0) return Math.sqrt(x); else { System.out.println("Argument negativ !"); // Eroare la compilare // Lipseste return pe aceasta ramura } } In cazul ˆın care ˆın declarat¸ia funct¸iei tipul returnat este un tip primitiv de date, valoarea returnat˘a la terminarea funct¸iei trebuie s˘a aib˘a obligatoriu acel tip sau un subtip al s˘au, altfel va fi furnizat˘a o eroare la compilare. In general, orice atribuire care implic˘a pierderi de date este tratat˘a de compilator ca eroare. int metoda() { return 1.2; // Eroare } int metoda() {
2.3. IMPLEMENTAREA METODELOR
53
return (int)1.2; // Corect } double metoda() { return (float)1; // Corect } Dac˘a valoarea returnat˘a este o referint¸˘a la un obiect al unei clase, atunci clasa obiectului returnat trebuie s˘a coincid˘a sau s˘a fie o subclas˘a a clasei specificate la declararea metodei. De exemplu, fie clasa Poligon ¸si subclasa acesteia Patrat. Poligon metoda1( ) { Poligon p = new Poligon(); Patrat t = new Patrat(); if (...) return p; // Corect else return t; // Corect } Patrat metoda2( ) { Poligon p = new Poligon(); Patrat t = new Patrat(); if (...) return p; // Eroare else return t; // Corect }
2.3.3
Trimiterea parametrilor c˘ atre o metod˘ a
Signatura unei metode este dat˘a de numarul ¸si tipul argumentelor primite de acea metod˘a. Tipul de date al unui argument poate fi orice tip valid al limbajului Java, atˆat tip primitiv cˆat ¸si tip referint¸˘a. TipReturnat metoda([Tip1 arg1, Tip2 arg2, ...]) Exemplu:
54
CAPITOLUL 2. OBIECTE S¸I CLASE void adaugarePersoana(String nume, int varsta, float salariu) // String este tip referinta // int si float sunt tipuri primitive
Spre deosebire de alte limbaje, ˆın Java nu pot fi trimise ca parametri ai unei metode referint¸e la alte metode (funct¸ii), ˆıns˘a pot fi trimise referint¸e la obiecte care s˘a cont¸in˘a implementarea acelor metode, pentru a fi apelate. Pˆana la aparit¸ia versiunii 1.5, ˆın Java o metod˘a nu putea primi un num˘ar variabil de argumente, ceea ce ˆınseamna c˘a apelul unei metode trebuia s˘a se fac˘a cu specificarea exact˘a a numarului ¸si tipurilor argumentelor. Vom analiza ˆıntr-o sect¸iune separat˘a modalitate de specificare a unui num˘ar variabil de argumente pentru o metod˘a. Numele argumentelor primite trebuie s˘a difere ˆıntre ele ¸si nu trebuie s˘a coincid˘a cu numele nici uneia din variabilele locale ale metodei. Pot ˆıns˘a s˘a coincid˘a cu numele variabilelor membre ale clasei, caz ˆın care diferent¸ierea dintre ele se va face prin intermediul variabile this. class Cerc { int x, y, raza; public Cerc(int x, int y, int raza) { this.x = x; this.y = y; this.raza = raza; } } In Java argumentele sunt trimise doar prin valoare (pass-by-value). Acest lucru ˆınseamn˘a c˘a metoda recept¸ioneaz˘a doar valorile variabilelor primite ca parametri. Cˆand argumentul are tip primitiv de date, metoda nu-i poate schimba valoarea decˆat local (ˆın cadrul metodei); la revenirea din metod˘a variabila are aceea¸si valoare ca ˆınaintea apelului, modific˘arile f˘acute ˆın cadrul metodei fiind pierdute. Cˆand argumentul este de tip referint¸˘a, metoda nu poate schimba valoarea referint¸ei obiectului, ˆıns˘a poate apela metodele acelui obiect ¸si poate modifica orice variabil˘a membr˘a accesibil˘a. A¸sadar, dac˘a dorim ca o metod˘a s˘a schimbe starea (valoarea) unui argument primit, atunci el trebuie s˘a fie neaparat de tip referint¸˘a.
2.3. IMPLEMENTAREA METODELOR
55
De exemplu, s˘a consider˘am clasa Cerc descris˘a anterior ˆın care dorim s˘a implement˘am o metod˘a care s˘a returneze parametrii cercului. // Varianta incorecta: class Cerc { private int x, y, raza; public void aflaParametri(int valx, int valy, int valr) { // Metoda nu are efectul dorit! valx = x; valy = y; valr = raza; } } Aceast˘a metod˘a nu va realiza lucrul propus ˆıntrucˆat ea prime¸ste doar valorile variabilelor valx, valy ¸ si valr ¸si nu referint¸e la ele (adresele lor de memorie), astfel ˆıncˆat s˘a le poat˘a modifica valorile. In concluzie, metoda nu realizeaz˘a nimic pentru c˘a nu poate schimba valorile variabilelor primite ca argumente. Pentru a rezolva lucrul propus trebuie s˘a definim o clas˘a suplimentar˘a care s˘a descrie parametrii pe care dorim s˘a-i afl˘am: // Varianta corecta class Param { public int x, y, raza; } class Cerc { private int x, y, raza; public void aflaParametri(Param param) { param.x = x; param.y = y; param.raza = raza; } } Argumentul param are tip referint¸˘a ¸si, de¸si nu ˆıi schimb˘am valoarea (valoarea sa este adresa de memorie la care se gase¸ste ¸si nu poate fi schimbat˘a),
56
CAPITOLUL 2. OBIECTE S¸I CLASE
putem schimba starea obiectului, adic˘a informat¸ia propriu-zis˘a cont¸inut˘a de acesta.
Varianta de mai sus a fost dat˘a pentru a clarifica modul de trimitere a argumentelor unei metode. Pentru a afla ˆıns˘a valorile variabilelor care descriu starea unui obiect se folosesc metode de tip getter ˆınsot¸ite de metode setter care s˘a permit˘a schimbarea st˘arii obiectului: class Cerc { private int x, y, raza; public int getX() { return x; } public void setX(int x) { this.x = x; } ... }
2.3.4
Metode cu num˘ ar variabil de argumente
Incepˆand cu versiunea 1.5 a limbajului Java, exist˘a posibilitate de a declara metode care s˘a primeasc˘a un num˘ar variabil de argumente. Noutatea const˘a ˆın folosirea simbolului ..., sintaxa unei astfel de metode fiind: [modificatori] TipReturnat metoda(TipArgumente ...
args)
args reprezint˘a un vector avˆand tipul specificat ¸si instant¸iat cu un num˘ar variabil de argumente, ˆın funct¸ie de apelul metodei. Tipul argumentelor poate fi referint¸˘a sau primitiv. Metoda de mai jos afi¸seaz˘a argumentele primite, care pot fi de orice tip: void metoda(Object ... args) { for(int i=0; i<args.length; i++) System.out.println(args[i]); } ... metoda("Hello"); metoda("Hello", "Java", 1.5);
2.3. IMPLEMENTAREA METODELOR
2.3.5
57
Supraˆınc˘ arcarea ¸si supradefinirea metodelor
Supraˆınc˘arcarea ¸si supradefinirea metodelor sunt dou˘a concepte extrem de utile ale program˘arii orientate obiect, cunoscute ¸si sub denumirea de polimorfism, ¸si se refer˘a la: • supraˆıncarcarea (overloading) : ˆın cadrul unei clase pot exista metode cu acela¸si nume cu condit¸ia ca signaturile lor s˘a fie diferite (lista de argumente primite s˘a difere fie prin num˘arul argumentelor, fie prin tipul lor) astfel ˆıncˆat la apelul funct¸iei cu acel nume s˘a se poat˘a stabili ˆın mod unic care dintre ele se execut˘a. • supradefinirea (overriding): o subclas˘a poate rescrie o metod˘a a clasei p˘arinte prin implementarea unei metode cu acela¸si nume ¸si aceea¸si signatur˘a ca ale superclasei. class A { void metoda() { System.out.println("A: metoda fara parametru"); } // Supraincarcare void metoda(int arg) { System.out.println("A: metoda cu un parametru"); } } class B extends A { // Supradefinire void metoda() { System.out.println("B: metoda fara parametru"); } } O metod˘a supradefinit˘a poate s˘a: • ignore complet codul metodei corespunz˘atoare din superclas˘a (cazul de mai sus): B b = new B(); b.metoda(); // Afiseaza "B: metoda fara parametru"
58
CAPITOLUL 2. OBIECTE S¸I CLASE • extind˘ a codul metodei p˘arinte, executˆand ˆınainte de codul propriu ¸si funct¸ia p˘arintelui: class B extends A { // Supradefinire prin extensie void metoda() { super.metoda(); System.out.println("B: metoda fara parametru"); } } . . . B b = new B(); b.metoda(); /* Afiseaza ambele mesaje: "A: metoda fara parametru" "B: metoda fara parametru" */
O metod˘a nu poate supradefini o metod˘a declarat˘a final˘a ˆın clasa p˘arinte. Orice clas˘a care nu este abstract˘a trebuie obligatoriu s˘a supradefineasc˘a metodele abstracte ale superclasei (dac˘a este cazul). In cazul ˆın care o clas˘a nu supradefine¸ste toate metodele abstracte ale p˘arintelui, ea ˆıns˘a¸si este abstract˘a ¸si va trebui declarat˘a ca atare. In Java nu este posibil˘a supraˆınc˘arcarea operatorilor.
2.4
Modificatori de acces
Modificatorii de acces sunt cuvinte rezervate ce controleaz˘a accesul celorlate clase la membrii unei clase. Specificatorii de acces pentru variabilele ¸si metodele unei clase sunt: public, protected, private ¸si cel implicit (la nivel de pachet), iar nivelul lor de acces este dat ˆın tabelul de mai jos: Specificator Clasa Sublasa Pachet Oriunde private X protected X X* X public X X X X implicit X X
˘ S¸I MEMBRI DE CLASA ˘ 2.5. MEMBRI DE INSTANT ¸A
59
A¸sadar, dac˘a nu este specificat nici un modificator de acces, implicit nivelul de acces este la nivelul pachetului. In cazul ˆın care declar˘am un membru ”protected” atunci accesul la acel membru este permis din subclasele clasei ˆın care a fost declarat dar depinde ¸si de pachetul ˆın care se gase¸ste subclasa: dac˘a sunt ˆın acela¸si pachet accesul este permis, dac˘a nu sunt ˆın acela¸si pachet accesul nu este permis decˆat pentru obiecte de tipul subclasei. Exemple de declarat¸ii: private int secretPersonal; protected String secretDeFamilie; public Vector pentruToti; long doarIntrePrieteni; private void metodaInterna(); public String informatii();
2.5
Membri de instant¸˘ a ¸si membri de clas˘ a
O clas˘a Java poate cont¸ine dou˘a tipuri de variabile ¸si metode : • de instant¸˘a: declarate f˘ ar˘ a modificatorul static, specifice fiec˘arei instant¸e create dintr-o clas˘a ¸si • de clas˘a: declarate cu modificatorul static, specifice clasei.
2.5.1
Variabile de instant¸˘ a ¸si de clas˘ a
Cˆand declar˘am o variabil˘a membr˘a f˘ar˘a modificatorul static, cum ar fi x ˆın exemplul de mai jos: class Exemplu { int x ; //variabila de instanta } se declar˘a de fapt o variabil˘a de instant¸˘a, ceea ce ˆınseamn˘a c˘a la fiecare creare a unui obiect al clasei Exemplu sistemul aloc˘a o zon˘a de memorie separat˘a pentru memorarea valorii lui x. Exemplu o1 = new Exemplu(); o1.x = 100;
60
CAPITOLUL 2. OBIECTE S¸I CLASE Exemplu o2 = new Exemplu(); o2.x = 200; System.out.println(o1.x); // Afiseaza 100 System.out.println(o2.x); // Afiseaza 200
A¸sadar, fiecare obiect nou creat va putea memora valori diferite pentru variabilele sale de instant¸˘a. Pentru variabilele de clas˘a (statice) sistemul aloc˘a o singur˘a zon˘a de memorie la care au acces toate instant¸ele clasei respective, ceea ce ˆınseamn˘a c˘a dac˘a un obiect modific˘a valoarea unei variabile statice ea se va modifica ¸si pentru toate celelalte obiecte. Deoarece nu depind de o anumit˘a instant¸a˘ a unei clase, variabilele statice pot fi referite ¸si sub forma: NumeClasa.numeVariabilaStatica class Exemplu { int x ; // Variabila de static long n; // Variabila de } . . . Exemplu o1 = new Exemplu(); Exemplu o2 = new Exemplu(); o1.n = 100; System.out.println(o2.n); o2.n = 200; System.out.println(o1.n); System.out.println(Exemplu.n); // o1.n, o2.n si Exemplu.n sunt
instanta clasa
// Afiseaza 100 // Afiseaza 200 // Afiseaza 200 referinte la aceeasi valoare
Init¸ializarea variabilelor de clas˘a se face o singur˘a dat˘a, la ˆınc˘arcarea ˆın memorie a clasei respective, ¸si este realizat˘a prin atribuiri obi¸snuite: class Exemplu { static final double PI = 3.14; static long nrInstante = 0; static Point p = new Point(0,0); }
˘ S¸I MEMBRI DE CLASA ˘ 2.5. MEMBRI DE INSTANT ¸A
2.5.2
61
Metode de instant¸˘ a ¸si de clas˘ a
Similar ca la variabile, metodele declarate f˘ar˘a modificatorul static sunt metode de instant¸˘a iar cele declarate cu static sunt metode de clas˘a (statice). Diferent¸a ˆıntre cele dou˘a tipuri de metode este urm˘atoarea: • metodele de instant¸˘a opereaz˘a atˆat pe variabilele de instant¸˘a cˆat ¸si pe cele statice ale clasei; • metodele de clas˘a opereaz˘a doar pe variabilele statice ale clasei. class Exemplu { int x ; // Variabila de instanta static long n; // Variabila de clasa void metodaDeInstanta() { n ++; // Corect x --; // Corect } static void metodaStatica() { n ++; // Corect x --; // Eroare la compilare ! } } Intocmai ca ¸si la variabilele statice, ˆıntrucˆat metodele de clas˘a nu depind de starea obiectelor clasei respective, apelul lor se poate face ¸si sub forma: NumeClasa.numeMetodaStatica Exemplu.metodaStatica(); // Corect, echivalent cu Exemplu obj = new Exemplu(); obj.metodaStatica(); // Corect, de asemenea Metodele de instant¸˘a nu pot fi apelate decˆat pentru un obiect al clasei respective: Exemplu.metodaDeInstanta(); // Eroare la compilare ! Exemplu obj = new Exemplu(); obj.metodaDeInstanta(); // Corect
62
CAPITOLUL 2. OBIECTE S¸I CLASE
2.5.3
Utilitatea membrilor de clas˘ a
Membrii de clas˘a sunt folosit¸i pentru a pune la dispozit¸ie valori ¸si metode independente de starea obiectelor dintr-o anumita clas˘a.
Declararea eficient˘ a a constantelor S˘a consider˘am situat¸ia cˆand dorim s˘a declar˘am o constant˘a. class Exemplu { final double PI = 3.14; // Variabila finala de instanta } La fiecare instant¸iere a clasei Exemplu va fi rezervat˘a o zon˘a de memorie pentru variabilele finale ale obiectului respectiv, ceea ce este o risip˘a ˆıntrucˆat aceste constante au acelea¸si valori pentru toate instant¸ele clasei. Declararea corect˘a a constantelor trebuie a¸sadar facut˘a cu modificatorii static ¸si final, pentru a le rezerva o singur˘a zon˘a de memorie, comun˘a tuturor obiectelor: class Exemplu { static final double PI = 3.14; // Variabila finala de clasa }
Num˘ ararea obiectelor unei clase Num˘ararea obiectelor unei clase poate fi f˘acut˘a extrem de simplu folosind o variabil˘a static˘a ¸si este util˘a ˆın situat¸iile cˆand trebuie s˘a control˘am diver¸si parametri legat¸i de crearea obiectelor unei clase. class Exemplu { static long nrInstante = 0; Exemplu() { // Constructorul este apelat la fiecare instantiere nrInstante ++; } }
˘ S¸I MEMBRI DE CLASA ˘ 2.5. MEMBRI DE INSTANT ¸A
63
Implementarea funct¸iilor globale Spre deosebire de limbajele de programare procedurale, ˆın Java nu putem avea funct¸ii globale definite ca atare, ˆıntrucˆat ”orice este un obiect”. Din acest motiv chiar ¸si metodele care au o funct¸ionalitate global˘a trebuie implementate ˆın cadrul unor clase. Acest lucru se va face prin intermediul metodelor de clas˘a (globale), deoarece acestea nu depind de starea particular˘a a obiectelor din clasa respectiv˘a. De exemplu, s˘a consider˘am funct¸ia sqrt care extrage radicalul unui num˘ar ¸si care se g˘ase¸ste ˆın clasa Math. Dac˘a nu ar fi fost funct¸ie de clas˘a, apelul ei ar fi trebuit f˘acut astfel (incorect, de altfel): // Incorect ! Math obj = new Math(); double rad = obj.sqrt(121); ceea ce ar fi fost extrem de nepl˘acut... Fiind ˆıns˘a metod˘a static˘a ea poate fi apelat˘a prin: Math.sqrt(121) . A¸sadar, funct¸iile globale necesare unei aplicat¸ii vor fi grupate corespunz˘ator ˆın diverse clase ¸si implementate ca metode statice.
2.5.4
Blocuri statice de init¸ializare
Variabilele statice ale unei clase sunt init¸ializate la un moment care precede prima utilizare activ˘a a clasei respective. Momentul efectiv depinde de implementarea ma¸sinii virtuale Java ¸si poart˘a numele de init¸ializarea clasei. Pe lˆang˘a setarea valorilor variabilelor statice, ˆın aceast˘a etap˘a sunt executate ¸si blocurile statice de init¸ializare ale clasei. Acestea sunt secvent¸e de cod de forma: static { // Bloc static de initializare; ... } care se comport˘a ca o metod˘a static˘a apelat˘a automat de c˘atre ma¸sina virtual˘a. Variabilele referite ˆıntr-un bloc static de init¸ializare trebuie s˘a fie obligatoriu de clas˘a sau locale blocului: public class Test { // Declaratii de variabile statice
64
CAPITOLUL 2. OBIECTE S¸I CLASE static int x = 0, y, z; // Bloc static de initializare static { System.out.println("Initializam..."); int t=1; y = 2; z = x + y + t; } Test() { /* La executia constructorului variabilele de clasa sunt deja initializate si toate blocurile statice de initializare au fost obligatoriu executate in prealabil. */ ... }
}
2.6 2.6.1
Clase imbricate Definirea claselor imbricate
O clas˘a imbricat˘a este, prin definit¸ie, o clas˘a membr˘a a unei alte clase, numit˘a ¸si clas˘a de acoperire. In funct¸ie de situat¸ie, definirea unei clase interne se poate face fie ca membru al clasei de acoperire - caz ˆın care este accesibil˘a tuturor metodelor, fie local ˆın cadrul unei metode. class ClasaDeAcoperire{ class ClasaImbricata1 { // Clasa membru } void metoda() { class ClasaImbricata2 { // Clasa locala metodei } }
2.6. CLASE IMBRICATE
65
} Folosirea claselor imbricate se face atunci cˆand o clas˘a are nevoie ˆın implementarea ei de o alt˘a clas˘a ¸si nu exist˘a nici un motiv pentru care aceasta din urm˘a s˘a fie declarat˘a de sine st˘at˘atoare (nu mai este folosit˘a nic˘aieri). O clas˘a imbricat˘a are un privilegiu special fat¸˘a de celelalte clase ¸si anume acces nerestrict¸ionat la toate variabilele clasei de acoperire, chiar dac˘a acestea sunt private. O clas˘a declarat˘a local˘a unei metode va avea acces ¸si la variabilele finale declarate ˆın metoda respectiv˘a. class ClasaDeAcoperire{ private int x=1; class ClasaImbricata1 { int a=x; } void metoda() { final int y=2; int z=3; class ClasaImbricata2 { int b=x; int c=y; int d=z; // Incorect } } } O clas˘a imbricat˘a membr˘a (care nu este local˘a unei metode) poate fi referit˘a din exteriorul clasei de acoperire folosind expresia ClasaDeAcoperire.ClasaImbricata A¸sadar, clasele membru pot fi declarate cu modificatorii public, protected, private pentru a controla nivelul lor de acces din exterior, ˆıntocmai ca orice variabil˘a sau metod˘a mebr˘a a clasei. Pentru clasele imbricate locale unei metode nu sunt permi¸si acest¸i modificatori. Toate clasele imbricate pot fi declarate folosind modificatorii abstract ¸si final, semnificat¸ia lor fiind aceea¸si ca ¸si ˆın cazul claselor obi¸snuite.
66
CAPITOLUL 2. OBIECTE S¸I CLASE
2.6.2
Clase interne
Spre deosebire de clasele obi¸snuite, o clas˘a imbricat˘a poate fi declarat˘a static˘a sau nu. O clas˘a imbricat˘a nestatic˘a se nume¸ste clasa intern˘ a. class ClasaDeAcoperire{ ... class ClasaInterna { ... } static class ClasaImbricataStatica { ... } } Diferent¸ierea acestor denumiri se face deoarece: • o ”clas˘a imbricat˘a” reflect˘a relat¸ia sintactic˘a a dou˘a clase: codul unei clase apare ˆın interiorul codului altei clase; • o ”clas˘a intern˘a” reflect˘a relat¸ia dintre instant¸ele a dou˘a clase, ˆın sensul c˘a o instant¸a a unei clase interne nu poate exista decat ˆın cadrul unei instant¸e a clasei de acoperire. In general, cele mai folosite clase imbricate sunt cele interne. A¸sadar, o clas˘a intern˘a este o clas˘a imbricat˘a ale carei instant¸e nu pot exista decˆat ˆın cadrul instant¸elor clasei de acoperire ¸si care are acces direct la tot¸i membrii clasei sale de acoperire.
2.6.3
Identificare claselor imbricate
Dup˘a cum ¸stim orice clas˘a produce la compilare a¸sa numitele ”unit˘a¸ti de compilare”, care sunt fi¸siere avˆand numele clasei respective ¸si extensia .class ¸si care cont¸in toate informat¸iile despre clasa respectiv˘a. Pentru clasele imbricate aceste unit˘a¸ti de compilare sunt denumite astfel: numele clasei de acoperire, urmat de simbolul ’$’ apoi de numele clasei imbricate. class ClasaDeAcoperire{ class ClasaInterna1 {} class ClasaInterna2 {} }
2.7. CLASE S¸I METODE ABSTRACTE
67
Pentru exemplul de mai sus vor fi generate trei fi¸siere: ClasaDeAcoperire.class ClasaDeAcoperire$ClasaInterna1.class ClasaDeAcoperire$ClasaInterna2.class In cazul ˆın care clasele imbricate au la rˆandul lor alte clase imbricate (situat¸ie mai put¸in uzual˘a) denumirea lor se face dup˘a aceea¸si regul˘a: ad˘augarea unui ’$’ ¸si apoi numele clasei imbricate.
2.6.4
Clase anonime
Exist˘a posibilitatea definirii unor clase imbricate locale, f˘ar˘a nume, utilizate doar pentru instant¸ierea unui obiect de un anumit tip. Astfel de clase se numesc clase anonime ¸si sunt foarte utile ˆın situat¸ii cum ar fi crearea unor obiecte ce implementeaz˘a o anumit˘a interfat¸˘a sau extind o anumit˘a clas˘a abstract˘a. Exemple de folosire a claselor anonime vor fi date ˆın capitolul ”Interfet¸e”, precum ¸si extensiv ˆın capitolul ”Interfat¸a grafic˘a cu utilizatorul”. Fi¸sierele rezultate ˆın urma compil˘arii claselor anonime vor avea numele de forma ClasaAcoperire.$1,..., ClasaAcoperire.$n, unde n este num˘arul de clase anonime definite ˆın clasa respectiv˘a de acoperire.
2.7
Clase ¸si metode abstracte
Uneori ˆın proiectarea unei aplicat¸ii este necesar s˘a reprezent˘am cu ajutorul claselor concepte abstracte care s˘a nu poat˘a fi instant¸iate ¸si care s˘a foloseasc˘a doar la dezvoltarea ulterioar˘a a unor clase ce descriu obiecte concrete. De exemplu, ˆın pachetul java.lang exist˘a clasa abstract˘a Number care modeleaz˘a conceptul generic de ”num˘ar”. Intr-un program nu avem ˆıns˘a nevoie de numere generice ci de numere de un anumit tip: ˆıntregi, reale, etc. Clasa Number serve¸ste ca superclas˘a pentru clasele concrete Byte, Double, Float, Integer, Long ¸si Short, ce implementeaz˘a obiecte pentru descrierea numerelor de un anumit tip. A¸sadar, clasa Number reprezint˘a un concept abstract ¸si nu vom putea instant¸ia obiecte de acest tip - vom folosi ˆın schimb subclasele sale. Number numar = new Number(); // Eroare Integer intreg = new Integer(10); // Corect
68
CAPITOLUL 2. OBIECTE S¸I CLASE
2.7.1
Declararea unei clase abstracte
Declararea unei clase abstracte se face folosind cuvˆantul rezervat abstract: [public] abstract class ClasaAbstracta [extends Superclasa] [implements Interfata1, Interfata2, ...] { // Declaratii uzuale // Declaratii de metode abstracte } O clas˘a abstract˘a poate avea modificatorul public, accesul implicit fiind la nivel de pachet, dar nu poate specifica modificatorul final, combinat¸ia abstract final fiind semnalat˘a ca eroare la compilare - de altfel, o clas˘a declarat˘a astfel nu ar avea nici o utilitate. O clas˘a abstract˘a poate cont¸ine acelea¸si elemente membre ca o clas˘a obi¸snuit˘a, la care se adaug˘a declarat¸ii de metode abstracte - f˘ar˘a nici o implementare.
2.7.2
Metode abstracte
Spre deosebire de clasele obi¸snuite care trebuie s˘a furnizeze implement˘ari pentru toate metodele declarate, o clas˘a abstract˘a poate cont¸ine metode f˘ar˘a nici o implementare. Metodele fara nici o implementare se numesc metode abstracte ¸si pot ap˘area doar ˆın clase abstracte. In fat¸a unei metode abstracte trebuie s˘a apar˘a obligatoriu cuvˆantul cheie abstract, altfel va fi furnizat˘a o eroare de compilare. abstract class ClasaAbstracta { abstract void metodaAbstracta(); // Corect void metoda(); // Eroare } In felul acesta, o clas˘a abstract˘a poate pune la dispozit¸ia subclaselor sale un model complet pe care trebuie s˘a-l implementeze, furnizˆand chiar implementarea unor metode comune tuturor claselor ¸si l˘asˆand explicitarea altora
2.7. CLASE S¸I METODE ABSTRACTE
69
fiec˘arei subclase ˆın parte. Un exemplu elocvent de folosire a claselor ¸si metodelor abstracte este descrierea obiectelor grafice ˆıntr-o manier˘a orientat˘a-obiect. • Obiecte grafice: linii, dreptunghiuri, cercuri, curbe Bezier, etc • St˘ari comune: pozit¸ia(originea), dimensiunea, culoarea, etc • Comportament: mutare, redimensionare, desenare, colorare, etc. Pentru a folosi st˘arile ¸si comportamentele comune acestor obiecte ˆın avantajul nostru putem declara o clas˘a generic˘a GraphicObject care s˘a fie superclas˘a pentru celelalte clase. Metodele abstracte vor fi folosite pentru implementarea comportamentului specific fiec˘arui obiect, cum ar fi desenarea iar cele obi¸snuite pentru comportamentul comun tuturor, cum ar fi schimbarea originii. Implementarea clasei abstracte GraphicObject ar putea ar˘ata astfel: abstract class GraphicObject { // Stari comune private int x, y; private Color color = Color.black; ... // Metode comune public void setX(int x) { this.x = x; } public void setY(int y) { this.y = y; } public void setColor(Color color) { this.color = color; } ... // Metode abstracte abstract void draw(); ... }
70
CAPITOLUL 2. OBIECTE S¸I CLASE
O subclas˘a care nu este abstract˘a a unei clase abstracte trebuie s˘a furnizeze obligatoriu implement˘ari ale metodelor abstracte definite ˆın superclas˘a. Implementarea claselor pentru obiecte grafice ar fi: class Circle extends GraphicObject { void draw() { // Obligatoriu implementarea ... } } class Rectangle extends GraphicObject { void draw() { // Obligatoriu implementarea ... } } Legat de metodele abstracte, mai trebuie ment¸ionate urm˘atoarele: • O clas˘a abstract˘a poate s˘a nu aib˘a nici o metod˘a abstract˘a. • O metod˘a abstract˘a nu poate ap˘area decˆat ˆıntr-o clas˘a abstract˘a. • Orice clas˘a care are o metod˘a abstract˘a trebuie declarat˘a ca fiind abstract˘a.
In API-ul oferit de platforma de lucru Java sunt numeroase exemple de ierarhii care folosesc la nivelele superioare clase abstracte. Dintre cele mai importante amintim: • Number: superclasa abstract˘a a tipurilor referint¸˘a numerice • Reader, Writer: superclasele abstracte ale fluxurilor de intrare/ie¸sire pe caractere • InputStream, OutputStream: superclasele abstracte ale fluxurilor de intrare/ie¸sire pe octet¸i • AbstractList, AbstractSet, AbstractMap: superclase abstracte pentru structuri de date de tip colect¸ie
2.8. CLASA OBJECT
71
• Component : superclasa abstract˘a a componentelor folosite ˆın dezvoltarea de aplicat¸ii cu interfat¸˘a grafic˘a cu utilizatorul (GUI), cum ar fi Frame, Button, Label, etc. • etc.
2.8 2.8.1
Clasa Object Orice clas˘ a are o superclas˘ a
Dup˘a cum am v˘azut ˆın sect¸iunea dedicat˘a modalit˘a¸tii de creare a unei clase, clauza ”extends” specific˘a faptul c˘a acea clas˘a extinde (mo¸stene¸ste) o alt˘a clas˘a, numit˘a superclas˘a. O clas˘a poate avea o singur˘a superclas˘a (Java nu suport˘a mo¸stenirea multipl˘a) ¸si chiar dac˘a nu specific˘am clauza ”extends” la crearea unei clase ea totu¸si va avea o superclas˘a. Cu alte cuvinte, ˆın Java orice clas˘a are o superclas˘a ¸si numai una. Evident, trebuie s˘a existe o except¸ie de la aceast˘a regul˘a ¸si anume clasa care reprezint˘a r˘ad˘acina ierarhiei format˘a de relat¸iile de mo¸stenire dintre clase. Aceasta este clasa Object. Clasa Object este ¸si superclasa implicit˘a a claselor care nu specific˘a o anumit˘a superclas˘a. Declarat¸iile de mai jos sunt echivalente: class Exemplu {} class Exemplu extends Object {}
2.8.2
Clasa Object
Clasa Object este cea mai general˘a dintre clase, orice obiect fiind, direct sau indirect, descendent al acestei clase. Fiind p˘arintele tuturor, Object define¸ste ¸si implementeaz˘a comportamentul comun al tuturor celorlalte clase Java, cum ar fi: • posibilitatea test˘arii egalit˘a¸tii valorilor obiectelor, • specificarea unei reprezent˘ari ca ¸sir de caractere a unui obiect , • returnarea clasei din care face parte un obiect, • notificarea altor obiecte c˘a o variabil˘a de condit¸ie s-a schimbat, etc.
72
CAPITOLUL 2. OBIECTE S¸I CLASE
Fiind subclas˘a a lui Object, orice clas˘a ˆıi poate supradefini metodele care nu sunt finale. Metodele cel mai uzual supradefinite sunt: clone, equals/hashCode, finalize, toString. • clone Aceast˘a metod˘a este folosit˘a pentru duplicarea obiectelor (crearea unor clone). Clonarea unui obiect presupune crearea unui nou obiect de acela¸si tip ¸si care s˘a aib˘a aceea¸si stare (acelea¸si valori pentru variabilele sale). • equals, hashCode Acestea sunt, de obicei, supradefinite ˆımpreun˘a. In metoda equals este scris codul pentru compararea egalit˘a¸tii cont¸inutului a dou˘a obiecte. Implicit (implementarea din clasa Object), aceast˘a metod˘a compar˘a referint¸ele obiectelor. Uzual este redefinit˘a pentru a testa dac˘a st˘arile obiectelor coincid sau dac˘a doar o parte din variabilele lor coincid. Metoda hashCode returneaza un cod ˆıntreg pentru fiecare obiect, pentru a testa consistent¸a obiectelor: acela¸si obiect trebuie s˘a returneze acela¸si cod pe durata execut¸iei programului. Dac˘a dou˘a obiecte sunt egale conform metodei equals, atunci apelul metodei hashCode pentru fiecare din cele dou˘a obiecte ar trebui s˘a returneze acela¸si intreg. • finalize In aceast˘a metod˘a se scrie codul care ”cur˘a¸t˘a dup˘a un obiect” ˆınainte de a fi eliminat din memorie de colectorul de gunoaie. (vezi ”Distrugerea obiectelor”) • toString Este folosit˘a pentru a returna o reprezentare ca ¸sir de caractere a unui obiect. Este util˘a pentru concatenarea ¸sirurilor cu diverse obiecte ˆın vederea afi¸s˘arii, fiind apelat˘a automat atunci cˆand este necesar˘a transformarea unui obiect ˆın ¸sir de caractere. Exemplu obj = new Exemplu(); System.out.println("Obiect=" + obj); //echivalent cu System.out.println("Obiect=" + obj.toString());
2.8. CLASA OBJECT
73
S˘a consider˘am urm˘atorul exemplu, ˆın care implement˘am part¸ial clasa numerelor complexe, ¸si ˆın care vom supradefini metode ale clasei Object. De asemenea, vom scrie un mic program TestComplex ˆın care vom testa metodele clasei definite. Listing 2.1: Clasa numerelor complexe class Complex { private double a ; // partea reala private double b ; // partea imaginara public Complex ( double a , double b ) { this . a = a ; this . b = b ; } public Complex () { this (1 , 0) ; } public boolean equals ( Object obj ) { if ( obj == null ) return false ; if (!( obj instanceof Complex ) ) return false ; Complex comp = ( Complex ) obj ; return ( comp . a == a && comp . b == b ) ; } public Object clone () { return new Complex (a , b ) ; } public String toString () { String semn = ( b > 0 ? " + " : " -" ) ; return a + semn + b + " i " ; } public Complex aduna ( Complex comp ) { Complex suma = new Complex (0 , 0) ; suma . a = this . a + comp . a ; suma . b = this . b + comp . b ; return suma ; } }
74
CAPITOLUL 2. OBIECTE S¸I CLASE
public class TestComplex { public static void main ( String c []) Complex c1 = new Complex (1 ,2) ; Complex c2 = new Complex (2 ,3) ; Complex c3 = ( Complex ) c1 . clone () ; System . out . println ( c1 . aduna ( c2 ) ) ; System . out . println ( c1 . equals ( c2 ) ) ; System . out . println ( c1 . equals ( c3 ) ) ; } }
2.9
{
// 3.0 + 5.0 i // false // true
Conversii automate ˆıntre tipuri
Dup˘a cum v˘azut tipurile Java de date pot fi ˆımp˘art¸ie ˆın primitive ¸si referint¸˘a. Pentru fiecare tip primitiv exist˘a o clas˘a corespunz˘atoare care permie lucrul orientat obiect cu tipul respectiv. byte short int long float double char boolean
Byte Short Integer Long Float Double Character Boolean
Fiecare din aceste clase are un constructor ce permite init¸ializarea unui obiect avˆand o anumit˘a valoare primitiv˘a ¸si metode specializate pentru conversia unui obiect ˆın tipul primitiv corespunz˘ator, de genul tipPrimitivValue: Integer obi = new Integer(1); int i = obi.intValue(); Boolean obb = new Boolean(true); boolean b = obb.booleanValue(); Incepˆand cu versiunea 1.5 a limbajului Java, atribuirile explicite ˆıntre tipuri primitve ¸si referint¸˘a sunt posibile, acest mecanism purtˆand numele de autoboxing, respectiv auto-unboxing. Conversia explicit˘a va fi facut˘a de c˘atre compilator.
2.10. TIPUL DE DATE ENUMERARE
75
// Doar de la versiunea 1.5 ! Integer obi = 1; int i = obi; Boolean obb = true; boolean b = obb;
2.10
Tipul de date enumerare
Incepˆand cu versiunea 1.5 a limbajului Java, exist˘a posibilitatea de a defini tipuri de date enumerare prin folosirea cuvˆantului cheie enum. Acest˘a solut¸ie simplific˘a manevrarea grupurilor de constante, dup˘a cum reiese din urm˘atorul exemplu: public class CuloriSemafor { public static final int ROSU = -1; public static final int GALBEN = 0; public static final int VERDE = 1; } ... // Exemplu de utilizare if (semafor.culoare = CuloriSemafor.ROSU) semafor.culoare = CuloriSemafor.GALBEN); ... Clasa de mai sus poate fi rescris˘a astfel: public enum CuloriSemafor { ROSU, GALBEN, VERDE }; ... // Utilizarea structurii se face la fel ... if (semafor.culoare = CuloriSemafor.ROSU) semafor.culoare = CuloriSemafor.GALBEN); ... Compilatorul este responsabil cu transformarea unei astfel de structuri ˆıntr-o clas˘a corespunz˘atoare.
76
CAPITOLUL 2. OBIECTE S¸I CLASE
Capitolul 3 Except¸ii 3.1
Ce sunt except¸iile ?
Termenul except¸ie este o prescurtare pentru ”eveniment except¸ional” ¸si poate fi definit ca un eveniment ce se produce ˆın timpul execut¸iei unui program ¸si care provoac˘a ˆıntreruperea cursului normal al execut¸iei acestuia. Except¸iile pot ap˘area din diverse cauze ¸si pot avea nivele diferite de gravitate: de la erori fatale cauzate de echipamentul hardware pˆan˘a la erori ce ¸tin strict de codul programului, cum ar fi accesarea unui element din afara spat¸iului alocat unui vector. In momentul cˆand o asemenea eroare se produce ˆın timpul execut¸iei va fi generat un obiect de tip except¸ie ce cont¸ine: • informat¸ii despre except¸ia respectiv˘a; • starea programului ˆın momentul producerii acelei except¸ii. public class Exemplu { public static void main(String args[]) { int v[] = new int[10]; v[10] = 0; //Exceptie ! System.out.println("Aici nu se mai ajunge..."); } } La rularea programului va fi generat˘a o except¸ie, programul se va opri la instruct¸iunea care a cauzat except¸ia ¸si se va afi¸sa un mesaj de eroare de genul: 77
78
CAPITOLUL 3. EXCEPT ¸ II "Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException :10 at Exceptii.main (Exceptii.java:4)"
Crearea unui obiect de tip except¸ie se nume¸ste aruncarea unei except¸ii (”throwing an exception”). In momentul ˆın care o metod˘a genereaz˘a (arunc˘a) o except¸ie sistemul de execut¸ie este responsabil cu g˘asirea unei secvent¸e de cod dintr-o metod˘a care s˘a o trateze. C˘autarea se face recursiv, ˆıncepˆand cu metoda care a generat except¸ia ¸si mergˆand ˆınapoi pe linia apelurilor c˘atre acea metod˘a. Secvent¸a de cod dintr-o metod˘a care trateaz˘a o anumit˘a except¸ie se nume¸ste analizor de except¸ie (”exception handler”) iar interceptarea ¸si tratarea ei se nume¸ste prinderea except¸iei (”catch the exception”). Cu alte cuvinte, la aparit¸ia unei erori este ”aruncat˘a” o except¸ie iar cineva trebuie s˘a o ”prind˘a” pentru a o trata. Dac˘a sistemul nu gase¸ste nici un analizor pentru o anumit˘a except¸ie, atunci programul Java se opre¸ste cu un mesaj de eroare (ˆın cazul exemplului de mai sus mesajul ”Aici nu se mai ajunge...” nu va fi afi¸sat).
Atent¸ie In Java tratarea erorilor nu mai este o opt¸iune ci o constrˆangere. In aproape toate situat¸ile, o secvent¸˘a de cod care poate provoca except¸ii trebuie s˘a specifice modalitatea de tratare a acestora.
3.2
”Prinderea” ¸si tratarea except¸iilor
Tratarea except¸iilor se realizeaz˘a prin intermediul blocurilor de instruct¸iuni try, catch ¸si finally. O secvent¸˘a de cod care trateaz˘a anumite except¸ii trebuie s˘a arate astfel: try { // Instructiuni care pot genera exceptii } catch (TipExceptie1 variabila) { // Tratarea exceptiilor de tipul 1
3.2. ”PRINDEREA” S¸I TRATAREA EXCEPT ¸ IILOR
79
} catch (TipExceptie2 variabila) { // Tratarea exceptiilor de tipul 2 } . . . finally { // Cod care se executa indiferent // daca apar sau nu exceptii } S˘a consider˘am urm˘atorul exemplu: citirea unui fi¸sier octet cu octet ¸si afisarea lui pe ecran. F˘ar˘a a folosi tratarea except¸iilor metoda responsabil˘a cu citirea fi¸sierului ar ar˘ata astfel: public static void citesteFisier(String fis) { FileReader f = null; // Deschidem fisierul System.out.println("Deschidem fisierul " + fis); f = new FileReader(fis); // Citim si afisam fisierul caracter cu caracter int c; while ( (c=f.read()) != -1) System.out.print((char)c); // Inchidem fisierul System.out.println("\\nInchidem fisierul " + fis); f.close(); } Aceast˘a secvent¸˘a de cod va furniza erori la compilare deoarece ˆın Java tratarea erorilor este obligatorie. Folosind mecanismul except¸iilor metoda citeste ˆı¸si poate trata singur˘a erorile care pot surveni pe parcursul execut¸iei sale. Mai jos este codul complte ¸si corect al unui program ce afi¸seaz˘a pe ecran cont¸inutul unui fi¸sier al c˘arui nume este primit ca argument de la linia de comand˘a. Tratarea except¸iilor este realizat˘a complet chiar de c˘atre metoda citeste.
80
CAPITOLUL 3. EXCEPT ¸ II Listing 3.1: Citirea unui fisier - corect
import java . io .*; public class CitireFisier { public static void citesteFisier ( String fis ) { FileReader f = null ; try { // Deschidem fisierul System . out . println ( " Deschidem fisierul " + fis ) ; f = new FileReader ( fis ) ; // Citim si afisam fisierul caracter cu caracter int c ; while ( ( c = f . read () ) != -1) System . out . print (( char ) c ) ; } catch ( File Not F o u n d E x c e p t i o n e ) { // Tratam un tip de exceptie System . err . println ( " Fisierul nu a fost gasit ! " ) ; System . err . println ( " Exceptie : " + e . getMessage () ) ; System . exit (1) ; } catch ( IOException e ) { // Tratam alt tip de exceptie System . out . println ( " Eroare la citirea din fisier ! " ) ; e . printStackTrace () ; } finally { if ( f != null ) { // Inchidem fisierul System . out . println ( " \ nInchidem fisierul . " ) ; try { f . close () ; } catch ( IOException e ) { System . err . println ( " Fisierul nu poate fi inchis ! " ) ; e . printStackTrace () ; } } } } public static void main ( String args []) { if ( args . length > 0) citesteFisier ( args [0]) ; else
3.2. ”PRINDEREA” S¸I TRATAREA EXCEPT ¸ IILOR
81
System . out . println ( " Lipseste numele fisierului ! " ) ; } }
Blocul ”try” contine instruct¸iunile de deschidere a unui fi¸sier ¸si de citire dintr-un fi¸sier, ambele putˆand produce except¸ii. Except¸iile provocate de aceste instruct¸iuni sunt tratate ˆın cele dou˘a blocuri ”catch”, cˆate unul pentru fiecare tip de except¸ie. Inchiderea fi¸sierului se face ˆın blocul ”finally”, deoarece acesta este sigur c˘a se va executa F˘ar˘a a folosi blocul ”finally”, ˆınchiderea fi¸sierului ar fi trebuit facut˘a ˆın fiecare situat¸ie ˆın care fi¸sierul ar fi fost deschis, ceea ce ar fi dus la scrierea de cod redundant. try { ... // Totul a decurs bine. f.close(); } ... catch (IOException e) { ... // A aparut o exceptie la citirea din fisier f.close(); // cod redundant } O problem˘a mai delicat˘a care trebuie semnalata ˆın aceasta situat¸ie este faptul c˘a metoda close, responsabil˘a cu ˆınchiderea unui fi¸sier, poate provoca la rˆandul s˘au except¸ii, de exemplu atunci cˆand fi¸sierul mai este folosit ¸si de alt proces ¸si nu poate fi ˆınchis. Deci, pentru a avea un cod complet corect trebuie s˘a trat˘am ¸si posibilitatea aparit¸iei unei except¸ii la metoda close.
Atent¸ie Obligatoriu un bloc de instruct¸iuni ”try” trebuie s˘a fie urmat de unul sau mai multe blocuri ”catch”, ˆın funct¸ie de except¸iile provocate de acele instruct¸iuni sau (opt¸ional) de un bloc ”finally”.
82
CAPITOLUL 3. EXCEPT ¸ II
3.3
”Aruncarea” except¸iilor
In cazul ˆın care o metod˘a nu ˆı¸si asum˘a responsabilitatea trat˘arii uneia sau mai multor except¸ii pe care le pot provoca anumite instruct¸iuni din codul s˘au atunci ea poate s˘a ”arunce” aceste except¸ii c˘atre metodele care o apeleaz˘a, urmˆand ca acestea s˘a implementeze tratarea lor sau, la rˆandul lor, s˘a ”arunce” mai departe except¸iile respective. Acest lucru se realizeaz˘a prin specificarea ˆın declarat¸ia metodei a clauzei throws: [modificatori] TipReturnat metoda([argumente]) throws TipExceptie1, TipExceptie2, ... { ... }
Atent¸ie O metod˘a care nu trateaz˘a o anumit˘a except¸ie trebuie obligatoriu s˘a o ”arunce”.
In exemplul de mai sus dac˘a nu facem tratarea except¸iilor ˆın cadrul metodei citeste atunci metoda apelant˘a (main) va trebui s˘a fac˘a acest lucru: Listing 3.2: Citirea unui fisier import java . io .*; public class CitireFisier { public static void citesteFisier ( String fis ) throws FileNotFoundException , IOException { FileReader f = null ; f = new FileReader ( fis ) ; int c ; while ( ( c = f . read () ) != -1) System . out . print (( char ) c ) ; f . close () ; }
3.3. ”ARUNCAREA” EXCEPT ¸ IILOR
83
public static void main ( String args []) { if ( args . length > 0) { try { citesteFisier ( args [0]) ; } catch ( File NotFo undE x c ep ti on e ) { System . err . println ( " Fisierul nu a fost gasit ! " ) ; System . err . println ( " Exceptie : " + e ) ; } catch ( IOException e ) { System . out . println ( " Eroare la citirea din fisier ! " ) ; e . printStackTrace () ; } } else System . out . println ( " Lipseste numele fisierului ! " ) ; } }
Observat¸i c˘a, ˆın acest caz, nu mai putem diferent¸ia except¸iile provocate de citirea din fi¸sier s de inchiderea fi¸sierului, ambele fiind de tipul IOException. De asemenea, inchiderea fi¸sierului nu va mai fi facut˘a ˆın situatia ˆın care apare o except¸ie la citirea din fi¸sier. Este situat¸ia ˆın care putem folosi blocul finally f˘ar˘a a folosi nici un bloc catch:
public static void citesteFisier(String fis) throws FileNotFoundException, IOException { FileReader f = null; try { f = new FileReader(numeFisier); int c; while ( (c=f.read()) != -1) System.out.print((char)c); } finally { if (f!=null) f.close(); } }
84
CAPITOLUL 3. EXCEPT ¸ II
Metoda apelant˘a poate arunca la rˆandul sˆau except¸iile mai departe c˘atre metoda care a apelat-o la rˆandul ei. Aceast˘a ˆınl˘ant¸uire se termin˘a cu metoda main care, dac˘a va arunca except¸iile ce pot ap˘area ˆın corpul ei, va determina trimiterea except¸iilor c˘atre ma¸sina virtual˘a Java. public void metoda3 throws TipExceptie { ... } public void metoda2 throws TipExceptie { metoda3(); } public void metoda1 throws TipExceptie { metoda2(); } public void main throws TipExceptie { metoda1(); } Tratarea except¸iilor de c˘atre JVM se face prin terminarea programului ¸si afi¸sarea informat¸iilor despre except¸ia care a determinat acest lucru. Pentru exemplul nostru, metoda main ar putea fi declarat˘a astfel: public static void main(String args[]) throws FileNotFoundException, IOException { citeste(args[0]); } Intotdeauna trebuie g˘asit compromisul optim ˆıntre tratarea local˘a a except¸iilor ¸si aruncarea lor c˘atre nivelele superioare, astfel ˆıncˆat codul s˘a fie cˆat mai clar ¸si identificarea locului ˆın care a ap˘arut except¸ia s˘a fie cˆat mai u¸sor de f˘acut.
Aruncarea unei except¸ii se poate face ¸si implicit prin instruct¸iunea throw ce are formatul: throw exceptie, ca ˆın exemplele de mai jos: throw new IOException("Exceptie I/O"); ... if (index >= vector.length) throw new ArrayIndexOutOfBoundsException(); ...
˘ 3.4. AVANTAJELE TRATARII EXCEPT ¸ IILOR
85
catch(Exception e) { System.out.println("A aparut o exceptie); throw e; } Aceast˘a instruct¸iune este folosit˘a mai ales la aruncarea except¸iilor proprii. (vezi ”Crearea propriilor except¸ii”)
3.4
Avantajele trat˘ arii except¸iilor
Prin modalitatea sa de tratare a except¸iilor, Java are urm˘atoarele avantaje fat¸˘a de mecanismul tradit¸ional de tratare a erorilor: • Separarea codului pentru tratarea unei erori de codul ˆın care ea poate s˘a apar˘a. • Propagarea unei erori pˆan˘a la un analizor de except¸ii corespunz˘ator. • Gruparea erorilor dup˘a tipul lor.
3.4.1
Separarea codului pentru tratarea erorilor
In programarea tradit¸ional˘a tratarea erorilor se combin˘a cu codul ce poate produce aparit¸ia lor producˆand a¸sa numitul ”cod spaghetti”. S˘a consider˘am urm˘atorul exemplu: o funct¸ie care ˆıncarc˘a un fi¸sier ˆın memorie: citesteFisier { deschide fisierul; determina dimensiunea fisierului; aloca memorie; citeste fisierul in memorie; inchide fisierul; } Problemele care pot ap˘area la aceasta funct¸ie, aparent simpl˘a, sunt de genul: ”Ce se ˆıntˆampl˘a dac˘a: ... ?” • fi¸sierul nu poate fi deschis • nu se poate determina dimensiunea fi¸sierului
86
CAPITOLUL 3. EXCEPT ¸ II • nu poate fi alocat˘a suficient˘a memorie • nu se poate face citirea din fi¸sier • fi¸sierul nu poate fi ˆınchis Un cod tradit¸ional care s˘a trateze aceste erori ar ar˘ata astfel: int citesteFisier() { int codEroare = 0; deschide fisierul; if (fisierul s-a deschis) { determina dimensiunea fisierului; if (s-a determinat dimensiunea) { aloca memorie; if (s-a alocat memorie) { citeste fisierul in memorie; if (nu se poate citi din fisier) { codEroare = -1; } } else { codEroare = -2; } } else { codEroare = -3; } inchide fisierul; if (fisierul nu s-a inchis && codEroare == 0) { codEroare = -4; } else { codEroare = codEroare & -4; } } else { codEroare = -5; } return codEroare; } // Cod "spaghetti"
Acest stil de progamare este extrem de susceptibil la erori ¸si ˆıngreuneaz˘a extrem de mult ˆınt¸telegerea sa. In Java, folosind mecansimul except¸iilor, codul ar arata, schematizat, astfel:
˘ 3.4. AVANTAJELE TRATARII EXCEPT ¸ IILOR
87
int citesteFisier() { try { deschide fisierul; determina dimensiunea fisierului; aloca memorie; citeste fisierul in memorie; inchide fisierul; } catch (fisierul nu s-a deschis) {trateaza eroarea;} catch (nu s-a determinat dimensiunea) {trateaza eroarea;} catch (nu s-a alocat memorie) {trateaza eroarea} catch (nu se poate citi din fisier) {trateaza eroarea;} catch (nu se poate inchide fisierul) {trateaza eroarea;} } Diferenta de claritate este evident˘a.
3.4.2
Propagarea erorilor
Propagarea unei erori se face pˆan˘a la un analizor de except¸ii corespunz˘ator. S˘a presupunem c˘a apelul la metoda citesteFisier este consecint¸a unor apeluri imbricate de metode: int metoda1() { metoda2(); ... } int metoda2() { metoda3; ... } int metoda3 { citesteFisier(); ...
88
CAPITOLUL 3. EXCEPT ¸ II }
S˘a presupunem de asemenea c˘a dorim s˘a facem tratarea erorilor doar ˆın metoda1. Tradit¸ional, acest lucru ar trebui f˘acut prin propagarea erorii produse de metoda citesteFisier pˆan˘a la metoda1: int metoda1() { int codEroare = metoda2(); if (codEroare != 0) //proceseazaEroare; ... } int metoda2() { int codEroare = metoda3(); if (codEroare != 0) return codEroare; ... } int metoda3() { int codEroare = citesteFisier(); if (codEroare != 0) return codEroare; ... } Dup˘a cum am vazut, Java permite unei metode s˘a arunce except¸iile ap˘arute ˆın cadrul ei la un nivel superior, adic˘a funct¸iilor care o apeleaz˘a sau sistemului. Cu alte cuvinte, o metod˘a poate s˘a nu ˆı¸si asume responsabilitatea trat˘arii except¸iilor ap˘arute ˆın cadrul ei: int metoda1() { try { metoda2(); } catch (TipExceptie e) { //proceseazaEroare; } ... }
˘ 3.4. AVANTAJELE TRATARII EXCEPT ¸ IILOR
89
int metoda2() throws TipExceptie { metoda3(); ... } int metoda3() throws TipExceptie { citesteFisier(); ... }
3.4.3
Gruparea erorilor dup˘ a tipul lor
In Java exist˘a clase corespunz˘atoare tuturor except¸iilor care pot ap˘area la execut¸ia unui program. Acestea sunt grupate ˆın funct¸ie de similarit˘a¸tile lor ˆıntr-o ierarhie de clase. De exemplu, clasa IOException se ocup˘a cu except¸iile ce pot ap˘area la operat¸ii de intrare/iesire ¸si diferent¸iaz˘a la rˆandul ei alte tipuri de except¸ii, cum ar fi FileNotFoundException, EOFException, etc. La rˆandul ei, clasa IOException se ˆıncadreaz˘a ˆıntr-o categorie mai larg˘a de except¸ii ¸si anume clasa Exception. Radacin˘a acestei ierarhii este clasa Throwable (vezi ”Ierarhia claselor ce descriu except¸ii”). Pronderea unei except¸ii se poate face fie la nivelul clasei specifice pentru acea except¸ie, fie la nivelul uneia din superclasele sale, ˆın funct¸ie de necesit˘a¸tile programului, ˆıns˘a, cu cˆat clasa folosit˘a este mai generic˘a cu atˆat tratarea except¸iilor programul ˆı¸si pierde din flexibilitate. try { FileReader f = new FileReader("input.dat"); /* Acest apel poate genera exceptie de tipul FileNotFoundException Tratarea ei poate fi facuta in unul din modurile de mai jos: */ } catch (FileNotFoundException e) { // Exceptie specifica provocata de absenta // fisierului ’input.dat’ } // sau
90
CAPITOLUL 3. EXCEPT ¸ II catch (IOException e) { // Exceptie generica provocata de o operatie IO } // sau catch (Exception e) { // Cea mai generica exceptie soft } //sau catch (Throwable e) { // Superclasa exceptiilor }
3.5
Ierarhia claselor ce descriu except¸ii
R˘ad˘acina claselor ce descriu except¸ii este clasa Throwable iar cele mai importante subclase ale sale sunt Error, Exception ¸si RuntimeException, care sunt la rˆandul lor superclase pentru o serie ˆıntreag˘a de tipuri de except¸ii.
Erorile, obiecte de tip Error, sunt cazuri speciale de except¸ii generate de funct¸ionarea anormal˘a a echipamentului hard pe care ruleaz˘a un program Java ¸si sunt invizibile programatorilor. Un program Java nu trebuie s˘a trateze aparit¸ia acestor erori ¸si este improbabil ca o metod˘a Java s˘a provoace asemenea erori.
3.6. EXCEPT ¸ II LA EXECUT ¸ IE
91
Except¸iile, obiectele de tip Exception, sunt except¸iile standard (soft) care trebuie tratate de c˘atre programele Java. Dup˘a cum am mai zis tratarea acestor except¸ii nu este o opt¸iune ci o constrˆangere. Except¸iile care pot ”sc˘apa” netratate descind din subclasa RuntimeException ¸si se numesc except¸ii la execut¸ie. Metodele care sunt apelate uzual pentru un obiect except¸ie sunt definite ˆın clasa Throwable ¸si sunt publice, astfel ˆıncˆat pot fi apelate pentru orice tip de except¸ie. Cele mai uzuale sunt: • getMessage - afi¸seaz˘a detaliul unei except¸ii; • printStackTrace - afi¸seaz˘a informat¸ii complete despre except¸ie ¸si localizarea ei; • toString - metod˘a mo¸stenit˘a din clasa Object, care furnizeaz˘a reprezentarea ca ¸sir de caractere a except¸iei.
3.6
Except¸ii la execut¸ie
In general, tratarea except¸iilor este obligatorie ˆın Java. De la acest principu se sustrag ˆıns˘a a¸sa numitele except¸ii la execut¸ie sau, cu alte cuvinte, except¸iile care provin strict din vina programatorului ¸si nu generate de o anumit˘a situat¸ie extern˘a, cum ar fi lipsa unui fi¸sier. Aceste except¸ii au o superclas˘a comun˘a RuntimeException ¸si ˆın acesata categorie sunt incluse except¸iile provocate de: • operat¸ii aritmetice ilegale (ˆımpˆart¸irea ˆıntregilor la zero); ArithmeticException • accesarea membrilor unui obiect ce are valoarea null; NullPointerException • accesarea eronat˘a a elementelor unui vector. ArrayIndexOutOfBoundsException Except¸iile la execut¸ie pot ap˘area uriunde ˆın program ¸si pot fi extrem de numeroare iar ˆıncercarea de ”prindere” a lor ar fi extrem de anevoioas˘a. Din acest motiv, compilatorul permite ca aceste except¸ii s˘a r˘amˆan˘a netratate, tratarea lor nefiind ˆıns˘a ilegal˘a. Reamintim ˆıns˘a c˘a, ˆın cazul aparit¸iei oric˘arui tip de except¸ie care nu are un analizor corespunz˘ator, programul va fi terminat.
92
CAPITOLUL 3. EXCEPT ¸ II int v[] = new int[10]; try { v[10] = 0; } catch (ArrayIndexOutOfBoundsException e) { System.out.println("Atentie la indecsi!"); e.printStackTrace(); } // Corect, programul continua v[11] = 0; /* Nu apare eroare la compilare dar apare exceptie la executie si programul va fi terminat. */ System.out.println("Aici nu se mai ajunge...");
Imp˘art¸irea la 0 va genera o except¸ie doar dac˘a tipul numerelor ˆımp˘art¸ite este aritmetic ˆıntreg. In cazul tipurilor reale (float ¸si double) nu va fi generat˘a nici o except¸ie, ci va fi furnizat ca rezultat o constant˘a care poate fi, funct¸ie de operatie, Infinity, -Infinity, sau Nan. int a=1, int b=0; System.out.println(a/b); // Exceptie la executie ! double x=1, y=-1, z=0; System.out.println(x/z); // Infinity System.out.println(y/z); // -Infinity System.out.println(z/z); // NaN
3.7
Crearea propriilor except¸ii
Adeseori poate ap˘area necesitatea cre˘arii unor except¸ii proprii pentru a pune ˆın evident¸a cazuri speciale de erori provocate de metodele claselor unei libr˘arii, cazuri care nu au fost prevazute ˆın ierarhia except¸iilor standard Java. O except¸ie proprie trebuie s˘a se ˆıncadreze ˆıns˘a ˆın ierarhia except¸iilor Java, cu alte cuvinte clasa care o implementeaz˘a trebuie s˘a fie subclas˘a a uneia deja existente ˆın aceasta ierarhie, preferabil una apropiat˘a ca semnificat¸ie, sau superclasa Exception.
3.7. CREAREA PROPRIILOR EXCEPT ¸ II
93
public class ExceptieProprie extends Exception { public ExceptieProprie(String mesaj) { super(mesaj); // Apeleaza constructorul superclasei Exception } } S˘a consider˘am urm˘atorul exemplu, ˆın care cre˘am o clas˘a ce descrie part¸ial o stiv˘a de numere ˆıntregi cu operat¸iile de ad˘augare a unui element, respectiv de scoatere a elementului din vˆarful stivei. Dac˘a presupunem c˘a stiva poate memora maxim 100 de elemente, ambele operat¸ii pot provoca except¸ii. Pentru a personaliza aceste except¸ii vom crea o clas˘a specific˘a denumit˘a ExceptieStiva: Listing 3.3: Exceptii proprii class ExceptieStiva extends Exception { public ExceptieStiva ( String mesaj ) { super ( mesaj ) ; } } class Stiva { int elemente [] = new int [100]; int n =0; // numarul de elemente din stiva public void adauga ( int x ) throws ExceptieStiva { if ( n ==100) throw new ExceptieStiva ( " Stiva este plina ! " ) ; elemente [ n ++] = x ; } public int scoate () throws ExceptieStiva { if ( n ==0) throw new ExceptieStiva ( " Stiva este goala ! " ) ; return elemente [n - -]; } }
Secvent¸a cheie este extends Exception care specific˘a faptul c˘a noua clas˘a ExceptieStiva este subclas˘a a clasei Exception ¸si deci implementeaz˘a obiecte ce reprezint˘a except¸ii.
94
CAPITOLUL 3. EXCEPT ¸ II
In general, codul ad˘augat claselor pentru except¸ii proprii este nesemnificativ: unul sau doi constructori care afi¸seaza un mesaj de eroare la ie¸sirea standard. Procesul de creare a unei noi except¸ii poate fi dus mai departe prin ad˘augarea unor noi metode clasei ce descrie acea except¸ie, ˆıns˘a aceasta dezvoltare nu ˆı¸si are rostul ˆın majoritatea cazurilor. Except¸iile proprii sunt descrise uzual de clase foarte simple, chiar f˘ar˘a nici un cod ˆın ele, cum ar fi: class ExceptieSimpla extends Exception { } Aceast˘a clas˘a se bazeaz˘a pe constructorul implicit creat de compilator ˆıns˘a nu are constructorul ExceptieSimpla(String s).
Capitolul 4 Intr˘ ari ¸si ie¸siri 4.1 4.1.1
Introducere Ce sunt fluxurile?
Majoritatea aplicat¸iilor necesit˘a citirea unor informat¸ii care se g˘asesc pe o surs˘a extern˘a sau trimiterea unor informat¸ii c˘atre o destinat¸ie extern˘a. Informat¸ia se poate g˘asi oriunde: ˆıntr-un fi¸sier pe disc, ˆın ret¸ea, ˆın memorie sau ˆın alt program ¸si poate fi de orice tip: date primitive, obiecte, imagini, sunete, etc. Pentru a aduce informat¸ii dintr-un mediu extern, un progam Java trebuie s˘a deschid˘a un canal de comunicat¸ie (flux) de la sursa informat¸iilor (fi¸sier, memorie, socket, etc) ¸si s˘a citeasc˘a secvent¸ial informat¸iile respective. Similar, un program poate trimite informat¸ii c˘atre o destinat¸ie extern˘a deschizˆand un canal de comunicat¸ie (flux) c˘atre acea destinat¸ie ¸si scriind secvent¸ial informat¸iile respective. Indiferent de tipul informat¸iilor, citirea/scrierea de pe/c˘atre un mediu extern respect˘a urm˘atorul algoritm: deschide canal comunicatie while (mai sunt informatii) { citeste/scrie informatie; } inchide canal comunicatie; Pentru a generaliza, atˆat sursa extern˘a a unor date cˆat ¸si destinat¸ia lor sunt v˘azute ca fiind ni¸ste procese care produc, respectiv consum˘a informat¸ii. 95
˘ S¸I IES¸IRI CAPITOLUL 4. INTRARI
96
Definit¸ii: Un flux este un canal de comunicat¸ie unidirect¸ional ˆıntre dou˘a procese. Un proces care descrie o surs˘a extern˘a de date se nume¸ste proces produc˘ ator. Un proces care descrie o destinat¸ie extern˘a pentru date se nume¸ste proces consumator. Un flux care cite¸ste date se nume¸ste flux de intrare. Un flux care scrie date se nume¸ste flux de ie¸sire.
Observat¸ii: Fluxurile sunt canale de comunicat¸ie seriale pe 8 sau 16 bit¸i. Fluxurile sunt unidirect¸ionale, de la produc˘ator la consumator. Fiecare flux are un singur proces produc˘ator ¸si un singur proces consumator. Intre dou˘a procese pot exista oricˆate fluxuri, orice proces putˆand fi atˆat producator cˆat ¸si consumator ˆın acela¸si timp, dar pe fluxuri diferite. Consumatorul ¸si producatorul nu comunic˘a direct printr-o interfat¸˘a de flux ci prin intermediul codului Java de tratare a fluxurilor.
Clasele ¸si intefet¸ele standard pentru lucrul cu fluxuri se g˘asesc ˆın pachetul java.io. Deci, orice program care necesit˘a operat¸ii de intrare sau ie¸sire trebuie s˘a cont¸in˘a instruct¸iunea de import a pachetului java.io: import java.io.*;
4.1.2
Clasificarea fluxurilor
Exist˘a trei tipuri de clasificare a fluxurilor: • Dup˘a direct¸ia canalului de comunicat¸ie deschis fluxurile se ˆımpart ˆın: – fluxuri de intrare (pentru citirea datelor) – fluxuri de ie¸sire (pentru scrierea datelor) • Dup˘a tipul de date pe care opereaz˘a: – fluxuri de octet¸i (comunicarea serial˘a se realizeaz˘a pe 8 bit¸i) – fluxuri de caractere (comunicarea serial˘a se realizeaz˘a pe 16 bit¸i)
4.1. INTRODUCERE
97
• Dup˘a act¸iunea lor: – fluxuri primare de citire/scriere a datelor (se ocup˘a efectiv cu citirea/scrierea datelor) – fluxuri pentru procesarea datelor
4.1.3
Ierarhia claselor pentru lucrul cu fluxuri
Clasele r˘ad˘acin˘a pentru ierarhiile ce reprezint˘a fluxuri de caractere sunt: • Reader- pentru fluxuri de intrare ¸si • Writer- pentru fluxuri de ie¸sire. Acestea sunt superclase abstracte pentru toate clasele ce implementeaz˘a fluxuri specializate pentru citirea/scrierea datelor pe 16 bit¸i ¸si vor cont¸ine metodele comune tuturor. Ca o regul˘a general˘a, toate clasele din aceste ierarhii vor avea terminat¸ia Reader sau Writer ˆın funct¸ie de tipul lor, cum ar fi ˆın exemplele: FileReader, BufferedReader, FileWriter, BufferedWriter, etc. De asemenea, se observ˘a ca o alt˘a regul˘a general˘a, faptul c˘a unui flux de intrare XReader ˆıi corespunde uzual un flux de ie¸sire XWriter, ˆıns˘a acest lucru nu este obligatoriu.
Clasele radacin˘a pentru ierarhia fluxurilor de octet¸i sunt: • InputStream- pentru fluxuri de intrare ¸si • OutputStream- pentru fluxuri de ie¸sire. Acestea sunt superclase abstracte pentru clase ce implementeaz˘a fluxuri specializate pentru citirea/scrierea datelor pe 8 bit¸i. Ca ¸si ˆın cazul fluxurilor pe caractere denumirile claselor vor avea terminat¸ia superclasei lor: FileInputStream, BufferedInputStream, FileOutputStream, BufferedOutputStream, etc., fiec˘arui flux de intrare XInputStream corespunzˆandu-i uzual un flux de ie¸sire XOutputStream, f˘ar˘a ca acest lucru s˘a fie obligatoriu.
˘ S¸I IES¸IRI CAPITOLUL 4. INTRARI
98
Pˆan˘a la un punct, exist˘a un paralelism ˆıntre ierarhia claselor pentru fluxuri de caractere ¸si cea pentru fluxurile pe octet¸i. Pentru majoritatea programelor este recomandat ca scrierea ¸si citirea datelor s˘a se fac˘a prin intermediul fluxurilor de caractere, deoarece acestea permit manipularea caracterelor Unicode ˆın timp ce fluxurile de octet¸i permit doar lucrul pe 8 biti caractere ASCII.
4.1.4
Metode comune fluxurilor
Superclasele abstracte Reader ¸si InputStream definesc metode similare pentru citirea datelor. Reader InputStream int read() int read() int read(char buf[]) int read(byte buf[]) ... ... De asemenea, ambele clase pun la dispozit¸ie metode pentru marcarea unei locat¸ii ˆıntr-un flux, saltul peste un num˘ar de pozit¸ii, resetarea pozit¸iei curente, etc. Acestea sunt ˆıns˘a mai rar folosite ¸si nu vor fi detaliate. Superclasele abstracte Writer ¸si OutputStream sunt de asemenea paralele, definind metode similare pentru scrierea datelor: Reader void write(int c) void write(char buf[]) void write(String str) ...
InputStream void write(int c) void write(byte buf[]) ...
Inchiderea oric˘arui flux se realizeaz˘a prin metoda close. In cazul ˆın care aceasta nu este apelat˘a explicit, fluxul va fi automat ˆınchis de c˘atre colectorul de gunoaie atunci cˆand nu va mai exista nici o referint¸˘a la el, ˆıns˘a acest lucru trebuie evitat deoarece, la lucrul cu fluxrui cu zon˘a tampon de memorie, datele din memorie vor fi pierdute la ˆınchiderea fluxului de c˘atre gc. Metodele referitoare la fluxuri pot genera except¸ii de tipul IOException sau derivate din aceast˘a clas˘a, tratarea lor fiind obligatorie.
4.2. FOLOSIREA FLUXURILOR
4.2
99
Folosirea fluxurilor
A¸sa cum am v˘azut, fluxurile pot fi ˆımp˘art¸ite ˆın funct¸ie de activitatea lor ˆın fluxuri care se ocup˘a efectiv cu citirea/scrierea datelor ¸si fluxuri pentru procesarea datelor (de filtrare). In continuare, vom vedea care sunt cele mai importante clase din cele dou˘a categorii ¸si la ce folosesc acestea, precum ¸si modalit˘a¸tile de creare ¸si utilizare a fluxurilor.
4.2.1
Fluxuri primitive
Fluxurile primitive sunt responsabile cu citirea/scrierea efectiv˘a a datelor, punˆand la dispozit¸ie implement˘ari ale metodelor de baz˘a read, respectiv write, definite ˆın superclase. In funct¸ie de tipul sursei datelor, ele pot fi ˆımp˘art¸ite astfel: • Fi¸sier FileReader, FileWriter FileInputStream, FileOutputStream Numite ¸si fluxuri fi¸sier, acestea sunt folosite pentru citirea datelor dintr-un fi¸sier, respectiv scrierea datelor ˆıntr-un fi¸sier ¸si vor fi analizate ˆıntr-o sect¸iune separat˘a (vezi ”Fluxuri pentru lucrul cu fi¸siere”). • Memorie CharArrayReader, CharArrayWriter ByteArrayInputStream, ByteArrayOutputStream Aceste fluxuri folosesc pentru scrierea/citirea informat¸iilor ˆın/din memorie ¸si sunt create pe un vector existent deja. Cu alte cuvinte, permit tratarea vectorilor ca surs˘a/destinat¸ie pentru crearea unor fluxuri de intrare/ie¸sire. StringReader, StringWriter Permit tratarea ¸sirurilor de caractere aflate ˆın memorie ca surs˘a/destinat¸ie pentru crearea de fluxuri. • Pipe PipedReader, PipedWriter PipedInputStream, PipedOutputStream Implementeaz˘a componentele de intrare/ie¸sire ale unei conducte de
˘ S¸I IES¸IRI CAPITOLUL 4. INTRARI
100
date (pipe). Pipe-urile sunt folosite pentru a canaliza ie¸sirea unui program sau fir de execut¸ie c˘atre intrarea altui program sau fir de execut¸ie.
4.2.2
Fluxuri de procesare
Fluxurile de procesare (sau de filtrare) sunt responsabile cu preluarea datelor de la un flux primitiv ¸si procesarea acestora pentru a le oferi ˆıntr-o alt˘a form˘a, mai util˘a dintr-un anumit punct de vedere. De exemplu, BufferedReader poate prelua date de la un flux FileReader ¸si s˘a ofere informat¸ia dintr-un fi¸sier linie cu linie. Fiind primitiv, FileReader nu putea citi decˆat caracter cu caracter. Un flux de procesare nu poate fi folosit decˆat ˆımpreun˘a cu un flux primitiv. Clasele ce descriu aceste fluxuri pot fi ˆımpartite ˆın funct¸ie de tipul de procesare pe care ˆıl efectueaza astfel: • ”Bufferizare” BufferedReader, BufferedWriter BufferedInputStream, BufferedOutputStream Sunt folosite pentru a introduce un buffer ˆın procesul de citire/scriere a informat¸iilor, reducˆand astfel num˘arul de acces˘ari la dispozitivul ce reprezint˘a sursa/destinat¸ia original˘a a datelor. Sunt mult mai eficiente decˆat fluxurile f˘ar˘a buffer ¸si din acest motiv se recomand˘a folosirea lor ori de cˆate ori este posibil (vezi ”Citirea ¸si scrierea cu zona tampon”). • Filtrare FilterReader, FilterWriter FilterInputStream, FilterOutputStream Sunt clase abstracte ce definesc o interfat¸˘a comun˘a pentru fluxuri care filtreaz˘a automat datele citite sau scrise (vezi ”Fluxuri pentru filtrare”). • Conversie octet¸i-caractere InputStreamReader, OutputStreamWriter Formeaz˘a o punte de legatur˘a ˆıntre fluxurile de caractere ¸si fluxurile de octet¸i. Un flux InputStreamReader cite¸ste octet¸i dintr-un flux InputStream ¸si ˆıi converte¸ste la caractere, folosind codificarea standard a caracterelor sau o codificare specificat˘a de program. Similar, un flux OutputStreamWriter converte¸ste caractere ˆın octet¸i ¸si trimite rezutatul c˘atre un flux de tipul OutputStream.
4.2. FOLOSIREA FLUXURILOR
101
• Concatenare SequenceInputStream Concateneaz˘a mai multe fluxuri de intrare ˆıntr-unul singur (vezi ”Concatenarea fi¸sierelor”). • Serializare ObjectInputStream, ObjectOutputStream Sunt folosite pentru serializarea obiectelor (vezi ”Serializarea obiectelor”). • Conversie tipuri de date DataInputStream, DataOutputStream Folosite la scrierea/citirea datelor de tip primitiv ˆıntr-un format binar, independent de ma¸sina pe care se lucreaz˘a (vezi ”Folosirea claselor DataInputStream ¸si DataOutputStream”). • Num˘ arare LineNumberReader LineNumberInputStream Ofer˘a ¸si posibilitatea de num˘arare automat˘a a liniilor citite de la un flux de intrare. • Citire ˆın avans PushbackReader PushbackInputStream Sunt fluxuri de intrare care au un buffer de 1-caracter(octet) ˆın care este citit ˆın avans ¸si caracterul (octetul) care urmeaz˘a celui curent citit. • Afi¸sare PrintWriter PrintStream Ofer˘a metode convenabile pentru afisarea informat¸iilor.
4.2.3
Crearea unui flux
Orice flux este un obiect al clasei ce implementeaz˘a fluxul respectiv. Crearea unui flux se realizeaz˘a a¸sadar similar cu crearea obiectelor, prin instruct¸iunea new ¸si invocarea unui constructor corespunz˘ator al clasei respective: Exemple:
102
˘ S¸I IES¸IRI CAPITOLUL 4. INTRARI
//crearea unui flux de intrare pe caractere FileReader in = new FileReader("fisier.txt"); //crearea unui flux de iesire pe caractere FileWriter out = new FileWriter("fisier.txt"); //crearea unui flux de intrare pe octeti FileInputStream in = new FileInputStream("fisier.dat"); //crearea unui flux de iesire pe octeti FileOutputStrem out = new FileOutputStream("fisier.dat"); A¸sadar, crearea unui flux primitiv de date care cite¸ste/scrie informat¸ii de la un dispozitiv extern are formatul general: FluxPrimitiv numeFlux = new FluxPrimitiv(dispozitivExtern); Fluxurile de procesare nu pot exista de sine st˘at˘atoare ci se suprapun pe un flux primitiv de citire/scriere a datelor. Din acest motiv, constructorii claselor pentru fluxurile de procesare nu primesc ca argument un dispozitiv extern de memorare a datelor ci o referint¸a la un flux primitiv responsabil cu citirea/scrierea efectiv˘a a datelor: Exemple: //crearea unui flux de intrare printr-un buffer BufferedReader in = new BufferedReader( new FileReader("fisier.txt")); //echivalent cu FileReader fr = new FileReader("fisier.txt"); BufferedReader in = new BufferedReader(fr); //crearea unui flux de iesire printr-un buffer BufferedWriter out = new BufferedWriter( new FileWriter("fisier.txt"))); //echivalent cu FileWriter fo = new FileWriter("fisier.txt"); BufferedWriter out = new BufferedWriter(fo); A¸sadar, crearea unui flux pentru procesarea datelor are formatul general:
4.2. FOLOSIREA FLUXURILOR
103
FluxProcesare numeFlux = new FluxProcesare(fluxPrimitiv); In general, fluxurile pot fi compuse ˆın succesiuni oricˆat de lungi: DataInputStream in = new DataInputStream( new BufferedInputStream( new FileInputStream("fisier.dat")));
4.2.4
Fluxuri pentru lucrul cu fi¸siere
Fluxurile pentru lucrul cu fi¸siere sunt cele mai usor de ˆınteles, ˆıntrucˆat operat¸ia lor de baz˘a este citirea, respectiv scrierea unui caracter sau octet dintr-un sau ˆıntr-un fi¸sier specificat uzual prin numele s˘au complet sau relativ la directorul curent. Dup˘a cum am v˘azut deja, clasele care implementeaz˘a aceste fluxuri sunt urm˘atoarele: FileReader, FileWriter - caractere FileInputStream, FileOutputStream - octeti Constructorii acestor clase accept˘a ca argument un obiect care s˘a specifice un anume fi¸sier. Acesta poate fi un ¸sir de caractere, on obiect de tip File sau un obiect de tip FileDesciptor (vezi ”Clasa File”). Constructorii clasei FileReader sunt: public FileReader(String fileName) throws FileNotFoundException public FileReader(File file) throws FileNotFoundException public FileReader(FileDescriptor fd) Constructorii clasei FileWriter: public FileWriter(String fileName) throws IOException public FileWriter(File file) throws IOException public FileWriter(FileDescriptor fd) public FileWriter(String fileName, boolean throws IOException
append)
˘ S¸I IES¸IRI CAPITOLUL 4. INTRARI
104
Cei mai uzuali constructori sunt cei care primesc ca argument numele fi¸sierului. Ace¸stia pot provoca except¸ii de tipul FileNotFoundException ˆın cazul ˆın care fi¸sierul cu numele specificat nu exist˘a. Din acest motiv orice creare a unui flux de acest tip trebuie f˘acut˘a ˆıntr-un bloc try-catch sau metoda ˆın care sunt create fluxurile respective trebuie s˘a arunce except¸iile de tipul FileNotFoundException sau de tipul superclasei IOException.
S˘a consider˘am ca exemplu un program care copie cont¸inutul unui fi¸sier cu numele ”in.txt” ˆıntr-un alt fi¸sier cu numele ”out.txt”. Ambele fi¸siere sunt considerate ˆın directorul curent. Listing 4.1: Copierea unui fisier import java . io .*; public class Copiere { public static void main ( String [] args )
{
try { FileReader in = new FileReader ( " in . txt " ) ; FileWriter out = new FileWriter ( " out . txt " ) ; int c ; while (( c = in . read () ) != -1) out . write ( c ) ; in . close () ; out . close () ; } catch ( IOException e ) { System . err . println ( " Eroare la operatiile cu fisiere ! " ) ; e . printStackTrace () ; } } }
In cazul ˆın care vom lansa aplicat¸ia iar ˆın directorul curent nu exist˘a un fi¸sier cu numele ”in.txt”, va fi generat˘a o except¸ie de tipul FileNotFoundException. Aceasta va fi prins˘a de program deoarece, IOException este superclas˘a pentru FileNotFoundException. Dac˘a exist˘a fi¸sierul ”in.txt”, aplicat¸ia va crea un nou fi¸sier ”out.txt” ˆın care va fi copiat cont¸inutul primului. Dac˘a exist˘a deja fi¸sierul ”out.txt” el va fi re-
4.2. FOLOSIREA FLUXURILOR
105
scris. Dac˘a doream s˘a facem operat¸ia de ad˘augare(append) ¸si nu de rescriere pentru fi¸sierul ”out.txt” foloseam: FileWriter out = new FileWriter("out.txt", true);
4.2.5
Citirea ¸si scrierea cu buffer
Clasele pentru citirea/scrierea cu zona tampon sunt: BufferedReader, BufferedWriter - caractere BufferedInputStream, BufferedOutputStream - octeti Sunt folosite pentru a introduce un buffer (zon˘a de memorie) ˆın procesul de citire/scriere a informat¸iilor, reducˆand astfel numarul de acces˘ari ale dispozitivului ce reprezint˘a sursa/destinat¸ia atelor. Din acest motiv, sunt mult mai eficiente decˆat fluxurile f˘ar˘a buffer ¸si din acest motiv se recomand˘a folosirea lor ori de cˆate ori este posibil. Clasa BufferedReader cite¸ste ˆın avans date ¸si le memoreaz˘a ˆıntr-o zon˘a tampon. Atunci cˆand se execut˘a o operat¸ie de citire, caracterul va fi preluat din buffer. In cazul ˆın care buffer-ul este gol, citirea se face direct din flux ¸si, odat˘a cu citirea caracterului, vor fi memorati ˆın buffer ¸si caracterele care ˆıi urmeaz˘a. Evident, BufferedInputStream funct¸ioneaz˘a dup˘a acela¸si principiu, singura diferent¸˘a fiind faptul c˘a sunt citit¸i octet¸i. Similar lucreaza ¸si clasele BufferedWriter ¸si BufferedOutputStream. La operat¸iile de scriere datele scrise nu vor ajunge direct la destinat¸ie, ci vor fi memorate jntr-un buffer de o anumit˘a dimensiune. Atunci cˆand bufferul este plin, cont¸inutul acestuia va fi transferat automat la destinat¸ie. Fluxurile de citire/scriere cu buffer sunt fluxuri de procesare ¸si sunt folosite prin suprapunere cu alte fluxuri, dintre care obligatoriu unul este primitiv. BufferedOutputStream out = new BufferedOutputStream( new FileOutputStream("out.dat"), 1024) //1024 este dimensiunea bufferului Constructorii cei mai folosit¸i ai acestor clase sunt urm˘atorii: BufferedReader(Reader in) BufferedReader(Reader in, int dim_buffer) BufferedWriter(Writer out)
106
˘ S¸I IES¸IRI CAPITOLUL 4. INTRARI
BufferedWriter(Writer out, int dim_buffer) BufferedInputStream(InputStream in) BufferedInputStream(InputStream in, int dim_buffer) BufferedOutputStream(OutputStream out) BufferedOutputStream(OutputStream out, int dim_buffer) In cazul constructorilor ˆın care dimensiunea buffer-ului nu este specificat˘a, aceasta prime¸ste valoarea implicit˘a de 512 octet¸i (caractere). Metodele acestor clase sunt cele uzuale de tipul read ¸si write. Pe lˆanga acestea, clasele pentru scriere prin buffer mai au ¸si metoda flush care gole¸ste explicit zona tampon, chiar dac˘a aceasta nu este plin˘a. BufferedWriter out = new BufferedWriter( new FileWriter("out.dat"), 1024) //am creat un flux cu buffer de 1024 octeti for(int i=0; i<1000; i++) out.write(i); //bufferul nu este plin, in fisier nu s-a scris nimic out.flush(); //bufferul este golit, datele se scriu in fisier
Metoda readLine Este specific˘a fluxurilor de citire cu buffer ¸si permite citirea linie cu linie a datelor de intrare. O linie reprezint˘a o succesiune de caractere terminat˘a cu simbolul pentru sfˆar¸sit de linie, dependent de platforma de lucru. Acesta este reprezentat ˆın Java prin secvent¸a escape ’\n’; BufferedReader br = new BufferedReader(new FileReader("in")) String linie; while ((linie = br.readLine()) != null) { ... //proceseaza linie } br.close(); }
4.2. FOLOSIREA FLUXURILOR
4.2.6
107
Concatenarea fluxurilor
Clasa SequenceInputStream permite unei aplicatii s˘a combine serial mai multe fluxuri de intrare astfel ˆıncˆat acestea s˘a apar˘a ca un singur flux de intrare. Citirea datelor dintr-un astfel de flux se face astfel: se cite¸ste din primul flux de intrare specificat pˆana cˆand se ajunge la sfˆarsitul acestuia, dup˘a care primul flux de intrare este ˆınchis ¸si se deschide automat urm˘atorul flux de intrare din care se vor citi ˆın continuare datele, dup˘a care procesul se repet˘a pˆana la terminarea tuturor fluxurilor de intrare. Constructorii acestei clase sunt: SequenceInputStream(Enumeration e) SequenceInputStream(InputStream s1, InputStream s2) Primul construieste un flux secvential dintr-o mult¸ime de fluxuri de intrare. Fiecare obiect ˆın enumerarea primit˘a ca parametru trebuie s˘a fie de tipul InputStream. Cel de-al doilea construie¸ste un flux de intrare care combin˘a doar dou˘a fluxuri s1 ¸si s2, primul flux citit fiind s1. Exemplul cel mai elocvent de folosirea a acestei clase este concatenarea a dou˘a sau mai multor fi¸siere: Listing 4.2: Concatenarea a dou˘a fi¸siere /* Concatenarea a doua fisiere ale caror nume sunt primite de la linia de comanda . Rezultatul concatenarii este afisat pe ecran . */ import java . io .*; public class Concatenare { public static void main ( String args []) { if ( args . length <= 1) { System . out . println ( " Argumente insuficiente ! " ) ; System . exit ( -1) ; } try { FileInputStream f1 = new FileInputStream ( args [0]) ; FileInputStream f2 = new FileInputStream ( args [1]) ; SequenceInputStream s = new S eq u e nc e I np u tS t r ea m ( f1 , f2 ) ; int c ; while (( c = s . read () ) != -1) System . out . print (( char ) c ) ;
˘ S¸I IES¸IRI CAPITOLUL 4. INTRARI
108
s . close () ; // f1 si f2 sunt inchise automat } catch ( IOException e ) { e . printStackTrace () ; } } }
Pentru concatenarea mai multor fi¸siere exist˘a dou˘a variante: • folosirea unei enumer˘ari - primul constructor (vezi ”Colect¸ii”); • concatenarea pe rˆand a acestora folosind al 2-lea constructor; concatenarea a 3 fi¸siere va construi un flux de intrare astfel: FileInputStream f1 = new FileInputStream(args[0]); FileInputStream f2 = new FileInputStream(args[1]); FileInputStream f3 = new FileInputStream(args[2]); SequenceInputStream s = new SequenceInputStream( f1, new SequenceInputStream(f2, f3));
4.2.7
Fluxuri pentru filtrarea datelor
Un flux de filtrare se ata¸seaz˘a altui flux pentru a filtra datele care sunt citite/scrise de c˘atre acel flux. Clasele pentru filtrarea datelor superclasele abstracte: • FilterInputStream - pentru filtrarea fluxurilor de intrare ¸si • FilterOutputStream - pentru filtrarea fluxurilor de ie¸sire. Cele mai importante fluxruri pentru filtrarea datelor sunt implementate de clasele: DataInputStream, DataOutputStream BufferedInputStream, BufferedOutputStream LineNumberInputStream PushbackInputStream PrintStream
4.2. FOLOSIREA FLUXURILOR
109
Observat¸i c˘a toate aceste clase descriu fluxuri de octet¸i. Filtrarea datelor nu trebuie v˘azut˘a ca o metod˘a de a elimina anumit¸i octeti dintr-un flux ci de a transforma ace¸sti octet¸i ˆın date care s˘a poat˘a fi interpretate sub alt˘a form˘a. A¸sa cum am v˘azut la citirea/scrierea cu zon˘a tampon, clasele de filtrare BufferedInputStream ¸si BufferedOutputStream colecteaz˘a datele unui flux ˆıntr-un buffer, urmˆand ca citirea/scrierea s˘a se fac˘a prin intermediu acelui buffer. A¸sadar, fluxurile de filtrare nu elimin˘a date citite sau scrise de un anumit flux, ci introduc o noua modalitate de manipulare a lor, ele mai fiind numite ¸si fluxuri de procesare. Din acest motiv, fluxurile de filtrare vor cont¸ine anumite metode specializate pentru citirea/scrierea datelor, altele decˆat cele comune tuturor fluxurilor. De exemplu, clasa BufferedInputStream pune la dispozit¸ie metoda readLine pentru citirea unei linii din fluxul de intrare. Folosirea fluxurilor de filtrare se face prin ata¸sarea lor de un flux care se ocup˘a efectiv de citirea/scrierea datelor: FluxFiltrare numeFlux = new FluxFiltrare(referintaAltFlux);
4.2.8
Clasele DataInputStream ¸si DataOutputStream
Aceste clase ofer˘a metode prin care un flux nu mai este v˘azut ca o ˆınsiruire de octet¸i, ci de date primitive. Prin urmare, vor furniza metode pentru citirea ¸si scrierea datelor la nivel de tip primitiv ¸si nu la nivel de octet. Clasele care ofer˘a un astfel de suport implementeaz˘a interfet¸ele DataInput, respectiv DataOutput. Acestea definesc metodele pe care trebuie s˘a le pun˘a la dispozit¸ie ˆın vederea citireii/scrierii datelor de tip primitiv. Cele mai folosite metode, altele decˆat cele comune tuturor fluxurilor, sunt date ˆın tabelul de mai jos:
˘ S¸I IES¸IRI CAPITOLUL 4. INTRARI
110 DataInputStream readBoolean readByte readChar readDouble readFloat readInt readLong readShort readUTF
DataOutputStream writeBoolean writeByte writeChar writeDouble writeFloat writeInt writeLong writeShort writeUTF
Aceste metode au denumirile generice de readXXX ¸si writeXXX, specificate de interfetele DataInput ¸si DataOutput ¸si pot provoca except¸ii de tipul IOException. Denumirile lor sunt sugestive pentru tipul de date pe care ˆıl prelucreaz˘a. mai put¸in readUTF ¸si writeUTF care se ocup˘a cu obiecte de tip String, fiind singurul tip referint¸˘a permis de aceste clase. Scrierea datelor folosind fluxuri de acest tip se face ˆın format binar, ceea ce ˆınseamn˘a c˘a un fi¸sier ˆın care au fost scrise informat¸ii folosind metode writeXXX nu va putea fi citit decˆat prin metode readXXX. Transformarea unei valori ˆın format binar se nume¸ste serializare. Clasele DataInputStream ¸si DataOutputStream permit serializarea tipurilor primitive ¸si a ¸sirurilor de caractere. Serializarea celorlalte tipuri referint¸˘a va fi f˘acut˘a prin intermediul altor clase, cum ar fi ObjectInputStream ¸si ObjectOutputStream (vezi ”Serializarea obiectelor”).
4.3
Intr˘ ari ¸si ie¸siri formatate
Incepˆand cu versiunea 1.5, limbajul Java pune la dispozit¸ii modalit˘a¸ti simplificate pentru afi¸sarea formatat˘a a unor informat¸ii, respectiv pentru citirea de date formatate de la tastatur˘a.
4.3.1
Intr˘ ari formatate
Clasa java.util.Scanner ofer˘a o solut¸ie simpl˘a pentru formatarea unor informat¸ii citite de pe un flux de intrare fie pe octet¸i, fie pe caractere, sau chiar dintr-un obiect de tip File. Pentru a citi de la tastatur˘a vom specifica ca argument al constructorului fluxul System.in:
4.4. FLUXURI STANDARD DE INTRARE S¸I IES¸IRE
111
Scanner s = Scanner.create(System.in); String nume = s.next(); int varsta = s.nextInt(); double salariu = s.nextDouble(); s.close();
4.3.2
Ie¸siri formatate
Clasele PrintStream ¸si PrintWriter pun la dispozit¸iile, pe lˆang˘a metodele print, println care ofereau posibilitatea de a afi¸sa un ¸sir de caractere, ¸si metodele format, printf (echivalente) ce permit afi¸sarea formatat˘a a unor variabile. System.out.printf("%s %8.2f %2d %n", nume, salariu, varsta); Formatarea ¸sirurilor de caractere se bazeaz˘a pe clasa java.util.Formatter.
4.4
Fluxuri standard de intrare ¸si ie¸sire
Mergˆand pe linia introdus˘a de sistemul de operare UNIX, orice program Java are : • o intrare standard • o ie¸sire standard • o ie¸sire standard pentru erori In general, intrarea standard este tastatura iar ie¸sirea standard este ecranul. Intrarea ¸si ie¸sirea standard sunt reprezentate de obiecte pre-create ce descriu fluxuri de date care comunic˘a cu dispozitivele standard ale sistemului. Aceste obiecte sunt definite publice ˆın clasa System ¸si sunt: • System.in - fluxul standar de intrare, de tip InputStream • System.out - fluxul standar de ie¸sire, de tip PrintStream • System.err - fluxul standar pentru erori, de tip PrintStream
112
4.4.1
˘ S¸I IES¸IRI CAPITOLUL 4. INTRARI
Afisarea informat¸iilor pe ecran
Am v˘azut deja numeroase exemple de utilizare a fluxului standard de ie¸sire, el fiind folosit la afi¸sarea oric˘aror rezultate pe ecran (ˆın modul consola): System.out.print (argument); System.out.println(argument); System.out.printf (format, argumente...); System.out.format (format, argumente...); Fluxul standard pentru afi¸sarea erorilor se folose¸ste similar ¸si apare uzual ˆın secvent¸ele de tratare a except¸iilor. Implicit, este acela¸si cu fluxul standard de ie¸sire. catch(Exception e) { System.err.println("Exceptie:" + e); } Fluxurile de ie¸sire pot fi folosite a¸sadar f˘ar˘a probleme deoarece tipul lor este PrintStream, clas˘a concret˘a pentru scrierea datelor. In schimb, fluxul standard de intrare System.out este de tip InputStream, care este o clas˘a abstract˘a, deci pentru a-l putea utiliza eficient va trebui sa-l folosim ˆımpreuna cu un flux de procesare(filtrare) care s˘a permit˘a citirea facil˘a a datelor.
4.4.2
Citirea datelor de la tastatur˘ a
Uzual, vom dori s˘a folosim metoda readLine pentru citirea datelor de la tastatura ¸si din acest motiv vom folosi intrarea standard ˆımpreun˘a cu o clas˘a de procesare care ofer˘a aceast˘a metod˘a. Exemplul tipic este: BufferedReader stdin = new BufferedReader( new InputStreamReader(System.in)); System.out.print("Introduceti o linie:"); String linie = stdin.readLine() System.out.println(linie); In exemplul urm˘ator este prezentat un program care afi¸seaza liniile introduse de la tastatur˘a pˆan˘a ˆın momentul ˆın care se introduce linia ”exit” sau o linie vid˘a ¸si ment¸ioneaz˘adac˘a ¸sirul respectiv reprezint˘a un num˘ar sau nu.
4.4. FLUXURI STANDARD DE INTRARE S¸I IES¸IRE
113
Listing 4.3: Citirea datelor de la tastatur˘a /* Citeste siruri de la tastatura si verifica daca reprezinta numere sau nu */ import java . io .*; public class EsteNumar { public static void main ( String [] args ) { BufferedReader stdin = new BufferedReader ( new InputStreamReader ( System . in ) ) ; try { while ( true ) { String s = stdin . readLine () ; if ( s . equals ( " exit " ) || s . length () ==0) break ; System . out . print ( s ) ; try { Double . parseDouble ( s ) ; System . out . println ( " : DA " ) ; } catch ( Numb erFor ma t E x c e p t i o n e ) { System . out . println ( " : NU " ) ; } } } catch ( IOException e ) { System . err . println ( " Eroare la intrarea standard ! " ) ; e . printStackTrace () ; } } }
Incepˆand cu versiunea 1.5, varianta cea mai comod˘a de citire a datelor de la tastatur˘a este folosirea clasei java.util.Scanner.
4.4.3
Redirectarea fluxurilor standard
Redirectarea fluxurilor standard presupune stabilirea unei alte surse decˆat tastatura pentru citirea datelor, respectiv alte destinat¸ii decˆat ecranul pentru cele dou˘a fluxuri de ie¸sire. In clasa System exist˘a urm˘atoarele metode statice care realizeaz˘a acest lucru: setIn(InputStream) - redirectare intrare setOut(PrintStream) - redirectare iesire setErr(PrintStream) - redirectare erori
˘ S¸I IES¸IRI CAPITOLUL 4. INTRARI
114
Redirectarea ie¸sirii este util˘a ˆın special atunci cˆand sunt afi¸sate foarte multe date pe ecran. Putem redirecta afisarea c˘atre un fi¸sier pe care s˘a-l citim dup˘a execut¸ia programului. Secvent¸a clasic˘a de redirectare a ie¸sirii este c˘atre un fi¸sier este: PrintStream fis = new PrintStream( new FileOutputStream("rezultate.txt"))); System.setOut(fis); Redirectarea erorilor ˆıntr-un fi¸sier poate fi de asemenea util˘a ¸si se face ˆıntr-o manier˘a similar˘a: PrintStream fis = new PrintStream( new FileOutputStream("erori.txt"))); System.setErr(fis); Redirectarea intr˘arii poate fi folositoare pentru un program ˆın mod consol˘a care prime¸ste mai multe valori de intrare. Pentru a nu le scrie de la tastatur˘a de fiecare dat˘a ˆın timpul test˘arii programului, ele pot fi puse ˆıntrun fi¸sier, redirectˆand intrarea standard c˘atre acel fi¸sier. In momentul cˆand testarea programului a luat sfˆarsit redirectarea poate fi eliminat˘a, datele fiind cerute din nou de la tastatur˘a. Listing 4.4: Exemplu de folosire a redirect˘arii: import java . io .*; class Redirectare { public static void main ( String [] args ) { try { BufferedInputS t r ea m in = new B uf f e re d I np u tS t r ea m ( new FileInputStream ( " intrare . txt " ) ) ; PrintStream out = new PrintStream ( new FileOutputStream ( " rezultate . txt " ) ) ; PrintStream err = new PrintStream ( new FileOutputStream ( " erori . txt " ) ) ; System . setIn ( in ) ; System . setOut ( out ) ; System . setErr ( err ) ; BufferedReader br = new BufferedReader ( new InputStream Reader ( System . in ) ) ;
4.4. FLUXURI STANDARD DE INTRARE S¸I IES¸IRE
115
String s ; while (( s = br . readLine () ) != null ) { /* Liniile vor fi citite din fisierul intrare . txt si vor fi scrise in fisierul rezultate . txt */ System . out . println ( s ) ; } // Aruncam fortat o exceptie throw new IOException ( " Test " ) ; } catch ( IOException e ) { /* Daca apar exceptii , ele vor fi scrise in fisierul erori . txt */ System . err . println ( " Eroare intrare / iesire ! " ) ; e . printStackTrace () ; } } }
4.4.4
Analiza lexical˘ a pe fluxuri (clasa StreamTokenizer)
Clasa StreamTokenizer proceseaz˘a un flux de intrare de orice tip ¸si ˆıl ˆımparte ˆın ”atomi lexicali”. Rezultatul va consta ˆın faptul c˘a ˆın loc s˘a se citeasc˘a octet¸i sau caractere, se vor citi, pe rˆand, atomii lexicali ai fluxului respectiv. Printr-un atom lexical se ˆın]elege ˆın general: • un identificator (un ¸sir care nu este ˆıntre ghilimele) • un numar • un ¸sir de caractere • un comentariu • un separator Atomii lexicali sunt desp˘art¸iti ˆıntre ei de separatori. Implicit, ace¸sti separatori sunt cei obi¸snut¸i: spat¸iu, tab, virgul˘a, punct ¸si virgula, etc., ˆıns˘a pot fi schimbat¸i prin diverse metode ale clasei. Constructorii clasei sunt:
116
˘ S¸I IES¸IRI CAPITOLUL 4. INTRARI
public StreamTokenizer(Reader r) public StreamTokenizer(InputStream is) Identificarea tipului ¸si valorii unui atom lexical se face prin intermediul variabilelor: • TT EOF - atom ce marcheaz˘a sfˆaar¸situl fluxului • TT EOL - atom ce marcheaz˘a sfˆar¸situl unei linii • TT NUMBER - atom de tip num˘ar • TT WORD- atom de tip cuvˆant • ttype- tipul ultimului atom citit din flux • nval- valoarea unui atom numeric • sval - valoarea unui atom de tip cuvˆant Citirea atomilor din flux se face cu metoda nextToken(), care returnez˘a tipul atomului lexical citit ¸si scrie ˆın variabilele nval sau sval valoarea corespunzato˘are atomului. Exemplul tipic de folosire a unui analizor lexical este citirea unei secvent¸e de numere ¸si ¸siruri aflate ˆıntr-un fi¸sier sau primite de la tastatur˘a: Listing 4.5: Citirea unor atomi lexicali dintr-un fisier /* Citirea unei secvente de numere si siruri dintr - un fisier specificat si afisarea tipului si valorii lor */ import java . io .*; public class CitireAtomi { public static void main ( String args []) throws IOException { BufferedReader br = new BufferedReader ( new FileReader ( " fisier . txt " ) ) ; StreamTokenizer st = new StreamTokenizer ( br ) ; int tip = st . nextToken () ; // Se citeste primul atom lexical
4.5. CLASA RANDOMACCESFILE (FIS¸IERE CU ACCES DIRECT)
117
while ( tip != StreamTokenizer . TT_EOF ) { switch ( tip ) { case StreamTokenizer . TT_WORD : System . out . println ( " Cuvant : " + st . sval ) ; break ; case StreamTokenizer . TT_NUMBER : System . out . println ( " Numar : " + st . nval ) ; } tip = st . nextToken () ; // Trecem la urmatorul atom } } }
A¸sadar, modul de utilizare tipic pentru un analizor lexical este ˆıntr-o bucla ”while”, ˆın care se citesc atomii unul cˆate unul cu metoda nextToken, pˆana se ajunge la sfˆarsitul fluxului (TT EOF). In cadrul buclei ”while” se determin˘a tipul atomul curent curent (ˆıntors de metoda nextToken) ¸si apoi se afl˘a valoarea numeric˘a sau ¸sirul de caractere corespunz˘ator atomului respectiv. In cazul ˆın care tipul atomilor nu ne intereseaz˘a, este mai simplu s˘a citim fluxul linie cu linie ¸si s˘a folosim clasa StringTokenizer, care realizeaz˘a ˆımp˘art¸irea unui ¸sir de caractere ˆın atomi lexicali, sau metoda split a clasei String.
4.5
Clasa RandomAccesFile (fi¸siere cu acces direct)
Dup˘a cum am v˘azut, fluxurile sunt procese secvent¸iale de intrare/ie¸sire. Acestea sunt adecvate pentru lucrul cu medii secvent¸iale de memorare a datelor, cum ar fi banda magnetic˘a sau pentru transmiterea informat¸iilor prin ret¸ea, desi sunt foarte utile ¸si pentru dispozitive ˆın care informat¸ia poate fi accesat˘a direct. Clasa RandomAccesFile are urm˘atoarele caracteristici: • permite accesul nesecvent¸ial (direct) la cont¸inutul unui fi¸sier; • este o clas˘a de sine st˘at˘atoare, subclas˘a direct˘a a clasei Object; • se g˘ase¸ste ˆın pachetul java.io;
118
˘ S¸I IES¸IRI CAPITOLUL 4. INTRARI
• implementeaz˘a interfet¸ele DataInput ¸si DataOutput, ceea ce ˆınseamna ca sunt disponibile metode de tipul readXXX, writeXXX, ˆıntocmai ca la clasele DataInputStream ¸si DataOutputStream; • permite atˆat citirea cˆat ¸si scriere din/in fi¸siere cu acces direct; • permite specificarea modului de acces al unui fi¸sier (read-only, readwrite). Constructorii acestei clase sunt: RandomAccessFile(StringnumeFisier, StringmodAcces) throws IOException RandomAccessFile(StringnumeFisier, StringmodAcces) throws IOException unde modAcces poate fi: • ”r” - fi¸sierul este deschis numai pentru citire (read-only) • ”rw” - fi¸sierul este deschis pentru citire ¸si scriere (read-write) Exemple: RandomAccesFile f1 = new RandomAccessFile("fisier.txt", "r"); //deschide un fisier pentru citire RandomAccesFile f2 = new RandomAccessFile("fisier.txt", "rw"); //deschide un fisier pentru scriere si citire Clasa RandomAccesFile suport˘a not¸iunea de pointer de fi¸sier. Acesta este un indicator ce specific˘a pozit¸ia curent˘a ˆın fi¸sier. La deschiderea unui fi¸sier pointerul are valoarea 0, indicˆand ˆınceputul fi¸sierului. Apeluri la metodele de citire/scrirere deplaseaz˘a pointerul fi¸sierului cu num˘arul de octet¸i citit¸i sau scri¸si de metodele respective. In plus fat¸˘ade metodele de tip read ¸si write clasa pune la dispozitie ¸si metode pentru controlul pozit¸iei pointerului de fi¸sier. Acestea sunt: • skipBytes - mut˘a pointerul fi¸sierului ˆınainte cu un num˘ar specificat de octet¸i • seek - pozit¸ioneaza pointerului fi¸sierului ˆınaintea unui octet specificat • getFilePointer - returneaz˘a pozit¸ia pointerului de fi¸sier.
4.6. CLASA FILE
4.6
119
Clasa File
Clasa File nu se refer˘a doar la un fi¸sier ci poate reprezenta fie un fi¸sier anume, fie multimea fi¸sierelor dintr-un director. Specificarea unui fi¸sier/director se face prin specificarea c˘aii absolute spre acel fi¸sier sau a c˘aii relative fat¸˘a de directorul curent. Acestea trebuie s˘a respecte convent¸iile de specificare a c˘ailor ¸si numelor fi¸sierelor de pe platforma de lucru. Utilitate clasei File const˘a ˆın furnizarea unei modalit˘a¸ti de a abstractiza dependent¸ele cailor ¸si numelor fi¸sierelor fat¸˘ade ma¸sina gazd˘a, precum ¸si punerea la dispozit¸ie a unor metode pentru lucrul cu fisere ¸si directoare la nivelul sistemului de operare. Astfel, ˆın aceast˘a clas˘a vom g˘asi metode pentru testarea existent¸ei, ¸stergerea, redenumirea unui fi¸sier sau director, crearea unui director, listarea fi¸sierelor dintr-un director, etc. Trebuie ment¸ionat ¸si faptul c˘a majoritatea constructorilor fluxurilor care permit accesul la fi¸siere accept˘a ca argument un obiect de tip File ˆın locul unui ¸sir ce reprezint˘a numele fi¸sierului respectiv. File f = new File("fisier.txt"); FileInputStream in = new FileInputStream(f) Cel mai uzual constructor al clasei File este: public File(String numeFisier) Metodele mai importante ale clasei File au denumiri sugestive ¸si vor fi prezentate prin intermediul exemplului urm˘ator care listeaz˘a fi¸sierele ¸si subdirectoarele unui director specificat ¸si, pentru fiecare din ele afi¸seaz˘a diverse informat¸ii: Listing 4.6: Listarea cont¸inutului unui director /* Programul listeaza fisierele si subdirectoarele unui director . Pentru fiecare din ele vor fi afisate diverse informatii . Numele directorului este primit ca argument de la linia de comanda , sau este directorul curent . */ import java . io .*;
˘ S¸I IES¸IRI CAPITOLUL 4. INTRARI
120 import java . util .*; public class ListareDirector {
private static void info ( File f ) { // Afiseaza informatii despre un fisier sau director String nume = f . getName () ; if ( f . isFile () ) System . out . println ( " Fisier : " + nume ) ; else if ( f . isDirectory () ) System . out . println ( " Director : " + nume ) ; System . out . println ( " Cale absoluta : " + f . getAbsolutePath () + " \ n Poate citi : " + f . canRead () + " \ n Poate scrie : " + f . canWrite () + " \ n Parinte : " + f . getParent () + " \ n Cale : " + f . getPath () + " \ n Lungime : " + f . length () + " \ n Data ultimei modificari : " + new Date ( f . lastModified () ) ) ; System . out . println ( " - - - - - - - - - - - - - - " ) ; } public static void main ( String [] args ) { String nume ; if ( args . length == 0) nume = " . " ; // directorul curent else nume = args [0]; try { File director = new File ( nume ) ; File [] continut = director . listFiles () ; for ( int i = 0; i < continut . length ; i ++) info ( continut [ i ]) ; } catch ( Exception e ) { e . printStackTrace () ; } } }
Capitolul 5 Interfet¸e 5.1 5.1.1
Introducere Ce este o interfat¸˘ a?
Interfet¸ele duc conceptul de clas˘a abstract˘a cu un pas ˆınainte prin eliminarea oric˘aror implement˘ari de metode, punˆand ˆın practic˘a unul din conceptele program˘arii orientate obiect ¸si anume cel de separare a modelului unui obiect (interfat¸˘a) de implementarea sa. A¸sadar, o interfat¸˘a poate fi privita ca un protocol de comunicare ˆıntre obiecte. O interfat¸˘a Java define¸ste un set de metode dar nu specific˘a nici o implementare pentru ele. O clas˘a care implementeaz˘a o interfat¸˘a trebuie obligatoriu s˘a specifice implement˘ari pentru toate metodele interfet¸ei, supunˆandu-se a¸sadar unui anumit comportament. Definit¸ie O interfat¸˘a este o colect¸ie de metode f˘ar˘a implementare ¸si declarat¸ii de constante. Interfet¸ele permit, al˘aturi de clase, definirea unor noi tipuri de date. 121
122
CAPITOLUL 5. INTERFET ¸E
5.2
Folosirea interfet¸elor
5.2.1
Definirea unei interfet¸e
Definirea unei interfet¸e se face prin intermediul cuvˆantului cheie interface: [public] interface NumeInterfata [extends SuperInterfata1, SuperInterfata2...] { /* Corpul interfetei: Declaratii de constane Declaratii de metode abstracte */ } O interfat¸˘a poate avea un singur modificator ¸si anume public. O interfat¸˘a public˘a este accesibil˘a tuturor claselor, indiferent de pachetul din care fac parte, implicit nivelul de acces fiind doar la nivelul pachetului din care face parte interfat¸a. O interfat¸˘a poate extinde oricˆate interfet¸e. Acestea se numesc superinterfet¸e ¸si sunt separate prin virgul˘a. (vezi ”Mo¸stenirea multipl˘a prin intermediul interfet¸elor”). Corpul unei interfet¸e poate cont¸ine: • constante: acestea pot fi sau nu declarate cu modificatorii public, static ¸si final care sunt implicit¸i, nici un alt modificator neputˆand ap˘area ˆın declarat¸ia unei variabile dintr-o interfat¸˘a. Constantele unei interfet¸e trebuie obligatoriu init¸ializate. interface Exemplu { int MAX = 100; // Echivalent cu: public static final int MAX = 100; int MAX; // Incorect, lipseste initializarea private int x = 1; // Incorect, modificator nepermis }
5.2. FOLOSIREA INTERFET ¸ ELOR
123
• metode f˘ ar˘ a implementare: acestea pot fi sau nu declarate cu modificatorul public, care este implicit; nici un alt modificator nu poate ap˘area ˆın declarat¸ia unei metode a unei interfet¸e. interface Exemplu { void metoda(); // Echivalent cu: public void metoda(); protected void metoda2(); // Incorect, modificator nepermis
Atent¸ie • Variabilele unei interfet¸e sunt implicit publice chiar dac˘a nu sunt declarate cu modificatorul public. • Variabilele unei interfet¸e sunt implicit constante chiar dac˘a nu sunt declarate cu modificatorii static ¸si final. • Metodele unei interfet¸e sunt implicit publice chiar dac˘a nu sunt declarate cu modificatorul public. • In variantele mai vechi de Java era permis ¸si modificatorul abstract ˆın declaratia interfet¸ei ¸si ˆın declarat¸iile metodelor, ˆıns˘a acest lucru nu mai este valabil, deoarece atˆat interfat¸a cˆat ¸si metodele sale nu pot fi altfel decˆat abstracte.
5.2.2
Implementarea unei interfet¸e
Implementarea uneia sau mai multor interfet¸e de c˘atre o clas˘a se face prin intermediul cuvˆantului cheie implements: class NumeClasa implements NumeInterfata sau class NumeClasa implements Interfata1, Interfata2, ...
124
CAPITOLUL 5. INTERFET ¸E
O clas˘a poate implementa oricˆate interfet¸e sau poate s˘a nu implementeze nici una. In cazul ˆın care o clas˘a implementeaz˘a o anumit˘a interfat¸˘a, atunci trebuie obligatoriu s˘a specifice cod pentru toate metodele interfet¸ei. Din acest motiv, odat˘a creata ¸si folosit˘a la implementarea unor clase, o interfat¸˘a nu mai trebuie modificat˘a, ˆın sensul c˘a ad˘augarea unor metode noi sau schimbarea signaturii metodelor existente vor duce la erori ˆın compilarea claselor care o implementeaz˘a. Evident, o clas˘a poate avea ¸si alte metode ¸si variabile membre ˆın afar˘a de cele definite ˆın interfat¸˘a.
Atent¸ie Modificarea unei interfet¸e implic˘a modificarea tuturor claselor care implementeaz˘a acea interfat¸˘a.
O interfat¸˘a nu este o clas˘a, dar orice referint¸˘a de tip interfat¸˘a poate primi ca valoare o referint¸a la un obiect al unei clase ce implementeaz˘a interfat¸a respectiv˘a. Din acest motiv, interfet¸ele pot fi privite ca tipuri de date ¸si vom spune adesea c˘a un obiect are tipul X, unde X este o interfat¸˘a, dac˘a acesta este o instant¸˘a a unei clase ce implementeaz˘a interfat¸a X. Implementarea unei interfet¸e poate s˘a fie ¸si o clas˘a abstract˘a.
5.2.3
Exemplu: implementarea unei stive
S˘a consider˘am urm˘atorul exemplu. Dorim s˘a implement˘am un nou tip de date numit Stack, care s˘a modeleze not¸iunea de stiv˘a de obiecte. Obiectele de tip stiv˘a, indiferent de implementarea lor, vor trebui s˘a cont¸in˘a metodele: • push - adaug˘a un nou element in stıv˘a • pop - elimin˘a elementul din vˆarful stivei • peek - returneaz˘a varful stivei • empty - testeaz˘a dac˘a stiva este vid˘a • toString - returneaz˘a cont¸inutul stivei sub forma unui ¸sir de caractere.
5.2. FOLOSIREA INTERFET ¸ ELOR
125
Din punctul de vedere al structurii interne, o stiv˘a poate fi implementat˘a folosind un vector sau o list˘a ˆınl˘ant¸uit˘a, ambele solut¸ii avˆand avantaje ¸si dezavantaje. Prima solut¸ie este mai simplu de ˆınt¸eles, ˆın timp ce a doua este mai eficient˘a din punctul de vedere al folosirii memoriei. Deoarece nu dorim s˘a leg˘am tipul de date Stack de o anumit˘a implementare structural˘a, ˆıl vom defini prin intermediul unei interfet¸e. Vom vedea imediat avantajele acestei abord˘ari. Listing 5.1: Interfat¸a ce descrie stiva public interface Stack { void push ( Object item ) throws StackException ; void pop () throws StackException ; Object peek () throws StackException ; boolean empty () ; String toString () ; }
Pentru a trata situat¸iile anormale care pot ap˘area atunci cˆand ˆıncerc˘am s˘a punem un element ˆın stiv˘a ¸si nu este posibil din lips˘a de memorie, sau ˆıncerc˘am s˘a acces˘am vˆarful stivei ¸si aceasta este vid˘a, vom defini o except¸ie proprie StackException: Listing 5.2: Tipul de except¸ie generat de stiv˘a public class StackException extends Exception { public StackException () { super () ; } public StackException ( String msg ) { super ( msg ) ; } }
D˘am ˆın continuare prima implementare a stivei, folosind un vector: Listing 5.3: Implementarea stivei folosind un vector // Implementarea stivei folosind un vector de obiecte . public class StackImpl1 implements Stack { private Object items []; // Vectorul ce contine obiectele
126
CAPITOLUL 5. INTERFET ¸E
private int n =0; // Numarul curent de elemente din stiva public StackImpl1 ( int max ) { // Constructor items = new Object [ max ]; } public StackImpl1 () { this (100) ; } public void push ( Object item ) throws StackException { if ( n == items . length ) throw new StackException ( " Stiva este plina ! " ) ; items [ n ++] = item ; } public void pop () throws StackException { if ( empty () ) throw new StackException ( " Stiva este vida ! " ) ; items [ - - n ] = null ; } public Object peek () throws StackException { if ( empty () ) throw new StackException ( " Stiva este vida ! " ) ; return items [n -1]; } public boolean empty () { return ( n ==0) ; } public String toString () { String s = " " ; for ( int i =n -1; i >=0; i - -) s += items [ i ]. toString () + " " ; return s ; } }
Remarcat¸i c˘a, de¸si ˆın interfat¸˘a metodele nu sunt declarate explicit cu modificatorul public, ele sunt totu¸si publice ¸si trebuie declarate ca atare ˆın clas˘a. Trebuie remarcat ¸si faptul c˘a metoda toString este definit˘a deja ˆın clasa Object, deci clasa noastr˘a o are deja implementat˘a ¸si nu am fi obt¸inut nici o eroare la compilare dac˘a nu o implementam explicit. Ceea ce facem acum este de fapt supradefinirea ei.
5.2. FOLOSIREA INTERFET ¸ ELOR
127
O alt˘a observat¸ie important˘a se refer˘a la faptul c˘a trebuie s˘a declar˘am ˆın cadrul interfet¸ei ¸si except¸iile aruncate de metode, ce trebuie obligatoriu tratate. S˘a vedem acum modalitatea de implementare a stivei folosind o list˘a ˆınl˘ant¸uit˘a: Listing 5.4: Implementarea stivei folosind o list˘a // Implementarea stivei folosind o lista inlantuita . public class StackImpl2 implements Stack { class Node { // Clasa interna ce reprezinta un nod al listei Object item ; // informatia din nod Node link ; // legatura la urmatorul nod Node ( Object item , Node link ) { this . item = item ; this . link = link ; } } private Node top = null ; // Referinta la varful stivei public void push ( Object item ) { Node node = new Node ( item , top ) ; top = node ; } public void pop () throws StackException { if ( empty () ) throw new StackException ( " Stiva este vida ! " ) ; top = top . link ; } public Object peek () throws StackException { if ( empty () ) throw new StackException ( " Stiva este vida ! " ) ; return top . item ; } public boolean empty () { return ( top == null ) ; } public String toString () { String s = " " ; Node node = top ; while ( node != null ) {
128
CAPITOLUL 5. INTERFET ¸E s += ( node . item ) . toString () + " " ; node = node . link ; } return s ; }
}
Singura observat¸ie pe care o facem aici este c˘a, de¸si metoda push din interfat¸˘a declar˘a aruncarea unor except¸ii de tipul StackException, nu este obligatoriu ca metoda din clas˘a s˘a specifice ¸si ea acest lucru, atˆat timp cˆat nu genereaz˘a except¸ii de acel tip. Invers este ˆıns˘a obligatoriu. In continuare este prezentat˘a o mic˘a aplicat¸ie demonstrativ˘a care folose¸ste tipul de date nou creat ¸si cele dou˘a implement˘ari ale sale: Listing 5.5: Folosirea stivei public class TestStiva { public static void afiseaza ( Stack s ) { System . out . println ( " Continutul stivei este : " + s ) ; } public static void main ( String args []) { try { Stack s1 = new StackImpl1 () ; s1 . push ( " a " ) ; s1 . push ( " b " ) ; afiseaza ( s1 ) ; Stack s2 = new StackImpl2 () ; s2 . push ( new Integer (1) ) ; s2 . push ( new Double (3.14) ) ; afiseaza ( s2 ) ;
} catch ( StackException e ) { System . err . println ( " Eroare la lucrul cu stiva ! " ) ; e . printStackTrace () ; } } }
Observat¸i folosirea interfet¸ei Stack ca un tip de date, ce aduce flexibilitate sporit˘a ˆın manevrarea claselor ce implementeaz˘a tipul respectiv. Metoda
5.3. INTERFET ¸ E S¸I CLASE ABSTRACTE
129
afiseaza accept˘a ca argument orice obiect al unei clase ce implementeaz˘a Stack. Observat¸ie In pachetul java.util exist˘a clasa Stack care modeleaz˘a not¸iune de stiv˘a de obiecte ¸si, evident, aceasta va fi folosit˘a ˆın aplicat¸iile ce au nevoie de acest tip de date. Exemplu oferit de noi nu are leg˘atur˘a cu aceast˘a clas˘a ¸si are rol pur demonstrativ.
5.3
Interfet¸e ¸si clase abstracte
La prima vedere o interfat¸˘a nu este altceva decˆat o clas˘a abstract˘a ˆın care toate metodele sunt abstracte (nu au nici o implementare). A¸sadar, o clas˘a abstract˘a nu ar putea ˆınlocui o interfat¸˘ a? Raspunsul la intrebare depinde de situat¸ie, ˆıns˘a ˆın general este ’Nu’. Deosebirea const˘a ˆın faptul c˘a unele clase sunt fort¸ate s˘a extind˘a o anumit˘a clas˘a (de exemplu orice applet trebuie s˘a fie subclasa a clasei Applet) ¸si nu ar mai putea sa extind˘a o alt˘a clas˘a, deoarece ˆın Java nu exista decˆat mo¸stenire simpla. Fara folosirea interfet¸elor nu am putea fort¸a clasa respectiv˘a s˘a respecte diverse tipuri de protocoale. La nivel conceptual, diferent¸a const˘a ˆın: • extinderea unei clase abstracte fort¸eaz˘a o relat¸ie ˆıntre clase; • implementarea unei interfet¸e specific˘a doar necesitatea implement˘arii unor anumie metode. In multe situat¸ii interfet¸ele ¸si clasele abstracte sunt folosite ˆımpreun˘a pentru a implementa cˆat mai flexibil ¸si eficient o anumit˘a ierarhie de clase. Un exemplu sugestiv este dat de clasele ce descriu colect¸ii. Ca sa particulariz˘am, exist˘a: • interfat¸a List care impune protocolul pe care trebuie s˘a ˆıl respecte o clas˘a de tip list˘a, • clasa abstract˘a AbstractList care implementeaz˘a interfat¸a List ¸si ofer˘a implement˘ari concrete pentru metodele comune tuturor tipurilor de list˘a,
130
CAPITOLUL 5. INTERFET ¸E
• clase concrete, cum ar fi LinkedList, ArrayList care extind AbstractList.
5.4
Mo¸stenire multipl˘ a prin interfet¸e
Interfet¸ele nu au nici o implementare ¸si nu pot fi instant¸iate. Din acest motiv, nu reprezint˘a nici o problem˘a ca anumite clase s˘a implementeze mai multe interfet¸e sau ca o interfat¸˘a s˘a extind˘a mai multe interfet¸e (s˘a aib˘a mai multe superinterfet¸e) class NumeClasa implements Interfata1, Interfata2, ... interface NumeInterfata extends Interfata1, Interfata2, ... O interfat¸˘a mosteneste atˆat constantele cˆat ¸si declarat¸iile de metode de la superinterfet¸ele sale. O clas˘a mo¸steneste doar constantele unei interfet¸e ¸si responsabilitatea implement˘arii metodelor sale. S˘a consider˘am un exemplu de clasa care implementeaza mai multe interfet¸e: interface Inotator { void inoata(); } interface Zburator { void zboara(); } interface Luptator { void lupta(); } class Erou implements Inotator, Zburator, Luptator { public void inoata() {} public void zboara() {} public void lupta() {} } Exemplu de interfat¸˘a care extinde mai multe interfet¸e: interface Monstru { void ameninta(); } interface MonstruPericulos extends Monstru { void distruge();
˘ PRIN INTERFET 5.4. MOS¸TENIRE MULTIPLA ¸E } interface Mortal { void omoara(); } interface Vampir extends void beaSange(); } class Dracula implements public void ameninta() public void distruge() public void omoara()() public void beaSange() }
131
MonstruPericulos, Mortal {
Vampir { {} {} {} {}
Evident, pot ap˘area situat¸ii de ambiguitate, atunci cˆand exist˘a constante sau metode cu acelea¸si nume ˆın mai multe interfet¸e, ˆıns˘a acest lucru trebuie ˆıntotdeauna evitat, deoarece scrierea unui cod care poate fi confuz este un stil prost de programare. In cazul in care acest lucru se ˆıntˆampl˘a, compilatorul nu va furniza eroare decˆat dac˘a se ˆıncearc˘a referirea constantelor ambigue f˘ar˘a a le prefixa cu numele interfet¸ei sau dac˘a metodele cu acela¸si nume nu pot fi deosbite, cum ar fi situat¸ia cˆand au aceea¸si list˘a de argumente dar tipuri returnate incompatibile. interface I1 { int x=1; void metoda(); } interface I2 { int x=2; void metoda(); //int metoda(); }
//corect //incorect
class C implements I1, I2 { public void metoda() { System.out.println(I1.x); //corect System.out.println(I2.x); //corect System.out.println(x); //ambiguitate
132
CAPITOLUL 5. INTERFET ¸E
} } S˘a recapitul˘am cˆateva lucruri legate de clase ¸si interfet¸e: • O clas˘a nu poate avea decˆat o superclas˘a. • O clas˘a poate implementa oricˆate interfet¸e. • O clas˘a trebuie obligatoriu s˘a trateze metodele din interfet¸ele pe care la implementeaz˘a. • Ierarhia interfet¸elor este independent˘a de ierarhia claselor care le implementeaz˘a.
5.5
Utilitatea interfet¸elor
Dup˘a cum am v˘azut, o interfat¸˘a define¸ste un protocol ce poate fi implementat de orice clas˘a, indiferent de ierarhia de clase din care face parte. Interfet¸ele sunt utile pentru: • definirea unor similaritati ˆıntre clase independente f˘ar˘a a fort¸a artificial o legatur˘a ˆıntre ele; • asigur˘a c˘a toate clasele care implementeaz˘a o interfat¸˘a pun la dipozit¸ie metodele specificate ˆın interfat¸˘a - de aici rezult˘a posibilitatea implement˘arii unor clase prin mai multe modalit˘a¸ti ¸si folosirea lor ˆıntr-o manier˘a unitar˘a; • definirea unor grupuri de constante; • transmiterea metodelor ca parametri;
5.5.1
Crearea grupurilor de constante
Deoarece orice variabil˘a a unei interfet¸e este implicit declarat˘a cu public, static si final, interfet¸ele reprezint˘a o metod˘a convenabil˘a de creare a unor grupuri de constante care s˘a fie folosite global ˆıntr-o aplicat¸ie:
5.5. UTILITATEA INTERFET ¸ ELOR
133
public interface Luni { int IAN=1, FEB=2, ..., DEC=12; } Folosirea acestor constante se face prin expresii de genul NumeInterfata.constanta, ca ˆın exemplul de mai jos: if (luna < Luni.DEC) luna ++ else luna = Luni.IAN;
5.5.2
Transmiterea metodelor ca parametri
Deoarece nu exist˘a pointeri propriu-zi¸si, transmiterea metodelor ca parametri este realizat˘a ˆın Java prin intermediul interfet¸elor. Atunci cˆand o metod˘a trebuie s˘a primeasc˘a ca argument de intrare o referint¸˘a la o alt˘a funct¸ie necesar˘a execut¸iei sale, cunoscut˘a doar la momentul execut¸iei, atunci argumentul respectiv va fi declarat de tipul unei interfet¸e care cont¸ine metoda respectiv˘a. La execut¸ie metoda va putea primi ca parametru orice obiect ce implementeaz˘a interfat¸a respectiv˘a ¸si deci cont¸ine ¸si codul funct¸iei respective, aceasta urmˆand s˘a fie apelat˘a normal pentru a obt¸ine rezultatul dorit. Aceast˘a tehnic˘a, denumit˘a ¸si call-back, este extrem de folosit˘a ˆın Java ¸si trebuie neap˘arat ˆınt¸eleas˘a. S˘a consider˘am mai multe exemple pentru a clarifica lucrurile. Primul exemplu se refer˘a la explorarea nodurilor unui graf. In fiecare nod trebuie s˘a se execute prelucrarea informat¸iei din nodul respectiv prin intermediul unei funct¸ii primite ca parametru. Pentru aceasta, vom defini o interfat¸a˘ Functie care va specifica metoda trimis˘a ca parametru.
interface Functie { public void executa(Nod u); } class Graf { //... void explorare(Functie f) { //...
134
CAPITOLUL 5. INTERFET ¸E if (explorarea a ajuns in nodul v) { f.executa(v); //... }
} } //Definim doua functii class AfisareRo implements Functie { public void executa(Nod v) { System.out.println("Nodul curent este: " + v); } } class AfisareEn implements Functie { public void executa(Nod v) { System.out.println("Current node is: " + v); } } public class TestCallBack { public static void main(String args[]) { Graf G = new Graf(); G.explorare(new AfisareRo()); G.explorare(new AfisareEn()); } } Al doilea xemplu va fi prezentat ˆın sect¸iunea urm˘atoare, ˆıntrucˆat face parte din API-ul standard Java ¸si vor fi puse ˆın evident¸˘a, prin intermediul s˘au, ¸si alte tehnici de programare.
5.6
Interfat¸a FilenameFilter
Instant¸ele claselor ce implementeaz˘a aceasta interfat¸˘a sunt folosite pentru a crea filtre pentru fi¸siere ¸si sunt primite ca argumente de metode care listeaz˘a cont¸inutul unui director, cum ar fi metoda list a clasei File. A¸sadar, putem spune c˘a metoda list prime¸ste ca argument o alt˘a funct¸ie care specific˘a dac˘a un fi¸sier va fi returnat sau nu (criteriul de filtrare).
5.6. INTERFAT ¸ A FILENAMEFILTER
135
Interfat¸a FilenameFilter are o singur˘a metod˘a: accept care specific˘a criteriul de filtrare ¸si anume, testeaz˘a dac˘a numele fi¸sierului primit ca parametru ˆındepline¸ste condit¸iile dorite de noi. Definit¸ia interfet¸ei este: public interface FilenameFilter { public boolean accept(File dir, String numeFisier); } A¸sadar, orice clas˘a de specificare a unui filtru care implementez˘a interfat¸a FilenameFilter trebuie s˘a implementeze metoda accept a acestei interfet¸e. Aceste clase mai pot avea ¸si alte metode, de exemplu un constructor care s˘a primeasca criteriul de filtrare. In general, o clas˘a de specificare a unui filtru are urm˘atorul format: class FiltruFisiere implements FilenameFilter { String filtru; // Constructorul FiltruFisiere(String filtru) { this.filtru = filtru; } // Implementarea metodei accept public boolean accept(File dir, String nume) { if (filtrul este indeplinit) return true; else return false; } } Metodele cele mai uzuale ale clasei String folosite pentru filtrarea fi¸sierelor sunt: • endsWith - testeaz˘a dac˘a un ¸sir are o anumit˘a terminat¸ie • indexOf - testeaz˘a dac˘a un ¸sir cont¸ine un anumit sub¸sir, returnˆand pozit¸ia acestuia, sau 0 ˆın caz contrar.
136
CAPITOLUL 5. INTERFET ¸E
Instant¸ele claselor pentru filtrare sunt primite ca argumente de metode de listare a cont¸inutului unui director. O astfel de metod˘a este list din clasa File: String[] list (FilenameFilter filtru) Observati c˘a aici interfat¸a este folosit˘a ca un tip de date, ea fiind substituit˘a cu orice clas˘a care o implementeaz˘a. Acesta este un exemplu tipic de transmitere a unei funct¸ii (funct¸ia de filtrare accept) ca argument al unei metode. S˘a consider˘am exemplul complet ˆın care dorim s˘a list˘am fi¸sierele din directorul curent care au o anumit˘a extensie. Listing 5.6: Listarea fi¸sierelor cu o anumit˘a extensie /* Listarea fisierelor din directorul curent care au anumita extensie primita ca argument . Daca nu se primeste nici un argument , vor fi listate toate . */ import java . io .*; class Listare { public static void main ( String [] args ) { try { File director = new File ( " . " ) ; String [] list ; if ( args . length > 0) list = director . list ( new Filtru ( args [0]) ) ; else list = director . list () ; for ( int i = 0; i < list . length ; i ++) System . out . println ( list [ i ]) ; } catch ( Exception e ) { e . printStackTrace () ; } } } class Filtru implements FilenameFilter { String extensie ; Filtru ( String extensie ) { this . extensie = extensie ;
5.6. INTERFAT ¸ A FILENAMEFILTER
137
} public boolean accept ( File dir , String nume ) { return ( nume . endsWith ( " . " + extensie ) ) ; } }
5.6.1
Folosirea claselor anonime
In cazul ˆın care nu avem nevoie de clasa care reprezint˘a filtrul pentru listarea fi¸sierelor dintr-un director decˆat o singur˘a dat˘a, pentru a evita crearea unei noi clase de sine st˘at˘atoare care s˘a fie folosit˘a pentru instant¸ierea unui singur obiect, putem folosi clas˘a intern˘a anonim˘a, aceast˘a situat¸ie fiind un exemplu tipic de folosire a acestora. Listing 5.7: Folosirea unei clase anonime /* Listarea fisierelor din directorul curent folosind o clasa anonima pentru filtru . */ import java . io .*; class Listare { public static void main ( String [] args ) { try { File director = new File ( " . " ) ; String [] list ; if ( args . length > 0) { final String extensie = args [0]; list = director . list ( new FilenameFilter () { // Clasa interna anonima public boolean accept ( File dir , String nume ) { return ( nume . endsWith ( " . " + extensie ) ) ; } }) ; } else list = director . list () ; for ( int i = 0; i < list . length ; i ++) System . out . println ( list [ i ]) ; } catch ( Exception e ) { e . printStackTrace () ; }
138
CAPITOLUL 5. INTERFET ¸E
} }
A¸sadar, o modalitate uzual˘a de folosire a claselor anonime pentru instant¸ierea unui obiect care trebuie s˘a respecte o interfat¸˘a este: metoda(new Interfata() { // Implementarea metodelor interfetei });
5.7
Compararea obiectelor
Am v˘azut ˆın primul capitol c˘a o solut¸ie facil˘a ¸si eficient˘a de sortare a unui vector este folosirea metodei sort din clasa java.util.Arrays. int v[]={3, 1, 4, 2}; java.util.Arrays.sort(v); // Sorteaza vectorul v // Acesta va deveni {1, 2, 3, 4} In cazul ˆın care elementele din vector sunt de tip primitiv, ca in exemplul de mai sus, nu exist˘a nici o problem˘a ˆın a determina ordinea fireasc˘a a elementelor. Ce se ˆınt˘ampl˘a ˆıns˘a atunci cˆand vectorul cont¸ine referint¸e la obiecte de un anumit tip ? S˘a consider˘am urm˘atorul exemplu, ˆın care dorim s˘a sort˘am un vector format din instant¸e ale clasei Persoana, definit˘a mai jos: Listing 5.8: Clasa Persoana (f˘ar˘a suport pentru comparare) class Persoana { int cod ; String nume ; public Persoana ( int cod , String nume ) { this . cod = cod ; this . nume = nume ; } public String toString () { return cod + " \ t " + nume ; } }
5.7. COMPARAREA OBIECTELOR
139
Programul urm˘ator ar trebui s˘a sorteze un vector de persoane: Listing 5.9: Sortarea unui vector de tip referint¸˘a class Sortare { public static void main ( String args []) { Persoana p [] = new Persoana [4]; p [0] = new Persoana (3 , " Ionescu " ) ; p [1] = new Persoana (1 , " Vasilescu " ) ; p [2] = new Persoana (2 , " Georgescu " ) ; p [3] = new Persoana (4 , " Popescu " ) ; java . util . Arrays . sort ( p ) ; System . out . println ( " Persoanele ordonate dupa cod : " ) ; for ( int i =0; i < p . length ; i ++) System . out . println ( p [ i ]) ; } }
La execut¸ia acestei aplicat¸ii va fi obt¸inut˘a o except¸ie, deoarece metoda sort nu ¸stie care este ordinea natural˘a a obiectelor de tip Persoana. Va trebui, ˆıntr-un fel sau altul, s˘a specific˘am acest lucru.
5.7.1
Interfat¸a Comparable
Interfat¸a Comparable impune o ordine total˘a asupra obiectelor unei clase ce o implementeaz˘a. Aceast˘a ordine se nume¸ste ordinea natural˘ a a clasei ¸si este specificat˘a prin intermediul metodei compareTo. Definit¸ia interfet¸ei este: public interface Comparable { int compareTo(Object o); } A¸sadar, o clas˘a ale c˘arei instant¸e trebuie s˘a fie comparabil va implementa metoda compareTo care trebuie s˘a returneze: • o valoare strict negativ˘ a: dac˘a obiectul curent (this) este mai mic decˆa obiectul primit ca argument; • zero: dac˘a obiectul curent este egal decˆa obiectul primit ca argument;
140
CAPITOLUL 5. INTERFET ¸E
• o valoare strict pozitiv˘ a: dac˘a obiectul curent este mai mare decˆa obiectul primit ca argument. Reamintim c˘a metoda equals, mo¸stenit˘a din Object de toate clasele, determin˘a dac˘a dou˘a obiecte sunt egale (au aceea¸si valoare). Spunem c˘a ordinea natural˘a a unei clase C este consitent˘ a cu equals dac˘a ¸si numai dac˘a (e1.compareTo((Object)e2) == 0) are aceeas¸si valoare logic˘a cu e1.equals((Object)e2, pentru orice e1, e2 instant¸e ale lui C. null nu este instant¸˘a a nici unei clase ¸si e.compareTo(null) trebuie s˘a arunce o except¸ie de tip NullPointerException chiar dac˘a e.equals(null) returneaz˘a false. S˘a presupunem c˘a dorim ca ordinea natural˘a a persoanelor s˘a fie dup˘a codul lor intern. Listing 5.10: Clasa Persoana cu suport pentru comparare class Persoana implements Comparable { int cod ; String nume ; public Persoana ( int cod , String nume ) { this . cod = cod ; this . nume = nume ; } public String toString () { return cod + " \ t " + nume ; } public boolean equals ( Object o ) { if (!( o instanceof Persoana ) ) return false ; Persoana p = ( Persoana ) o ; return ( cod == p . cod ) && ( nume . equals ( p . nume ) ) ; } public int compareTo ( Object o ) { if ( o == null ) throw new Null P o i n t e r E x c e p t i o n () ; if (!( o instanceof Persoana ) ) throw new Clas sCas tExce ptio n ( " Nu pot compara ! " ) ; Persoana p = ( Persoana ) o ;
5.7. COMPARAREA OBIECTELOR
141
return ( cod - p . cod ) ; } }
Observat¸i folosirea operatorului instanceof, care verific˘a dac˘a un obiect este instant¸˘a a unei anumite clase. Metoda compareTo va arunca o except¸ie de tipul ClassCastException dac˘a se ˆıncearc˘a compararea unui obiect de tip Persoana cu un obiect de alt tip. Metoda equals va returna, pur ¸si simplu, false.
5.7.2
Interfat¸a Comparator
In cazul ˆın care dorim s˘a sort˘am elementele unui vector ce cont¸ine referint¸e dup˘a alt criteriu decˆat ordinea natural˘a a elemenetelor, avem nevoie de o alt˘a solut¸ie. Aceasta este oferit˘a tot de metoda sort din clasa java.util.Arrays, dar ˆın varianta ˆın care, pe lˆang˘a vectorul ce trebuie sortat, vom transmite un argument de tip Comparator care s˘a specifice modalitatea de comparare a elementelor. Interfat¸a java.util.Comparator cont¸ine metoda compare, care impune o ordine total˘a asupra elementelor unei colect¸ii. Aceasta returneaz˘a un ˆıntreg cu aceea¸si semnificat¸ie ca la metoda compareTo a interfet¸ei Comparator ¸si are urm˘atoarea definit¸ie: int compare(Object o1, Object o2); S˘a presupunem c˘a dorim s˘a sort˘am persoanele ordonate dup˘a numele lor. Pentru definirea comparatorului vom folosi o clas˘a anonim˘a. Listing 5.11: Sortarea unui vector folosind un comparator import java . util .*; class Sortare { public static void main ( String args []) { Persoana p [] = new Persoana [4]; p [0] = new Persoana (3 , " Ionescu " ) ; p [1] = new Persoana (1 , " Vasilescu " ) ; p [2] = new Persoana (2 , " Georgescu " ) ; p [3] = new Persoana (4 , " Popescu " ) ; Arrays . sort (p , new Comparator () { public int compare ( Object o1 , Object o2 ) { Persoana p1 = ( Persoana ) o1 ; Persoana p2 = ( Persoana ) o2 ; return ( p1 . nume . compareTo ( p2 . nume ) ) ;
142
CAPITOLUL 5. INTERFET ¸E } }) ; System . out . println ( " Persoanele ordonate dupa nume : " ) ; for ( int i =0; i < p . length ; i ++) System . out . println ( p [ i ]) ;
} }
Observat¸i cum compararea a dou˘a ¸siruri de caractere se face tot cu metoda compareTo, clasa String implemenˆand interfat¸a Comparable.
5.8
Adaptori
In cazul ˆın care o interfat¸˘a cont¸ine mai multe metode ¸si, la un moment dat, avem nevoie de un obiect care implementeaz˘a interfat¸a respectiv dar nu specific˘a cod decˆat pentru o singur˘a metod˘a, el trebui totu¸si s˘a implementeze toate metodele interfet¸ei, chiar dac˘a nu specific˘a nici un cod. interface X { void metoda_1(); void metoda_2(); ... void metoda_n(); } ... // Avem nevoie de un obiect de tip X // ca argument al unei functii functie(new X() { public void metoda_1() { // Singura metoda care ne intereseaza ... } // Trebuie sa apara si celelalte metode // chiar daca nu au implementare efectiva public void metoda_2() {} public void metoda_3() {} ... public void metoda_n() {}
5.8. ADAPTORI
143
}); Aceast˘a abordare poate fi nepl˘acut˘a dac˘a avem frecvent nevoie de obiecte ale unor clase ce implementeaz˘a interfat¸a X. Solut¸ia la aceast˘a problem˘a este folosirea adaptorilor. Definit¸ie Un adaptor este o clas˘a abstract˘a care implementeaz˘a o anumit˘a interfat¸˘a f˘ar˘a a specifica cod nici unei metode a interfet¸ei.
public abstract class XAdapter implements X { public void metoda_1() {} public void metoda_2() {} ... public void metoda_n() {} } In situat¸ia cˆand avem nevoie de un obiect de tip X vom folosi clasa abstract˘a, supradefinind doar metoda care ne intereseaz˘a: functie(new XAdapter() { public void metoda_1() { // Singura metoda care ne intereseaza ... } }); Mai multe exemple de folosire a adaptorilor vor fi date ˆın capitolul ”Interfat¸a grafic˘a cu utilizatorul”.
144
CAPITOLUL 5. INTERFET ¸E
Capitolul 6 Organizarea claselor 6.1
Pachete
Definit¸ie Un pachet este o colect¸ie de clase ¸si interfet¸e ˆınrudite din punctul de vedere al funct¸ionalit˘a¸tii lor. Sunt folosite pentru g˘asirea ¸si utilizarea mai u¸soar˘a a claselor, pentru a evita conflictele de nume ¸si pentru a controla accesul la anumite clase. In alte limbaje de programare pachetele se mai numesc libr˘arii sau bibilioteci.
6.1.1
Pachetele standard (J2SDK)
Platforma standard de lucru Java se bazeaz˘a pe o serie de pachete cu ajutorul c˘arora se pot construi ˆıntr-o manier˘a simplificat˘a aplicat¸iile. Exist˘a deci un set de clase deja implementate care modeleaz˘a structuri de date, algoritmi sau diverse not¸iuni esent¸iale ˆın dezvoltarea unui program. Cele mai importante pachete ¸si suportul oferit de lor sunt: • java.lang - clasele de baz˘a ale limbajului Java • java.io - intr˘ari/ie¸siri, lucrul cu fi¸siere • java.util - clase ¸si interfet¸e utile • java.applet - dezvoltarea de appleturi 145
146
CAPITOLUL 6. ORGANIZAREA CLASELOR
• java.awt - interfat¸a grafic˘a cu utilizatorul • java.awt.event - mecanismele de tratare e evenimentelor generate de utilizator • java.beans - scrierea de componente reutilizabile • java.net - programare de ret¸ea • java.sql - lucrul cu baze de date • java.rmi - execut¸ie la distant¸˘a Remote Message Interface • java.security - mecanisme de securitate: criptare, autentificare • java.math - operat¸ii matematice cu numere mari • java.text - lucrul cu texte, date ¸si numere independent de limb˘a • java.lang.reflect - introspect¸ie • javax.swing - interfat¸a grafic˘a cu utilizatorul, mult ˆımbog˘a¸tit˘a fat¸˘a de AWT. • ...
6.1.2
Folosirea membrilor unui pachet
Conform specificat¸iilor de acces ale unei clase ¸si ale mebrilor ei, doar clasele publice ¸si membrii declarat¸i publici ai unei clase sunt accesibili ˆın afara pachetului ˆın care se g˘asesc. Dup˘a cum am v˘azut ˆın sect¸iunea ”Specificatori de acces pentru membrii unei clase”, accesul implicit ˆın Java este la nivel de pachet. Pentru a folosi o clas˘a public˘a dintr-un anumit pachet, sau pentru a apela o metod˘a public˘a a unei clase publice a unui pachet, exist˘a trei solut¸ii: • specificarea numelui complet al clasei • importul clasei respective • importul ˆıntregului pachet ˆın care se g˘ase¸ste clasa.
6.1. PACHETE
147
Specificarea numelui complet al clasei se face prin prefixarea numelui scurt al clasei cu numele pachetului din care face parte: numePachet.NumeClasa. Button java.awt java.awt.Button
- numele scurt al clasei - pachetul din care face parte - numele complet al clasei
Aceast˘a metod˘a este recomandat˘a doar pentru cazul ˆın care folosirea acelei clase se face o singur˘a dat˘a sau foarte rar. De exemplu, ar fi extrem de nepl˘acut s˘a scriem de fiecare dat˘a cˆand vrem s˘a declar˘am un obiect grafic secvent¸e de genul: java.awt.Button b1 java.awt.Button b2 java.awt.TextField java.awt.TextField
= new java.awt.Button("OK"); = new java.awt.Button("Cancel"); tf1 = new java.awt.TextField("Neplacut"); tf2 = new java.awt.TextField("Tot neplacut");
In aceste situat¸ii, vom importa ˆın aplicat¸ia noastr˘a clasa respectiv˘a, sau ˆıntreg pachetul din care face parte. Acest lucru se realizeaz˘a prin instruct¸iunea import, care trebuie s˘a apar˘a la ˆınceputul fi¸sierelor surs˘a, ˆınainte de declararea vreunei clase sau interfet¸e.
6.1.3
Importul unei clase sau interfet¸e
Se face prin instruct¸iunea import ˆın care specific˘am numele complet al clasei sau interfet¸ei pe care dorim s˘a o folosim dintr-un anumit pacehet: import numePachet.numeClasa; //Pentru exemplul nostru: import java.awt.Button; import java.awt.TextField; Din acest moment, vom putea folosi ˆın clasele fi¸sierului ˆın care am plasat instruct¸iunea de import numele scurt al claselor Button ¸si TextField: Button b1 Button b2 TextField TextField
= new Button("OK"); = new Button("Cancel"); tf1 = new TextField("Placut"); tf2 = new TextField("Foarte placut");
148
CAPITOLUL 6. ORGANIZAREA CLASELOR
Aceast˘a abordare este eficient˘a ¸si recomandat˘a ˆın cazul ˆın care nu avem nevoie decˆat de cˆateva clase din pachetul respectiv. Dac˘a ˆın exemplul nostru am avea nevoie ¸si de clasele Line, Point, Rectangle, Polygon, ar trebui s˘a avem cˆate o instruct¸iune de import pentru fiecare dintre ele: import import import import import import
java.awt.Button; java.awt.TextField; java.awt.Rectangle; java.awt.Line; java.awt.Point; java.awt.Polygon;
In aceast˘a situat¸ie ar fi mai simplu s˘a folosim importul la cerere din ˆıntregul pachet ¸si nu al fiec˘arei clase ˆın parte.
6.1.4
Importul la cerere dintr-un pachet
Importul la cerere dintr-un anumit pachet se face printr-o instruct¸iune import ˆın care specific˘am numele pachetului ale c˘arui clase ¸si interfet¸e dorim s˘a le folosim, urmat de simbolul *. Se nume¸ste import la cerere deoarece ˆınc˘arcarea claselor se face dinamic, ˆın momentul apel˘arii lor. import numePachet.*; //Pentru exemplul nostru: import java.awt.*; Din acest moment, vom putea folosi ˆın clasele fi¸sierului ˆın care am plasat instruct¸iunea de import numele scurt al tuturor claselor pachetului importat: Button b = new Button("OK"); Point p = new Point(0, 0);
Atent¸ie * nu are semnificat¸ia uzual˘a de la fi¸siere de wildcard (masc˘a) ¸si nu poate fi folosit decˆat ca atare. O expresie de genul import java.awt.C*; va produce o eroare de compilare.
6.1. PACHETE
149
In cazul ˆın care sunt importate dou˘a sau mai multe pachete care cont¸in clase (interfet¸e) cu acela¸si nume, atunci referirea la ele trebuie f˘acut˘a doar folosind numele complet, ˆın caz contrar fiind semnalat˘a o ambiguitate de c˘atre compilator. import java.awt.*; // Contine clasa List import java.util.*; // Contine interfata List ... List x;
//Declaratie ambigua
java.awt.List a = new java.awt.List(); //corect java.util.List b = new ArrayList(); //corect Sunt considerate importate automat, pentru orice fi¸sier surs˘a, urm˘atoarele pachete: • pachetul java.lang import java.lang.*; // Poate sau nu sa apara // Mai bine nu... • pachetul curent • pachetul implicit (f˘ar˘a nume)
6.1.5
Importul static
Aceast˘a facilitate, introdus˘a ˆıncepˆand cu versiunea 1.5, permite referirea constantelor statice ale unei clase f˘ar˘a a mai specifica numele complet al acesteia ¸si este implementat˘a prin ad˘augarea cuvˆantului cheie static dup˘a cel de import: import static numePachet.NumeClasa.*; Astfel, ˆın loc s˘a ne referim la constantele clasei cu expresii de tipul NumeClasa.CONSTANTA, putem folosi doar numele constantei.
150
CAPITOLUL 6. ORGANIZAREA CLASELOR
// Inainte de versiuna 1.5 import java.awt.BorderLayout.*; ... fereastra.add(new Button(), BorderLayout.CENTER); // Incepand cu versiunea 1.5 import java.awt.BorderLayout.*; import static java.awt.BorderLayout.*; ... fereastra.add(new Button(), CENTER);
Atent¸ie Importul static nu import˘a decˆat constantele statice ale unei clase, nu ¸si clasa ˆın sine.
6.1.6
Crearea unui pachet
Toate clasele ¸si interfet¸ele Java apartin la diverse pachete, grupate dup˘a funct¸ionalitatea lor. Dup˘a cum am v˘azut clasele de baz˘a se g˘asesc ˆın pachetul java.lang, clasele pentru intr˘ari/ie¸siri sunt ˆın java.io, clasele pentru interfat¸a grafic˘a ˆın java.awt, etc. Crearea unui pachet se realizeaz˘a prin scriere la ˆınceputul fi¸sierelor surs˘a ce cont¸in clasele ¸si interfet¸ele pe care dorim s˘a le grup˘am ˆıntr-un pachet a instruct¸iunii: package numePachet; S˘a consider˘am un exemplu: presupunem c˘a avem dou˘a fi¸siere surs˘a Graf.java ¸si Arbore.java. //Fisierul Graf.java package grafuri; class Graf {...} class GrafPerfect extends Graf {...} //Fisierul Arbore.java package grafuri; class Arbore {...}
6.1. PACHETE
151
class ArboreBinar extends Arbore {...}
Clasele Graf, GrafPerfect, Arbore, ArboreBinar vor face parte din acela¸si pachet grafuri. Instruct¸iunea package act¸ioneaz˘a asupra ˆıntregului fi¸sier surs˘a la ˆınceputul c˘aruia apare. Cu alte cuvinte nu putem specifica faptul c˘a anumite clase dintr-un fi¸sier surs˘a apart¸in unui pachet, iar altele altui pachet. Dac˘a nu este specificat un anumit pachet, clasele unui fi¸sier surs˘a vor face parte din pachetul implicit (care nu are nici un nume). In general, pachetul implicit este format din toate clasele ¸si intefet¸ele directorului curent de lucru. Este recomandat ˆıns˘a ca toate clasele ¸si intefetele s˘a fie plasate ˆın pachete, pachetul implicit fiind folosit doar pentru aplicat¸ii mici sau prototipuri.
6.1.7
Denumirea unui pachet
Exist˘a posibilitatea ca doi programatori care lucreaz˘a la un proiect comun s˘a foloseasc˘a acela¸si nume pentru unele din clasele lor. De asemenea, se poate ca una din clasele unei aplicat¸ii s˘a aib˘a acela¸si nume cu o clas˘a a mediului Java. Acest lucru este posibil atˆat timp cˆat clasele cu acela¸si nume se gasesc ˆın pachete diferite, ele fiind diferent¸iate prin prefixarea lor cu numele pachetelor. Ce se ˆıntˆampl˘a ˆıns˘a cˆand doi programatori care lucreaz˘a la un proiect comun folosesc clase cu acela¸si nume, ce se gasesc ˆın pachete cu acela¸si nume ? Pentru a evita acest lucru, companiile folosesc inversul domeniului lor Internet ˆın denumirea pachetelor implementate ˆın cadrul companiei, cum ar fi ro.companie.numePachet. In cadrul aceleiasi companii, conflictele de nume vor fi rezolvate prin diverse convent¸ii de uz intern, cum ar fi folosirea numelui de cont al programatorilor ˆın denumirea pachetelor create de ace¸stia. De exemplu, programatorul cu numele Ion al companiei XSoft, avˆand contul
[email protected], ˆı¸si va prefixa pachetele cu ro.xsoft.ion, pentru a permite identificarea ˆın mod unic a claselor sale, indiferent de contextul ˆın care acestea vor fi integrate.
152
CAPITOLUL 6. ORGANIZAREA CLASELOR
6.2
Organizarea fi¸sierelor
6.2.1
Organizarea fi¸sierelor surs˘ a
Orice aplicat¸ie nebanal˘a trebuie s˘a fie construit˘a folosind o organizare ierarhic˘a a componentelor sale. Este recomandat ca strategia de organizare a fi¸sierelor surs˘a s˘a respecte urm˘atoarele convent¸ii: • Codul surs˘a al claselor ¸si interfet¸elor s˘a se gaseasc˘a ˆın fi¸siere ale c˘aror nume s˘a fie chiar numele lor scurt ¸si care s˘a aib˘a extensia .java.
Atent¸ie Este obligatoriu ca o clas˘a/interfat¸˘a public˘a s˘a se gaseasc˘a ˆıntr-un fi¸sier avˆand numele clasei(interfet¸ei) ¸si extenisa .java, sau compilatorul va furniza o eroare. Din acest motiv, ˆıntr-un fi¸sier surs˘a nu pot exista dou˘a clase sau interfet¸e publice. Pentru clasele care nu sunt publice acest lucru nu este obligatoriu, ci doar recomandat. Intr-un fi¸sier surs˘a pot exista oricˆate clase sau interfet¸e care nu sunt publice.
• Fi¸sierele surs˘a trebuie s˘a se g˘aseasc˘a ˆın directoare care s˘a reflecte numele pachetelor ˆın care se g˘asesc clasele ¸si interfet¸ele din acele fi¸siere. Cu alte cuvinte, un director va cont¸ine surse pentru clase ¸si interfet¸e din acela¸si pachet iar numele directorului va fi chiar numele pachetului. Dac˘a numele pachetelor sunt formate din mai multe unit˘a¸ti lexicale separate prin punct, atunci acestea trebuie de asemenea s˘a corespund˘a unor directoare ce vor descrie calea spre fi¸sierele surs˘a ale c˘aror clase ¸si interfet¸e fac parte din pachetele respective. Vom clarifica modalitatea de organizare a fi¸sierelor surs˘a ale unei aplicatii printr-un exemplu concret. S˘a presupunem c˘a dorim crearea unor componente care s˘a reprezinte diverse not¸iuni matematice din domenii diferite, cum ar fi geometrie, algebr˘a, analiz˘a, etc. Pentru a simplifica lucrurile, s˘a presupunem c˘a dorim s˘a cre˘am clase care s˘a descrie urm˘atoarele notiuni: poligon, cerc, poliedru, sfer˘ a, grup, funct¸ie. O prim˘a variant˘a ar fi s˘a construim cˆate o clas˘a pentru fiecare ¸si s˘a le plas˘am
6.2. ORGANIZAREA FIS¸IERELOR
153
ˆın acela¸si director ˆımpreuna cu un program care s˘a le foloseasca, ˆıns˘a, avˆand ˆın vedere posibila extindere a aplicat¸iei cu noi reprezent˘ari de not¸iuni matematice, aceast˘a abordare ar fi ineficient˘a. O abordare elegant˘a ar fi aceea ˆın care clasele care descriu not¸iuni din acela¸si domeniu sa se gaseasca ˆın pachete separate ¸si directoare separate. Ierarhia fi¸sierelor sursa ar fi: /matematica /surse /geometrie /plan Poligon.java Cerc.java /spatiu Poliedru.java Sfera.java /algebra Grup.java /analiza Functie.java Matematica.java Clasele descrise ˆın fi¸sierele de mai sus trebuie declarate ˆın pachete denumite corespunzator cu numele directoarelor ˆın care se gasesc: // Poligon.java package geometrie.plan; public class Poligon { . . . } // Cerc.java package geometrie.plan; public class Cerc { . . . }
// Poliedru.java package geometrie.spatiu; public class Poliedru { . . . }
154
CAPITOLUL 6. ORGANIZAREA CLASELOR
// Sfera.java package geometrie.spatiu; public class Sfera { . . . } // Grup.java package algebra; public class Grup { . . . } // Functie.java package analiza; public class Functie { . . . }
Matematica.java este clasa principal˘a a aplicat¸iei. Dup˘a cum se observ˘a, numele lung al unei clase trebuie s˘a descrie calea spre acea clas˘a ˆın cadrul fi¸sierelor surs˘a, relativ la directorul ˆın care se g˘ase¸ste aplicat¸ia.
6.2.2
Organizarea unit˘ a¸tilor de compilare (.class)
In urma compil˘arii fi¸sierelor surs˘a vor fi generate unit˘a¸ti de compilare pentru fiecare clas˘a ¸si interfat¸˘a din fi¸sierele surs˘a. Dup˘a cum ¸stim acestea au extensia .class ¸si numele scurt al clasei sau interfet¸ei respective. Spre deosebire de organizarea surselor, un fi¸sier .class trebuie s˘a se gaseasca ˆıntr-o ierarhie de directoare care s˘a reflecte numele pachetului din care face parte clasa respectiv˘a. Implicit, ˆın urma compil˘arii fi¸sierele surs˘a ¸si unit˘a¸tile de compilare se g˘asesc ˆın acela¸si director, ˆıns˘a ele pot fi apoi organizate separat. Este recomandatˆıns˘a ca aceast˘a separare s˘a fie f˘acut˘a automat la compilare. Revenind la exemplul de mai sus, vom avea urm˘atoarea organizare: /matematica /clase /geometrie /plan Poligon.class Cerc.class /spatiu
6.2. ORGANIZAREA FIS¸IERELOR
155
Poliedru.class Sfera.class /algebra Grup.class /analiza Functie.class Matematica.class Crearea acestei structuri ierarhice este facut˘a automat de c˘atre compilator. In directorul aplicatiei (matematica) cre˘am subdirectorul clase ¸si d˘am comanda: javac -sourcepath surse surse/Matematica.java -d clase sau javac -classpath surse surse/Matematica.java -d clase Opt¸iunea -d specific˘a directorul r˘ad˘acin˘a al ierarhiei de clase. In lipsa lui, fiecare unitate de compilare va fi plasat˘a ˆın acela¸si director cu fi¸sierul s˘au surs˘a. Deoarece compil˘am clasa principal˘a a plicat¸iei, vor fi compilate ˆın cascad˘a toate clasele referite de aceasta, dar numai acestea. In cazul ˆın care dorim s˘a compil˘am explicit toate fi¸sierele java dintr-un anumit director, de exemplu surse/geometrie/plan, putem folosi expresia: javac surse/geometrie/plan/*.java -d clase
6.2.3
Necesitatea organiz˘ arii fi¸sierelor
Organizarea fi¸sierelor surs˘a este necesar˘a deoarece ˆın momentul cˆand compilatorul ˆıntˆalneste un nume de clas˘a el trebuie s˘a poat˘a identifica acea clas˘a, ceea ce ˆınseamna c˘a trebuie s˘a gaseasc˘a fi¸serul surs˘a care o cont¸ine. Similar, unit˘a¸tile de compilare sunt organizate astfel pentru a da posibilitatea interpretorului s˘a gaseasc˘a ¸si s˘a ˆıncarce ˆın memorie o anumit˘a clas˘a ˆın timpul execut¸iei programului. Ins˘a aceast˘a organizare nu este suficient˘a deoarece specific˘a numai partea final˘a din calea c˘atre fi¸sierele .java ¸si .class, de exemplu /matematica/clase/geometrie/plan/Poligon.class. Pentru aceasta, atˆat la compilare cˆat ¸si la interpretare trebuie specificat˘a lista de directoare r˘ad˘acin˘a
156
CAPITOLUL 6. ORGANIZAREA CLASELOR
ˆın care se g˘asesc fi¸sierele aplicat¸iei. Aceast˘a list˘a se nume¸ste cale de cautare (classpath). Definit¸ie O cale de c˘autare este o list˘a de directoare sau arhive ˆın care vor fi c˘autate fi¸sierele necesare unei aplicat¸ii. Fiecare director din calea de cautare este directorul imediat superior structurii de directoare corespunz˘atoare numelor pachetelor ˆın care se g˘asesc clasele din directorul respectiv, astfel ˆıncˆat compilatorul ¸si interpretorul s˘a poat˘a construi calea complet˘a spre clasele aplicat¸iei. Implicit, calea de c˘autare este format˘a doar din directorul curent. S˘a consider˘am clasa principal˘a a aplicat¸iei Matematica.java: import geometrie.plan.*; import algebra.Grup; import analiza.Functie; public class Matematica { public static void main(String args[]) { Poligon a = new Poligon(); geometrie.spatiu.Sfera = new geometrie.spatiu.Sfera(); //... } } Identificarea unei clase referite ˆın program se face ˆın felul urm˘ator: • La directoarele aflate ˆın calea de c˘autare se adaug˘a subdirectoarele specificate ˆın import sau ˆın numele lung al clasei • In directoarele formate este c˘autat un fi¸sier cu numele clasei. In cazul ˆın care nu este g˘asit nici unul sau sunt g˘asite mai multe va fi semnalat˘a o eroare.
6.2.4
Setarea c˘ aii de c˘ autare (CLASSPATH)
Setarea c˘aii de c˘autare se poate face ˆın dou˘a modalit˘a¸ti: • Setarea variabilei de mediu CLASSPATH - folosind aceast˘a variant˘a toate aplicat¸iile Java de pe ma¸sina respectiv˘a vor c˘auta clasele necesare ˆın directoarele specificate ˆın variabila CLASSPATH.
6.3. ARHIVE JAR
157
UNIX: SET CLASSPATH = cale1:cale2:... DOS shell (Windows 95/NT/...): SET CLASSPATH = cale1;cale2;... • Folosirea opt¸iunii -classpath la compilarea ¸si interpretarea programelor - directoarele specificate astfel vor fi valabile doar pentru comanda curent˘a: javac - classpath
<surse java> java - classpath Lansarea ˆın execut¸ie a aplicatiei noastre, din directorul matematica, se va face astfel: java -classpath clase Matematica In concluzie, o organizare eficient˘a a fi¸sierelor aplicat¸iei ar ar˘ata astfel: /matematica /surse /clase compile.bat (javac -sourcepath surse surse/Matematica.java -d clase) run.bat (java -classpath clase Matematica)
6.3
Arhive JAR
Fi¸sierele JAR (Java Archive) sunt arhive ˆın format ZIP folosite pentru ˆımpachetarea aplicat¸iilor Java. Ele pot fi folosite ¸si pentru comprim˘ari obi¸snuite, diferent¸a fat¸˘a de o arhiv˘a ZIP obi¸snuit˘a fiind doar existent¸a unui director denumit META-INF, ce cont¸ine diverse informat¸ii auxiliare legate de aplicat¸ia sau clasele arhivate. Un fi¸sier JAR poate fi creat folosind utilitarul jar aflat ˆın distribut¸ia J2SDK, sau metode ale claselor suport din pachetul java.util.jar. Dintre beneficiile oferite de arhivele JAR amintim: • portabilitate - este un format de arhivare independent de platform˘a;
158
CAPITOLUL 6. ORGANIZAREA CLASELOR
• compresare - dimensiunea unei aplicat¸ii ˆın forma sa final˘a este redus˘a; • minimizarea timpului de ˆıncarcare a unui applet: dac˘a appletul (fi¸siere class, resurse, etc) este compresat ˆıntr-o arhiv˘a JAR, el poate fi ˆınc˘arcat ˆıntr-o singur˘a tranzact¸ie HTTP, f˘ar˘a a fi deci nevoie de a deschide cˆate o conexiune nou˘a pentru fiecare fi¸sier; • securitate - arhivele JAR pot fi ”semnate” electronic • mecanismul pentru lucrul cu fi¸siere JAR este parte integrata a platformei Java.
6.3.1
Folosirea utilitarului jar
Arhivatorul jar se g˘ase¸ste ˆın subdirectorul bin al directorului ˆın care este instalat kitul J2SDK. Mai jos sunt prezentate pe scurt operat¸iile uzuale: • Crearea unei arhive jar cf arhiva.jar fi¸ sier(e)-intrare • Vizualizare cont¸inutului jar tf nume-arhiva • Extragerea cont¸inutului jar xf arhiva.jar • Extragerea doar a unor fi¸siere jar xf arhiva.jar fi¸ sier(e)-arhivate • Executarea unei aplicat¸ii java -jar arhiva.jar • Deschiderea unui applet arhivat Ca ¸si ˆın cazul argumentelor trimise aplicat¸iilor de la linia de comand˘a, tipul parametrilor este ˆıntotdeauna ¸sir de caractere, indiferent dac˘a valoarea este ˆıntre ghilimele sau nu. Fiecare applet are ¸si un set de parametri prestabilit¸i ale c˘aror nume nu vor putea fi folosite pentru definirea de noi parametri folosind metoda de
14.5. DEFINIREA S¸I FOLOSIREA PARAMETRILOR
409
mai sus. Ace¸stia apar direct ˆın corpul tagului APPLET ¸si definesc informat¸ii generale despre applet. Exemple de astfel de parametri sunt CODE, WIDTH sau HEIGHT. Lista lor complet˘a va fi prezentata la descrierea tagului APPLET. Folosirea parametrilor primit¸i de c˘atre un applet se face prin intermediul metodei getParameter care prime¸ste ca argument numele unui parametru ¸si returneaz˘a valoarea acestuia. In cazul ˆın care nu exist˘a nici un parametru cu numele specificat, metoda ˆıntoarce null, caz ˆın care programul trebuie s˘a atribuie o valoare implicit˘a variabilei ˆın care se dorea citirea respectivului parametru. Orice applet poate pune la dispozit¸ie o ”documentat¸ie” referitoare la parametrii pe care ˆıi suport˘a, pentru a veni ˆın ajutorul utilizatorilor care doresc s˘a includ˘a appletul ˆıntr-o pagin˘a Web. Aceasta se realizeaz˘a prin supradefinirea metodei getParameterInfo, care returneaz˘a un vector format din triplete de ¸siruri. Fiecare element al vectorului este de fapt un vector cu trei elemente de tip String, cele trei ¸siruri reprezentˆand numele parametrului, tipul s˘au ¸si o descriere a sa. Informat¸iile furnizate de un applet pot fi citite din browserul folosit pentru vizualizare prin metode specifice acestuia. De exemplu, ˆın appletviewer informat¸iile despre parametri pot fi vizualizate la rubrica Info din meniul Applet, ˆın Netscape se folose¸ste opt¸iunea Page info din meniul View, etc. S˘a scriem un applet care s˘a afi¸seze un text primit ca parametru, folosind un font cu numele ¸si dimensiunea specificate de asemenea ca parametri. Listing 14.1: Folosirea parametrilor import java . applet . Applet ; import java . awt .*; public class TestParametri extends Applet String text , numeFont ; int dimFont ; public void init () { text = getParameter ( " textAfisat " ) ; if ( text == null ) text = " Hello " ; // valoare implicita numeFont = getParameter ( " numeFont " ) ; if ( numeFont == null ) numeFont = " Arial " ;
{
410
CAPITOLUL 14. APPLETURI
try { dimFont = Integer . parseInt ( getParameter ( " dimFont " ) ) ; } catch ( Numb erFo r m at Ex ce pt i o n e ) { dimFont = 16; } } public void paint ( Graphics g ) { g . setFont ( new Font ( numeFont , Font . BOLD , dimFont ) ) ; g . drawString ( text , 20 , 20) ; } public String [][] getParameterInfo () { String [][] info = { // Nume Tip Descriere { " textAfisat " , " String " , " Sirul ce va fi afisat " } , { " numeFont " , " String " , " Numele fontului " } , { " dimFont " , " int " , " Dimensiunea fontului " } }; return info ; } }
14.6
Tag-ul APPLET
Sintaxa complet˘a a tagului APPLET, cu ajutorul c˘aruia pot fi incluse appleturi ˆın cadrul paginilor Web este: <APPLET CODE = clasaApplet WIDTH = latimeInPixeli HEIGHT = inaltimeInPixeli [ARCHIVE = arhiva.jar] [CODEBASE = URLApplet] [ALT = textAlternativ] [NAME = numeInstantaApplet] [ALIGN = aliniere] [VSPACE = spatiuVertical]
14.6. TAG-UL APPLET
411
[HSPACE = spatiuOrizontal] > [< PARAM NAME = parametru1 VALUE = valoare1 >] [< PARAM NAME = parametru2 VALUE = valoare2 >] ... [text HTML alternativ] Atributele puse ˆıntre paranteze p˘atrate sunt opt¸ionale. • CODE = clasaApplet Numele fi¸sierului ce cont¸ine clasa principal˘a a appletului. Acesta va fi c˘autat ˆın directorul specificat de CODEBASE. Nu poate fi absolut ¸si trebuie obligatoriu specificat. Extensia ”.class” poate sau nu s˘a apar˘a. • WIDTH =latimeInPixeli, HEIGHT =inaltimeInPixeli Specific˘a l˘a¸timea ¸si ˆın˘alt¸imea suprafet¸ei ˆın care va fi afi¸sat appletul. Sunt obligatorii. • ARCHIVE = arhiva.jar Specific˘a arhiva ˆın care se g˘asesc clasele appletului. • CODEBASE = directorApplet Specific˘a URL-ul la care se g˘ase¸ste clasa appletului. Uzual se exprim˘a relativ la directorul documentului HTML. In cazul ˆın care lipse¸ste, se consider˘a implicit URL-ul documentului. • ALT = textAlternativ Specific˘a textul ce trebuie afi¸sat dac˘a browserul ˆınt¸elege tagul APPLET dar nu poate rula appleturi Java. • NAME =numeInstantaApplet Ofer˘a posibilitatea de a da un nume respectivei instant¸e a appletului, astfel ˆıncˆat mai multe appleturi aflate pe aceea¸si pagin˘a s˘a poat˘a comunica ˆıntre ele folosindu-se de numele lor. • ALIGN =aliniere Semnific˘a modalitatea de aliniere a appletului ˆın pagina Web. Acest atribut poate primi una din urm˘atoarele valori: left, right, top,
412
CAPITOLUL 14. APPLETURI texttop, middle, absmiddle, baseline, bottom, absbottom , seminificat¸iile lor fiind acelea¸si ca ¸si la tagul IMG.
• VSPACE =spatiuVertical, HSPACE = spatiuOrizontal Specific˘a numarul de pixeli dintre applet ¸si marginile suprafetei de afi¸sare. • PARAM Tag-urile PARAM sunt folosite pentru specificarea parametrilor unui applet (vezi ”Folosirea parametrilor”). • text HTML alternativ Este textul ce va fi afi¸sat ˆın cazul ˆın care browserul nu ˆıntelege tagul APPLET. Browserele Java-enabled vor ignora acest text.
14.7
Folosirea firelor de execut¸ie ˆın appleturi
La ˆınc˘arcarea unei pagini Web, fiec˘arui applet ˆıi este creat automat un fir de execut¸ie responsabil cu apelarea metodelor acestuia. Acestea vor rula concurent dup˘a regulile de planificare implementate de ma¸sina virtual˘a Java a platformei folosite. Din punctul de vedere al interfet¸ei grafice ˆıns˘a, fiecare applet aflat pe o pagin˘a Web are acces la un acela¸si fir de execut¸ie, creat de asemenea automat de c˘atre browser, ¸si care este responsabil cu desenarea appletului (apelul metodelor update ¸si paint) precum ¸si cu transmiterea mesajelor generate de c˘atre componente. Intrucˆat toate appleturile de pe pagin˘a ”ˆımpart” acest fir de execut¸ie, nici unul nu trebuie s˘a ˆıl solicite ˆın mod excesiv, deoarece va provoca funct¸ionarea anormal˘a sau chiar blocarea celorlalte. In cazul ˆın care dorim s˘a efectu˘am operat¸iuni consumatoare de timp este recomandat s˘a le realiz˘am ˆıntr-un alt fir de execut¸ie, pentru a nu bloca interact¸iunea utilizatorului cu appletul, redesenarea acestuia sau activitatea celorlalte appleturi de pe pagin˘a. S˘a considerm˘am mai ˆıntˆai dou˘a abord˘ari gre¸site de lucru cu appleturi. Dorim s˘a cre˘am un applet care s˘a afi¸seze la coordonate aleatoare mesajul ”Hello”, cu pauz˘a de o secund˘a ˆıntre dou˘a afi¸s˘ari. Prima variant˘a, gre¸sit˘a de altfel, ar fi:
14.7. FOLOSIREA FIRELOR DE EXECUT ¸ IE ˆIN APPLETURI
413
Listing 14.2: Incorect: blocarea metodei paint import java . applet .*; import java . awt .*; public class AppletRau1 extends Applet { public void paint ( Graphics g ) { while ( true ) { int x = ( int ) ( Math . random () * getWidth () ) ; int y = ( int ) ( Math . random () * getHeight () ) ; g . drawString ( " Hello " , x , y ) ; try { Thread . sleep (1000) ; } catch ( InterruptedE xc ep ti on e ) {} } } }
Motivul pentru care acest applet nu funct¸ioneaz˘a corect ¸si probabil va duce la anomalii ˆın funct¸ionarea browserului este c˘a firul de execut¸ie care se ocup˘a cu desenarea va r˘amˆane blocat ˆın metoda paint, ˆıncercˆand s˘a o termine. Ca regul˘a general˘a, codul metodei paint trebuie s˘a fie cˆat mai simplu de executat ceea ce, evident, nu este cazul ˆın appletul de mai sus. O alt˘a idee de rezolvare care ne-ar putea veni, de asemenea gre¸sit˘a, este urm˘atoarea : Listing 14.3: Incorect: appletul nu termin˘a init¸ializarea import java . applet .*; import java . awt .*; public class AppletRau2 extends Applet { int x , y ; public void init () { while ( true ) { x = ( int ) ( Math . random () * getWidth () ) ; y = ( int ) ( Math . random () * getHeight () ) ; repaint () ; try { Thread . sleep (1000) ; } catch ( InterruptedE x ce pt io n e ) {} } } public void paint ( Graphics g ) {
414
CAPITOLUL 14. APPLETURI g . drawString ( " Hello " , x , y ) ;
} }
Pentru a putea da o solut¸ie corect˘a problemei propuse, trebuie s˘a folosim un fir de execut¸ie propriu. Structura unui applet care doreste s˘a lanseze un fir de execut¸ie poate avea dou˘a forme. In prima situat¸ie appletul porne¸ste firul la init¸ialzarea sa iar acesta va rula, indiferent dac˘a appletul mai este sau nu vizibil, pˆan˘a la oprirea sa natural˘a (terminarea metodei run) sau pˆan˘a la ˆınchiderea sesiunii de lucru a browserului. Listing 14.4: Corect: folosirea unui fir de execut¸ie propriu import java . applet .*; import java . awt .*; public class AppletCorect1 extends Applet implements Runnable { int x , y ; Thread fir = null ; public void init () { if ( fir == null ) { fir = new Thread ( this ) ; fir . start () ; } } public void run () { while ( true ) { x = ( int ) ( Math . random () * getWidth () ) ; y = ( int ) ( Math . random () * getHeight () ) ; repaint () ; try { Thread . sleep (1000) ; } catch ( Inter ru pt ed Ex ce pt io n e ) {} } } public void paint ( Graphics g ) { g . drawString ( " Hello " , x , y ) ; } }
In cazul ˆın care firul de execut¸ie pornit de applet efectueaz˘a operatii ce
14.7. FOLOSIREA FIRELOR DE EXECUT ¸ IE ˆIN APPLETURI
415
au sens doar dac˘a appletul este vizibil, cum ar fi animatie, ar fi de dorit ca acesta s˘a se opreasca atunci cˆand appletul nu mai este vizibil (la apelul metodei stop) ¸si s˘a reporneasca atunci cˆand appletul redevine vizibil (la apelul metodei start). Un applet este considerat activ imediat dup˘a apelul metodei start ¸si devine inactiv la apelul metodei stop. Pentru a afla dac˘a un applet este activ se folose¸ste metoda isActive. S˘a modific˘am programul anterior, ad˘augˆand ¸si un contor care s˘a numere afi¸s˘arile de mesaje - acesta nu va fi incrementat pe perioada ˆın care appletul nu este activ. Listing 14.5: Folosirea metodelor start ¸si stop import java . applet .*; import java . awt .*; public class AppletCorect2 extends Applet implements Runnable { int x , y ; Thread fir = null ; boolean activ = false ; int n = 0; public void start () { if ( fir == null ) { fir = new Thread ( this ) ; activ = true ; fir . start () ; } } public void stop () { activ = false ; fir = null ; } public void run () { while ( activ ) { x = ( int ) ( Math . random () * getWidth () ) ; y = ( int ) ( Math . random () * getHeight () ) ; n ++; repaint () ; try { Thread . sleep (1000) ; } catch ( InterruptedE x c e p t i o n e ) {}
416
CAPITOLUL 14. APPLETURI }
} public void paint ( Graphics g ) { g . drawString ( " Hello " + n , x , y ) ; } }
Atent¸ie Este posibil ca unele browsere s˘a nu apele metoda stop ˆın situat¸iile prev˘azute ˆın specficiat¸iile appleturilor. Din acest motiv, corectitudinea unui applet nu trebuie s˘a se bazeze pa acest mecanism.
14.8
Alte metode oferite de clasa Applet
Pe lˆang˘a metodele de baz˘a: init, start, stop, destroy, clasa Applet ofer˘a metode specifice applet-urilor cum ar fi:
Punerea la dispozitie a unor informat¸ii despre applet Similar˘a cu metoda getParameterInfo ce oferea o ”documentat¸ie” despre parametrii pe care ˆıi accept˘a un applet, exist˘a metoda getAppletInfo ce permite specificarea unor informat¸ii legate de applet cum ar fi numele, autorul, versiunea, etc. Metoda returneaz˘a un sir de caractere continˆand informat¸iile respective. public String getAppletInfo() { return "Applet simplist, autor necunoscut, ver 1.0"; }
Aflarea adreselor URL referitoare la applet Se realizeaz˘a cu metodele:
14.8. ALTE METODE OFERITE DE CLASA APPLET
417
• getCodeBase - ce returneaz˘a URL-ul directorului ce cont¸ine clasa appletului; • getDocumentBase - returneaz˘a URL-ul directorului ce cont¸ine documentul HTML ˆın care este inclus appletul respectiv. Aceste metode sunt foarte utile deoarece permit specificarea relativ˘a a unor fi¸siere folosite de un applet, cum ar fi imagini sau sunete.
Afi¸sarea unor mesaje ˆın bara de stare a browserului Acest lucru se realizeaz˘a cu metoda showStatus public void init() { showStatus("Initializare applet..."); }
Afi¸sarea imaginilor Afi¸sarea imaginilor ˆıntr-un applet se face fie prin intermediul unei componente ce permite acest lucru, cum ar fi o suprafat¸˘a de desenare de tip Canvas, fie direct ˆın metoda paint a applet-ului, folosind metoda drawImage a clasei Graphics. In ambele cazuri, obt¸inerea unei referint¸e la imaginea respectiv˘a se va face cu ajutorul metodei getImage din clasa Applet. Aceasta poate primi ca argument fie adresa URL absolut˘a a fi¸sierului ce reprezint˘a imaginea, fie calea relativ˘a la o anumit˘a adres˘a URL, cum ar fi cea a directorului ˆın care se g˘ase¸ste documentul HTML ce cont¸ine appletul (getDocumentBase) sau a directorului ˆın care se g˘ase¸ste clasa appletului (getCodeBase). Listing 14.6: Afi¸sarea imaginilor import java . applet . Applet ; import java . awt .*; public class Imagini extends Applet Image img = null ;
{
public void init () { img = getImage ( getCodeBase () , " taz . gif " ) ; }
418
CAPITOLUL 14. APPLETURI
public void paint ( Graphics g ) { g . drawImage ( img , 0 , 0 , this ) ; } }
Aflarea contextului de execut¸ie Contextul de execut¸ie al unui applet se refer˘a la pagina ˆın care acesta ruleaz˘a, eventual ˆımpreun˘a cu alte appleturi, ¸si este descris de interfat¸a AppletContext. Crearea unui obiect ce implementeaz˘a aceast˘a interfat¸˘a se realizeaz˘a de c˘atre browser, la apelul metodei getAppletContext a clasei Applet. Prin intermediul acestei interfet¸e un applet poate ”vedea” ˆın jurul sau, putˆand comunica cu alte applet-uri aflate pe aceeasi pagin˘a sau cere browser-ului s˘a deschid˘a diverse documente. AppletContext contex = getAppletContext();
Afi¸sarea unor documente ˆın browser Se face cu metoda showDocument ce prime¸ste adresa URL a fi¸sierului ce cont¸ine documentul pe care dorim sa-l deschidem (text, html, imagine, etc). Aceast˘a metod˘a este accesat˘a prin intermediul contextului de execut¸ie al appletului. try { URL doc = new URL("http://www.infoiasi.ro"); getAppletContext().showDocument(doc); } catch(MalformedURLException e) { System.err.println("URL invalid! \n" + e); }
Comunicarea cu alte applet-uri Aceast˘a comunicare implic˘a de fapt identificarea unui applet aflat pe aceea¸si pagina ¸si apelarea unei metode sau setarea unei variabile publice a acestuia. Identificarea se face prin intermediu numelui pe care orice instant¸a a unui applet ˆıl poate specifica prin atributul NAME.
14.8. ALTE METODE OFERITE DE CLASA APPLET
419
Obt¸inerea unei referint¸e la un applet al c˘arui nume ˆıl cunoa¸stem sau obt¸inerea unei enumer˘ari a tuturor applet-urilor din pagin˘a se fac prin intermediul contextului de execut¸ie, folosind metodele getApplet, respectiv getApplets.
Redarea sunetelor Clasa Applet ofer˘a ¸si posibilitatea red˘arii de sunete ˆın format .au. Acestea sunt descrise prin intermediul unor obiecte ce implementeaz˘a interfat¸a AudioClip din pachetul java.applet. Pentru a reda un sunet aflat ˆıntr-un fi¸sier ”.au” la un anumit URL exist˘a dou˘a posibilit˘a¸ti: • Folosirea metodei play din clasa Applet care prime¸ste ca argument URL-ul la care se afl˘a sunetul; acesta poate fi specificat absolut sau relativ la URL-ul appletului • Crearea unui obiect de tip AudioClip cu metoda getAudioClip apoi apelarea metodelor start, loop ¸si stop pentru acesta.
Listing 14.7: Redarea sunetelor import java . applet .*; import java . awt .*; import java . awt . event .*; public class Sunete extends Applet implements ActionListener { Button play = new Button ( " Play " ) ; Button loop = new Button ( " Loop " ) ; Button stop = new Button ( " Stop " ) ; AudioClip clip = null ; public void init () { // Fisierul cu sunetul trebuie sa fie in acelasi // director cu appletul clip = getAudioClip ( getCodeBase () , " sunet . au " ) ; add ( play ) ; add ( loop ) ; add ( stop ) ; play . addActionListener ( this ) ; loop . addActionListener ( this ) ; stop . addActionListener ( this ) ;
420
CAPITOLUL 14. APPLETURI
} public void actionPerformed ( ActionEvent e ) { Object src = e . getSource () ; if ( src == play ) clip . play () ; else if ( src == loop ) clip . loop () ; else if ( src == stop ) clip . stop () ; } }
In cazul ˆın care appletul folose¸ste mai multe tipuri de sunete, este recomandat ca ˆınc˘arcarea acestora s˘a fie f˘acut˘a ˆıntr-un fir de execut¸ie separat, pentru a nu bloca temporar activitatea fireasc˘a a programului.
14.9
Arhivarea appleturilor
Dup˘a cum am v˘azut, pentru ca un applet aflat pe o pagin˘a Web s˘a poat˘a fi executat codul s˘au va fi transferat de pe serverul care g˘azduie¸ste pagina Web solicitat˘a pe ma¸sina clientului. Deoarece transferul datelor prin ret¸ea este un proces lent, cu cˆat dimensiunea fi¸sierlor care formeaz˘a appletul este mai redus˘a, cu atˆa ˆınc˘arcarea acestuia se va face mai repede. Mai mult, dac˘a appletul cont¸ine ¸si alte clase ˆın afar˘a de cea principal˘a sau diverse resurse (imagini, sunete, etc), acestea vor fi transferate prin ret¸ea abia ˆın momentul ˆın care va fi nevoie de ele, oprind temporar activitatea appletului pˆan˘a la ˆınc˘arcarea lor. Din aceste motive, cea mai eficient˘a modalitate de a distribui un applet este s˘a arhiv˘am toate fi¸sierele necesare acestuia. Arhivarea fi¸sierelor unui applet se face cu utilitarul jar, oferit ˆın distribut¸ia J2SDK. // Exemplu jar cvf arhiva.jar ClasaPrincipala.class AltaClasa.class imagine.jpg sunet.au // sau jar cvf arhiva.jar *.class *.jpg *.au
14.10. RESTRICT ¸ II DE SECURITATE
421
Includerea unui applet arhivat ˆıntr-o pagin˘a Web se realizeaz˘a specific˘and pe lˆang˘a numele clasei principale ¸si numele arhivei care o cont¸ine:
14.10
Restrict¸ii de securitate
Deoarece un applet se execut˘a pe ma¸sina utilizatorului care a solicitat pagina Web ce cont¸ine appletul respectiv, este foarte important s˘a existe anumite restrict¸ii de securitate care s˘a controleze activitatea acestuia, pentru a preveni act¸iuni r˘au intent¸ionate, cum ar fi ¸stergeri de fi¸siere, etc., care s˘a aduc˘a prejudicii utilizatorului. Pentru a realiza acest lucru, procesul care ruleaz˘a appleturi instaleaz˘a un manager de securitate, adic˘a un obiect de tip SecurityManager care va ”superviza” activitatea metodelor appletului, aruncˆand except¸ii de tip Security Exception ˆın cazul ˆın care una din acestea ˆıncearc˘a s˘a efectueze o operat¸ie nepermis˘a. Un applet nu poate s˘a: • Citeasc˘a sau s˘a scrie fi¸siere pe calculatorul pe care a fost ˆıncarcat (client). • Deschid˘a conexiuni cu alte ma¸sini ˆın afar˘a de cea de pe care provine (host). • Porneasc˘a programe pe ma¸sina client. • Citeasc˘a diverse propriet˘a¸ti ale sistemului de operare al clientului. Ferestrele folosite de un applet, altele decˆat cea a browserului, vor ar˘ata altfel decˆat ˆıntr-o aplicat¸ie obi¸snuit˘a, indicˆand faptul c˘a au fost create de un applet.
14.11
Appleturi care sunt ¸si aplicat¸ii
Deoarece clasa Applet este derivat˘a din Container, deci ¸si din Component, ea descrie o suprafat¸˘a de afi¸sare care poate fi inclus˘a ca orice alt˘a component˘a ˆıntr-un alt container, cum ar fi o fereastr˘a. Un applet poate funct¸iona ¸si ca o aplicat¸ie independent˘a astfel:
422
CAPITOLUL 14. APPLETURI
• Ad˘aug˘am metoda main clasei care descrie appletul, ˆın care vom face operat¸iunile urm˘atoare. • Cre˘am o instant¸˘a a appletului ¸si o ad˘aug˘am pe suprafat¸a unei ferestre. • Apel˘am metodele init ¸si start, care ar fi fost apelate automat de c˘atre browser. • Facem fereastra vizibil˘a.
Listing 14.8: Applet ¸si aplicat¸ie import java . applet . Applet ; import java . awt .*; public class AppletAplicatie extends Applet
{
public void init () { add ( new Label ( " Applet si aplicatie " ) ) ; } public static void main ( String args []) { AppletAplicatie applet = new AppletAplicatie () ; Frame f = new Frame ( " Applet si aplicatie " ) ; f . setSize (200 , 200) ; f . add ( applet , BorderLayout . CENTER ) ; applet . init () ; applet . start () ; f . show () ; } }
Capitolul 15 Lucrul cu baze de date 15.1
Introducere
15.1.1
Generalit˘ a¸ti despre baze de date
Aplicat¸iile care folosesc baze de date sunt, ˆın general, aplicat¸ii complexe folosite pentru gestionarea unor informat¸ii de dimensiuni mari ˆıntr-o manier˘a sigur˘a ¸si eficient˘a.
Ce este o baz˘ a de date ? La nivelul cel mai general, o baz˘a de date reprezint˘a o modalitate de stocare a unor informat¸ii (date) pe un suport extern, cu posibilitatea reg˘asirii acestora. Uzual, o baz˘a de date este memorat˘a ˆıntr-unul sau mai multe fi¸siere. Modelul clasic de baze de date este cel relat¸ional, ˆın care datele sunt memorate ˆın tabele. Un tabel reprezint˘a o structur˘a de date format˘a dintr-o mult¸ime de articole, fiecare articol avˆand definite o serie de atribute - aceste atribute corespund coloanelor tabelului, ˆın timp ce o linie va reprezenta un articol. Pe lˆanga tabele, o baz˘a de date mai poate cont¸ine: proceduri ¸si funct¸ii, utilizatori ¸si grupuri de utilizatori, tipuri de date, obiecte, etc. Dintre produc˘atorii cei mai important¸i de baze de date amintim companiile Oracle, Sybase, IBM, Informix, Microsoft, etc. fiecare furnizˆand o serie ˆıntreag˘a de produse ¸si utilitare pentru lucrul cu baze de date. Aceste produse sunt ˆın general referite prin termenii DBMS (Database Management System) sau, ˆın traducere, SGBD (Sistem de Gestiune a Bazelor de Date). In acest capitol vom analiza lucrul cu baze de date din perspectiva program˘arii 423
424
CAPITOLUL 15. LUCRUL CU BAZE DE DATE
ˆın limbajul Java, f˘ar˘a a descrie particularit˘a¸ti ale unei solut¸ii de stocare a datelor anume. Vom vedea c˘a, folosind Java, putem crea aplicat¸ii care s˘a ruleze f˘ar˘a nici o modificare folosind diverse tipuri de baze care au aceea¸si structur˘a, ducˆand ˆın felul acesta not¸iunea de portabilitate ¸si mai departe.
Crearea unei baze de date Crearea unei baze de date se face uzual folosind aplicat¸ii specializate oferite de produc˘atorul tipului respectiv de sistem de gestiune a datelor, dar exist˘a ¸si posibilitatea de a crea o baza folosind un script SQL. Acest aspect ne va preocupa ˆıns˘a mai put¸in, exemplele prezentate presupunˆand c˘a baza a fost creat˘a deja ¸si are o anumit˘a structur˘a specificat˘a.
Accesul la baza de date Se face prin intermediul unui driver specific tipului respectiv de SGBD. Acesta este responsabil cu accesul efectiv la datele stocate, fiind legatura dintre aplicat¸ie ¸si baza de date.
Limbajul SQL SQL (Structured Query Language) reprezint˘a un limaj de programare ce permite interogarea ¸si actualizarea informat¸iilor din baze de date relat¸ionale. Acesta este standardizat astfel ˆıncˆat diverse tipuri de drivere s˘a se comporte identic, oferind astfel o modalitate unitar˘a de lucru cu baze de date.
15.1.2
JDBC
JDBC (Java Database Connectivity) este o interfat¸˘a standard SQL de acces la baze de date. JDBC este constituit˘a dintr-un set de clase ¸si interfet¸e
˘ DE DATE 15.2. CONECTAREA LA O BAZA
425
scrise ˆın Java, furnizˆand mecanisme standard pentru proiectant¸ii aplicat¸iilor ce folosesc de baze de date. Folosind JDBC este u¸sor s˘a transmitem secvent¸e SQL c˘atre baze de date relat¸ionale. Cu alte cuvinte, nu este necesar s˘a scriem un program pentru a accesa o baz˘a de date Oracle, alt program pentru a accesa o baz˘a de date Sybase ¸si asa mai departe. Este de ajuns s˘a scriem un singur program folosind API-ul JDBC ¸si acesta va fi capabil s˘a comunice cu drivere diferite, trimit¸ˆand secvent¸e SQL c˘atre baza de date dorit˘a. Bineˆınt¸eles, scriind codul surs˘a ˆın Java, ne este asigurat˘a portabilitatea programului. Deci, iat˘a dou˘a motive puternice care fac combinat¸ia Java - JDBC demn˘a de luat ˆın seam˘a. Pachetele care ofer˘a suport pentru lucrul cu baze de date sunt java.sql ce reprezint˘a nucleul tehnologiei JDBC ¸si, preluat de pe platforma J2EE, javax.sql. In linii mari, API-ul JDBC ofer˘a urm˘atoarele facilit˘a¸ti: 1. Stabilirea unei conexiuni cu o baz˘a de date. 2. Efectuarea de secvent¸e SQL. 3. Prelucrarea rezultatelor obt¸inute.
15.2
Conectarea la o baz˘ a de date
Procesul de conectare la o baz˘a de date implic˘a efectuarea a dou˘a operat¸ii: 1. Inregistrarea unui driver corespunz˘ator. 2. Realizarea unei conexiuni propriu-zise. Definit¸ie O conexiune (sesiune) la o baz˘a de date reprezint˘a un context prin care sunt trimise secvent¸e SQL ¸si primite rezultate. Intr-o aplicat¸ie pot exista simultan mai multe conexiuni la baze de date diferite sau la aceea¸si baz˘a. Clasele ¸si interfet¸ele responsabile cu realizarea unei conexiuni sunt: • DriverManager - este clasa ce se ocup˘a cu ˆınregistrarea driverelor ce vor fi folosite ˆın aplicat¸ie;
426
CAPITOLUL 15. LUCRUL CU BAZE DE DATE
• Driver - interfat¸a pe care trebuie s˘a o implementeze orice clas˘a ce descrie un driver; • DriverPropertyInfo - prin intermediul acestei clase pot fi specificate diverse propriet˘a¸ti ce vor fi folosite la realizarea conexiunilor; • Connection - descrie obiectele ce modeleaz˘a o conexiune propriu-zis˘a cu baza de date.
15.2.1
Inregistrarea unui driver
Primul lucru pe care trebuie s˘a-l fac˘a o aplicat¸ie ˆın procesul de conectare la o baz˘a de date este s˘a ˆınregistreze la ma¸sina virtual˘a ce ruleaz˘a aplicat¸ia driverul JDBC responsabil cu comunicarea cu respectiva baz˘a de date. Acest lucru presupune ˆınc˘arcarea ˆın memorie a clasei ce implementeaz˘a driver-ul ¸si poate fi realizat˘a ˆın mai multe modalit˘a¸ti. a. Folosirea clasei DriverManager: DriverManager.registerDriver(new TipDriver()); b. Folosirea metodei Class.forName ce apeleaz˘a ClassLoader-ul ma¸sinii virtuale: Class.forName("TipDriver"); Class.forName("TipDriver").newInstance(); c. Setarea propriet˘a¸tii sistem jdbc.drivers, care poate fi realizat˘a ˆın dou˘a feluri: – De la linia de comand˘a: java -Djdbc.drivers=TipDriver Aplicatie – Din program: System.setProperty("jdbc.drivers", "TipDriver"); Folosind aceast˘a metod˘a, specificarea mai multor drivere se face separˆand numele claselor cu punct ¸si virgul˘a. Dac˘a sunt ˆınregistrate mai multe drivere, ordinea de precedent¸˘a ˆın alegerea driverului folosit la crearea unei noi conexiuni este: 1) Driverele ˆınregistrate folosind proprietatea jdbc.drivers la init¸ializarea ma¸sinii virtuale ce va rula procesul. 2) Driverele ˆınregistrate dinamic din aplicat¸ie.
˘ DE DATE 15.2. CONECTAREA LA O BAZA
15.2.2
427
Specificarea unei baze de date
O dat˘a ce un driver JDBC a fost ˆınregistrat, acesta poate fi folosit la stabilirea unei conexiuni cu o baz˘a de date. Avˆand ˆın vedere faptul ca pot exista mai multe drivere ˆınc˘arcate ˆın memorie, trebuie s˘a avem posibilitea de a specifica pe lˆang˘a un identificator al bazei de date ¸si driverul ce trebuie folosit. Aceasta se realizeaz˘a prin intermediul unei adrese specifice, numit˘a JDBC URL, ce are urm˘atorul format: jdbc:sub-protocol:identificator Cˆampul sub-protocol denume¸ste tipul de driver ce trebuie folosit pentru realizarea conexiunii ¸si poate fi odbc, oracle, sybase, db2 ¸si a¸sa mai departe. Identificatorul bazei de date este un indicator specific fiec˘arui driver corespunz˘ator bazei de date cu care aplicat¸ia dore¸ste s˘a interact¸ioneze. In funct¸ie de tipul driver-ului acest identificator poate include numele unei ma¸sini gazd˘a, un num˘ar de port, numele unui fi¸sier sau al unui director, etc., ca ˆın exemplele de mai jos: jdbc:odbc:test jdbc:oracle:[email protected]:1521:test jdbc:sybase:test jdbc:db2:test Subprotocolul odbc este un caz specical, ˆın sensul c˘a permite specificarea ˆın cadrul URL-ului a unor atribute ce vor fi realizate la crearea unei conexiuni. Sintaxa completa subprotocolului odbc este: jdbc:odbc:identificator[;atribut=valoare]* jdbc:odbc:test jdbc:odbc:test;CacheSize=20;ExtensionCase=LOWER jdbc:odbc:test;UID=duke;PWD=java La primirea unui JDBC URL, DriverManager-ul va parcurge lista driverelor ˆınregistrate ˆın memorie, pˆana cˆand unul dintre ele va recunoa¸ste URL-ul respectiv. Dac˘a nu exista nici unul potrivit, atunci va fi lansata o except¸ie de tipul SQLException, cu mesajul "no suitable driver".
428
15.2.3
CAPITOLUL 15. LUCRUL CU BAZE DE DATE
Tipuri de drivere
Tipurile de drivere existente ce pot fi folosite pentru realizarea unei conexiuni prin intermediul JDBC se ˆımpart ˆın urm˘atoarele categorii:
Tip 1. JDBC-ODBC Bridge
Acest tip de driver permite conectarea la o baz˘a de date care a fost ˆınregistrat˘a ˆın prealabil ˆın ODBC. ODBC (Open Database Conectivity) reprezint˘a o modalitate de a uniformiza accesul la baze de date, asociind acestora un identificator DSN (Data Source Name) ¸si diver¸si parametri necesari conect˘arii. Conectarea efectiv˘a la baza de date se va face prin intermediul acestui identificator, driver-ul ODBC efectuˆand comunicarea cu driverul nativ al bazei de date. De¸si simplu de utilizat, solut¸ia JDBC-ODBC nu este portabil˘a ¸si comunicarea cu baza de date sufer˘a la nivelul vitezei de execut¸ie datorit˘a multiplelor redirect˘ari ˆıntre drivere. De asemenea, atˆat ODBC-ul cˆat ¸si driver-ul nativ trebuie s˘a existe pe ma¸sina pe care ruleaz˘a aplicat¸ia. Clasa Java care descrie acest tip de driver JDBC este: sun.jdbc.odbc.JdbcOdbcDriver ¸si este inclus˘a ˆın distribut¸ia standard J2SDK. Specificarea bazei de date se face printr-un URL de forma: jdbc:odbc:identificator unde identif icator este profilul (DSN) creat bazei de date ˆın ODBC.
Tip 2. Driver JDBC - Driver nativ
˘ DE DATE 15.2. CONECTAREA LA O BAZA
429
Acest tip de driver transform˘a cererile JDBC direct ˆın apeluri c˘atre driverul nativ al bazei de date, care trebuie instalat ˆın prealabil. Clase Java care implementeaz˘a astfel de drivere pot fi procurate de la produc˘atorii de SGBD-uri, distribut¸ia standard J2SDK neincluzˆand nici unul.
Tip 3. Driver JDBC - Server
Acest tip de driver transform˘a cererile JDBC folosind un protocol de ret¸ea independent, acestea fiind apoi transormate folosind o aplicat¸ie server ˆıntr-un protocol specfic bazei de date. Introducerea serverului ca nivel intermediar aduce flexibilitate maxim˘a ˆın sensul c˘a vor putea fi realizate conexiuni cu diferite tipuri de baze, f˘ar˘a nici o modificare la nivelul clientului. Protocolul folosit este specific fiec˘arui produc˘ator.
Tip 4. Driver JDBC nativ
430
CAPITOLUL 15. LUCRUL CU BAZE DE DATE
Acest tip de driver transform˘a cererile JDBC direct ˆın cereri c˘atre baza de date folosind protocolul de ret¸ea al acesteia. Aceast˘a solut¸ie este cea mai rapid˘a, fiind preferat˘a la dezvoltarea aplicat¸iilor care manevreaz˘a volume mari de date ¸si viteza de execut¸ie este critic˘a. Drivere de acest tip pot fi procurate de la diver¸si produc˘atori de SGBD-uri.
15.2.4
Realizarea unei conexiuni
Metoda folosit˘a pentru realizarea unei conexiuni este getConnection din clasa DriverManager ¸si poate avea mai multe forme: Connection c = DriverManager.getConnection(url); Connection c = DriverManager.getConnection(url, username, password); Connection c = DriverManager.getConnection(url, dbproperties); Stabilirea unei conexiuni folosind driverul JDBC-ODBC String url = "jdbc:odbc:test" ; // sau url = "jdbc:odbc:test;UID=duke;PWD=java" ; try { Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); } catch(ClassNotFoundException e) { System.err.print("ClassNotFoundException: " + e) ; return ; } Connection con ; try { con = DriverManager.getConnection(url, "duke", "java"); } catch(SQLException e) { System.err.println("SQLException: " + e);
15.3. EFECTUAREA DE SECVENT ¸ E SQL
431
} finally { try{ con.close ; } catch(SQLException e) { System.err.println(SQLException: " + e) ; } } Stabilirea unei conexiuni folosind un driver MySql Folosirea diferitelor tipuri de drivere implic˘a doar schimbarea numelui clasei ce reprezint˘a driverul ¸si a modalit˘a¸tii de specificare a bazei de date. String url = "jdbc:mysql://localhost/test" ; // sau url = "jdbc:mysql://localhost/test?user=duke&password=java"; try { Class.forName("com.mysql.jdbc.Driver") ; } catch(ClassNotFoundException e) { ... O conexiune va fi folosit˘a pentru: • Crearea de secvent¸e SQL utilizate pentru interogarea sau actualizarea bazei. • Aflarea unor informat¸ii legate de baza de date (meta-date). De asemenea, clasa Connection asigur˘a facilit˘a¸ti pentru controlul tranzact¸iilor din memorie c˘atre baza de date prin metodele commit, rollback, setAutoCommit. Inchiderea unei conexiuni se realizeaz˘a prin metoda close.
15.3
Efectuarea de secvent¸e SQL
O dat˘a facut˘a conectarea cu metoda DriverManager.getConection, se poate folosi obiectul Connection rezultat pentru a se crea obiecte de tip Statement,PreparedStatement sau CallableStatement cu ajutorul c˘arora putem trimite secvent¸e SQL c˘atre baza de date. Cele mai uzuale comenzi SQL sunt cele folosite pentru: • Interogarea bazei de date: SELECT
432
CAPITOLUL 15. LUCRUL CU BAZE DE DATE
• Actualizarea datelor: INSERT, UPDATE, DELETE • Actualizarea structurii: CREATE, ALTER, DROP - acestea mai sunt numite instruct¸iuni DDL (Data Definition Language) • Apelarea unei proceduri stocate: CALL Dup˘a cum vom vedea, obt¸inerea ¸si prelucrarea rezultatelor unei interog˘ari este realizat˘a prin intermediul obiectelor de tip ResultSet.
15.3.1
Interfat¸a Statement
Interfat¸a Statement ofer˘a metodele de baz˘a pentru trimiterea de secvent¸e SQL c˘atre baza de date ¸si obt¸inerea rezultatelor, celelalte dou˘a interfet¸e: PreparedStatement ¸si CallableStatement fiind derivate din aceasta. Crearea unui obiect Statement se realizeaz˘a prin intermediul metodei createStatement a clasei Connection, f˘ar˘a nici un argument: Connection con = DriverManager.getConnection(url); Statement stmt = con.createStatement(); Execut¸ia unei secvent¸e SQL poate fi realizat˘a prin intermediul a trei metode:
1. executeQuery Este folosit˘a pentru realizarea de interog˘ari de tip SELECT. Metoda returneaz˘a un obiect de tip ResultSet ce va cont¸ine sub o form˘a tabelar˘a rezultatul interog˘arii. String sql = "SELECT * FROM persoane"; ResultSet rs = stmt.executeQuery(sql);
2. executeUpdate Este folosit˘a pentru actualizarea datelor (INSERT, UPDATE, DELETE) sau a structurii bazei de date (CREATE, ALTER, DROP). Metoda va returna un ˆıntreg ce semnific˘a num˘arul de linii afectate de operat¸iunea de actualizare a datelor, sau 0 ˆın cazul unei instruct¸iuni DDL.
15.3. EFECTUAREA DE SECVENT ¸ E SQL
433
String sql = "DELETE FROM persoane WHERE cod > 100"; int linii = stmt.executeUpdate(sql); // Nr de articole care au fost afectate (sterse) sql = "DROP TABLE temp"; stmt.executeUpdate(sql); // returneaza 0
3. execute Aceast˘a metod˘a va fi folosit˘a doar dacˆa este posibil ca rezultatul unei interog˘ari s˘a fie format din dou˘a sau mai multe obiecte de tip ResultSet sau rezultatul unei actualiz˘ari s˘a fie format din mai mule valori, sau o combinat¸ie ˆıntre aceste cazuri. Aceast˘a situat¸ie, de¸si mai rar˘a, este posibil˘a atunci cˆand sunt executate proceduri stocate sau secvent¸e SQL cunoscute abia la momentul execut¸iei, programatorul ne¸stiind deci dac˘a va fi vorba de o actualizare a datelor sau a structurii. Metoda ˆıntoarce true dac˘a rezultatul obt¸inut este format din obiecte de tip ResultSet ¸si false dac˘a e format din ˆıntregi. In funct¸ie de aceasta, pot fi apelate metodele: getResultSet sau getUpdateCount pentru a afla efectiv rezultatul comenzii SQL. Pentru a prelua toate rezultatele va fi apelat˘a metoda getMoreResults, dup˘a care vor fi apelate din nou metodele amintite, pˆan˘a la obt¸inerea valorii null, respectiv −1. Secvent¸a complet˘a de tratare a metodei execute este prezentat˘a mai jos: String sql = "comanda SQL necunoscuta"; stmt.execute(sql); while(true) { int rowCount = stmt.getUpdateCount(); if(rowCount > 0) { // Este o actualizare datelor System.out.println("Linii afectate = " + rowCount); stmt.getMoreResults(); continue; } if(rowCount = 0) { // Comanda DDL sau nici o linie afectata System.out.println("Comanda DDL sau 0 actualizari");
434
CAPITOLUL 15. LUCRUL CU BAZE DE DATE stmt.getMoreResults(); continue; } // rowCount este -1 // Avem unul sau mai multe ResultSet-uri ResultSet rs = stmt.getResultSet(); if(rs != null) { // Proceseaza rezultatul ... stmt.getMoreResults(); continue; } // Nu mai avem nici un rezultat break;
} Folosind clasa Statement, ˆın cazul ˆın care dorim s˘a introducem valorile unor variabile ˆıntr-o secvent¸˘a SQL, nu avem alt˘a solut¸ie decˆat s˘a cre˘am un ¸sir de caractere compus din instruct¸iuni SQL ¸si valorile variabilelor: int cod = 100; String nume = "Popescu"; String sql = "SELECT * FROM persoane WHERE cod=" + cod + " OR nume=’" + nume + "’"; ResultSet rs = stmt.executeQuery(sql);
15.3.2
Interfat¸a PreparedStatement
Interfat¸a PreparedStatement este derivat˘a din Statement, fiind diferit˘a de aceasta ˆın urm˘atoarele privint¸e: • Instant¸ele de tip PreparedStatement cont¸in secvent¸e SQL care au fost deja compilate (sunt ”preg˘atite”). • O secvent¸˘a SQL specificat˘a unui obiect PreparedStatement poate s˘a aib˘a unul sau mai mult¸i parametri de intrare, care vor fi specificat¸i prin intermediul unui semn de ˆıntrebare (”?”) ˆın locul fiec˘aruia dintre
15.3. EFECTUAREA DE SECVENT ¸ E SQL
435
ei. Inainte ca secvent¸a SQL s˘a poat˘a fi executat˘a fiec˘arui parametru de intrare trebuie s˘a i se atribuie o valoare, folosind metode specifice acestei clase. Execut¸ia repetat˘a a aceleia¸si secvent¸e SQL, dar cu parametri diferit¸i, va fi ˆın general mai rapid˘a dac˘a folosim PreparedStatement, deoarece nu mai trebuie s˘a cre˘am cˆate un obiect de tip Statement pentru fiecare apel SQL, ci refolosim o singur˘a instant¸˘a precompilat˘a furnizˆandu-i doar alte argumente. Crearea unui obiect de tip PreparedStatement se realizeaz˘a prin intermediul metodei prepareStatement a clasei Connection, specificˆan ca argument o secvent¸˘a SQL ce cont¸ine c˘ate un semn de ˆıntrebare pentru fiecare parametru de intrare: Connection con = DriverManager.getConnection(url); String sql = "UPDATE persoane SET nume=? WHERE cod=?"; Statement pstmt = con.prepareStatement(sql); Obiectul va pstmt cont¸ine o comand˘a SQL precompilat˘a care este trimis˘a imediat c˘atre baza de date, unde va a¸stepta parametri de intrare pentru a putea fi executat˘a. Trimiterea parametrilor se realizeaz˘a prin metode de tip setXXX, unde XXX este tipul corespunz˘ator parametrului, iar argumentele metodei sunt num˘arul de ordine al parametrului de intrare (al semnului de ˆıntrebare) ¸si valoarea pe care dorim s˘a o atribuim. pstmt.setString(1, "Ionescu"); pstmt.setInt(2, 100); Dup˘a stabilirea parametrilor de intrare secvent¸a SQL poate fi executat˘a. Putem apoi stabili alte valori de intrare ¸si refolosi obiectul PreparedStatement pentru execut¸ii repetate ale comenzii SQL. Este ˆıns˘a posibil ca SGBD-ul folosit s˘a nu suporte acest tip de operat¸iune ¸si s˘a nu ret¸in˘a obiectul precompilat pentru execut¸ii ulterioare. In aceast˘a situat¸ie folosirea interfet¸ei PreparedStatement ˆın loc de Statement nu va ˆımbun˘at˘a¸ti ˆın nici un fel performant¸a codului, din punctul de vedere al vitezei de execut¸ie a acestuia. Execut¸ia unei secvent¸e SQL folosind un obiect PreparedStatement se realizeaz˘a printr-una din metodele executeQuery, executeUpdate sau execute, semnificat¸iile lor fiind acelea¸si ca ¸si ˆın cazul obiectelor de tip Statement, cu singura deosebire c˘a ˆın cazul de fat¸˘a ele nu au nici un argument.
436
CAPITOLUL 15. LUCRUL CU BAZE DE DATE
String sql = "UPDATE persoane SET nume=? WHERE cod=?"; Statement pstmt = con.prepareStatement(sql); pstmt.setString(1, "Ionescu"); pstmt.setInt(2, 100); pstmt.executeUpdate(); pstmt.setString(1, "Popescu"); pstmt.setInt(2, 200); pstmt.executeUpdate(); sql = "SELECT * from persoane WHERE cod >= ?"; pstmt = con.prepareStatement(sql); pstmt.setInt(1, 100); ResultSet rs = pstmt.executeQuery(); Fiec˘arui tip Java ˆıi corespunde un tip generic SQL. Este responsabilitatea programatorului s˘a se asigure c˘a folose¸ste metoda adecvat˘a de tip setXXX la stabilirea valorii unui parametru de intrare. Lista tuturor tipurilor generice disponibile, numite ¸si tipuri JDBC, este definit˘a de clasa Types, prin constantelor declarate de aceasta. Metoda setObject permite specificarea unor valori pentru parametrii de intrare, atunci cˆand dorim s˘a folosim maparea implicit˘a ˆıntre tipurile Java ¸si cele JDBC sau atunci cˆand dorim s˘a preciz˘am explicit un tip JDBC. pstmt.setObject(1, "Ionescu", Types.CHAR); pstmt.setObject(2, 100, Types.INTEGER); // sau doar pstmt.setObject(2, 100); Folosind metoda setNull putem s˘a atribuim unui parametru de intrare valoare SQL NULL, trebuind ˆıns˘a s˘a specific˘am ¸si tipul de date al coloanei ˆın care vom scrie aceast˘a valoare. Acela¸si lucru poate fi realizat cu metode de tipul setXXX dac˘a argumentul folosit are valoarea null. pstmt.setNull(1, Types.CHAR); pstmt.setInt(2, null); Cu ajutorul metodelor setBytes sau setString avem posibilitatea de a specifica date de orice dimensiuni ca valori pentru anumite articole din baza de date. Exist˘a ˆıns˘a situat¸ii cˆand este de preferat ca datele de mari dimensiuni s˘a fie transferate pe ”buc˘a¸ti” de o anumit˘a dimensiune. Pentru a realiza
15.3. EFECTUAREA DE SECVENT ¸ E SQL
437
acest lucru API-ul JDBC pune la dispozit¸ie metodele setBinaryStream, setAsciiStream ¸si setUnicodeStream care ata¸seaz˘a un flux de intrare pe octet¸i, caractere ASCII, respectiv UNICODE, unui parametru de intrare. Pe m˘asur˘a ce sunt citite date de pe flux, ele vor fi atribuite parametrului. Exemplul de mai jos ilustreaz˘a acest lucru, atribuind coloanei continut cont¸inutul unui anumit fi¸sier: File file = new File("date.txt"); int fileLength = file.length(); InputStream fin = new FileInputStream(file); java.sql.PreparedStatement pstmt = con.prepareStatement( "UPDATE fisiere SET continut = ? WHERE nume = ’date.txt’"); pstmt.setUnicodeStream (1, fin, fileLength); pstmt.executeUpdate(); La execut¸ia secvent¸ei, fluxul de intrare va fi apelat repetat pentru a furniza datele ce vor fi scrise ˆın coloana continut a articolului specificat. Observat¸i c˘a este necesar ˆın˘a s˘a ¸stim dinainte dimensiunea datelor ce vor fi scrise, acest lucru fiind solicitat de unele tipuri de baze de date.
15.3.3
Interfat¸a CallableStatement
Interfat¸a CallableStatement este derivat˘a din PreparedStatement, instant¸ele de acest tip oferind o modalitate de a apela o procedur˘a stocat˘a ˆıntr-o baz˘a de date, ˆıntr-o manier˘a standar pentru toate SGBD-urile. Crearea unui obiect CallableStatement se realizeaz˘a prin metoda prepareCall a clasei Connection: Connection con = DriverManager.getConnection(url); CallableStatement cstmt = con.prepareCall( "{call proceduraStocata(?, ?)}"); Trimiterea parametrilor de intrare se realizeaz˘a ˆıntocmai ca la PreparedStatement, cu metode de tip setXXX. Dac˘a procedura are ¸si parametri de ie¸sire (valori returnate), ace¸stia vor trebui ˆınregistrat¸i cu metoda registerOutParameter ˆınainte de execut¸ia procedurii. Obt¸inerea valorilor rezultate ˆın parametrii de ie¸sie se va face cu metode de tip getXXX. CallableStatement cstmt = con.prepareCall(
438
CAPITOLUL 15. LUCRUL CU BAZE DE DATE
"{call calculMedie(?)}"); cstmt.registerOutParameter(1, java.sql.Types.FLOAT); cstmt.executeQuery(); float medie = cstmt.getDouble(1); Este posibil ca un parametru de intrare s˘a fie ¸si parametru de ie¸sire. In acest caz el trebuie s˘a primeasc˘a o valoare cu setXXX ¸si, de asemenea, va fi ˆınregistrat cu registerOutParameter, tipurile de date specificate trebuind s˘a coincid˘a.
15.3.4
Obt¸inerea ¸si prelucrarea rezultatelor
15.3.5
Interfat¸a ResultSet
In urma execut¸ie unei interog˘ari SQL rezultatul va fi reprezentat printr-un obiect de tip ResultSet, ce va cont¸ine toate liniile ce satisfac condit¸iile impuse de comanda SQL. Forma general˘a a unui ResultSet este tabelar˘a, avˆand un num˘ar de coloane ¸si de linii, funct¸ie de secvent¸a executat˘a. De asemenea, obiectul va cont¸ine ¸si meta-datele interog˘arii cum ar fi denumirele coloanelor selectate, num˘arul lor, etc. Statement stmt = con.createStatement(); String sql = "SELECT cod, nume FROM persoane"; ResultSet rs = stmt.executeQuery(sql); Rezultatul interog˘arii de mai sus va fi obiectul rs cu urm˘atoarea structur˘a: cod nume 100 Ionescu 200 Popescu Pentru a extrage informat¸iile din aceast˘a structur˘a va trebui s˘a parcurgem tabelul linie cu linie ¸si din fiecare s˘a extragem valorile de pe coloane. Pentru acest lucru vom folosi metode de tip getXXX, unde XXX este tipul de dat˘a al unei coloane iar argumentul primit indic˘a fie num˘arul de ordine din cadrul tabelului, fie numele acestuia. Coloanele sunt numerotate de la stˆanga la dreapta, ˆıncepˆand cu 1. In general, folosirea indexului coloanei ˆın loc de numele s˘au va fi mai eficient˘a. De asemenea, pentru maxim˘a portabilitate se recomand˘a citirea coloanelor ˆın ordine de la stˆanga la dreapta ¸si fiecare citire s˘a se fac˘a o singur˘a dat˘a.
15.3. EFECTUAREA DE SECVENT ¸ E SQL
439
Un obiect ResultSet folose¸ste un cursor pentru a parcurge articolele rezultate ˆın urma unei interog˘ari. Init¸ial acest cursor este pozit¸ionat ˆınaintea primei linii, fiecare apel al metodei next determinˆand trecerea la urm˘atoarea linie. Deoarece next returneaz˘a false cˆand nu mai sunt linii de adus, uzual va fi folosit˘a o bucl˘a while-loop petru a itera prin articolele tabelului: String sql = "SELECT cod, nume FROM persoane"; ResultSet rs = stmt.executeQuery(sql); while (rs.next()) { int cod = r.getInt("cod"); String nume = r.getString("nume"); /* echivalent: int cod = r.getInt(1); String nume = r.getString(2); */ System.out.println(cod + ", " + nume); } Implicit, un tabel de tip ResultSet nu poate fi modificat iar cursorul asociat nu se deplaseaz˘a decˆat ˆınainte, linie cu linie. A¸sadar, putem itera prin rezultatul unei interog˘ari o singur˘a dat˘a ¸si numai de la prima la ultima linie. Este ˆıns˘a posibil s˘a cre˘am ResultSet-uri care s˘a permit˘a modificarea sau deplasarea ˆın ambele sensuri. Exemplul urm˘ator va folosi un cursor care este modificabil ¸si nu va reflecta schimb˘arile produse de alt¸i utilizatori dup˘a crearea sa: Statement stmt = con.createStatement( ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE); String sql = "SELECT cod, nume FROM persoane"; ResultSet rs = stmt.executeQuery(sql); Dac˘a un ResultSet folose¸ste un cursor modificabil ¸si care poate naviga ˆın ambele sensuri, atunci are la dispozit¸ie o serie de metode ce se bazeaz˘a pe acest suport: • absolute - Deplaseaz˘a cursorul la o anumit˘a linie specificat˘a absolut; • updateXXX - Actualizeaz˘a valoarea unei coloane din linia curent˘a, unde XXX este un tip de date.
440
CAPITOLUL 15. LUCRUL CU BAZE DE DATE
• updateRow - Transfer˘a actualiz˘arile f˘acute liniei ˆın baza de date. • moveToInsertRow - deplaseaz˘a cursorul la o linie spceial˘a, numit˘a linie nou˘a, utilizate˘a pentru a introduce noi articole ˆın baza de date. Linia curent˘a anterioar˘a a cursorului va fi memorat˘a pentru a se putea reveni la ea. • insertRow - insereaz˘a articolul din zona linie nou˘ a ˆın baza de date; cursorul trebuie s˘a fie pozit¸ionat le linia nou˘a la execut¸ia acestei operat¸iuni. • moveToCurrentRow - revine la linia curent˘a din tabel. • deleteRow - ¸sterge linia curent˘a din tabel ¸si din baza de date; nu poate fi apelat˘a cˆand cursorul este ˆın modul linie nou˘ a. Nu toate sistemele de gestiune a bazelor de date ofer˘a suport pentru folosirea cursoarelor care pot fi modificate. Pentru a determina dac˘a baza de date permite acest lucru pot fi utilizate metodele supportsPositionedUpdate ¸si supportsPositionedDelete ale clasei DatabaseMetaData. In cazul ˆın care acest lucru este permis, este responsabilitatea driver-ului bazei de date s˘a asigure rezolvarea problemelor legate de actualizarea concurent˘a a unui cursor, astfel ˆıncˆat s˘a nu apar˘a anomalii.
15.3.6
Exemplu simplu
In continuare vom da un exemplul simplu de utilizare a claselor de baz˘a ment¸ionate anterior. Programul va folosi o baz˘a de date MySql, ce cont¸ine un tabel numit persoane, avˆand coloanele: cod, nume ¸si salariu. Scriptul SQL de creare a bazei este: create table persoane(cod integer, nume char(50), salariu double); Aplicat¸ia va goli tabelul cu persoane, dup˘a care va ad˘auga aleator un num˘ar de articole, va efectua afi¸sarea lor ¸si calculul mediei salariilor. Listing 15.1: Exemplu simplu de utilzare JDBC import java . sql .*; public class TestJdbc { public static void main ( String [] args ) {
15.3. EFECTUAREA DE SECVENT ¸ E SQL
441
String url = " jdbc : mysql :// localhost / test " ; try { Class . forName ( " com . mysql . jdbc . Driver " ) ; } catch ( Cl as sNo tF oun dE x c e p t i o n e ) { System . out . println ( " Eroare incarcare driver !\ n " + e ) ; return ; } try { Connection con = DriverManager . getConnection ( url ) ; // Golim tabelul persoane String sql = " DELETE FROM persoane " ; Statement stmt = con . createStatement () ; stmt . executeUpdate ( sql ) ; // Adaugam un numar de persoane generate aleator // Tabelul persoane are coloanele ( cod , nume , salariu ) int n = 10; sql = " INSERT INTO persoane VALUES (? , ? , ?) " ; PreparedStatement pstmt = con . prepareStatement ( sql ) ; for ( int i =0; i < n ; i ++) { int cod = i ; String nume = " Persoana " + i ; double salariu = 100 + Math . round ( Math . random () * 900) ; // salariul va fi intre 100 si 1000 pstmt . setInt (1 , cod ) ; pstmt . setString (2 , nume ) ; pstmt . setDouble (3 , salariu ) ; pstmt . executeUpdate () ; } // Afisam persoanele ordonate dupa salariu sql = " SELECT * FROM persoane ORDER BY salariu " ; ResultSet rs = stmt . executeQuery ( sql ) ; while ( rs . next () ) System . out . println ( rs . getInt ( " cod " ) + " , " + rs . getString ( " nume " ) + " , " + rs . getDouble ( " salariu " ) ) ;
// Calculam salariul mediu sql = " SELECT avg ( salariu ) FROM persoane " ; rs = stmt . executeQuery ( sql ) ; rs . next () ;
442
CAPITOLUL 15. LUCRUL CU BAZE DE DATE System . out . println ( " Media : " + rs . getDouble (1) ) ; // Inchidem conexiunea con . close () ; } catch ( SQLException e ) { e . printStackTrace () ; }
} }
15.4
Lucrul cu meta-date
15.4.1
Interfat¸a DatabaseMetaData
Dup˘a realizarea unui conexiuni la o baz˘a de date, putem apela metoda getMetaData pentru a afla diverse informat¸ii legate de baza respectiv˘a, a¸sa numitele meta-date (”date despre date”); Ca rezult al apelului metodei, vom obt¸ine un obiect de tip DatabaseMetaData ce ofer˘a metode pentru determinarea tabelelor, procedurilor stocate, capabilit˘a¸tilor conexiunii, gramaticii SQL suportate, etc. ale bazei de date. Programul urm˘ator afi¸seaz˘a numele tuturor tabelelor dintr-o baz˘a de dat ˆınregistrat˘a ˆın ODBC. Listing 15.2: Folosirea interfet¸ei DatabaseMetaData import java . sql .*; public class TestMetaData { public static void main ( String [] args ) { String url = " jdbc : odbc : test " ; try { Class . forName ( " sun . jdbc . odbc . JdbcOdbcDriver " ) ; } catch ( Cl as sNo t F o u n d E x c e p t i o n e ) { System . out . println ( " Eroare incarcare driver !\ n " + e ) ; return ; } try { Connection con = DriverManager . getConnection ( url ) ; DatabaseMetaData dbmd = con . getMetaData () ; ResultSet rs = dbmd . getTables ( null , null , null , null ) ;
15.4. LUCRUL CU META-DATE
443
while ( rs . next () ) System . out . println ( rs . getString ( " TABLE_NAME " ) ) ; con . close () ; } catch ( SQLException e ) { e . printStackTrace () ; } } }
15.4.2
Interfat¸a ResultSetMetaData
Meta-datele unui ResultSet reprezint˘a informat¸iile despre rezultatul cont¸inut ˆın acel obiect cum ar fi num˘arul coloanelor, tipul ¸si denumirile lor, etc. Acestea sunt obt¸inute apelˆand metoda getMetaData pentru ResultSet-ul respectiv, care va returna un obiect de tip ResultSetMetaData ce poate fi apoi folosit pentru extragerea informat¸iilor dorite. ResultSet rs = stmt.executeQuery("SELECT * FROM tabel"); ResultSetMetaData rsmd = rs.getMetaData(); // Aflam numarul de coloane int n = rsmd.getColumnCount(); // Aflam numele coloanelor Sring nume[] = new String[n+1]; for(int i=1; i<=n; i++) nume[i] = rsmd.getColumnName(i);
444
CAPITOLUL 15. LUCRUL CU BAZE DE DATE
Capitolul 16 Lucrul dinamic cu clase 16.1
Inc˘ arcarea claselor ˆın memorie
Dup˘a cum ¸stim execut¸ia unei aplicat¸ii Java este realizat˘a de c˘atre ma¸sina virtual˘a Java (JVM), aceasta fiind responsabil˘a cu interpretarea codului de octet¸i rezultat ˆın urma compil˘arii. Spre deosebire de alte limbaje de programare cum ar fi C sau C++, un program Java compilat nu este descris de un fi¸sier executabil ci de o mult¸ime de fi¸siere cu extensia .class corespunz˘atoare fiec˘arei clase a programului. In plus, aceste clase nu sunt ˆın˘arcate toate ˆın memorie la pornirea aplicat¸iei, ci sunt ˆın˘arcate pe parcursul execut¸ie acesteia atunci cˆand este nevoie de ele, momentul efectiv ˆın care se realizeaz˘a acest lucru depinzˆand de implementarea ma¸sinii virtuale. Ciclul de viat¸˘a al unei clase are a¸sadar urm˘atoarele etape: 1. Inc˘arcarea - Este procesul reg˘asirii reprezent˘arii binare a unei clase (fi¸sierul .class) pe baza numelui complet al acestuia ¸si ˆınc˘arcarea acesteia ˆın memorie. In urma acestui proces, va fi instant¸iat un obiect de tip java.lang.Class, corespunz˘ator clasei respective. Operat¸iunea de ˆınc˘arcare a unei clase este realizat˘a la un moment ce precede prima utilizare efectiv˘a a sa. 2. Editarea de leg˘aturi - Specific˘a incorporarea noului tip de date ˆın JVM pentru a putea fi utlizat. 3. Init¸ializarea - Const˘a ˆın execut¸ia blocurilor statice de init¸ializare ¸si init¸ializarea variabilelor de clas˘a. 445
446
CAPITOLUL 16. LUCRUL DINAMIC CU CLASE
4. Desc˘arcarea - Atunci cˆand nu mai exist˘a nici o referint¸˘a de tipul clasei respective, obiectul de tip Class creat va fi marcat pentru a fi eliminat din memorie de c˘atre garbage collector. Inc˘arcarea claselor unei aplicat¸ii Java ˆın memorie este realizat˘a prin intermediul unor obiecte pe care le vom numi generic class loader. Acestea sunt de dou˘a tipuri: 1. Class loader-ul primordial (eng. bootstrap) - Reprezint˘a o parte integrant˘a a ma¸sinii virtuale, fiind responsabil cu ˆınc˘arcarea claselor standard din distribut¸ia Java. 2. Class loader-e proprii - Acestea nu fac parte intrinsec˘a din JVM ¸si sunt instant¸e ale clasei java.lang.ClassLoader. Aceasta este o clas˘a abstract˘a, tipul efectiv al obiectului fiind a¸sadar derivat din aceasta. Dup˘a cum vom vedea, la execut¸ia unui program Java vor fi create implicit dou˘a obiecte de tip ClassLoader pentru ˆınc˘arcarea ˆın memorei a claselor proprii ale aplicat¸iei. Exist˘a ˆıns˘a posibilitarea de a crea noi tipuri derivate din ClassLoader specializate pentru ˆınc˘arcarea claselor conform unor specificat¸ii anume care s˘a realizeze diverse optimiz˘ari. Astfel, ˆınc˘arcarea unei clase poate determina ˆınc˘arcarea unor altor clase care sigur vor fi folosite ˆımpreun˘a cu prima, sau a unor resurse ce sunt necesare funct¸ion˘arii acesteia, etc.
Incepˆand cu versiunea 1.2 de Java, a fost introdus un model de tip delegat, ˆın care class loader-ele sunt dispuse ierarhic ˆıntr-un arbore, r˘ad˘acina acestuia fiind class loader-ul primordial. Fiecare instant¸a de tip ClassLoader va avea a¸sadar un p˘arinte (evident, mai put¸in r˘ad˘acina), acesta fiind specificat la crearea sa. In momentul cˆand este solicitat˘a ˆınc˘arcarea unei clase, un classloader poate delega ˆın primul rˆand operat¸iunea de ˆınc˘arcare p˘arintelui s˘au care va delega la rˆandul s˘au cererea mai departe pˆan˘a la class loader-ul primordial sau pˆan˘a unul din ace¸stia reu¸se¸ste s˘a o ˆıncarce. Abia ˆın cazul ˆın care nici unul din ace¸stia nu a reu¸sit, va ˆıncerca s˘a execute operat¸iunea de ˆınc˘arcare a clasei. Dac˘a nici ea nu va reu¸si, va fi aruncat˘a o except¸ie de tipul ClassNotFoundException. De¸si acest comportament nu este obligatoriu, ˆın multe situat¸ii el este de preferat, pentru a minimiza ˆınc˘arcarea aceleia¸si clase de mai multe ori, folosind class loader-e diferite.
˘ 16.1. INCARCAREA CLASELOR ˆIN MEMORIE
447
Implicit, Java 2 JVM ofer˘a trei class loader-e, unul primordial ¸si dou˘a proprii, cunoscute sub numele de: • Boostrap Class Loader - Class loader-ul primordial. Acesta este responsabil cu ˆınc˘arcarea claselor din distribut¸ia Java standard (cele din pachetele java.*, javax.*, etc.). • Extension Class Loader - Utilizat pentru ˆınc˘arcarea claselor din directoarele extensiilor JRE. • System Class Loader - Acesta este responsabil cu ˆınc˘arcarea claselor proprii aplicat¸iilor Java (cele din CLASSPATH). Tipul acestuia este java.lang.URLClassLoader.
Intrucˆat tipurile de date Java pot fi ˆınc˘arcate folosind diverse instant¸e de tip ClassLoader, fiecare obiect Class va ret¸ine class loader-ul care a fost folosit pentru ˆınc˘arcare, acesta putˆand fi obt¸inut cu metoda getClassLoader.
Inc˘arcarea dinamic˘a a unei clase ˆın memorie se refer˘a la faptul c˘a nu cunoast¸em tipul acesteia decˆat la execut¸ia preogramului, moment ˆın care putem solicita ˆınc˘arcarea sa, specificˆand numele s˘au complet prin intermediul unui ¸sir de caractere. Acest lucru poate fi realizat prin mai multe modalit˘a¸ti, cele mai comune metode fiind:
448
CAPITOLUL 16. LUCRUL DINAMIC CU CLASE
• loadClass apelat˘a pentru un obiect de tip ClassLoader
ClassLoader loader = new MyClassLoader(); loader.loadClass("NumeCompletClasa"); • Class.forName Aceast˘a metoda va ˆınc˘arca respectiva clas˘a folosind class loader-ul obiectului curent (care o apeleaz˘a): Class c = Class.forName("NumeCompletClasa"); // echivalent cu ClassLoader loader = this.getClass().getClassLoader(); loader.loadClass("ClasaNecunoscuta"); // Clasele standard pot fi si ele incarcate astfel Class t = Class.forName("java.lang.Thread"); Dac˘a dorim s˘a instant¸iem un obiect dintr-o clas˘a ˆınc˘arcat˘a dinamic putem folosi metoda newInstance, cu condit¸ia s˘a existe constructorul f˘ar˘a argumente pentru clasa respectiv˘a. Dup˘a cum vom vedea ˆın sect¸iunea urm˘atoare, mai exist˘a ¸si alte posibilit˘a¸ti de a instant¸ia astfel de obiecte. Class c = Class.forName("java.awt.Button"); Button b = (Button) c.newInstance();
Folosirea interfet¸elor sau a claselor abstracte ˆımpreun˘a cu ˆınc˘arcarea dinamic˘a a claselor ofer˘a un mecanism extrem de puternic de lucru ˆın Java. Vom detalia acest lucru prin intermediul unui exepmplu. S˘a presupunem c˘a dorim s˘a cre˘am o aplicat¸ie care s˘a genereze aleator un vector de numere dup˘a care s˘a aplice o anumit˘a funct¸ie acestui vector. Numele funct¸iei care trebuie apelat˘a va fi introdus de la tastatur˘a, iar implementarea ei va fi cont¸inut˘a ˆıntr-o clas˘a a directorului curent. Toate funct¸iile vor extinde clasa abstract˘a Funct ¸ie. In felul acesta, aplicat¸ia poate fi extins˘a cu noi funct¸ii f˘ar˘a a schimba codul ei, tot ce trebuie s˘a facem fiind s˘a scriem noi clase care extind Functie ¸si s˘a implement˘am metoda executa. Aceasta va returna 0 dac˘a metoda s-a executat corect, −1 ˆın caz contrar.
˘ 16.1. INCARCAREA CLASELOR ˆIN MEMORIE
449
Listing 16.1: Exemplu de ˆınc˘arcare dinamic˘a a claselor import java . util .*; import java . io .*; public class TestFunctii { public static void main ( String args []) throws IOException { // Generam un vector aleator int n = 10; int v [] = new int [ n ]; Random rand = new Random () ; for ( int i =0; i < n ; i ++) v [ i ] = rand . nextInt (100) ; // Citim numele unei functii BufferedReader stdin = new BufferedReader ( new InputStreamReader ( System . in ) ) ; String numeFunctie = " " ; while (! numeFunctie . equals ( " gata " ) ) { System . out . print ( " \ nFunctie : " ) ; numeFunctie = stdin . readLine () ; try { // Incarcam clasa Class c = Class . forName ( numeFunctie ) ; // Cream un obiect de tip Functie Functie f = ( Functie ) c . newInstance () ; // Setam vectorul f . setVector ( v ) ; // sau f . v = v ; // Executam functia int ret = f . executa () ; System . out . println ( " \ nCod returnat : " + ret ) ; } catch ( Cl as sNo tF oun d E x c e p t i o n e ) { System . err . println ( " Functie inexistenta ! " ) ; } catch ( In st ant ia tio n E x c e p t i o n e ) { System . err . println ( " Functia nu poate fi instantiata ! "); } catch ( Il le gal Ac ces s E x c e p t i o n e ) { System . err . println ( " Functia nu poate fi accesata ! " ) ;
450
CAPITOLUL 16. LUCRUL DINAMIC CU CLASE } }
} }
Listing 16.2: Clasa abstract˘a ce descrie funct¸ia public abstract class Functie { public int v [] = null ; public void setVector ( int [] v ) { this . v = v ; } public abstract int executa () ; }
Listing 16.3: Un exemplu de funct¸ie import java . util .*; public class Sort extends Functie { public int executa () { if ( v == null ) return -1; Arrays . sort ( v ) ; for ( int i =0; i < v . length ; i ++) System . out . print ( v [ i ] + " " ) ; return 0; } }
Listing 16.4: Alt exemplu de funct¸ie public class Max extends Functie { public int executa () { if ( v == null ) return -1; int max = v [0]; for ( int i =1; i < v . length ; i ++) if ( max < v [ i ]) max = v [ i ]; System . out . print ( max ) ; return 0;
˘ 16.1. INCARCAREA CLASELOR ˆIN MEMORIE
451
} }
Un obiect de tip URLClassLoader ment¸ine o list˘a de adrese URL de unde va ˆıncerca s˘a ˆıncarce ˆın memorie clasa al c˘arei nume ˆıl specific˘am ca argument al metodelor de mai sus. Implicit, la crearea class loader-ului aceast˘a list˘a este completat˘a cu informat¸iile din variabila sistem CLASSPATH sau cu cele specificate prin opt¸iunea -classpath la lansarea aplicat¸iei. Folosind metoda getURLs putem afla aceste adrese, iar cu addURL putem ad˘auga o nou˘a adres˘a de c˘autare a claselor. Bineˆınt¸eles, adresele URL pot specifica ¸si directoare ale sistemului de fi¸siere local. S˘a presupunem c˘a ˆın directorul c:\clase\demo exist˘a clasa cu numele Test, aflat˘a ˆın pachetul demo ¸si dorim s˘a o ˆınc˘arc˘am dinamic ˆın memorie: // Obtinem class loaderul curent URLClassLoader urlLoader = (URLClassLoader) this.getClass().getClassLoader(); // Adaugam directorul sub forma unui URL urlLoader.addURL(new File("c:\\clase").toURL()); // Incarcam clasa urlLoader.loadClass("demo.Test"); Dup˘a ce o clas˘a a fost ˆınc˘arcat˘a folosind un class loader, ea nu va mai putea fi desc˘arcat˘a explicit din memorie. In cazul ˆın care dorim s˘a avem posibilitatea de a o reˆınc˘arca, deoarece a fost modificat˘a ¸si recompilat˘a, trebuie s˘a folosim class-loadere proprii ¸si s˘a instant¸iem noi obiecte de tip ClassLoader, ori de cˆate ori dorim s˘a fort¸˘am reˆınc˘arcarea claselor. Crearea unui class loader propriu se face uzual prin extinderea clasei URLClassLoader, o variant˘a simplist˘a fiind prezentat˘a mai jos: public class MyClassLoader extends URLClassLoader{ public MyClassLoader(URL[] urls){ super(urls); } } Inc˘arcarea claselor folosind clasa nou creat˘a se va face astfel:
452
CAPITOLUL 16. LUCRUL DINAMIC CU CLASE
// La initializare URLClassLoader systemLoader = (URLClassLoader) this.getClass().getClassLoader(); URL[] urls = systemLoader.getURLs(); // Cream class loaderul propriu MyClassLoader myLoader = new MyClassLoader(urls); myLoader.loadClass("Clasa"); ... // Dorim sa reincarcam clasa myLoader.loadClass("Clasa"); // nu functioneaza ! // Cream alt class loader MyClassLoader myLoader = new MyClassLoader(urls); myLoader.loadClass("Clasa"); // reincarca clasa
16.2
Mecanismul reflect˘ arii
Mecanismul prin care o clas˘a, interfat¸˘a sau obiect ”reflect˘a” la momentul execut¸iei structura lor intern˘a se nume¸ste reflectare (eng. reflection), acesta punˆand la dispozit¸ie metode pentru: • Determinarea clasei unui obiect. • Aflarea unor informat¸ii despre o clas˘a (modificatori, superclasa, constructori, metode). • Instant¸ierea unor clase al c˘aror nume este ¸stiut abia la execut¸ie. • Setarea sau aflarea atributelor unui obiect, chiar dac˘a numele acestora este ¸stiut abia la execut¸ie. • Invocarea metodelor unui obiect al c˘aror nume este ¸stiut abia la execut¸ie. • Crearea unor vectori a c˘aror dimensiune ¸si tip nu este ¸stiut decˆat la execut¸ie. Suportul pentru reflectare este inclus ˆın distribut¸ia standard Java, fiind cunoscut sub numele de Reflection API ¸si cont¸ine urm˘atoarele clase:
˘ 16.2. MECANISMUL REFLECTARII
453
• java.lang.Class • java.lang.Object • Clasele din pachetul java.lang.reflect ¸si anume: – Array – Constructor – Field – Method – Modifier
16.2.1
Examinarea claselor ¸si interfet¸elor
Examinarea claselor ¸si interfet¸elor se realizeaz˘a cu metode ale clasei java.lang.Class, un obiect de acest tip putˆand s˘a reprezinte atˆat o clas˘a cˆat ¸si o interfat¸˘a, diferent¸ierea acestora f˘acˆandu-se prin intermediul metodei isInterface. Reflection API pune la dispozit¸ie metode pentru obt¸inerea urm˘atoarelor informat¸ii:
Aflarea instant¸ei Class corespunz˘ator unui anumit obiect sau tip de date: Class c = obiect.getClass(); Class c = java.awt.Button.class; Class c = Class.forName("NumeClasa"); Tipurile primitive sunt descrise ¸si ele de instant¸e de tip Class avˆand forma TipPrimitiv.class: int.class, double.class, etc., diferent¸ierea lor f˘acˆanduse cu ajutorul metodei isPrimitive.
Aflarea numelui unei clase - Se realizeaz˘a cu metoda getName: Class clasa = obiect.getClass(); String nume = clasa.getName();
454
CAPITOLUL 16. LUCRUL DINAMIC CU CLASE
Aflarea modificatorilor unei clase - Se realizeaz˘a cu metoda getModifiers, aceasta returnˆand un num˘ar ˆıntreg ce codific˘a tot¸i modificatorii clasei. Pentru a determina u¸sor prezent¸a unui anumit modificator se folosesc metodele statice ale clasei Modifier isPublic, isAbstract ¸si isFinal: Class clasa = obiect.getClass(); int m = clasa.getModifiers(); String modif = ""; if (Modifier.isPublic(m)) modif += "public "; if (Modifier.isAbstract(m)) modif += "abstract "; if (Modifier.isFinal(m)) modif += "final "; System.out.println(modif + "class" + c.getName());
Aflarea superclasei - Se realizeaz˘a cu metoda getSuperclass ce returneaz˘a o instant¸˘a de tip Class, corespunz˘atoare tipului de date al superclasei sau null pentru clasa Object. Class c = java.awt.Frame.class; Class s = c.getSuperclass(); System.out.println(s); // java.awt.Window Class c = java.awt.Object.class; Class s = c.getSuperclass(); // null
Aflarea interfet¸elor implementate de o clas˘a sau extinse de o interfat¸˘a - Se realizeaz˘a cu metoda getInterfaces, ce returneaz˘a un vector de tip Class[]. public void interfete(Class c) { Class[] interf = c.getInterfaces(); for (int i = 0; i < interf.length; i++) { String nume = interf[i].getName(); System.out.print(nume + " ");
˘ 16.2. MECANISMUL REFLECTARII
455
} } ... interfete(java.util.HashSet.class); // Va afisa interfetele implementate de HashSet: // Cloneable, Collection, Serializable, Set
interfete(java.util.Set); // Va afisa interfetele extinse de Set: // Collection
Aflarea variabilelor membre - Se realizeaz˘a cu una din metodele getFields sau getDeclaredFields, ce returnez˘a un vector de tip Field[], diferent¸a ˆıntre cele dou˘a constˆand ˆın faptul c˘a prima returneaz˘a toate variabilele membre, inclusiv cele mo¸stenite, ˆın timp ce a doua le returnez˘a doar pe cele declarate ˆın cadrul clasei. La rˆandul ei, clasa Field pune la dispozit¸ie metodele getName, getType ¸si getModifiers pentru a obt¸ine numele, tipul, respectiv modificatorii unei variabile membru. Cu ajutorul metodei getField este posibil˘a obt¸inerea unei referint¸e la o variabil˘a mebr˘a cu un anumit nume specificat.
Aflarea constructorilor - Se realizeaz˘a cu metodele getConstructors sau getDeclaredConstructors, ce returneaz˘a un vector de tip Constructor[]. Clasa Constructor pune la dispozit¸ie metodele getName, getParameterTypes, getModifiers, getExceptionTypes pentru a obt¸ine toate informat¸iile legate de respectivul constructor. Cu ajutorul metodei getConstructor este posibil˘a obt¸inerea unei referint¸e la constructor cu o signatur˘a specificat˘a.
Aflarea metodelor - Se realizeaz˘a cu metodele getMethods sau getDeclaredMethods, ce returneaz˘a un vector de tip Method[]. Clasa Method pune la dispozit¸ie metodele getName, getParameterTypes, getModifiers, getExceptionTypes, getReturnType pentru a obt¸ine toate informat¸iile legate
456
CAPITOLUL 16. LUCRUL DINAMIC CU CLASE
de respectiva metod˘a. Cu ajutorul metodei getMethod este posibil˘a obt¸inerea unei referint¸e la o metod˘a cu o signatur˘a specificat˘a.
Aflarea claselor imbricate - Se realizeaz˘a cu metodele getClasses sau getDeclaredClasses, ce returnez˘a un vector de tip Class[].
Aflarea clasei de acoperire - Se realizeaz˘a cu metoda getDeclaringClass. Aceast˘a metod˘a o reg˘asim ¸si ˆın clasele Field, Constructor, Method, pentru acestea returnˆand clasa c˘arei ˆıi apart¸ine variabila, constructorul sau metoda respectiv˘a.
16.2.2
Manipularea obiectelor
Pe lˆang˘a posibilitatea de a examina structura unei anumite clase la momentul execut¸iei, folosind Reflection API avem posibilitatea de a lucra dinamic cu obiecte, bazˆandu-ne pe informat¸ii pe care le obt¸inem abia la execut¸ie.
Crearea obiectelor Dup˘a cum st¸im, crearea obiectelor se realizeaz˘a cu operatorul new urmat de un apel la un constructor al clasei pe care o instant¸iem. In cazul ˆın care numele clasei nu este cunoscut decˆat la momentul execut¸iei nu mai putem folosi aceast˘a metod˘a de instant¸iere. In schimb, avem la dispozit¸ie alte dou˘a variante: • Metoda newInstance din clasa java.lang.Class Aceasta permite instant¸ierea unui obiect folosind constructorul f˘ar˘a argumente al acestuia. Dac˘a nu exist˘a un astfel de constructor sau nu este accesibil vor fi generate except¸ii de tipul InstantiationException, respectiv IllegalAccessException. Class c = Class.forName("NumeClasa"); Object o = c.newInstance(); // Daca stim tipul obiectului
˘ 16.2. MECANISMUL REFLECTARII
457
Class c = java.awt.Point.class; Point p = (Point) c.newInstance(); • Metoda newInstance din clasa Constructor Aceasta permite instant¸ierea unui obiect folosind un anumit constructor, cel pentru care se face apelul. Evident, aceast˘a solut¸ie presupune ˆın primul rˆand obt¸inerea unui obiect de tip Constructor cu o anumit˘a signatur˘a, apoi specificarea argumentelor la apelarea sa. S˘a rescriem exemplul de mai sus, apelˆand constructorul cu dou˘a argumente al clasei Point. Class clasa = java.awt.Point.class; // Obtinem constructorul dorit Class[] signatura = new Class[] {int.class, int.class}; Constructor ctor = clasa.getConstructor(signatura); // Pregatim argumentele // Ele trebuie sa fie de tipul referinta corespunzator Integer x = new Integer(10); Integer y = new Integer(20); Object[] arg = new Object[] {x, y}; // Instantiem Point p = (Point) ctor.newInstance(arg); Except¸ii generate de metoda newInstance sunt: InstantiationException, IllegalAccessException, IllegalArgumentException ¸si InvocationTargetException. Metoda getConstructor poate provoca except¸ii de tipul NoSuchMethodException.
Invocarea metodelor Invocarea unei metode al c˘arei nume ˆıl cunoa¸stem abia la momentul execut¸iei se realizeaz˘a cu metoda invoke a clasei Method. Ca ¸si ˆın cazul constructorilor, trebuie s˘a obt¸inem ˆıntˆai o referint¸˘a la metoda cu signatura corespunz˘atoare ¸si apoi s˘a specific˘am argumentele. In plus, mai putem obt¸ine valoarea returnat˘a. S˘a presupunem c˘a dorim s˘a apel˘am metoda contains
458
CAPITOLUL 16. LUCRUL DINAMIC CU CLASE
a clasei Rectangle care determin˘a dac˘a un anumit punct se g˘ase¸ste ˆın interiorul drepunghiului. Metoda contains are mai multe variante, noi o vom apela pe cea care accept˘a un argument de tip Point. Class clasa = java.awt.Rectangle.class; Rectangle obiect = new Rectangle(0, 0, 100, 100); // Obtinem metoda dorita Class[] signatura = new Class[] {Point.class}; Method metoda = clasa.getMethod("contains", signatura); // Pregatim argumentele Point p = new Point(10, 20); Object[] arg = new Object[] {p}; // Apelam metoda metoda.invoke(obiect, arg); Dac˘a num˘arul argumentelor metodei este 0, atunci putem folosi valoarea null ˆın locul vectorilor ce reprezint˘a signatura, respectiv parametri de apelare ai metodei. Except¸iile generate de metoda invoke sunt: IllegalAccessException ¸si InvocationTargetException. Metoda getMethod poate provoca except¸ii de tipul NoSuchMethodException.
Setarea ¸si aflarea variabilelor membre Pentur setarea ¸si aflarea valorilor variabilelor membre sunt folosite metodele set ¸si get ale clasei Field. S˘a presupunem c˘a dorim s˘a set˘am variabila x a unui obiect de tip Point ¸si s˘a obt¸inem valoarea variabilei y a aceluia¸si obiect: Class clasa = java.awt.Point.class; Point obiect = new Point(0, 20); // Obtinem variabilele membre Field x, y; x = clasa.getField("x"); y = clasa.getField("y");
˘ 16.2. MECANISMUL REFLECTARII
459
// Setam valoarea lui x x.set(obiect, new Integer(10)); // Obtinem valoarea lui y Integer val = y.get(obiect); Except¸iile generate de metodele get, set sunt: IllegalAccessException ¸si IllegalArgumentException. Metoda getField poate provoca except¸ii de tipul NoSuchFieldException.
Revenind la exemplul din sect¸iunea anterioar˘a cu apelarea dinamic˘a a unor funct¸ii pentru un vector, s˘a presupunem c˘a exist˘a deja un num˘ar ˆınsemnat de clase care descriu diferite funct¸ii dar acestea nu extind clasa abstract˘a Functie. Din acest motiv, solut¸ia anterioar˘a nu mai este viabil˘a ¸si trebuie s˘a folosim apelarea metodei executa ˆıntr-un mod dinamic. Listing 16.5: Lucru dinamic cu metode ¸si variabile import java . lang . reflect .*; import java . util .*; import java . io .*; public class TestFunctii2 { public static void main ( String args []) throws IOException { // Generam un vector aleator int n = 10; int v [] = new int [ n ]; Random rand = new Random () ; for ( int i =0; i < n ; i ++) v [ i ] = rand . nextInt (100) ; // Citim numele unei functii BufferedReader stdin = new BufferedReader ( new InputStreamReader ( System . in ) ) ; String numeFunctie = " " ; while (! numeFunctie . equals ( " gata " ) ) { System . out . print ( " \ nFunctie : " ) ; numeFunctie = stdin . readLine () ;
460
CAPITOLUL 16. LUCRUL DINAMIC CU CLASE try { // Incarcam clasa Class c = Class . forName ( numeFunctie ) ; // Cream un obiect de tip Functie Object f = c . newInstance () ; // Setam vectorul ( setam direct variabila v ) Field vector = c . getField ( " v " ) ; vector . set (f , v ) ; // Apelam metoda ’ executa ’ // Folosim null pentru ca nu avem argumente Method m = c . getMethod ( " executa " , null ) ; Integer ret = ( Integer ) m . invoke (f , null ) ; System . out . println ( " \ nCod returnat : " + ret ) ; } catch ( Exception e ) { System . err . println ( " Eroare la apelarea functiei ! " ) ; } }
} }
16.2.3
Lucrul dinamic cu vectori
Vectorii sunt reprezentat¸i ca tip de date tot prin intermediul clasei java.lang.Class, diferent¸ierea f˘acˆandu-se prin intermediul metodei isArray. Tipul de date al elementelor din care este format vectorul va fi obt¸inut cu ajutorul metodei getComponentType, ce ˆıntoarce o referint¸˘a de tip Class. Point []vector = new Point[10]; Class c = vector.getClass(); System.out.println(c.getComponentType()); // Va afisa: class java.awt.Point Lucrul dinamic cu obiecte ce reprezint˘a vectori se realizeaz˘a prin intermediul clasei Array. Aceasta cont¸ine o serie de metode statice ce permit: • Crearea de noi vectori: newInstance • Aflarea num˘arului de elemente: getLength
˘ 16.2. MECANISMUL REFLECTARII
461
• Setarea / aflarea elementelor: set, get Exemplul de mai jos creeaz˘a un vector ce cont¸ine numerele ˆıntregi de la 0 la 9: Object a = Array.newInstance(int.class, 10); for (int i=0; i < Array.getLength(a); i++) Array.set(a, i, new Integer(i)); for (int i=0; i < Array.getLength(a); i++) System.out.print(Array.get(a, i) + " ");