Kihagyás

Hozzáférés szabályozása

Az előadás videója elérhető a itt.

Implementáció elrejtése

Az objektum orientált tervezés legfőbb célja az, hogy meghatározzuk a rendszer változó és nem változó dolgait. A cél, hogy az osztályt felhasználó más osztályok lehetőleg egy olyan interfészt lássanak, ami nem változik időben, így ha szükséges is módosítani a programot, az a felhasználókat nem fogja érinteni.

Osztálykönyvtárak esetén különösen fontos ez, hiszen sokkal nagyobb számban használhatják fel azokat, így elég problémás lenne, hogyha egy felismert hiba javítása miatt az összes kliensnek módosítania kellene a kódját.

Az elérés vezérlők (access specifiers) lesznek azok, amikkel szabályozni tudjuk, hogy a kód mely részét láthatják mások is, és melyek azok, amik szigorúan csak az osztály fennhatósága alá tartoznak. Javában a következő elérésekkel találkozhatunk (növekvő szigorúság szerint): public, protected, friendly (package private, nincs rá kulcsszó, azaz ha nem írunk semmit ki, akkor ez van érvényben), private. Ahhoz, hogy megértsük ezeknek a módosítóknak a lehetőségeit, jobban meg kell ismerjük, hogyan is épül fel egy összetettebb java program.

Csomagok és megadásuk

Amikor egy osztályt megírunk, akkor a terveinknek megfelelően meg kell mondjuk azt is, mely csomagba kerüljön ez az osztály. A jó tervezés az, amikor a szorosan együttműködő osztályok kerülnek egy közös csomagba, azok az osztályok, amik csak használják a másik osztályt, de a működésükről nem kell tudjanak, azok pedig különböző csomagba kerülnek.

Minden fordítási egység első sora egy olyan sor, ami a package kulcsszóval kezdődik, majd következik a package neve, ami adott esetben több elemű, pontokkal elválasztott útvonal. Ez a package név már tükrözi azt is, hogy az adott fordítási egységet fizikailag hol kell elhelyezni a projektünkben (ha IDE-t használunk, akkor persze ezt megteszi helyettünk a környezet). Tulajdonképpen a package neve egy relatív útvonal a projekt forrás könyvtárának gyökerétől.

Ha hiányzik ez a package kezdetű sor az állományunkból, akkor az a default package-be tartozik, ami gyakorlatilag a projekt forrásának gyökere.

Ha használni akarunk egy csomagot, illetve csomag elemeit, ami nem az aktuális fordítási egységünk csomagja, akkor azt a használathoz importálnunk kell:

1
2
import mypackage.*;
MyClass m;

vagy a használni kívánt elemre teljes eléréssel kell hivatkoznunk:

1
mypackage.MyClass m;

Természetesen a saját csomagot nem kell importálni, illetve a java.lang csomagot sem, ezeket bárhol hivatkozhatjuk. A default package elemeit pedig a default package-ben érjük el.

Csomagok nevei

Ahhoz, hogy a csomagokat egyértelműen tudjuk hivatkozni, a csomagneveknek egyedieknek kell lennie. Gyakorlatilag a csomag nevében a teljes elérési útvonal bele van kódolva. Ahhoz, hogy tényleg egyedi legyen minden csomag, a konvenció szerint ebbe az útvonalba bele szokták tenni az adott csomag szerzői azt a fordított domain nevet, ami azonosítja őket. Azaz esetünkben lehet például egy ilyen: hu.u-szeged.inf.java.

Persze ennek használata nem kötelező, de tegyük fel, van két egyetem, mindkettőn a lelkes Java programozók megalkotnak egy-egy szuper math csomagot, akkor ha nem lenne valami egyedi ez elnevezésekben, adott programon belül nem lehetne mind a kettőt használni, ha azok tartalmaznak azonos nevű osztályokat is.

Amikor egy osztályban egy másik osztályra hivatkozunk, akkor a virtuális gépnek kell megtalálnia, hogy a hivatkozott osztályt honnan kell betöltenie. Ebben segít neki a csomagnevek hierarchikus felépítése, illetve a CLASSPATH környezeti változó, amely megmondja, mely gyökér elemekből indulva keressen egy adott elemet a virtuális gép.

