Professional Documents
Culture Documents
Ez a blog azért jött létre, mert tapasztalatom szerint még egy nem kezdő Java
programozót is érhetnek meglepetések manapság állásinterjún. Többen azt
javasolták, gyűjtsem össze azokat a témaköröket, kérdéseket, jegyzeteket, amiket
hosszú estéken át olvasgattam, hogy egy helyen legyen. Íme.
Minden metódus paraméter a hívó által használt megfelelő argumentum értékével inicializálódik.
Minden lokális változónak explicit értéket kell adni, mielőtt használják, vagy inicializációval vagy
hozzárendeléssel (assignment).
Kódértelmezési példa
Egy pici elgondolkodtató feladat egy interjúról:
Tyre.java:
Tyre(int s) {
size = s;
}
Car.java:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
CarTest.java:
import java.util.List;
/**
* Question: what will be the output?
*/
public static void main(String[] args) {
Car car = new Car();
car.addTire(new Tyre(10));
car.addTire(new Tyre(11));
Címkék: példa
Osztályok típusai
A legtöbbet olyan osztályokat használunk, amelyek nem másik osztályba vagy interfészbe vannak
beágyazva. Az ilyen osztályt legfelső szintű osztálynak nevezzük (top level class).
A Java nyelv megengedi osztályok deklarálását más osztályokban is, ezeket beágyazott osztályoknak
nevezzük (nested class). Ezeknek több fajtája van.
Egy beágyazott osztály kétféle lehet: statikus vagy nem statikus. Az első fajtát egyszerűen csak statikus
beágyazott osztálynak nevezzük (static nested class), a második neve pedig belső osztály (inner class). A
beágyazott osztály tagja az őt tartalmazó osztálynak, azaz a nem statikus beágyazott osztályok
közvetlenül elérik a tartalmazó osztály más tagjait is, a statikusak azonban nem. Míg egy normális, azaz
felső szintű osztály csak public vagy package private lehet, addig a beágyazott osztályok mind a négy
féle hozzáféréssel rendelkezhetnek: public, protected, private és package private.
logikailag összetartozó osztályok (ha egy osztályt csak egyetlen másik használ, a kettőt egy
helyen lehet deklarálni és egyiket a másikba ágyazni)
növeli az enkapszulációt (példa: van két osztályunk, az egyiknek el kell érnie a másik
adattagjait, melyek egyébként privátként lennének deklarálva - ebben az esetben ha
beágyazzuk az osztályt, az adattagok elérhetőek maradnak a beágyazott osztály számára,
viszont nem lesznek láthatóak más osztályok számára)
könnyebben fenntartható (olvasható) kód
Mint normális esetben, a statikus beágyazott osztály nem érheti el a nem statikus tagokat (csak ha
példányreferencián keresztül teszi azt) és osztályreferencián keresztül hivatkozunk rá.
Egy belső osztály egy példánya a beágyazó osztály példányával együtt - azon belül - él, azaz maga nem
hivatkozhat semmilyen statikus tagra, de bármilyen példányváltozóra igen.
Két speciális belső osztályról kell még beszélni, ezek a név nélküli (anonymous classes) és lokális
osztályok (local classes).
Lokális osztályok
Deklarálhatunk egy belső osztályt egy metóduson belül is. Ezek a lokális (belső) osztályok.
Az előzőt megtehetjük úgy is, hogy nem adunk nevet az osztályunknak, az ilyeneket nevezzük anoním /
név nélküli belső osztályoknak, ilyenkor az osztály példányosítása és definiálása egyszerre, egy helyen
történik.
mivel az anoním osztálynak nincs neve, konstruktora sem lehet; ha szükség van konstruktorra,
akkor vagy lokális osztályt kell használni, vagy inicializáló blokkot, amit pont erre a helyzetre
találtak ki
hasonlóan a lokális osztályokhoz, nem használhatóak
a public, protected, private, statickulcsszavak és statikus mezőket stb. sem lehet definiálni
benne, kivéve ha az final is
akkor ajánlott használni, ha az osztályt, amit megadunk, rögtön használjuk is vagy csak
egyszer kell használni, esetleg az osztálynak nagyon rövid a megvalósítása.
Vegyük pl. a java.io.File osztály list() metódusát, ami kilistázza a könyvtárban lévő fájlokat. Mielőtt
azonban visszaadja a listát, egy szűrőn futtatja át azokat, amit nekünk kell megadni paraméterként.
Amikor egy ilyen fájl szűrőt adunk meg, akkor gyakorlatilag egy adapter osztályt definiálunk, amely
olyan kódot definiál, amit egy másik objektum hív meg. Adapter osztályokat általában célszerű anoním
osztályként megadni.
File myFile = new File("/src");
String[] listOfFiles = myFile.list(new FilenameFilter() {
public boolean accept(File f, String s) { return s.endsWith(".java"); }
});
A tagosztály olyan nem statikus osztály,mely közvetlenül egy másik osztályban vagy interfészben van. A
tagosztály neve nem lehet ugyanaz, mint a befoglaló osztály neve és nem tartalmazhat statikus változót
/ metódust / osztályt sem, kivéve, ha az static final.
Címkék: osztály
Absztrakt osztály tartalmazhat static mezőket és/vagy metódusokat is, ezeket ugyanúgy
osztályreferencián keresztül lehet elérni, mint normális esetben.
Ha egy osztály implementál egy interfészt, akkor annak minden egyes metódusát implementálnia kell. Ez
alól egyetlen kivétel az absztrakt osztály. A fordító akkor sem fog panaszkodni, ha az interfész metódus
meg sem jelenik, mint absztrakt metódus az absztrakt osztályban. Ezeket majd a származtatott
osztályokban kell csak implementálni.
Címkék: interfész, osztály
Konstruktorok
Két kis feladat egy nagy multinacionális cég tesztjéből:
class X {
X(int i) {
System.out.println(i);
}
}
class Y extends X {
Y(int i) {
System.out.println(i);
}
}
A megoldás:
1)
A kód nem fordul, mert nincs az X osztályban alapértelmezett konstruktor. Feloldható az Y osztályban a
super(i);
utasítás használatával a
System.out.println(i);
helyett.
2)
Itt a helyzet hasonló, tetézve azzal, hogy a szülő konstruktorát a super kulcsszóval mindig a legelső
helyen kell meghívni.
A miértre is egyszerű a válasz, aki elolvassa az alábbiakat, meg fogja érteni. (A sietősek kedvéért
kiemeltem a lényeget.)
Ha egy osztály nem ad meg semmilyen konstruktort, akkor a fordító automatikusan biztosít egyet: ez a
paraméterek nélküli alapértelmezett konstruktor (default constructor).
Fordítási idejű hibát fogunk látni, ha utóbbi esetben az ősosztály alapértelmezett konstruktora nem
elérhető vagy nincs.
Alapértelmezett konstruktor nem dobhat semmilyen kivételt (a nem alapértelmezettek dobhatnak, ezt a
throws kulcsszó használatával kell jelezni). Az alapértelmezett konstruktor olyan elérhetőséggel
rendelkezik, amilyennel az osztályt magát deklaráljuk.
Azaz, minden osztálynak van konstruktora, akkor is, ha explicit azt nem adtuk meg. Azonban, figyeljünk
arra, hogy az alapértelmezett konstruktort csak akkor kapja meg automatikusan egy osztály, ha
semmilyen konstruktora nincs. Ha már csak egyet is megadunk, akkor ez a szabály nem él. Ez okozhat
problémákat, többek között a fenti esetekben is. Az ökölszabály tehát:
Ha bármilyen konstruktort definiálsz egy osztályban, akkor az alapértelmezettet mindenképpen meg
kell adni.
Azaz a válasz a miértre az, hogy mivel az Y osztályunk nem hívja meg az ősosztályának egyetlen
konstruktorát sem, a fordító azt feltételezi, hogy az ősosztály alapértelmezett konstruktorát hívjuk az
első helyen, de az X osztályban, mivel explicit megadtunk már egy konstruktort, a fordító nem rakja be
az alapértelmezettet, azaz nem tudja azt meghívni, így hibát jelez. Ez a helyzet a második esetben is, a
már tárgyalt super kulcsszó rossz elhelyezése mellett.
Olvasnivaló:
Java language specification 3rd edition
Java world - Understanding constructors
Java Tutorial: Constructors
Equals és a hashCode
public boolean equals(Object obj)
ellenőrzi, hogy egy másik objektum egyenlő-e azzal az objektummal, amire ezt a metódust
meghívtuk
az alapértelmezett implementáció azt ellenőrzi, hogy két objektum referenciája azonos-e
(x==y), ezt hívjuk sekély hasonlításnak (shallow comparison)
azon osztályok, melyek felüldefiniálják ezt a metódust, mély hasonlítást (deep comparison)
kell használniuk, azaz minden egyes releváns adatát össze kell hasonlítani az objektumnak
Az equals tulajdonságai:
reflexív
szimmetrikus
tranzitív
konzisztens (ha két objektum egyenlő, akkor amíg nem módosulnak, egyenlőnek kell
maradniuk)
null összehasonlítás biztos (ha null az objektum, amivel összehasonlítjuk, mindig hamisat kell
visszaadni)
ha két objektum egyenlő, hashCode-juk is azonos kell legyen
az adott objektum hash kódjának egész számú reprezentációját adja vissza
a hash alapú collecition-ök (HashTable, HashMap stb.) használják
minden osztály, ami az equals() metódust felüldefiniálja, a hashCode() metódust is felül kell,
hogy definiálja
A hashCode() tulajdonságai:
konzisztens
ha két objektum egyenlő az equals() metódus szerint, hash kódjuk is azonos kell legyen
ugyanazzal a hash kóddal rendelkező objektumok lehetnek különbözőek
Ugyan nem megkövetelt, de célszerű úgy programozni, hogy különböző objektumoknak különböző legyen
a hash kódja is, ezzel javítunk az általunk implementált típusú objektumokat tartalmazó, hash alapján
működő collection-ök teljesítményén.
Összefoglalás
Ha két objektum azonos (egyenlő), ugyanazt a hash kódot kell adniuk is, de fordítva ez nem igaz, azaz
ha két objektum azonos hash kóddal rendelkezik, attól még nem biztos, hogy egyenlőek is.
Irodalom:
Java 1.5 API
Címkék: equals, hashcode
Aki adatbázisokkal foglalkozik, annak illik ismernie az izolációs szinteket. Az izolációs szint egy olyan
tulajdonság, mely azt határozza meg, hogy az egyidejűleg futó adatbázis tranzakciók láthatják-e egymás
változtatásait, illetve ha igen, akkor hogyan. A gyakorlatban minél magasabb az izolációs szint, annál
nagyobb mértékben használ az adatbázis-kezelő zárolást (lock), azaz annál nagyobb a zárolásból eredő
többletterhelés, valamint a holtpont kialakulásának (deadlock) a lehetősége is, viszont minél
alacsonyabb az izolációs szint, annál inkább fordulhatnak elő bizonyos nemkívánatos jelenségek
adatbázisból olvasáskor.
1. Serializable
2. Repeatable reads
3. Read uncommitted
4. Read committed
Serializable
Ez a legmagasabb izolációs szint. Minden tranzakció teljesen izoláltan fut le, mintha egymás után
hajtódnának végre. Az adatbázis-kezelő végrehajthat több tranzakciót is egyszerre, de csakis akkor, ha a
soros feldolgozás látszata fenntartható, azaz ugyanazt az eredményt kapjuk, mintha a két vagy több
tranzakció egymás után hajtódott volna végre.
Read committed
Read uncommitted
Olvasási anomáliák
Phantom read
Ha egy tranzakció lefutásakor 2 azonos lekérdezés hajtódik végre egymás után és más eredménnyel
térnek vissza, akkor azt phantom read-nek nevezzük. Ez akkor fordulhat elő, ha SELECT .. WHERE
tartománylekérdezések esetén nem használunk tartomány zárolást (range lock), azaz T1 tranzakció
lekérdez egy tartományt, majd a műveletet még egyszer végrehajtja egy tranzakción belül, de a kettő
között T2 tranzakció olyan sorokat inzertál a T1 általá használ táblába,melyek kielégítik a WHERE
feltételt, azaz beleesnek a tartományba.
Non-repeatable reads
Ez akkor fordulhat elő, ha egy T1 tranzakció végrehajt egy lekérdezést, de mielőtt az befejeződne (a
COMMIT hívással), T2 tranzakció felülírja a T1 által olvasott sor(oka)t. A T1 tranzakció tehát a régi
értékekről tud, de már újak vannak a táblában.
serializable / repeatable reads izolációs szinteken a régi értékeket kell visszakapni a T1
tranzakcióból
read committed / read uncommitted izolációs szinteken az adatbázis-kezelő visszaadhatja az
új értéket is akár
Dirty reads
Ha egy T1 tranzakció olvashatja egy másik, T2 tranzakció által módosított adatokat, mielőtt a T2
befejeződne a COMMIT hívással, azt dirty read-nek nevezzük. Azaz, a tranzakciók még nem kommitált
(azaz visszahívható) adatokat is olvashatnak az adatbázisból.
Összefoglalás
Irodalom:
http://en.wikipedia.org/wiki/ACID
http://en.wikipedia.orgwiki/Isolation_level