Fejezetek az "Effective Java" c. könyvből



Hasonló dokumentumok
Helyes-e az alábbi kódrészlet? int i = 1; i = i * 3 + 1; int j; j = i + 1; Nem. Igen. Hányféleképpen lehet Javaban megjegyzést írni?

Osztályok. 4. gyakorlat

és az instanceof operátor

Java VIII. Az interfacei. és az instanceof operátor. Az interfészről általában. Interfészek JAVA-ban. Krizsán Zoltán

Programozási technológia

Vé V g é r g e r h e a h j a tá t s á i s s z s ál á ak a Runnable, Thread

OOP #14 (referencia-elv)

Programozási nyelvek Java

OOP: Java 8.Gy: Abstract osztályok, interfészek

Programozás II. 3. gyakorlat Objektum Orientáltság C++-ban

Java bevezet o Kab odi L aszl o Kab odi L aszl o Java bevezet o

Programozási nyelvek Java

Java VI. Egy kis kitérő: az UML. Osztály diagram. Általános Informatikai Tanszék Utolsó módosítás:

Programozás II. 2. gyakorlat Áttérés C-ről C++-ra

JAVA PROGRAMOZÁS 2.ELŐADÁS

Java programozási nyelv 4. rész Osztályok II.

List<String> l1 = new ArrayList<String>(); List<Object> l2 = l1; // error

Széchenyi István Egyetem. Programozás III. Varjasi Norbert

Generikus osztályok, gyűjtemények és algoritmusok

Programozási technológia

Osztályszintű elérés, kivételkezelés, fájlkezelés

OBJEKTUM ORIENTÁLT PROGRAMOZÁS JAVA NYELVEN. vizsgatételek

JUnit. JUnit használata. IDE támogatás. Parancssori használat. Teszt készítése. Teszt készítése

Java programozási nyelv 5. rész Osztályok III.

OOP: Java 11.Gy: Enumok, beágyazott osztályok. 13/1 B ITv: MAN

Programozás I. 5. gyakorlat. Szegedi Tudományegyetem Természettudományi és Informatikai Kar

Programozási nyelvek és módszerek Java Thread-ek

Programozási nyelvek Java

Segédanyag: Java alkalmazások gyakorlat

Informatika terméktervezőknek

Kivételkezelés, beágyazott osztályok. Nyolcadik gyakorlat

Concurrency in Swing

Szoftvertechnológia alapjai Java előadások

Az osztályok csomagokba vannak rendezve, minden csomag tetszőleges. Könyvtárhierarhiát fed: Pl.: java/util/scanner.java

Abstract osztályok és interface-ek. 7-dik gyakorlat

Programozási nyelvek II. JAVA EA+GY 1. gyakolat

1. Mi a fejállományok szerepe C és C++ nyelvben és hogyan használjuk őket? 2. Milyen alapvető változókat használhatunk a C és C++ nyelvben?

Mi a különbség az extends és az implements között. Mikor melyiket kell használni? Comperable-t megvalósító oasztályokban össze lehet hasonlitani

Collections. Összetett adatstruktúrák

OOP. Alapelvek Elek Tibor

Java VI. Miskolci Egyetem Általános Informatikai Tanszék. Utolsó módosítás: Ficsor Lajos. Java VI.: Öröklődés JAVA6 / 1

Objektumorientált programozás C# nyelven

STL gyakorlat C++ Izsó Tamás május 9. Izsó Tamás STL gyakorlat/ 1

Magas szintű programozási nyelvek 2 Előadás jegyzet

C++ programozási nyelv

Szoftvertechnológia alapjai Java előadások

Programozás I. 3. gyakorlat. Szegedi Tudományegyetem Természettudományi és Informatikai Kar

Pelda öröklődésre: import java.io.*; import java.text.*; import java.util.*; import extra.*;

JAVA PROGRAMOZÁS 3.ELŐADÁS

Programozás I. Első ZH segédlet

