11. Osztályok létrehozása

Ez a fejezet az osztályok fő alkotóelemeit mutatja be.

Az osztály definíciója 2 fő alkotóelemből áll:

az osztály deklarációból,
és az osztály törzsből.

A következő Bicycle osztály példáján bemutatjuk az osztály elemeit.

public class Bicycle {

Az osztály deklaráció az osztály kódjának az első sora. Minimálisan az osztály deklaráció meghatározza az osztály nevét. Az osztálytörzs az osztály deklarációt követi, és kapcsos zárójelek között áll. Az osztály törzs tartalmazza mindazt a kódot, amely hozzájárul az osztályból létrehozott objektumok életciklusához: konstruktorok, új objektumok inicializálására, változó deklarációk, amelyek megadják az osztály és objektumának állapotát, és eljárásokat az osztály és objektumai viselkedésének meghatározására.

    private int cadence;
    private int gear;
    private int speed;

Az osztály három tagváltozót definiál az osztálytörzsön belül. A következő konsrtruktor a tagváltozók kezdőértékeinek beállítására biztosít lehetőséget.

    public Bicycle(int startCadence, int startSpeed, int startGear) {
        gear = startGear;
        cadence = startCadence;
        speed = startSpeed;
    }

Végül négy metódus teszi teljessé az osztályt:

    public void setCadence(int newValue) {
        cadence = newValue;
    }
    public void setGear(int newValue) {
        gear = newValue;
    }
    public void applyBrake(int decrement) {
        speed -= decrement;
    }
    public void speedUp(int increment) {
        speed += increment;
    }
}

11.1. Osztályok deklarálása

Több alkalommal láthatta, hogy az osztálydefiníciók a következő formában állnak:

class MyClass {
    // tagváltozók, konstruktor és metódus deklarációk
}

A kód első sorát osztálydeklarációnak nevezzük. A megelőző osztálydeklaráció egy minimális osztálydeklaráció; csak azokat az elemeket tartalmazza, amelyek feltétlenül szükségesek. Néhány aspektusa ennek az osztálynak ugyan nincs specifikálva, alapértelmezettek. A legfontosabb az, hogy a Myclass osztály közvetlen ősosztálya az Object osztály. Több információ is megadható az osztályról, ideértve az ősosztálya nevét, azt, hogy implementál-e interfészt vagy nem, hogy lehetnek-e leszármazott osztályai és így tovább, mindezt az osztálydeklaráción belül.

A következő lista bemutatja az összes lehetséges elemet, mely előfordulhat egy osztálydeklarációban előfordulásuk szükséges sorrendjében.

  • public (opcionális) az osztály nyilvánosan hozzáférhető
  • abstract (opcionális) az osztályt nem lehet példányosítani
  • final (opcionális) az osztály nem lehet őse más osztálynak
  • class NameOfClass az osztály neve
  • extends Super (opcionális) az osztály őse
  • implement Interfaces (opcionális) az osztály által implementált interfészek
  • { osztálytörzs }az osztály működését biztosítja

11.2. Tagváltozók deklarálása

A Bicycle a következő kód szerint definiálja tagváltozóit:

    private int cadence;
    private int gear;
    private int speed;

Ez a kód deklarálja a tagváltozókat, de más változókat, mint például a lokális változókat nem. A tagváltozók deklarációja az osztálytörzsben, bármilyen konstruktoron és eljáráson kívül történik meg. Az itt deklarált tagváltozók int típusúak. Természetesen a deklarációk komplexebbek is lehetnek.

A private kulcsszó mint privát tagokat vezeti be a tagokat. Ez azt jelenti, hogy csak a Bicycle osztály tagjai férhetnek hozzájuk.

Nem csak típust, nevet és hozzáférési szintet lehet meghatározni, hanem más attribútumokat is, ideértve azt, hogy a változó-e, vagy konstans. A következő lista tartalmazza az összes lehetséges tagváltozó deklarációs elemet.

hozzáférési szint (opcionális) A következő négy hozzáférési szint szerint vezérelhető, hogy más osztályok hogyan férhessenek hozzá a tagváltozókhoz: public, protected, csomag szintű, vagy private.

  • static (opcionális) Osztályváltozót, és nem példányváltozót deklarál.
  • final (opcionális) a változó értéke végleges, meg nem változtatható (konstans) Fordítási idejű hibát okoz, ha a program megpróbál megváltoztatni egy final változót. Szokás szerint a konstans értékek nevei nagybetűvel íródnak. A következő változó deklaráció a PI változót határozza meg, melynek értéke π (.3.141592653589793), és nem lehet megváltoztatni: final double PI = 3.141592653589793;
  • transient (opcionális) a változót átmenetiként azonosítja
  • volatile (opcionális) Megakadályozza, hogy a fordító végrehajtson bizonyos optimalizálásokat a tagon.
  • típusnév: A változó típusa és neve. Mint más változóknak, a tagváltozóknak is szükséges, hogy típusa legyen. Használhatók egyszerű típusnevek, mint például az int, float vagy boolean. Továbbá használhatók referencia típusok, mint például a tömb, objektum vagy interfész nevek. Egy tagváltozó neve lehet minden megengedett azonosító, mely szokás szerint kisbetűvel kezdődik. Két vagy több tagváltozónak nem lehet megegyező neve egy osztályon belül

