17. Csomagok

A típusok könnyebb megtalálásához és használatához, névütközések elkerüléséhez és az elérés szabályozásához a programozók egybe csomagolhatják az összetartozó típusaikat csomagokká.

Definíció: A csomag összetartozó típusok gyűjteménye.

A Java platform típusai funkciók szerint különböző csomagokba vannak szervezve: az alapvető osztályok a java.lang csomagban, az I/O osztályok a java.io-ban, és így tovább. Ezen kívül a saját típusainkat is tehetjük csomagokba.

A következő osztályokat megvizsgálva látszik, hogy közös csomagba érdemes őket sorolni, mivel grafikus objektumok csoportjába tartoznak a körök, téglalapok, vonalak és pontok. Ha írunk egy Draggable interfészt, az azt megvalósító osztályok lehetővé teszik a vonszolást is.

//in the Graphic.java file
public abstract class Graphic {
    . . .
}
//in the Circle.java file
public class Circle extends Graphic implements Draggable {
    . . .
}
//in the Rectangle.java file
public class Rectangle extends Graphic implements Draggable {
    . . .
}
//in the Draggable.java file
public interface Draggable {
    . . .
}

A következő okok miatt érdemes az osztályokat és interfészeket közös csomagba helyezni:

  • Más programozók számára is látszik, hogy kapcsolódó típusokról van szó.
  • Más programozók is láthatják, hol kell keresni a grafikához kapcsolódó osztályokat.
  • A típusaink nevei nem kerülnek összeütközésbe más csomagok neveivel, mert a csomagok önálló névtereket hoznak létre.
  • A típusaink korlátlanul láthatják egymást, de egyéb típusok csak korlátozottan férhetnek a
    típusokhoz.

17.1. Csomag létrehozása

A csomag létrehozásához mindössze bele kell tenni egy típust (osztály, interfész). A csomag megnevezést a forrásállomány elején, a típusdefiníciók előtt kell megtenni.

package graphics;

public class Circle extends Graphic implements Draggable {
    . . .
}

Ez után a graphics csomag összes forráskódja elején ugyanezt a csomagmegjelölést kell alkalmaznunk, hogy közös csomagba kerüljenek:

package graphics;

public class Rectangle extends Graphic implements Draggable {
    . . .
}

Ha nem alkalmazunk csomagmegjelölést, akkor az osztály(ok) egy úgynevezett alapértelmezett név nélküli csomagba kerülnek.

17.2. Egy csomag elnevezése

Az egész világon írnak a programozók a Java programnyelvhez osztályokat, interfészeket, kulcsszavakat és megjegyzéseket, és valószínűleg két programozó ugyanazt a nevet két különböző feladatú osztálynál fogja használni. Valójában, az előző példa esetén, amikor definiálunk egy Rectangle osztályt, akkor a Rectangle osztály már benne van a java.awt csomagban. A fordító mégis engedélyezi két osztálynak ugyanazt a nevet. Miért? Mert azok különböző csomagokban vannak, és mindegyik osztálynak a teljes neve magába foglalja a csomag nevét. Tehát a graphics csomagban levő Rectangle osztály teljes neve graphics.Rectangle, és a java.awt csomagban levő Rectangle osztály teljes neve java.awt.Rectangle. Ez rendszerint csak akkor működik jól, hogyha két egymástól független programozó nem ugyanazt a nevet adja a csomagoknak. Mivel hárítható el ez a probléma? Megállapodással.

Megállapodás: Fordított domain (tartomány) nevet használnak a csomagok nevének, ilyen módon: com.company.package. Névütközés előfordulhat egyetlen cégen belül is, amit a cégnek le kell kezelni egy belső megállapodással. Lehet, hogy emiatt tartalmazni fog tartomány vagy projekt neveket a társaság neve után, mint például com.company.region.package.

17.3. Csomag tagok használata

Csak a publikus csomag tagok érhetőek el a csomagon kívül. Ahhoz hogy használjunk egy publikus csomag tagot a csomagján kívülről, a következők valamelyikét kell tennünk (vagy akár többet):

  • A teljes (vagy más néven minősített) nevén keresztül kell hivatkoznunk rá
  • Importáljuk a csomag tagot
  • Importáljuk a tag teljes csomagját

Mindegyiket különböző szituációkban alkalmazhatjuk, amelyeket a következő részekben tisztázunk.

Név szerinti hivatkozás egy csomag tagra

Eddig a példákban a típusokra az egyszerű nevükön keresztül hivatkoztunk, mint például Rectangle. Akkor használhatjuk egy csomag tagjának az egyszerű nevét, ha az osztály ugyanabban a csomagban van, mint a tag, vagy ha a tag importálva van.

Ha megpróbálunk használni egy tagot egy másik csomagból, és a csomagot nem importáltuk, akkor a tag teljes nevét használnunk kell. A Rectangle osztály teljes neve:
graphics.Rectangle