Elképzelhető, hogy két beimportált csomag tartalmaz ugyanolyan nevű elemeket. Amíg ezeket nem akarjuk használni, addig nincs gond, viszont ha használni szeretnénk valamelyiket, akkor azt a használat helyén egyértelműen meg kell mondjuk, hogy melyiket szeretnénk használni.

Java forráskódok

Egy fordítási egység nem feltétlenül áll egyetlen egy osztályból. Többet is tartalmazhat, viszont csak egyetlen lehet benne publikus, aminek a neve meg kell egyezzen a fordítási egység nevével (a .java kiterjesztés nélkül). A többi osztálya a fordítási egységnek rejtett lesz.

Amikor lefordítjuk a fordítási egységet, akkor minden egyes osztályból keletkezik egy bájtkód (.class kiterjesztésű állomány).

Javaban nincs linker, minden .class önálló egység. Arra van csak lehetőség, hogy a class állományokból egy jar-t hozzunk létre, amely tulajdonképpen egy zip fájl, ami megőrzi a classok relatív elhelyeszkedését.

A virtuális gép amikor egy .class állományt értelmez és végrehajt, meghatározza, mely egyéb classok használatára lesz szüksége, azokat (tranzitívan) betölti és feldolgozza.

Elérés vezérlése

És akkor jöjjön a lényeg. Az osztály tagjai elé oda kell írni az elérés vezérlés kulcsszavát (csak arra az egy tagra lesz érvényes). Ezek a kulcsszavak a public, protected, private, illetve ha nem írunk ki semmit, az a friendly, vagy package private elérés, ami azt jelenti, hogy a csomagon belül az elemet mindenki eléri, viszont csomagon kívülről nem elérhető. Szorosan kapcsolódó osztályokat így lehet csoportosítani.

Ha nem friendly-t használunk, akkor minden osztálynak saját magának kell megadnia, mi az, ami elérhető benne és kinek. Ami nem kell másnak, az mindig legyen private. Az interfész rész legyen public. Amit szeretnénk, hogy a leszármazottaknak (és azonos csomagban levőknek) elérhető legyen, az legyen protected.

Nemcsak adattagoknak, metódusoknak, de még a konstruktornak is megadhatjuk az elérését, ezzel pedig tudjuk egy-egy osztály direkt létrehozást korlátozni.

Azok számára, akik vizuálisabbak, foglaljuk össze a fentieket egy táblázattal, amelyben megadjuk, hogy elhelyezkedéstől függően a módosítók hogyan szabályozzák a láthatóságot:

public protected default private
saját osztályban + + + +
saját package-ben + + + -
saját package-ben + leszármazott osztályban + + - -
bárhol + - - -

Példa

Legyen egy osztályunk egy protected metódussal (azaz olyan metódus, ami csak adott csomagban + a származtatott osztályokban érhető el):

1
2
3
class Suti {
    protected void harap() {}
}

Származtassunk ebből az osztályból egy másik osztályt, amely egy publikus metódusában használja az előbbi metódust (megteheti, mert a protected tagot látjuk a származtatottban):

1
2
3
4
5
class Ludlab extends Suti {
    public void megesz() {
        harap(); /* ... */
    }
}

Ezen osztályoktól elkülönítve egy teljesen másik csomagban hozzunk létre egy osztályt, ami megpróbálja használni az előbbiek metódusait:

1
2
3
4
5
6
7
public class SutiPelda {
    public static void main(String[] args) {
        Ludlab a = new Ludlab();
        a.harap(); // nem elérhető
        a.megesz(); // ok
    }
}

Értelemszerűen a publikus metódus elérésével nincsen baj, a protectedet azonban nem engedi használni a fordító, azt csak áttételesen tudjuk elérni.

Osztály elérés vezérlése

Maguknak az osztályoknak is szabályozható a láthatósága. Ahogy az már szóba került, minden fordítási egységben legfeljebb egy publikus osztály lehet, aminek neve megegyezik a fordítási egység nevével. Ezek azok az osztályok, amiket a kliens is elérhet.

Ha egy osztály elé nem tesszük ki ezt a public elérési módosítót, akkor az package private lesz, azaz csomagon belül látszódnak, az azon kívül nem.

A private, illetve protected módosítókat csak belső osztályok elé tehetjük be (mindjárt lesz ezekről is szó).


Utolsó frissítés: 2021-05-04 07:53:32