11.3. Metódusok deklarálása

A következő példa a setGear metódus, amely a sebességváltást teszi lehetővé:

    public void setGear(int newValue) {
        gear = newValue;
    }

Mint az osztályt, a metódust is két nagyobb rész határoz meg: a metódus deklarációja és a metódus törzse. A metódus deklaráció meghatározza az összes metódus tulajdonságát úgy, mint az elérési szint, visszatérő típus, név, és paraméterek. A metódus törzs az a rész, ahol minden művelet helyet foglal. Olyan instrukciókat tartalmaz, amelyre a metódus végrehajtásához van szükség.

A metódus deklaráció kötelező elemei: a metódus neve, visszatérő típusa, és egy zárójelpár: (). A következő táblázat megmutat minden lehetséges részt a metódus deklarációjában.

  • hozzáférési szint (opcionális) A metódus hozzáférési szintje
  • static (opcionális) Osztály metódust deklarál
  • abstract (opcionális) Jelzi, hogy a metódusnak nincs törzse
  • final (opcionális) Jelzi, hogy a metódus nem írható felül a leszármazottakban
  • native (opcionális) Jelzi, hogy a metódust más nyelven készült
  • synchronized (opcionális) A metódus kér egy monitort a szinkronizált futáshoz
  • returnType methodName Az metódus visszatérő típusa és neve
  • ( paramList ) A paraméterlista a metódushoz
  • throws exceptions (opcionális) A metódus le nem kezelt kivételei

A metódus neve

A metódus neve szokás szerint kis betűvel kezdődik, hasonlóan a változók neveihez. Általában az osztályon belül egyedi neve van a metódusnak.

Ha a metódus neve, paraméterlistája és visszatérési értéke megegyezik az ősében definiált metódussal, akkor felülírja azt.

Javában az is megengedett, hogy ugyanazzal a névvel, de különböző paraméterlistával hozzunk létre metódusokat.

Nézzük a következő példát:

public class DataArtist {
    ...
    public void draw(String s) {
        ...
    }
    public void draw(int i) {
        ...
    }
    public void draw(float f) {
        ...
    }
}

Azonos paraméterlistával, de különböző típusú visszatérési értékkel nem lehet metódusokat létrehozni.

11.4. Konstruktorok

Minden osztályban van legalább egy konstruktor. A konstruktor inicializálja az új objektumot. A neve ugyanaz kell, hogy legyen, mint az osztályé. Például a Bicycle nevű egyszerű osztálynak a konstruktora is Bicycle:

    public Bicycle(int startCadence, int startSpeed,
                   int startGear) {
        gear = startGear;
        cadence = startCadence;
        speed = startSpeed;
    }

Ebben a példában a konstruktor a paraméter alapján tudja létrehozni a kívánt méretű tömböt.

A konstruktor nem metódus, így nincs visszatérő típusa. A konstruktor a new operátor hatására hívódik meg, majd visszaadja a létrejött objektumot. Egy másik konstruktor csupán a 0 kezdőértékeket állítja be:

    public Bicycle() {
        gear = 0;
        cadence = 0;
        speed = 0;
    }

Mindkét konstruktornak ugyanaz a neve (Bicycle), de a paraméterlistájuk különböző. A metódusokhoz hasonlóan a konstruktorokat is megkülönbözteti a Java platform a paraméterek száma és típusa alapján. Ezért nem írhatunk két ugyanolyan paraméterlistával rendelkező konstruktort. (Különben a fordító nem lenne képes őket megkülönböztetni, így fordítási hibát adna.)

Amikor létrehozunk egy osztályt, meg kell adnunk, hogy az egyes példányok milyen konstruktorokkal legyen létrehozhatók. A korábban bemutatott Rectangle osztály négy konstruktort tartalmaz, és így lehetővé teszi a különböző inicializálási lehetőségek közti választást.

Nem kell konstruktorokat írni az osztályainkhoz, ha úgy is el tudja látni a feladatát. A rendszer automatikusan létrehoz egy paraméter nélküli konstruktort, különben nem tudnánk példányt létrehozni.

A konstruktor deklarációjánál használhatjuk a következő hozzáférési szinteket:

  • private Csak ez az osztály használhatja ezt a konstruktort. Ha minden konstruktorok privát, akkor az osztályban lehet egy publikus osztály metódus, mely létrehoz és inicializál egy példányt.
  • protected Az osztály és leszármazott osztályai, valamint az osztállyal azonos csomagban elhelyezkedő osztályok használhatják ezt a konstruktort.
  • public Minden osztály használhatja ezt a konstruktort.
  • nincs megadva: Csak az osztállyal azonos csomagban elhelyezkedő osztályokból lesz elérhető ez a konstruktor.

11.5. Információátadás metódus vagy konstruktor számára