A következőképpen használhatjuk a minősített nevet, hogy létrehozzuk a graphics.Rectangle osztály egy példányát:
graphics.Rectangle myRect = new graphics.Rectangle();

Ha minősített neveket használunk, valószínűleg bosszantó lesz, hogy újra és újra be kell gépelni a graphics.Rectangle-t. Ezen kívül rendezetlen és nehezen olvasható programot kapunk. Ilyen esetekben inkább importáljuk a tagot.

Egy csomag tag importálása

Ahhoz hogy importálhassuk a megadott tagot az aktuális fájlban, a fájl elején ki kell adni az import utasítást az osztály vagy interfész definiálása előtt, de a package utasítás után, ha van olyan. Úgy tudjuk importálni a Circle osztályt a graphics csomagból, hogy:
import graphics.Circle;

Most már tudunk hivatkozni a Circle osztályra az egyszerű nevével:
Circle myCircle = new Circle();

Ez a szemlélet akkor működik jól, ha csak néhány tagot használunk a graphics csomagból. De ha sok típusát használjuk egy csomagnak, akkor inkább importáljuk az egész csomagot.

Egy teljes csomag importálása

Ahhoz hogy egy csomagnak az összes típusát importáljuk, az import utasítást a csillag (*) helyettesítő karakterrel kell használnunk:
import graphics.*;

Most már hivatkozhatunk bármelyik osztályra vagy interfészre a graphics csomagból az egyszerű rövid nevével:

Circle myCircle = new Circle();
Rectangle myRectangle = new Rectangle();

Az import utasításban a csillag karakterrel az összes osztályát megadjuk a csomagnak. Nem használhatunk olyan megfeleltetést, amely egy csomagban egy osztály részhalmazára utal. Például helytelen megfeleltetés az, hogy a graphics csomagból az ’A’ betűvel kezdődő összes osztály importáljuk:
import graphics.A*;

Ez az utasítás fordítási hibát generál. Az import utasítással, egy csomagnak egyetlen tagját vagy egész csomagot importálhatunk.

Ugyanígy nem megengedett, hogy egyszerre több csomagot importáljunk, pl.:
import graphics.*.*;

Megjegyzés: Ha kevesebb tagot akarunk importálni, akkor megengedett, hogy csak egy osztályt, és annak a belső osztályait importáljuk. Például, ha a graphics.Rectangle osztálynak a belső osztályait akarjuk használni, mint Rectangle.DoubleWide és Rectangle.Square, a következőképpen importálhatjuk:
import graphics.Rectangle.*;

Könnyítésül a Java fordító automatikusan importál három teljes csomagot:

  • A névtelen csomagot (ha nem hozunk létre csomagot, vagyis nem használjuk a package utasítást)
  • Az alapértelmezés szerinti aktuális csomagot (ha létrehozunk csomagot)
  • A java.lang csomagot

Megjegyzés: A csomagok nem hierarchikusak. Ha például importáljuk a java.util.*-ot, nem hivatkozhatunk a Pattern osztályra. Minden esetben úgy kell hivatkoznunk, hogy java.util.regex.Pattern, vagy ha importáljuk a java.util.regex.*-ot, akkor csak egyszerűen Pattern.

Névütközés megszüntetése

Ha egy tag ugyanazon a néven megtalálható egy másik csomagban, és mindkét csomag importálva van, akkor a tagra teljes nevével kell hivatkoznunk. Például a Rectangle osztály definiálva van a graphics csomagban, és a java.awt csomagban is. Ha a graphics és java.awt is importálva van, akkor a következő nem egyértelmű:
Rectangle rect;

Az ilyen esetekben a minősített nevet kell használnunk, hogy pontosan meg tudjuk adni, melyik Rectangle osztályt akarjuk használni:
graphics.Rectangle rect;

17.4. Forrás és osztály fájlok menedzselése

A Java környezet sok megvalósításánál számít a hierarchikus fájlrendszer a forrás és osztály állományok menedzseléséhez, bár maga a Java nem igényli ezt. A stratégia a következő.

A forráskód egy osztályt, interfészt vagy felsorolt típust tartalmaz, amelynek neve a típus egyszerű neve és a kiterjesztése: .java. A forrásfájlt egy könyvtárba tesszük, amelynek a neve kifejezi a csomag nevét, amelyikhez a típus tartozik. Például, a forráskódban lévő Rectangle osztály adja a fájl nevét, Rectangle.java, és a fájl egy graphics nevű könyvtárban lesz. A graphics könyvtár bárhol lehet a fájl rendszeren. A lenti ábra mutatja, hogyan működik.

Könyvtárstruktúra 1. példa

A csomag tag teljes neve és a fájl elérési útja hasonló:

Osztály név graphics.Rectangle
Fájl elérési út graphics\Rectangle.java