Osztályok. construct () destruct() $b=new Book(); $b=null; unset ($b); book.php: <?php class Book { private $isbn; public $title;

Osztálytervezés és implementációs ajánlások

Osztálytervezés és implementációs ajánlások

Számítástechnika II. BMEKOKAA Előadás. Dr. Bécsi Tamás

Java VII. Polimorfizmus a Java nyelvben

Programozási nyelvek Java

Bevezetés a Python programozási nyelvbe

Objektum Orientált Programozás. 11. Kivételkezelés 44/1B IT MAN

Java programozási nyelv

Objektumorientált programozás C# nyelven III.

Java VII. Polimorfizmus a Java nyelvben

Programozás III CSOMAGOK. Az összetartozó osztályok és interfészek egy csomagba (package) kerülnek.

Számítástechnika II. BMEKOKAA Előadás. Dr. Bécsi Tamás

Objektumorientált programozás C# nyelven

Vizuális és eseményvezérelt programozás , II. félév BMF NIK

.Net adatstruktúrák. Készítette: Major Péter

C++ programozási nyelv

Segédanyag: Java alkalmazások gyakorlat

Interfészek. PPT 2007/2008 tavasz.

C#, OOP. Osztályok tervezése C#-ban

3. Gyakorlat Ismerkedés a Java nyelvvel

Bevezetés a programozásba Előadás: Objektumszintű és osztályszintű elemek, hibakezelés

Programozás III KIINDULÁS. Különböző sportoló típusok vannak: futó, magasugró, focista, akik teljesítményét más-más módon határozzuk meg.

Java III. I I. Osztálydefiníció (Bevezetés)

Objektumorientált programozás C# nyelven

7. K: JAVA alapok Konzultáció

Függőség injekció Konstantinusz Kft 2010

Java III. I I. Osztálydefiníció (Bevezetés)

C# osztályok. Krizsán Zoltán

Bevezetés a programozásba Előadás: Tagfüggvények, osztály, objektum

OOP és UML Áttekintés

Programozás BMEKOKAA146. Dr. Bécsi Tamás 5. előadás

C# osztálydeníció. Krizsán Zoltán 1. .net C# technológiák tananyag objektum orientált programozás tananyag

Objektum orientált kiterjesztés A+ programozási nyelvhez

Java Programozás 9. Gy: Java alapok. Adatkezelő 5.rész

Miután létrehoztuk, szeretnénk neki beszédesebb nevet adni. A név változtatásához a következőt kell tenni:

JAVA nyelvi alapok. Adatbányászati technikák (VISZM185) Dávid István

Programozási nyelvek Java

Dr. Pál László, Sapientia EMTE, Csíkszereda WEB PROGRAMOZÁS 2.ELŐADÁS. Objektumorientált programozás

PHP5 Új generáció (2. rész)

Java Programozás 4. Gy: Java GUI. Tipper, MVC kalkulátor

OOP: Java 8.Gy: Gyakorlás

Programozási technikák Pál László. Sapientia EMTE, Csíkszereda, 2009/2010

Objektumorientált programozás C# nyelven

Generikus Típusok, Kollekciók

Java és web programozás

III. OOP (objektumok, osztályok)

Delegátumok C#-ban Krizsán Zoltán iit

Programozás II. labor

Elemi Alkalmazások Fejlesztése II.

Átírás:

Budapesti Műszaki és Gazdaságtudományi Egyetem Java technológia

A könyv Joshua Bloch, Effective Java, Programming Language Guide, Sun Microsystems Inc., 2001. A könyvben 57 fejezet ("item") található, amelyek egy-egy problémát mutatnak be, tanulságos példákkal illusztrálva. Az előadás a könyvben található fejezetek közül tartalmaz néhányat, minden fejezetnél fel van tüntetve a könyvbeli fejezet száma és címe is. Az előadásban bemutatott példaprogamok részben a könyvből származnak. 2

Item 2 Enforce the singleton property with a private constructor. A singletonok olyan objektumok, amelyeket legfeljebb egyszer lehet példányosítani. Ezek gyakran olyan rendszerkomponenseket reprezentálnak, amelyekből csak egy létezik (például a képernyő, vagy a billentyűzet). A singleton osztályokat mindig úgy kell megvalósítani, hogy a singleton tulajdonságot automatikusan kikényszerítse. 3

Item 2 A megvalósítások közös jellemzője, hogy a konstruktor privát, így a példányok létrehozását az osztály szigorúan kézbentarthatja. Az első lehetséges megoldás egy publikus mező alkalmazása: public class Singleton { private Singleton () { public static final Singleton INSTANCE = new Singleton (); Ekkor az egyedi példányhoz az alábbi módon férhetünk hozzá: Singleton s = Singleton.INSTANCE; 4

Item 2 A másik lehetőség, hogy egy statikus metódust alkalmazunk a példány "kinyerésére". Ennek a megoldásnak az az előnye, hogy az API megváltoztatása nélkül később megszüntethetjük az osztály singleton tulajdonságát. public class Singleton { private Singleton () { public static getinstance () { return INSTANCE; private static final Singleton INSTANCE = new Singleton (); 5

Item 2 Az utóbbi megoldás esetén a getinstance metódust használhatjuk a példány lekérésére: Singleton s = Singleton.getInstance (); Ha a példány létrehozása időigényes, és nem biztos, hogy a program futása során bárkinek is szüksége lesz rá, érdemes a "lusta példányosítás" módszerét alkalmazni. public class Singleton { private Singleton () { public static getinstance () { if (instance == null) instance = new Singleton (); return instance; private static Singleton instance; 6

Item 3 Enforce noninstantiability with a private constructor. Előfordul, hogy olyan osztályt készítünk, amelyet nem arra szánunk, hogy példányokat hozzunk létre belőle. Az ilyen osztályok tipikusan egy-egy adattípushoz vagy objektumhoz tartozó műveletek csoportosítására valók, mint a java.util.arrays, vagy a java.util.collections. Az ilyen osztályok példányosítását célszerű megakadályozni úgy, hogy elhelyezünk benne egy privát konstruktort. public class NonInstantiable { private NonInstantiable () { 7

Item 3 A konstruktornak természetesen nem kell csinálnia semmit, célja csak az, hogy megakadályozzuk a fordító által automatikusan generált publikus default konstruktor létrejöttét. Bár az abstract módosítóval is elérhetnénk azt, hogy az osztályt ne lehessen példányosítani, ez nem célszerű, mert ha valaki egy leszármazottat készít, az már példányosítható lesz. Következmény: az egyedüli privát konstruktor léte megakadályozza az osztály leszármaztatását, hiszen a leszármazottak konstruktora mindig meghívja az ős konstruktorát, ebben az esetben azonban nincs hozzáférhető konstruktor az ősben. 8

Item 14 Favor composition over inheritance. A leszármaztatás célja általában az, hogy egy meglévő osztályt új tulajdonságokkal, funkciókkal bővítsünk, illetve a meglévő funkciók viselkedését megváltoztassuk. A leszármaztatással azonban az a probléma, hogy megsérti az encapsulation elvét. Tegyük fel, hogy egy olyan HashSet variánst akarunk készíteni, amely nyilvántartja, hogy hány elemet adtak hozzá. 9

Item 14 A naív megoldás az alábbi: public class CounterHashSet extends HashSet { public CounterHashSet () { public CounterHashSet (Collection c) { super (c); public CounterHashSet (int ic) { super (ic); public CounterHashSet (int ic,float lf) { super (ic,lf); public boolean add (Object o) { counter++; return super.add (o); public boolean addall (Collection c) { counter += c.size (); return super.addall (c); public int getcounter () { return counter; private int counter = 0; 10

Item 14 A kisebb probléma az, hogy a HashSet négy konstruktorának megfelelő konstruktorokat el kellett készíteni. A nagyobbik probléma az, hogy ha az alábbi kódot lefuttatjuk: CounterHashSet chs = new CounterHashSet (); chs.addall (Arrays.asList (new String[] { "alma","szolo","korte" )); System.out.println (chs.getcounter ()); A várt 3 helyett 6-ot kapunk. Miért? Azért, mert a HashSet megvalósításában az addall nem csinál mást, mint a paraméterként átadott Collection minden elemére meghívja az add metódust. A CounterHashSet megoldás tehát azért rossz, mert függ a HashSet belső megvalósításától. 11

Item 14 Jobb megoldás, ha kompozíciót (composition) alkalmazunk. A kompozíció azt jelenti, hogy a kiegészítendő osztály egy példányát beburkoljuk egy új osztályba, ez a burkoló osztály (wrapper class). Ezt a megoldást dekorátornak (decorator) is hívják, mert a burkoló osztály új funkciókkal "dekorálja" a másik osztályt. A kiegészítendő osztály metódusait továbbító metódusokon keresztül (forwarding method) továbbítjuk a burkoló osztályba. Ezt a megoldást gyakran (tévesen) delegációnak (delgation) nevezik. 12

Item 14 public class CounterSet implements Set { public CounterSet (Set set) { this.set = set; public boolean add (Object o) { counter++; return set.add (o); public boolean addall (Collection c) { counter += c.size (); return set.addall (c); public void clear () { set.clear (); public boolean contains (Object o) { return set.contains (o); // a Set interface többi metódusa public int getcounter () { return counter; private int counter = 0; private Set set; 13

Item 14 A fenti megoldás legfőbb előnye, hogy jól működik, mert az adott Set implementációjától független: CounterSet cs = new CounterSet (new HashSet ()); cs.addall (Arrays.asList (new String[] { "alma","szolo","korte" )); System.out.println (cs.getcounter ()); További előny, hogy tetszőleges Setre alkalmazható, mert a Set interface-en alapul. Ebből kifolyólag a konkrét megvalósítás konstruktorainak megfelelő konstruktorokat sem kell megvalósítani. Mivel csak burkolóként működik, akár ideiglenesen is beburkolhatunk vele egy Setet. Hátrányai, hogy visszahívó (callback) helyzetekben nem működik, mert a burkolt objektum nem tud a burkolóról, illetve hogy a továbbító metódusokat meg kell valósítani. 14

Item 14 Öröklődést általában csak akkor szabad használni, ha az A osztályból leszármaztatott B osztályra igaz az, hogy "a B az egy A". A fenti kitétel tipikusan igaz a Barack és a Kajszi osztályokra, de nem igaz a Körte és a Villanykörte osztályokra. Akkor is érdemes kompozíciót használni, ha a kibővítendő osztálynak olyan API hiányosságai vannak, amelyeket el szeretnénk fedni a felhasználók elől. 15

Item 27 Return zero-length arrays, not nulls. Gyakori, ám helytelen megoldás, hogy az olyan metódusok, amelyek egy tömböt adnak vissza, nulla méretű tömb helyett nullt adnak vissza: public Object[] getobjects () { if (!objectsavailable) return null; Ez egyrészt azért nem szerencsés, mert a hívót meglepi ez a viselkedés, másrészt azért, mert a visszatérési érték korrekt kezeléséhez egy plusz feltételvizsgálat szükséges: 16

Item 27 Object[] objects = getobjects (); if (objects == null) System.out.println ("Object count: 0"); else System.out.println ("Object count: " + objects.length); Ha azonban a metódus null helyett nulla méretű tömböt ad vissza, ez a probléma nem jelentkezik: public Object[] getobjects () { if (!objectsavailable) return new Object [0]; 17

Item 27 Object[] objects = getobjects (); System.out.println ("Object count: " + objects.length); Mivel egy nulla méretű tömb immutábilis, elég egyetlen példányt használni belőle, így nem kell minden alkalommal újat létrehozni: private static final Object[] EMPTY_ARRAY = new Object [0]; public Object[] getobjects () { if (!objectsavailable) return EMPTY_ARRAY; 18

Item 31 Avoid float and double if exact answers are required. A float és a double lebegőpontos típusok. Céljuk az hogy, széles értéktartományon jelenítsenek meg törtszámokat, de pontos számításokra nem alkalmasak. Vannak olyan számok, amelyeket nem lehet pontosan megjeleníteni floatban vagy double-ben: System.out.println (0.1 0.42); Ez -0.32 helyett -0.31999999999999995-öt ír ki. Ez a viselkedés például pénzügyi számítások esetén teljesen elfogadhatatlan. 19

Item 31 A következő program 1 Forintból von ki 10 filléreket: double money = 1; for (int i = 0;i < 10;i++) { money -= 0.1; System.out.println ("marad: " + money); Az eredmény szomorú: marad: 0.9 marad: 0.8 marad: 0.7000000000000001 marad: 0.6000000000000001 marad: 0.5000000000000001 marad: 0.40000000000000013 marad: 0.30000000000000016 marad: 0.20000000000000015 marad: 0.10000000000000014 marad: 1.3877787807814457E-16 20

Item 31 A megoldás: ne használjuk a float, illetve double típusokat, hanem az int, long, illetve BigDecimal típusok valamelyikét: BigDecimal TIZ_FILLER = new BigDecimal ("0.1"); BigDecimal money = new BigDecimal ("1"); for (int i = 0;i < 10;i++) { money = money.subtract (TIZ_FILLER); System.out.println ("marad: " + money); Most már jobb a helyzet: marad: 0.9 marad: 0.8 marad: 0.1 marad: 0.0 21

Item 31 A BigDecimal egy immutábilis, tetszőleges pontosságú decimális értéket reprezentál. Egy skálázatlan értékből, és egy skálatényezőből áll. Használatának előnye az intként, illetve longként ábrázolt fixpontos számokkal szemben, hogy a skálatényező követésére nincs szükség, illetve a BigDecimal osztály megvalósítja az összes szükséges műveletet. Hátránya, hogy operator overloading híján némileg kényelmetlen a használata. 22

Item 33 Beware the performance of string concatenation. A stringek összefűzésére gyakori módszer a + operátor használata. Ez a megoldás teljesen jó akkor, ha csak néhány stringet kell összefűzni, de nagyon rosszul skálázható. Ennek oka az, hogy a String immutábilis, így két string összefűzésekor mindkettő tartalma átmásolódik egy harmadikba. A string összefűzés futásideje így az összefűzendő stringek számában o(n2). 23

Item 33 Az alábbi metódus egy borzasztóan hatékonytalan megoldás stringek összefűzésére: public String concatenate (String[] strings) { String result = ""; for (int i = 0;i < strings.length;i++) result = result + strings [i]; return result; A megoldás a StringBuffer osztály alkalmazása. A fenti metódus lényegesen hatékonyabb változata tehát: public String concatenate (String[] strings) { StringBuffer result = new StringBuffer (); for (int i = 0;i < strings.length;i++) result.append (strings [i]); return result.tostring (); 24

Item 34 Refer to objects by their interfaces. Az alábbi példa a szokásos megoldást mutatja: ArrayList list = new ArrayList (); A fenti megoldás hátránya, hogy az ArrayList típus túlságosan megköti a list változót: nem használhatunk más List implementációt az ArrayList helyett, ha mégis mást akarunk használni, át kell írni a list típusát. Sokkal szerencsésebb tehát azt az interface-t megadni típusként, amely alapján a változót használjuk: List list = new ArrayList (); 25

Item 34 Ebben az esetben könnyű áttérni másik List megvalósításra: List list = new Vector (); Ez az elv metódusparaméterek esetén még fontosabb, hogy ne nyírbáljuk meg a felhasználó szabadságát: public void method (ArrayList list) { helyett: public void method (List list) { A fentiek alól kivétel lehet, ha nincs értelmesen használható interface, vagy egy konkrét implementáció speciális funkcióit akarjuk használni, mint például a LinkedList esetében. 26

Item 47 Don't ignore exceptions. Az alábbihoz hasonló kódrészleteket, sajnos, nagyon gyakran láthatunk: try { catch (Exception e) { Nyilvánvaló, hogy a fenti kódrészlet nagyon veszélyes. A kivételek lenyelése miatt a program végrehajtása a hiba ellenére is folytatódik, így a probléma később, a program egy teljesen más részében jelenik meg, rendkívül nehezen felderíthető helyzetet idézve elő. 27

Item 47 A kivételek lenyelésénél még az is jobb, ha ellenőrzetlen kivételeket hagyunk terjedni felfelé a hívási lánc mentén, mert akkor legalább leáll a program, és a stack trace alapján (általában) azonosítható a hiba forrása. Kivételek lenyelése kivételes esetekben mégis megengedhető, például real-time alkalmazásokban (video lejátszás), ahol egy-egy ütem kiesése megengedhető. A "legális lenyelés" még egy jellegzetes esete: try { Thread.sleep (500); catch (InterruptedException e) { Fontos, hogy ilyenkor a lehető legkonkrétabb kivételtípust adjuk meg a catch blokkban (mint a fenti példában is). 28

Item 50 Never invoke wait outside a loop. A wait metódust egy megadott feltétel bekövetkeztére való várakozásra használjuk. Naív (és rossz) használata: synchronized (object) { object.wait (); Elvileg egy másik szál ébreszti fel ezt a szálat egy notify vagy notifyall hívással, ha a várt feltétel bekövetkezett. 29

Item 50 A fenti megoldás azonban önmagában kevés. Ha a feltétel már korábban bekövetkezett, mint ahogy a szál elaludt, a notify hatástalan marad, és a szál soha nem ébred fel többé. Emiatt szükség van a feltétel előzetes vizsgálatára elalvás előtt: synchronized (object) { if (!condition) object.wait (); 30

Item 50 Még mindig baj van akkor, ha: a feltétel hamissá vált a notify meghívása, és a szál felébredése között, egy szál meghívta a notify-t, noha a feltétel nem következett be (például ha több szál várakozik különböző feltételekre, az ébresztés pedig a notifyall hívással történik), hamis ébredés (spurious wakeup) történik, vagyis a szál notify nélkül is felébred (ez a VM-en kívüli jelenség). A fentiek megelőzésére az ébredés után is meg kell vizsgálni a feltétel fennállását. 31

Item 50 synchronized (object) { while (!condition) object.wait (); A fenti megoldás tökéletes, mert mind ébredés előtt, mind ébredés után megvizsgálja a feltételt, és ha szükséges, "visszaalszik". A wait metódust tehát kizárólag ciklusban szabad használni. 32