A metódus vagy konstruktor deklaráció megmutatja a paraméterek számát és típusát. Például a következő metódus kiszámolja a kölcsön havi törlesztő részleteit:

public double computePayment(double loanAmt, double rate,
                             double futureValue,
                             int numPeriods) {
    double I, partial1, denominator, answer;
    I = rate / 100.0;
    partial1 = Math.pow((1 + I), (0.0 - numPeriods));
    denominator = (1 - partial1) / I;
    answer = ((-1 * loanAmt) / denominator)
               - ((futureValue * partial1) / denominator);
    return answer;
}

Ebben a metódusnak négy paramétere van: a kölcsön összege, kamata, végösszege, fizetés gyakorisága. Az első három dupla pontosságú lebegő pontos szám, míg a negyedik egész szám.

Ahogy ennél a metódusnál is látható, a metódus vagy konstruktor paraméterlistája változó deklarációk vesszővel elválasztott listája, ahol minden változó deklaráció típus-név párokkal van megadva. Amint láthatjuk a computePayment metódus törzsében a paraméternevekkel egyszerűen hivatkozunk az értékükre.

Paraméterek típusa

A metódusok vagy konstruktorok paraméterei típussal rendelkeznek. A típus lehet primitív (int, float, stb.), amint láthattuk a computePayment metódusnál, vagy referencia típusú (osztályok, tömbök esetén). A következő metódus egy tömböt lap paraméterként, majd létrehoz egy új Polygon objektumot, és inicializálja a Point objektumok tömbjéből. (Feltételezzük, hogy a Point egy osztály, amelyik x, y koordinátákat tartalmaz.)

public static Polygon polygonFrom (Point[] listOfPoints) {
    ...
}

A Java nyelv nem engedi, hogy metódust egy másik metóduson belül helyezzünk el.

Paraméter nevek

Amikor metódusban vagy konstruktorban paramétert deklarálunk, eldönthetjük, hogy milyen nevet adunk a paraméternek. Ezt a nevet használhatjuk a metódus törzsében a paraméter értékek elérésére.
A paraméter nevének egyedinek kell lennie a hatáskörén belül. Vagyis nem lehet ugyanaz, mint egy másik paraméter, vagy lokális változó vagy bármely paraméter neve egy catch záradékban ugyanazon a metóduson vagy konstruktoron belül. Lehet viszont ugyanaz a név, mint az osztály egy tagváltozója. Ebben az esetben, azt mondjuk, hogy a paraméter elfedi a tagváltozót. Ilyen elfedett tagváltozót is el lehet érni, bár kicsit körülményes. Például nézzük meg a következő Circle osztályt és a setOrigin metódust:

public class Circle {
    private int x, y, radius;
    public void setOrigin(int x, int y) {
        ...
    }
}

A Circle osztálynak három tagváltozója van: x, y és radius. A setOrigin metódus két paramétert vár, mindkettőnek ugyanaz a neve, mint a tagváltozónak. A metódus mindkét paramétere elrejti az azonos nevű tagváltozót. Ha a metódus törzsében használjuk az x vagy y nevet, akkor a paraméterekre és nem a tagváltozókra hivatkozunk. A tagváltozó eléréséhez minősített nevet kell használni, amiről később olvashat.

Paraméter-átadás érték szerint

A paraméter-átadás érték szerint történik. Amikor meghívunk egy metódust vagy egy konstruktort, akkor a metódus megkapja az érték másolatát. Amikor a paraméter referencia típusú, akkor a referencián keresztül ugyanazt az objektumot érhetjük el, mivel csak a referencia értéke másolódott át: meghívhatjuk az objektumok metódusait és módosíthatjuk az objektum publikus változóit.

Nézzük meg a következő getRGBColor metódust, amelyik megkísérli visszaadni a paraméterein keresztül a tagváltozói értékeit:

public class Pen {
    private int redValue, greenValue, blueValue;
    ...
    public void getRGBColor(int red, int green, int blue) {
        red = redValue;
        green = greenValue;
        blue = blueValue;
    }
}

Ez így nem működik. A red, green és blue változó létezik ugyan, de a hatásköre a getRGBColor metóduson belül van. Amikor a metódusból visszatér a program, ezek a változók megszűnnek.

Írjuk újra a getRGBColor metódust, hogy az történjen, amit szerettünk volna. Először is, kell egy új RGBColor típus, hogy megőrizze a red, green és blue színek értékét:

public class RGBColor {
    public int red, green, blue;
}

Most már átírhatjuk a getRGBColor metódust, hogy paraméterként RGBColor objektumot várjon. A getRGBColor metódus visszatér az aktuális toll színével a beállított red, green és blue tagváltozó értékével az RGBColor paraméter segítségével:

public class Pen {
    private int redValue, greenValue, blueValue;
    ...
    public void getRGBColor(RGBColor aColor) {
        aColor.red = redValue;
        aColor.green = greenValue;
        aColor.blue = blueValue;
    }
}

