2011. március 21., hétfő

Változók alapértelmezett értékei

Minden változónak rendelkeznie kell értékekkel, mielőtt használnánk őket. A Java nyelvben minden változó kap alapértelmezett értéket, erre a szabályok a következőek:

Minden osztályváltozó, példányváltozó vagy tömb elem az alapértelmezett  értékével inicializálódik:
  • A byte típus alapértelmezett értéke nulla, azaz (byte)0
  • A short típus alapértelmezett értéke nulla, azaz (short)0
  • Az int típus alapértelmezett értéke nulla, azaz 0
  • A long típus alapértelmezett értéke nulla, azaz 0L
  • A float típus alapértelmezett értéke (pozitív) nulla, azaz 0.0f
  • A double típus alapértelmezett értéke (pozitív) nulla, azaz 0.0d
  • A char típus alapértelmezett értéke a null karakter, azaz '\u0000'
  • A boolean típus alapértelmezett értéke hamis, azaz false
  • Referencia típusok alapértelmezett értéke null


Minden metódus paraméter a hívó által használt megfelelő argumentum értékével inicializálódik.


Minden konstruktor paraméter a példányosító kifejezés vagy explicit konstruktorhívás megfelelő argumentumának értékével inicializálódik.


Egy kivételkezelő paraméter a dobott kivételt reprezentáló objektummal 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).

2011. március 18., péntek

Kódértelmezési példa

Egy pici elgondolkodtató feladat egy interjúról:

Tyre.java:

public class Tyre {

    private int size;

    Tyre(int s) {
        size = s;
    }

    public int getSize() {
        return size;
    }

    public void setSize(int size) {
        this.size = size;
    }

}

Car.java:

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Car {

    private List<Tyre> tires = new ArrayList<Tyre>();

    public void setTires(List<Tyre> tires) {
        this.tires.clear();
        this.tires.addAll(tires);
    }

    public void addTire(Tyre tire) {
        this.tires.add(tire);
    }

    public List<Tyre> getTires() {
        return Collections.unmodifiableList(tires);
    }

}

CarTest.java:

import java.util.List;

public class CarTest {

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

        List<Tyre> carTires = car.getTires();
        System.out.println("Before: " + carTires.size());
        car.setTires(carTires);
        System.out.println("After (1): " + car.getTires().size());
        System.out.println("After (2): " + carTires.size());
    }

}

Kérdés: Mi lesz a kimenete a programnak?

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.