Emlékezzünk vissza, hogy a megállapodás értelmében a cégek fordított internet domén nevet használnak a csomagok elnevezésére. A példában a cég domén neve hobnob.com, ezért mindegyik csomagnak a nevét com.hobnob fogja megelőzni. A csomag névnek mindegyik összetevője megegyezik egy-egy alkönyvtárral. Így ha Hobnob-nak van egy graphics csomagja, amely tartalmazza a Rectangle.java forrásfájlt, akkor ezek alkönyvtárak sorozatában tárolódnak:

Könyvtárstruktúra 2. példa

Mikor lefordítjuk a forrásállományt, a fordító létrehoz különböző kimeneti állományt minden egyes definiált osztálynak és interfésznek. A kimeneti állománynak az alap neve az osztály vagy interfész neve és a kiterjesztése: .class, ahogyan a következő ábra is mutatja:

Kimeneti állományok

A .class fájlnak ugyanúgy, mint egy .java fájlnak is kell a csomagokat kifejező könyvtárszerkezet. Azonban nem lehet ugyanabban a könyvtárban, mint a forrás. Külön-külön könyvtárba kell rendezni a forrást és az osztályt:

Könyvtárszerkezet

Ha ez alapján csináljuk, oda tudjuk adni az osztály könyvtárakat más programozóknak anélkül, hogy megmutatnánk a forrásunkat. Ekkor használhatjuk a Java fordítónak a –d opcióját az osztály fájlok megadásához, ehhez hasonlóan:
javac –d classes sources\com\hobnob\graphics\Rectangle.java

A forrás és osztály állományok kezeléséhez erre a módszerre van szükségünk, így a fordító és a Java virtuális gép (JVM) megtalálja az összes típust, amit a programunk használ. Mikor a fordító összeakad egy új osztállyal a program fordításkor, meg fogja találni az osztályt, úgy hogy feloldja a neveket, típusvizsgálatot hajt végre és így tovább. Hasonlóan, amikor a JVM összeakad egy új osztállyal program futáskor, meg fogja találni az osztályhoz tartozó metódusokat. A fordító és a JVM az osztályokat a class path–ban listázott könyvtárakban és JAR fájlokban keresi.

Definíció: A class path, könyvtárak vagy JAR fájlok egy rendezett listája, amelyben class fájlokat kereshetünk. Fizikailag ez egy CLASSPATH nevű környezeti változót jelent.

Mindegyik könyvtár listázva van a class path-ban egy legfelső szinten lévő könyvtárban, amelyben csomag könyvtárak láthatók. A legfelső szintű könyvtártól a fordító és a JVM összeállítja az elérési útnak a maradékát, az alaphoz a csomagot és az osztály névnél az osztályt. A fordító és a JVM is összeállítja az elérési út nevet a .class fájlhoz a csomag teljes nevével.

Alapértelmezésként a fordító és a JVM az aktuális könyvtárban és a JAR fájlban keres, amely magába foglalja a Java környezeti class fájlokat. Más szóval, az aktuális könyvtár és a Java környezeti class fájlok automatikusan benne vannak a class path-ban. Legtöbbször az osztályokat megtalálják ezen a két helyen. Így nem kell aggódnunk az osztályaink elérési útja miatt. Néhány esetben azonban lehet, hogy meg kell adni osztályaink elérési útját.

17.5. Ellenőrző kérdések

  • Mi a hasonlóság az alkönyvtárak és a csomagok között?
  • Hogyan kell egy Java osztályt egy adott csomagba tenni?
  • Mit jelent a package direktíva és mi a paramétere?
  • Hogyan lehet egy csomagban levő osztályt elérni?
  • Mit egyszerűsít az import direktíva?
  • Hogyan leli fel a virtuális gép a class fájlokat?
  • Mi a CLASSPATH környezeti változó?

Igaz vagy hamis? Indokolja!

  • Különböző csomagokban lehetnek azonos nevű típusok.
  • Csomagot létrehozni a create package utasítással lehet.
  • Egy csomag típusait a uses kulcsszó segítségével lehet elérhetővé tenni.
  • Ha egy forrásállományban nem nevezünk meg tartalmazó csomagot, akkor a forrásállományban szereplő osztályok semelyik csomagnak nem lesznek részei.
  • Ha két csomagban van azonos nevű típus, akkor nem importálhatjuk egyszerre mindkettőt.
  • A package utasítás csak a forrásállomány legelső utasítása lehet.
  • A csomag kizárólag logikai szintű csoportosítás.
  • Egy csomag csak publikus osztályokat tartalmazhat.
  • Egy fordítási egységben csak egy osztályt lehet deklarálni.
  • Elnevezési konvenció szerint a csomag neve nagybetűvel kezdődik, a többi kicsi.
  • Egy fordítási egység kötelezően tartalmaz package deklarációt.
  • Egy fordítási egység kötelezően tartalmaz import deklarációt.
  • Ha az osztály deklarációjánál nem adunk meg láthatóságot, akkor arra a csomag más osztályaiból lehet hivatkozni.

Melyik a helyes sorrend egy forrásállomány esetén?

  • package, import, class
  • class, import, package
  • import, package, class
  • package, class, import