Az így visszaadott RBColor objektum a getRGBColor metóduson kívül is megtartja értékét, mivel az aColor egy objektumra hivatkozik, amely a metódus hatáskörén kívül létezik.

Megjegyzés: Természetesen az is egy – bizonyos értelemben egyszerűbb – megoldás lehetett volna, hogy a három érték lekérdezéséhez három lekérdező metódust készítünk. Így nem lett volna szükség egy új típus bevezetésére. E megoldás a következő pont elolvasása után el is készíthető.

11.6. A metódusok visszatérési értéke

A metódusok visszatérési értékének típusa a metódus deklarációjakor adható meg. A metóduson belül a return utasítással lehet a visszaadott értéket előállítani. A void-ként deklarált metódusok nem adnak vissza értéket, és nem kell, hogy tartalmazzanak return utasítást. Minden olyan metódus, amely nem void-ként lett deklarálva, kötelezően tartalmaz return utasítást. Sőt a fordító azt is kikényszeríti, hogy minden lehetséges végrehajtási ág return utasítással végződjön.

Tekintsük a következő isEmpty metódust a Stack osztályból:

public boolean isEmpty() {
    if (top == 0) {
        return true;
    } else {
        return false;
    }
}

A visszaadott érték adattípusa meg kell, hogy egyezzen a metódus deklarált visszatérési értékével; ezért nem lehetséges egész számot visszaadni olyan metódusból, aminek logikai a visszatérési értéktípusa. A fenti isEmpty metódus deklarált visszatérési érték típusa logikai (boolean), és a metódus megvalósítása szerint igaz (true), vagy hamis (false) logikai érték kerül visszaadásra a teszt eredményének megfelelően.

Megjegyzés: A fenti kód helyett általában tömörebb írásmódot szokás alkalmazni. Ennek ellenére ebben a jegyzetben a jobb érthetőség kedvéért időnként a hosszabb formát alkalmazzuk. A következő kód az előzővel teljesen ekvivalens:

public boolean isEmpty() {
    return top == 0;
}

Az isEmpty metódus elemi (vagy primitív) típust ad vissza. Egy metódus referencia adattípust is visszaadhat. Például ha a Stack osztály definiálja a pop metódust, amely az Object referencia adattípust adja vissza:

public Object pop() {
    if (top == 0) {
        throw new EmptyStackException();
    }
    Object obj = items[--top];
    items[top] = null;
    return obj;
}

Amikor egy metódus visszatérési típusa egy osztály, (mint ahogy a fenti pop esetén), a visszaadott objektum típusa meg kell, hogy egyezzen a visszatérési típussal, vagy leszármazottja kell, hogy legyen annak. Tegyük fel, hogy van egy olyan osztályhierarchia, amelyben ImaginaryNumber a java.lang.Number leszármazott osztálya, ami viszont az Object leszármazott osztálya. Ezt mutatja az alábbi ábra:

Ezek után tegyük fel, hogy egy metódus úgy kerül deklarálásra, hogy egy Number-t ad vissza:

public Number returnANumber() {
    ...
}

A returnANumber metódus visszaadhat egy ImaginaryNumber típust, de nem adhat vissza Object típust. ImaginaryNumber egy Number, mivel a Number leszármazott osztálya. Ugyanakkor az Object nem feltétlenül Number is egyben, – lehet akár String, vagy akár egész más típusú is.

Interfész neveket is lehet visszatérési típusként használni. Ebben az esetben a visszaadott objektumnak a megadott interfészt (vagy leszármazottját) kell implementálnia.

11.7. A this kulcsszó használata

A példa metóduson, vagy a konstruktoron belül a this az aktuális objektumra való hivatkozást (referenciát) jelenti – azaz arra az objektumra, amelynek a metódusa vagy a konstruktora meghívásra kerül. Az aktuális objektum bármelyik tagja hivatkozható egy példány metódusból, vagy egy konstruktorból a this használatával. A leggyakoribb használat oka az, hogy egy változó tag egy paraméter által kerül elfedésre a metódus vagy a konstruktor számára.

Például a következő konstruktor a HSBColor osztály számára inicializálja az objektum tagváltozóit a konstruktornak átadott paramétereknek megfelelően. A konstruktor mindegyik paramétere elfed egy-egy objektum tagváltozót, ezért az objektum tagváltozóira a konstruktorban a this megadással kell hivatkozzon:

public class HSBColor {
    private int hue, saturation, brightness;
    public HSBColor (int hue, int saturation, int brightness){
        this.hue = hue;
        this.saturation = saturation;
        this.brightness = brightness;
    }
}

A konstruktoron belül a this kulcsszó használható arra is, hogy egy ugyanabba az osztályba tartozó másik konstruktort meghívjunk. Ezt a metódust explicit konstruktor hívásnak nevezzük. Az alábbi Rectangle osztály másként kerül implementálásra, mint korábbi megoldás.

public class Rectangle {
    private int x, y;
    private int width, height;
    public Rectangle() {
        this(0, 0, 0, 0);
    }
    public Rectangle(int width, int height) {
        this(0, 0, width, height);
    }
    public Rectangle(int x, int y, int width, int height) {
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
    }
    ...
}