Beágyazott osztályok használata

  • 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

    Statikus beágyazott osztályok

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

    TopLevelClass.StaticNestedClass instance = new TopLevelClass.StaticNestedClass();
    

    Belső osztályok

    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.

    TopLevelClass top = new TopLevelClass();
    TopLevelClass.InnerClass myObject = top.new InnerClass();
    

    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.

    • a lokális osztály csak azon a blokkon belül használható, ahol definiálták
    • lokális osztály deklarálásakor nem használhatóak a public, protected, private, static kulcsszavak (ezek csak az osztály tagjainál használhatóak, lokális változó vagy osztály deklarálásakor nem)
    • a fentiek miatt statikus tagot / metódust / osztályt sem tartalmazhatnak a lokális osztályok, csak konstansokat, ha az static final - ként van deklarálva
    • a lokális osztály eléri a lokális változókat, metódus paramétereket, kivétel paramétereket is, de akkor és csak akkor, ha azok final - ként vannak deklarálva (Erre az a magyarázat, hogy mivel a lokális osztályok élettartama hosszabb lehet, mint magának a metódusnak a lefutási ideje, a fordító a lokális változókról egy másolatot hoz létre, amit a lokális osztály használni fog. A másolat és a lokális változó közötti azonosságot csak úgy garantálhatjuk, hogy a lokális változó final - ként van deklarálva.)

    Név nélküli 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, static kulcsszavak é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.

    Példa név nélküli osztály használatára

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



    Tagosztályok (member classes)

    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.



    2011. március 17., csütörtök

    Absztrakt osztályok és interfészek

    Egy osztályt az abstract kulcsszóval absztrakt osztályként lehet deklarálni, ami annyit jelent, hogy tartalmazhat absztrakt metódusokat. Absztrakt osztályt nem lehet példányosítani, csak más osztályt származtatni belőle, aminek meg kell adnia az esetleges absztrakt metódusok implementációját (ha nem adja meg az implementációkat, akkor a származtatott osztályt is absztraktként kell deklarálni).

    Egy metódust szintén az abstract kulcsszó használatával lehet absztrakt metódusként deklarálni és nem adhat meg implementációt, azaz csak deklarálni kell.

    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.

    Absztrakt osztály és interfész közötti különbségek

    • az absztrakt osztály hasonló az interfészhez, de megadhat részleges implementációt, amit a származtatott osztályok egészíthetnek ki
    • ha ansztrakt osztályunk csak absztrakt metódusokat tartalmaz, akkor azt inkább interfészként kell deklarálni
    • az interfészek biztosítják a Java nyelvben a többszörös öröklődést; egy osztály több interfészt is implementálhat egyszerre, függetlenül attól, hogy azok mennyire állnak kapcsolatban egymással (pl. Serializable, Comparable, Cloneable stb.)
    • az absztrakt osztály leszármazott osztályai közös implementációt is tartalmaznak (az absztrakt osztályban nem absztraktként definiált metódusok), de egyedi viselkedést is megadnak (ezek az absztrakt metódusok)
    • absztrakt osztály tartalmazhat nem static és final mezőket is
    • interfészekben minden metódus implicit absztraktként van deklarálva
    Absztrakt osztály mint interfész implementáció

    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.

    2011. március 9., szerda

    Konstruktorok

    Két kis feladat egy nagy multinacionális cég tesztjéből:

    1) Mi a hiba az alábbi programban? Miért?

    class X {
        X(int i) {
            System.out.println(i);
        }
    }
    
    class Y extends X {
        Y(int i) {
            System.out.println(i);
        }
    }
    

    2) Mi a hiba az alábbi kódban? Miért?

    class Base {
        int i;
        Base(int j) {
            i = j;
        }
    }
    
    class Derived extends Base {
        Derived(int j) {
            System.out.println(j);
            super(j);
        }
    }
    

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

    • Ha az osztály az Object leszármazottja, egy üres (paraméterek nélküli) alapértelmezett konstruktort kap
    • Egyéb esetben pedig a szülő alapértelmezett (paraméterek nélküli) konstruktorát hívja meg a super kulcsszó használatával

    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

    2011. március 6., vasárnap

    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

    public int hashCode()

    •  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

    Ami nagyon fontos, és el szokott siklani a figyelme sokaknak e felett:

    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

    Izolációs szintek adatbázisokban

    Adatbázisokkal kapcsolatban fontos megemlíteni, hogy a tranzakciók megbízhatóságát négy tulajdonág befolyásolja. Ezeket ACID tulajdonságoknak nevezik szaknyelven (ACID = Atomicity, Consistency, Isolation, Durability, azaz magyarul atomicitás, konzisztencia, izoláció és tartósság).

    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.

    A négy izolációs szint (fentről lefelé csökkenő sorrendben):
    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.

    • az olvasási és írási zárolás a tranzakció végén szűnik meg
    • range lock (SELECT .. WHERE lekérdezések esetén) a phantom read elkerülésére (ld. lejjebb)

    Repeatable reads

    • az írási és olvasási zárolás a tranzakció végén szűnik meg
    • nincs range lock → phantom read anomália előfordulhat

    Read committed

    • az írási zárolás a tranzakció végén szűnik meg
    • az olvasási zárolás csak a SELECT művelet végéig tart → non-repeatable reads anomália előfordulhat (ld. lejjebb)
    • nincs range lock → phantom read anomália előfordulhat

    Read uncommitted


    Ez a legalacsonyabb izolációs szint.

    • dirty reads anomália is előfordulhat (ld. lejjebb)


    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

    Izolációs szintek és anomáliák

    Izolációs szint Dirty read Non-repeatable read Phantom read
    Read uncommitted X X X
    Non-repeatable read - X X
    Repeatable read - - X
    Serializable - - -

    Izolációs szintek és a zárolási típusok

    Izolációs szint Range lock Read lock Write lock
    Read uncommitted - - -
    Non-repeatable read - - X
    Repeatable read - X X
    Serializable X X X


    Irodalom:
    http://en.wikipedia.org/wiki/ACID
    http://en.wikipedia.orgwiki/Isolation_level