Ez az osztály konstruktorok halmazát tartalmazza. Mindegyik konstruktor inicializálja a négyszög (Rectangle) tagváltozóinak egy részét, vagy az összeset. A konstruktorok konstans kezdőértéket adnak minden olyan tagváltozónak, amelynek nem ad értéket valamelyik paraméter. Például a paraméter nélküli konstruktor a négy paraméteres konstruktort hívja, és nullákat ad kezdőértékeknek.

Ahogy az eddigiekben is, a fordítóprogram a paraméterek száma és típusa alapján határozza meg, hogy melyik konstruktort kell meghívni.

Amennyiben használjuk, úgy az explicit konstruktorhívás a konstruktor első utasítása kell, hogy legyen.

11.8. Osztálytagok elérhetőségének felügyelete

Egy elérési szint meghatározza, hogy lehetséges-e más osztályok számára használni egy adott tagváltozót, illetve meghívni egy adott metódust. A Java programozási nyelv négy elérési szintet biztosít a tagváltozók és a metódusok számára. Ezek a private, protected, public, és amennyiben nincsen jelezve, a csomag szintű elérhetőség. Az alábbi tábla az egyes szintek láthatóságát mutatja:

osztály csomag leszármazott összes
private I N N N
nincs I I N N
protected I I I N
public I I I I

Az első oszlop azt mutatja, hogy maga az osztály elérheti-e az adott jelzővel megjelölt tagokat. Ahogy az látható, az osztály mindig elérheti saját tagjait. A második oszlop azt mutatja, hogy az eredeti osztállyal azonos csomagban lévő más osztályok – függetlenül a szülői kapcsolatoktól – elérhetik-e a tagokat. Egy csomagban lévő csoportok osztályokkal és interfészekkel állnak kapcsolatban, továbbá elérési védelmet és tárterület felügyeletet biztosítanak. (A csomagokról egy későbbi fejezetben lesz szó.) A harmadik oszlop azt jelzi, hogy az osztály leszármazottai elérhetik-e a tagokat – függetlenül attól, hogy melyik csomagban vannak. A negyedik oszlop pedig azt jelzi, hogy az összes osztály elérheti-e a tagokat.

Az elérési szintek két módon hatnak. Az első mód az, amikor külső forrásból származó osztályokat (például a Java platform osztályait) használjuk, ekkor az elérési szintek meghatározzák, hogy ezen osztályok melyik tagjait tudjuk elérni. A másik mód az, hogy amennyiben saját osztályokat írunk, meghatározhatjuk az elérési szintet minden tagváltozóhoz, metódushoz, illetve konstruktorhoz, amelyek az osztályban szerepelnek.

Tekintsük az osztályok egy halmazát, és nézzük, hogyan működnek az elérési szintek. A következő ábra az alábbi példában szereplő négy osztályt és a közöttük fennálló kapcsolatokat mutatja:

Package

11.8.1. Osztály elérési szint

Nézzük az Alpha osztály listáját. Ennek tagjait más osztályok is el akarják érni. Az Alpha elérési szintenként egy tagváltozót és egy metódust tartalmaz. Az Alpha egy One nevű csomagban van:

package One;
public class Alpha {
    private   int iamprivate = 1;
              int iampackage = 2;  //csomag elérés
    protected int iamprotected = 3;
    public    int iampublic = 4;
    private void privateMethod() {
        System.out.println("iamprivate Method");
    }
    void packageMethod() { // csomag elérés
        System.out.println("iampackage Method");
    }
    protected void protectedMethod() {
        System.out.println("iamprotected Method");
    }
    public void publicMethod() {
        System.out.println("iampublic Method");
    }
    public static void main(String[] args) {
        Alpha a = new Alpha();
        a.privateMethod();   // megengedett
        a.packageMethod();   // megengedett
        a.protectedMethod(); // megengedett
        a.publicMethod();    // megengedett
        System.out.println("iamprivate: "
            + a.iamprivate);    // megengedett
        System.out.println("iampackage: "
            + a.iampackage);    // megengedett
        System.out.println("iamprotected: "
            + a.iamprotected"); // megengedett
        System.out.println("iampublic: "
            + a.iampublic);     // megengedett
    }
}

Ahogy az látható, az Alpha úgy hivatkozik valamennyi tagváltozójára és valamennyi metódusára, ahogy az az előző táblázat osztály oszlopában szerepelt. A program kimenete a következő lesz:

iamprivate Method
iampackage Method
iamprotected Method
iampublic Method
iamprivate: 1
iampackage: 2
iamprotected: 3
iampublic: 4

Egy tag elérési szintje azt határozza meg, hogy melyik osztályok érhetik el az illető tagot, és nem azt, hogy melyik példányok érhetik el. Például egy osztály valamennyi példánya elérheti egy másik publikus tagjait.

Az Alpha osztályhoz hozzávehetünk egy példány metódust, ami összehasonlítja az aktuális Alpha objektumot (this) egy másik objektummal az iamprivate változói alapján:

package One;
public class Alpha {
    ...
    public boolean isEqualTo(Alpha anotherAlpha) {
        if (this.iamprivate == anotherAlpha.iamprivate) {
        //megengedett
            return true;
        } else {
            return false;
        }
    }
}

11.8.2. Csomag elérési szint

Tekintsük a következő DeltaOne osztályt, amely az Alpha-val azonos csomagba tartozik. Az előző táblázat csomag oszlopa meghatározza azokat a változókat és metódusokat, amelyeket ez az osztály használhat.

package One;
public class DeltaOne {
    public static void main(String[] args) }
        Alpha a = new Alpha();
        //a.privateMethod();  // nem megengedett
        a.packageMethod();    // megengedett
        a.protectedMethod();  // megengedett
        a.publicMethod();     // megengedett
        //System.out.println("iamprivate: "
        //  + a.iamprivate);   // nem megengedett
        System.out.println("iampackage: " + a.iampackage);   // megengedett
        System.out.println("iamprotected: " + a.iamprotected); // megengedett
        System.out.println("iampublic: " + a.iampublic);    // megengedett
    }
}

A DeltaOne nem hivatkozhat az iamprivate változóra, és nem hívhatja meg a privateMethod metódust, ugyanakkor elérheti az Alpha többi tagját. Amennyiben a kommentezett sorok elől eltávolítjuk a // jelölést, és így próbáljuk meg lefordítani az osztályt, úgy a fordítóprogram hibát fog jelezni. A megjegyzésekkel futtatva a következő eredményt kapjuk:

iampackage Method
iamprotected Method
iampublic Method
iampackage: 2
iamprotected: 3
iampublic: 4

11.8.3. Leszármazott osztály elérési szint

A következő, AlphaTwo nevű osztály az Alpha leszármazott osztálya, de egy másik csomagban található. Az előző táblázat leszármazott oszlopa jelzi, hogy melyik tagváltozókat és metódusokat lehet használni:

package two;
import one.*;
public class AlphaTwo extends Alpha {
    public static void main(String[] args) {
        Alpha a = new Alpha();
        //a.privateMethod();   // nem megengedett
        //a.packageMethod();   // nem megengedett
        //a.protectedMethod(); // nem megengedett
        a.publicMethod()       // megengedett
        //System.out.println("iamprivate: " + a.iamprivate);    // nem megengedett
        //System.out.println("iampackage: " + a.iampackage);    // nem megengedett
        //System.out.println("iamprotected: " + a.iamprotected);  // nem megengedett
        System.out.println("iampublic " + a.iampublic);      // megengedett
        AlphaTwo a2 = new AlphaTwo();
        a2.protectedMethod();   // megengedett
        System.out.println("iamprotected: " + a2.iamprotected); // megengedett
    }
}

Vegyük észre, hogy AlphaTwo nem hívhatja a protectedMethod metódust, és nem érheti el az iamprotected tagot az Alpha példányban (ez az ősosztály), de hívhatja a protectedMethod metódust és elérheti az iamprotected-et az AlphaTwo példányában (vagy AlphaTwo leszármazott osztály példányában). Más szóval a protected elérési szint csak azt teszi lehetővé egy leszármazott osztály számára, hogy egy védett (protected) tagot csak akkor tudjon elérni, ha az illető tag hivatkozása ugyanolyan típusú osztályban, vagy leszármazott osztályban van. Az AlphaTwo futásának eredménye a következő lesz:

iampublic Method
iampublic: 4
iamprotected Method
iamprotected: 3

11.8.4. Nyilvános elérési szint

Végül a következő DeltaTwo nem kapcsolódik az osztály hierarchiában Alpha-hoz, és más csomagban is van, mint Alpha. Az előző táblázat utolsó oszlopa szerint DeltaTwo csak az Alpha nyilvános (public) tagjait érheti el.

package two;
import one.*;
public class DeltaTwo {
    public static void main(String[] args) {
        Alpha alpha = new Alpha();
        //alpha.privateMethod();   // nem megengedett
        //alpha.packageMethod();   // nem megengedett
        //alpha.protectedMethod(); // nem megengedett
        alpha.publicMethod();      // megengedett
        //System.out.println("iamprivate: " + a.iamprivate);      // nem megengedett
        //System.out.println("iampackage: " + a.iampackage);      // nem megengedett
        //System.out.println("iamprotected: " + a.iamprotected);    // nem megengedett
        System.out.println("iampublic: " + a.iampublic);        // megengedett
    }
}

A DeltaTwo outputja a következő lesz:

iampublic Method
iampublic: 4

Ha más programozók is használnak egy kész osztályt, szükséges lehet annak biztosítása, hogy a téves használat ne vezessen hibákhoz. Az elérési szintek segíthetnek ebben. A következő javaslatok segítenek annak meghatározásában, hogy egy adott taghoz melyik elérési szint a legmegfelelőbb.
Használjuk a leginkább korlátozó, még észszerű elérési szintet az egyes tagokra. Hacsak valami különösen nem mond ellene, használjuk a private szintet.
Kerüljük a publikus tagváltozókat, kivéve a konstansok estében. A publikus tagváltozók használata oda vezethet, hogy valamelyik speciális implementációhoz fog kapcsolódni a program, és ez hibákat, tévedéseket fog eredményezni. Továbbá, ha egy tagváltozót csak a hívó metódus tud megváltoztatni, akkor ezt a változást jelezni lehet a többi osztály vagy objektum felé. Ugyanakkor a változás jelzése lehetetlen, ha egy tagváltozó publikus elérésű. A publikus elérés biztosítása ugyanakkor teljesítmény nyereséget eredményezhet.

Megjegyzés: Ebben az oktatási anyagban sok példa használ publikus tagváltozókat. A példák és elvi kódrészletek nem szükségszerűen felelnek meg azoknak a szigorú tervezési szabályoknak, amik egy API számára előírások.

  • Korlátozzuk a védett (protected) és a csomag (package) elérésű tagváltozók számát.
  • Ha egy tagváltozó JavaBeans tulajdonság, akkor az kötelezően private elérésű.

11.9. A példányok és az osztály tagok

Az osztályokról és az osztály tagokról már volt szó a nyelvi alapismeretek részben. Ez a rész bemutatja, hogy hogyan deklarálhatunk osztályt és osztálypéldányt. A következő osztály (AClass) deklarál egy példányváltozót, egy példánymetódust, egy osztályváltozót, egy osztály metódust, és végül a main metódust, ami szintén osztály metódus.

public class AClass {
    public int instanceInteger = 0;
    public int instanceMethod() {
        return instanceInteger;
    }
    public static int classInteger = 0;
    public static int classMethod() {
        return classInteger;
    }
    public static void main(String[] args) {
        AClass anInstance = new AClass();
        AClass anotherInstance = new Aclass();
        anInstance.instanceInteger = 1;
        anotherInstance.instanceInteger = 2;
        System.out.println(anInstance.instanceMethod());
        System.out.println(anotherInstance.instanceMethod());
        //System.out.println(instanceMethod());    //illegal
        //System.out.println(instanceInteger);     //illegal
        AClass.classInteger = 7;
        System.out.println(classMethod());
        System.out.println(anInstance.classMethod());
        anInstance.classInteger = 9;
        System.out.println(anInstance.classMethod());
        System.out.println(anotherInstance.classMethod());
    }
}

Itt látható a program kimenete:

1
2
7
7
9
9

Ha nincs egyéb meghatározás, akkor egy osztályon belül deklarált tag példány tag lesz. Így az instanceInteger és az instanceMethod mindketten példány tagok. A futtató rendszer a program összes példányváltozójából objektumonként készít egy példányt. Így az anInstance és az anotherInstance objektumok tartalmaznak egy-egy instanceInteger tagváltozót.

Hozzá tudunk férni a példányokhoz és meghívhatunk egy példánymetódust egy hivatkozáson keresztül. Ha az illegal felirattal megjelölt sorok elejéről kitöröljük a //-t, és megpróbáljuk lefordítani a programot, akkor egy hibaüzenetet kapunk.

Egy osztálytag a static módosítóval kerül deklarálásra. A main metóduson kívül az AClass deklarál egy osztályváltozót és egy osztálymetódust, melyeket classInteger-nek és classMethod-nak hívnak. A futtató rendszer osztályonként lefoglal egy osztályváltozót, függetlenül az osztály által lefoglalt példányok számától. A rendszer lefoglalja a memóriát az osztályváltozónak, legkésőbb akkor, amikor az először felhasználásra kerül.
Az osztály minden példányában elérhetőek az osztály osztályváltozói. Hozzáférhetünk az osztályváltozóhoz a példányokon keresztül, valamint az osztályon keresztül is. Hasonlóképpen egy osztály metódus is elérhető az osztályban vagy egy példányon keresztül. Megjegyezzük, hogyha a program megváltoztatja a classVariable értékét, akkor az megváltozik az összes osztálypéldányban is.

11.9.1 A példányok és az osztály tagok inicializálása

Osztály vagy példányváltozónak a deklarációnál adhatunk legegyszerűbben kezdőértéket:

public class BedAndBreakfast {
    public static final int MAX_CAPACITY = 10;
    private boolean full = false;
}

Ez jól működik egyszerű adattípusok esetében. Akkor is működik, ha tömböket vagy osztályokat készítünk. De vannak korlátai is:
Az inicializálás csak kifejezést tartalmazhat, nem lehet pl. egy if-else utasítás.

Az inicializáló kifejezés nem hívhat olyan függvényt, amely futásidejű kivételt dobhat. Ha olyan függvényt hív, amely futásidejű kivételt dob, mint pl. a NullPointerException, nem tudjuk a kivételt elkapni.

Ha ezek a korlátok gátolnak abban, hogy tagváltozót inicializáljunk a deklarációban, az inicializáló kód máshová is elhelyezhető. Egy osztályváltozó inicializálásához tegyük a kódot a statikus inicializáló blokkba, ahogy a következő példa mutatja. Egy példány inicializálásához tegyük a kódot a konstruktorba.

Statikus inicializáló blokk használata

Itt egy példa a statikus inicializáló blokkra:

import java.util.ResourceBundle;
class Errors {
    static ResourceBundle errorStrings;
    static {
        try {
            errorStrings =
               ResourceBundle.getBundle("ErrorStrings");
        } catch (java.util.MissingResourceException e) {
            //error recovery code here
        }
    }
}

A statikus inicializáló blokk a static kulcsszóval kezdődik, és mint általában, itt is kapcsos zárójelek közé tesszük a kódot. Az errorStrings a statikus inicializáló blokkban kerül inicializálásra, mert a getBundle metódus dobhat kivételt.

Egy osztály tartalmazhat bármennyi statikus inicializáló blokkot, amelyek bárhol lehetnek az osztály törzsében. A futtatórendszer garantálja, hogy a forráskódban elfoglalt helyük sorrendjében kerülnek meghívásra az inicializáló blokkok, még mielőtt a legelső alkalommal használná az így inicializált osztályváltozókat a program.

Példányváltozók inicializálása

Példányváltozók inicializálása az osztály konstruktorában is történhet. Ha az előző példában szereplő errorStrings példányváltozó lenne, akkor az inicializáló kódot az osztály konstruktorába tehetjük, az alábbi példa szerint:

import java.util.ResourceBundle;
class Errors {
    ResourceBundle errorStrings;
    Errors() {
        try {
            errorStrings =
              ResourceBundle.getBundle("ErrorStrings");
        } catch (java.util.MissingResourceException e) {
            //error recovery code here
        }
    }
}

11.10. Ellenőrző kérdések

  • Mi az információelrejtés előnye, és hogyan valósul meg Javában?
  • Mi az üzenet? Hogyan valósul meg Javában?
  • Mi az osztály? Hogyan hozzuk létre Javában?
  • Mi az objektum? Hogyan hozunk létre Javában?
  • Mi a metódus aláírása (szignatúrája)?
  • Mi a void típus?
  • Mik játszódnak le egy metódus híváskor?
  • Hogyan adja vissza a metódus a visszatérési értékét?
  • Mi történik a metódus által deklarált változókkal?
  • Mi az objektum, mi az osztály és mi a kapcsolatuk?
  • Mi a különbség a példányváltozó és az osztályváltozó között?
  • Mi az objektumreferencia?
  • Mit jelent a null?
  • Hogyan hívhatjuk meg egy objektum metódusait az objektumreferencián keresztül?
  • Mi a konstruktor?
  • Hogyan hívjuk meg a konstruktort?
  • Mi az alapértelmezett konstruktor?
  • Mikor generál alapértelmezett konstruktort a fordító maga?
  • Hogyan hivatkozhatunk egy objektum példányváltozóira az objektumreferencián keresztül?
  • Mit jelent az, hogy a Jáva rendszerben egy szemétgyűjtő működik? Mik ennek a következményei?
  • Mi a statikus változó?
  • Elérhető-e a statikus változó nem statikus metódusból?
  • Milyen referenciával lehet elérni a statikus változót?
  • Mi a statikus metódus?
  • Elérhet-e példányváltozót statikus metódus?

Igaz vagy hamis? Indokolja!

  • Az objektumot létrehozásakor inicializálni kell.
  • Az objektum kezdeti állapotát a konstruktor állítja be.
  • Az osztálymetódus elvileg elérheti a példányváltozót.
  • A példánymetódus elvileg elérheti az osztályváltozót.
  • A this a megszólított objektum referenciája.
  • A konstruktor visszatérési értéke boolean.
  • A konstruktor neveként ajánlatos az osztály nevét adni, de ez nem kötelező.
  • Egy statikus metódus meghívhatja ugyanazon osztály egy nem statikus metódusát a this kulcsszó segítségével.
  • Az osztály konstruktorából meghívható az osztály egy másik, túlterhelt konstruktora, annak nevére való hivatkozással.
  • Egy osztálynak minden esetben van paraméter nélküli konstruktora.
  • Ha az osztálynak nincs explicit konstruktora, akkor a rendszer megad egy alapértelmezés szerinti, paraméter nélkülit.
  • A konstruktor lehet final.
  • A konstruktor blokkja lehet üres.
  • Az osztályinicializáló blokk beállítja az objektum kezdeti értékeit.
  • Az inicializálók közül először futnak le az osztályinicializálók, és csak azután kerülnek végrehajtásra a példányinicializálók.
  • Egy objektum létrehozható saját osztályából de csak osztálymetódusból.

A következő osztály esetén melyik a helyes konstruktor definíció?

public class Test {
    ....
}
  • public void Test() {…}
  • public Test() {…}
  • public static Test() {…}
  • public static void Test() {…}

A következő metódus esetén milyen típusú kifejezést írjunk a return után?

  • public void add(int a) {...}
  • void
  • int
  • semmit