Porebski, Java, J˙ZYKI PROGRAMOWANIA OBIEKTOWEGO


JĘZYKI PROGRAMOWANIA OBIEKTOWEGO

1. JAVA

Początki języka Java sięgają roku 1990, gdy Bill Joy napisał dokument pod tytułem “Further”, w którym sugerował inżynierom Sun Microsystems stworzenie obiektowego œrodowiska w oparciu o C++. Dokument ten miał pewien wpływ na twórców projektu Green (James Gosling, Patrick Naughton i Mike Sheridan). W roku 1991 w ramach projektu Green opracowano w języku C kompilator oraz interpretator wynalezionego przez Goslinga języka OAK (Object Application Kernel), który miał być narzędziem do oprogramowania “inteligentnych” konsumenckich urządzeń elektronicznych. Ponieważ nazwa “OAK” okazała się zastrzeżona, zmieniono ją na “Java”.

Obecnie należy raczej mówić o œrodowisku Java, na które składa się:

Obiektowy język Java, którego składnia wykazuje znaczne podobieństwo do składni języka C++. Nazwa pliku z programem Ÿródłowym w języku Java, ma postać “nazwa.java”, gdzie “nazwa” musi być nazwą zdefiniowanej w tym pliku klasy. Jeżeli plik “nazwa.java” zawiera definicje wielu klas, a wœród nich jedną klasę publiczną, to “nazwa” musi być nazwą tej klasy publicznej.

Kompilator, który przetwarza program “nazwa.java” na tak zwany B-kod (bytecode, J-code), zapisywany automatycznie w plikach z rozszerzeniem nazwy “.class”. B-kod jest przenoœną postacią programu, która może być zinterpretowana przez odpowiednią maszynę wirtualną, to jest “urządzenie logiczne”, na którym będzie wykonywany program binarny.

Specyfikacja maszyny wirtualnej Java (JVM * Java Virtual Machine). JVM można uważać za abstrakcyjny komputer, który wykonuje programy, zapisane w plikach z rozszerzeniem nazwy “.class”. Maszyna wirtualna może być implementowana na rzeczywistych komputerach na wiele sposobów, na przykład jako interpretator wbudowany w przeglądarkę WWW (np. Netscape), lub jako oddzielny program, który interpretuje pliki “nazwa.class”. Może to być także implementacja polegająca na przekształceniu * tuż przed rozpoczęciem fazy wykonania * pliku z B-kodem na program wykonalny, specyficzny dla danej maszyny. Mechanizm ten można okreœlić jako tworzenie kodu wykonalnego w locie (ang. Just-In-Time, np. kompilator JIT firmy Symantec). Interpretatory B-kodu, tj. różne maszyny wirtualne, są także często napisane w języku Java.

Biblioteka Javy. Œrodowisko języka Java zawiera bogatą bibliotekę, a w niej zbiór składników dla prostego, niezależnego od platformy graficznego interfejsu użytkownika.

Rysunek 1-1 ilustruje usytuowanie œrodowiska programowego języka Java, posadowionego na dowolnej platformie sprzętowo- programowej komputera (platforma sprzętowo-programowa oznacza sprzęt komputera i jego system operacyjny).

0x01 graphic

Rys. 1-1. Usytuowanie systemu Java

Pokazany w częœci a) rysunku blok Java API (Application Programming Interface) reprezentuje klasy, interfejsy i obiekty wchodzące w skład aktualnej maszyny wirtualnej, którą zwykle okreœla się jako platformę języka Java (Java Platform). Umożliwiają one programom języka Java dostęp do zasobów komputera. Może to być dostęp (względnie) systemowo niezależny (implementowany przez klasę System w pakiecie JDK) i na dostęp systemowo zależny (implementowany przez klasę Runtime, reprezentującą œrodowisko wykonawcze w pakiecie JDK), jak pokazano w częœci b) rysunku 1-1.

Tak więc programy użytkownika można kompilować na dowolnej platformie sprzętowo-programowej, na której posadowiono kompilator języka Java. Otrzymany w wyniku kompilacji B-kod można traktować jako zbiór instrukcji kodu dla dowolnej implementacji maszyny wirtualnej, jak pokazano na rysunku 1-2.

0x01 graphic

Rys. 1-2. Przetwarzanie programów użytkownika

1.1. Elementarny program: tekst Ÿródłowy, kompilacja, interpretacja

Java wprowadza swoistą terminologię dla swoich konstrukcji syntaktycznych i jednostek (modułów) kompilacji. Programem w języku Java jest aplikacja (application) lub aplet (applet). Aplikacja jest programem samodzielnym, zaœ aplet jest programem wbudowanym (np. w przeglądarkę WWW). Każda aplikacja musi zawierać dokładnie jeden moduł Ÿródłowy nazywany modułem głównym aplikacji zawierającym definicje klas, w którym jedna z klas zawiera publiczną funkcję klasy (funkcje takie są poprzedzane słowem kluczowym static) main. Tekst Ÿródłowy najprostszego programu może mieć postać:

//plik Hello.java

public class Hello {

public static void main(String args[])

{

System.out.print(*Hello, World!\n*);

} //end main

} // end Hello

Dla skompilowania powyższego programu jego tekst Ÿródłowy należy umieœcić w pliku o nazwie Hello.java. Zakładając, że dysponujemy systemem JDK z kompilatorem javac, program skompilujemy poleceniem:

javac Hello.java

Udana kompilacja wygeneruje plik z B-kodem o nazwie Hello.class, zawierający sekwencję instrukcji dla interpretatora JVM. Kod ten wykonujemy przez wywołanie interpretatora o nazwie java poleceniem:

java Hello

Interpretator wyszuka plik o nazwie Hello.class, ustali, czy klasa Hello zawiera publiczną metodę statyczną main i wykona instrukcje zawarte w bloku main. Zauważmy przy okazji, że w języku Java wszystkie stałe, zmienne i funkcje są elementami składowymi klas; nie ma wielkoœci globalnych, definiowanych poza klasą. Ponadto nie deklaruje się metod (funkcji) składowych jako rozwijalnych (inline) bądŸ nie - decyzja należy do kompilatora.

W przykładowym programie do metody main jako parametr jest przekazywana (z wiersza rozkazowego) tablica obiektów (łańcuchów) klasy String; metoda main nie zwraca wyniku (typem zwracanym jest void), zaœ wartoœcią parametru arg[0] jest pierwszy po nazwie programu spójny ciąg znaków. Ciało main zawiera jedną instrukcję

System.out.print(*Hello, World!\n*);

(W języku Java każda instrukcja kończy się œrednikiem, który pełni rolę symbolu terminalnego).

Słowo System jest nazwą klasy w standardowym œrodowisku języka. Klasa System zawiera statyczny obiekt składowy typu PrintStream o nazwie out; wywołanie System.out oznacza pisanie do standardowego strumienia wyjœciowego. Klasa PrintStream zawiera szereg przeciążeń metody o nazwie print; jedno z nich przyjmuje parametr typu String. Kompilator automatycznie tłumaczy literał stały *Hello, World\n* na odpowiedni obiekt klasy String; odnoœnik (referencja) do tego obiektu jest przekazywana do metody System.out.print(). Metoda print() generuje jeden wiersz wyjœciowy i powraca do metody main, która kończy wykonanie.

1.2. Klasy: definicja, dziedziczenie, tworzenie obiektów

Klasę Javy można traktować jako wzorzec i jednoczeœnie generator obiektów. Jako wzorzec klasa zapewnia hermetyzację (zamknięcie w jednej jednostce syntaktycznej) danych i metod oraz ukrywanie informacji, które nie powinny być widoczne dla użytkownika. Jako generator zapewnia tworzenie obiektów za pomocą operatora new, którego argumentem jest konstruktor klasy.

Definicja klasy ma postać:

Deklaracja klasy

{

Ciało klasy

}

Deklaracja klasy składa się w najprostszym przypadku ze słowa kluczowego class i nazwy klasy. Przed słowem kluczowym class może wystąpić jeden ze specyfikatorów: abstract, public, final, lub dwa z nich, np. public abstract, public final. Specyfikator abstract odnosi się do klas abstrakcyjnych, które nie mogą mieć wystąpień, zaœ final deklaruje, że dana klasa nie może mieć podklas. Brak specyfikatora oznacza, że dana klasa jest dostępna tylko dla klas zdefiniowanych w tym samym pakiecie. Specyfikator public mówi, że klasa jest dostępna publicznie. Klasa abstrakcyjna może zawierać metody abstrakcyjne (bez implementacji, poprzedzone słowem kluczowym abstract; w miejscu ciała metody abstrakcyjnej występuje œrednik).

Po nazwie klasy mogą wystąpić frazy: `extends nazwa_superklasy' oraz `implements nazwy_interfejsów'. Fraza `extends nazwa_superklasy' mówi, że klasa dziedziczy (zawsze publicznie) od klasy nazwa_superklasy, zaœ `implements nazwy_interfejsów' deklaruje, że w danej klasie zostaną zdefiniowane metody, zadeklarowane w implementowanych interfejsach. Jeżeli dana klasa implementuje więcej niż jeden interfejs, wtedy nazwy kolejnych interfejsów oddziela się przecinkami.

Podklasa klasy abstrakcyjnej zawierającej metody abstrakcyjne może podawać definicje metod abstrakcyjnych. Podklasa podająca te definicje staje się klasą konkretną, tj. może mieć wystąpienia. Każda klasa, która odziedziczy metodę abstrakcyjną, ale nie dostarczy jej implementacji, sama staje się klasą abstrakcyjną, a jej definicja także musi być poprzedzona słowem kluczowym abstract.

Uwaga. W języku Java każda klasa dziedziczy od predefiniowanej klasy Object. Zatem, jeżeli w definicji klasy nie występuje fraza extends, to jest to równoważne niejawnemu wystąpieniu w tej definicji frazy `extends Object'.

Zauważmy, że oprócz słowa kluczowego class i nazwy klasy wszystkie pozostałe elementy w deklaracji klasy są opcjonalne. Jeżeli nie umieœcimy ich w deklaracji, to kompilator przyjmie domyœnie, że klasa jest niepubliczną, nieabstrakcyjną i niefinalną podklasą predefiniowanej klasy Object.

Ciało klasy jest zamknięte w nawiasy klamrowe i może zawierać zmienne składowe (to jest pola lub zmienne wystąpienia), zmienne klasy (statyczne, tj. poprzedzone słowem kluczowym static), konstruktory i metody oraz funkcje klasy (statyczne). Nazwa każdej zmiennej składowej, zmiennej klasy, metody lub funkcji klasy musi być poprzedzona nazwą typu (np. boolean, double, char, float, int, long, void). Przed nazwą typu może wystąpić jeden ze specyfikatorów dostępu: private (dostęp tylko dla elementów klasy, np. private double d;), protected (dostęp tylko w podklasie, nawet jeœli podklasa należy do innego pakietu; nie dotyczy zmiennych klasy; przynależnoœć klasy do pakietu omówimy za chwilę) lub public (dostęp publiczny). Brak specyfikatora oznacza, że dany element jest dostępny tylko dla klas w tym samym pakiecie. Po specyfikatorze dostępu może wystąpić słowo kluczowe final. Słowo final przed nazwą typu zmiennej wystąpienia lub zmiennej klasy deklaruje jej niemodyfikowalnoœć (np. public static final int i=10;), zaœ w odniesieniu do metody oznacza, że nie może ona być redefiniowana w podklasie (np. public final void f(int i) {/* ... */ }). Lokalne zmienne finalne, które zostały zadeklarowane, ale jeszcze nie zainicjowane, nazywa się blank final.

Dostęp do elementów klasy uzyskuje się za pomocą operatora kropkowego. Jeżeli element danej klasy (zmienna lub metoda) przesłania (overrides) jakiœ element swojej superklasy, to można się do niego odwołać za pomocą słowa kluczowego super, jak w poniższym przykładzie:

class ASillyClass /* Deklaracja klasy */

{

static final int MAX = 100; /** Definicja stałej */

boolean aVariable;/* Deklaracja zmiennej wystąpienia */

static public int x = 10; //Definicja zmiennej klasy

void aMethod() { //Definicja metody

aVariable = true;// Instrukcja przypisania

} // end aMethod

} // end aSillyClass

class ASillerClass extends ASillyClass {

boolean aVariable;

void aMethod() {

aVariable = false;

super.aMethod(); /* Wywołanie metody superklasy */

System.out.println(aVariable);

System.out.println(super.aVariable);

} // end aMethod

} // end ASillerClass

Klasy i omawiane niżej interfejsy są typami referencyjnymi (odnoœnikowymi). Wartoœciami zmiennych tych typów są odnoœniki do wartoœci lub zbiorów wartoœci reprezentowanych przez te zmienne. Np. instrukcja

ASillyClass oob;

jedynie powiadamia kompilator, że będziemy używać zmiennej oob, której typem jest ASillyClass. Do zmiennej oob możemy przypisać dowolny obiekt typu ASillyClass utworzony za pomocą operatora new:

oob = new ASillyClass();

W powyższej instrukcji argumentem operatora new jest generowany przez kompilator konstruktor ASillyClass() klasy ASillyClass, który inicjuje obiekt utworzony przez operator new. Operator new zwraca odnoœnik do tego obiektu, po czym przypisuje go do zmiennej oob.

Jeżeli dana klasa nie zawiera deklaracji konstruktorów, to kompilator dostarcza konstruktor domyœlny z pustym wykazem argumentów, który w swoim bloku wywołuje konstruktor super() jej bezpoœredniej nadklasy. WeŸmy dla ilustracji definicję klasy Point:

public class Point { int x, ,y; }

Jest ona równoważna definicji

public class Point { int x, ,y; public Point() { super(); } }

z niejawnym wywołaniem dostarczanego przez kompilator konstruktora superklasy, od której bezpoœrednio dziedziczy klasa Point.

Podobne, niejawne wywołania konstruktora super() są wykonywane w drzewach dziedziczenia. Rozpatrzmy następujący program:

//plik Super1.java

class Point { int x,y; Point() { x=1;y=2; } }

class CPoint extends Point { public int color = 0xFF00FF; }

public class Super1 {

public static void main(String args[]) {

CPoint cp = new CPoint();

System.out.println(cp.color= + cp.color);

System.out.println(cp.x= + cp.x);

}//end main

}//end Super1

Instrukcja CPoint cp = new CPoint(); tworzy nowe wystąpienie klasy CPoint. Najpierw jest przydzielany obszar w pamięci dla obiektu cp, aby mógł przechowywać wartoœci x oraz y, po czym pola te są inicjowane do wartoœci domyœlnych (tutaj zero dla każdego pola). Następnie jest wołany konstruktor Cpoint(). Ponieważ klasa CPoint nie deklaruje konstruktorów, kompilator automatycznie dostarczy konstruktor o postaci CPoint(){super();}, który wywoła konstruktor klasy Point bez argumentów, tak jakby zamiast super() napisano:

Point(){super();x=1;y=2; },

przy czym teraz super() w bloku konstruktora klasy Point oznaczałoby wywołanie konstruktora klasy Object.

W hierarchii dziedziczenia konstruktor super() może być wywoływany jawnie, jak w przykładzie poniżej:

//plik Super2.java

class Point { int x,y; Point(int x, int y)

{ this.x = x; this.y = y; } }

class CPoint extends Point {

static final int WHITE = 0, BLACK = 1;

int color;

CPoint(int x, int y) { this(x,y,WHITE); }

CPoint(int x, int y, int color) { super(x,y); this.color=color; }

}

public class Super2 {

public static void main(String args[]) {

int a = 10, b = 20;

CPoint cp = new CPoint(a,b);

System.out.println(cp.color= + cp.color);

System.out.println(cp.x= + cp.x);

}//end main

}//end Super2

Zmienna this w definicji konstruktora klasy Point jest odnoœnikiem (referencją), identyfikującym obiekt, na rzecz którego wywołuje się konstruktor. Tak więc lewa strona instrukcji this.x = x identyfikuje pole x obiektu klasy Point, zaœ prawa strona jest wartoœcią argumentu x, którą inicjuje się to pole. Natomiast słowo kluczowe this w definicji dwuargumentowego konstruktora klasy CPoint służy do wywołania konstruktora trójargumentowego tej klasy w instrukcji this(x,y,WHITE).

Zatem w instrukcji CPoint cp = new CPoint(a,b); konstruktor CPoint(int,int) wywołuje ze swojego bloku (instrukcja this(x,y,WHITE);) drugi konstruktor, CPoint(int,int,int), dostarczając mu argument WHITE. Drugi konstruktor woła konstruktor superklasy, przekazuje mu współrzędne x oraz y, a następnie inicjuje pole color wartoœcią WHITE.

Uwaga. instrukcja { this(argumenty); musi być pierwszą instrukcją w ciele konstruktora lub metody; to samo dotyczy instrukcji super(argumenty);.

Dostęp do zmiennych składowych klasy (statycznych) jest możliwy bez tworzenia obiektów tej klasy. Np. dla klasy ASillyClass możemy napisać instrukcję:

System.out.println(ASillyClass.x);.

Gdyby w klasie ASillyClass zdefiniować statyczną funkcję klasy, np.

public static void bMethod(){/*instrukcje */}

to w takiej funkcji nie istnieje odnoœnik this, a zatem żadna z jej instrukcji nie może się bezpoœrednio odwołać do niestatycznej składowej x; odwołanie byłoby możliwe jedynie przez zadeklarowanie zmiennej odnoœnikowej do klasy ASillyClass i zainicjowanie jej odnoœnikiem do utworzonego za pomocą operatora new nowego obiektu, jak pokazano w poniższej sekwencji instrukcji:

ASillyClass ob = new ASillyClass();

System.out.println(ob.x);.

1.3. Interfejsy

Konstrukcja o postaci

interface nazwa {

/* Deklaracje metod i definicje stałych */

}

jest w języku Java typem definiowanym przez użytkownika. Deklaracja metody składa się z sygnatury (sygnatura metody zawiera typ zwracany, nazwę metody i typy argumentów) i terminalnego œrednika. Ponieważ interfejs może zawierać jedynie deklaracje metod i definicje stałych, odpowiada on klasie abstrakcyjnej z zadeklarowanymi publicznymi polami danych i metodami abstrakcyjnymi. Słowo kluczowe interface można poprzedzić specyfikatorem dostępu public; wskazuje to, że taki interfejs może być używany przez dowolna klasę w dowolnym pakiecie. Brak tego specyfikatora oznacza, że dany interfejs może być używany tylko przez te klasy, które są zdefiniowane w tym samym pakiecie. Np. interfejs Sleeper mógłby mieć postać:

public interface Sleeper {

public void wakeUp(); //deklaracja metody

public long ONE_SECOND = 1000; //deklaracja stałej

public long ONE_MINUTE = 60000; //deklaracja stałej

}

Zauważmy, że w deklaracjach stałych nie pisaliœmy słowa kluczowego final przed ich typami. W ogólnoœci w definicji interfejsu wszystkie metody są domyœlnie public i abstract, zaœ stałe są domyœlnie public, static i final. Używanie tych słów kluczowych jako specyfikatorów metod i stałych jest uważane przejaw złego stylu programowania. Ponadto zabrania zabrania się używania specyfikatorów private i protected. Jeżeli pewna klasa implementuje interfejs, dostęp do zadeklarowanej w interfejsie metody lub stałej uzyskuje się za pomocą operatora kropkowego, np. Sleeper.ONE_SECOND.

WeŸmy dla przykładu dwa interfejsy, PlaneLike i BoatLike

interface PlaneLike {

void takeOff();

float kmph();

}

interface BoatLike {

void swim();

float knots();

}

i zdefiniujmy ich implementacje w klasach Plane i Boat, które dziedziczą od wspólnej superklasy Vehicle:

class Vehicle {}

class Plane extends Vehicle implements PlaneLike {

/* Plane must implement kmph(), takeOff() */

public void takeOff() { System.out.println(Plane is taking off); }

public float kmph() { return 600; }

}

class Boat extends Vehicle implements BoatLike {

/* Boat must implement knots(),swim() */

public void swim() { System.out.println(Boat is swimming); }

public float knots() { return 20; }

}

Poprawne będą wówczas deklaracje

Plane biplane = new Plane();

biplane.takeOff();

Boat vessel = new Boat();

vessel.swim();

a także deklaracje

PlaneLike aircraft = new Plane();

aircraft.takeOff();

BoatLike motorboat = new Boat();

motorboat.swim();

Załóżmy teraz, że chcielibyœmy skonstruować klasę SeaPlane, której obiekty powinny się zachowywać w pewnych okolicznoœciach jak pojazdy wodne, zaœ w innych jak pojazdy powietrzne. W języku, który wyposażono w mechanizm dziedziczenia mnogiego (np. C++) klasa SeaPlane miałaby dwie superklasy: Plane i Boat, jak pokazano w częœci a) rysunku 1-3.

0x01 graphic

Rys. 1-3. a) Graf dziedziczenia mnogiego dla SeaPlane.

b) Graf dziedziczenia pojedynczego dla SeaPlane

W języku Java podobny efekt można osiągnąć poprzez implementację w klasie SeaPlane obu interfejsów, t.j. PlaneLike i BoatLike (częœć b rysunku 1-3):

class SeaPlane extends Vehicle implements

PlaneLike, Boatlike {

/** SeaPlane must implement kmph(), takeOff(),

knots(), swim() */

}

Dla osiągnięcia pożądanego zachowania się obiektów klasy SeaPlane moglibyœmy umieœcić w głównym module Ÿródłowym Multi1.java następujący kod:

public class Multi1 {

public static void main(String args[])

{

Boat vessel = new Boat();

Plane biplane = new Plane();

System.out.println(Let's starting! );

PlaneLike ref1 = new SeaPlane(biplane);

ref1.takeOff();

System.out.println(ref1.kmph());

BoatLike ref2 = new SeaPlane(vessel);

ref2.swim();

System.out.println(ref2.knots());

}

}

Jednak ze względu na wielokrotną używalnoœć kodu lepszym rozwiązaniem będzie taka definicja klasy SeaPlane, która wykorzystuje mechanizm delegacji, tj. bezpoœredniej współpracy z klasami Plane i Boat:

class SeaPlane extends Vehicle implements

PlaneLike, BoatLike {

// define a Plane and Boat instance variables

// i.e. collaborate with Plane and Boat classes

private Plane itAsPlane;

private Boat itAsBoat;

//Konstruktor

SeaPlane(Plane itAsPlane){ this.itAsPlane=itAsPlane; }

//Konstruktor

SeaPlane(Boat itAsBoat){ this.itAsBoat=itAsBoat; }

// forward the messages to the appropriate collaborator

public float kmph() { return itAsPlane.kmph(); }

public void takeOff() { itAsPlane.takeOff(); }

public float knots() { return itAsBoat.knots(); }

public void swim() { itAsBoat.swim(); }

}

Interfejs nie może dziedziczyć klas, ale może dziedziczyć dowolnie wiele interfejsów. Np. korzystając z podanych wyżej definicji moglibyœmy utworzyć interfejs

interface SeaPlaneLike extends PlaneLike, BoatLike{

public long SPEED_LIMIT = 1000;

}

i wykorzystać go w klasie SeaPlane, implementując metody zadeklarowane w interfejsach PlaneLike i BoatLike. Zauważmy przy okazji, że klasy implementujące dany interfejs mogą traktować zadeklarowane w nim stałe tak, jak gdyby były one odziedziczone.

Przykład hierarchii dziedziczenia interfejsów z biblioteki Javy pokazano na rysunku 1-4.

0x01 graphic

Rys. 1-4. Drzewa dziedziczenia dla interfejsów Collection i Map.

1.4. Pliki Ÿródłowe i pakiety

Program języka Java może się składać z wielu niezależnie kompilowalnych modułów Ÿródłowych, w których umieszcza się definicje klas oraz interfejsów. Moduły Ÿródłowe są przechowywane w plikach o nazwie Nazwa.java, gdzie Nazwa jest nazwą klasy publicznej; pliki te stanowią jednostki kompilacji. Jeżeli w pliku Nazwa.java zdefiniowano tylko jedną klasę, to w wyniku kompilacji tego pliku powstaje plik wynikowy Nazwa.class. Jeżeli program jest aplikacją, to w zestawie modułów Ÿródłowych musi się znaleŸć dokładnie jeden moduł Ÿródłowy (moduł główny aplikacji) z klasą publiczną, która zawiera publiczną i statyczną funkcję main (każdy inny moduł Ÿródłowy może zawierać klasę z funkcją main, jeżeli nie jest to klasa publiczna).

Moduł Ÿródłowy, w którym definicje klas oraz interfejsów poprzedzono deklaracją pakietu o postaci package nazwa_pakietu; staje się pakietem. Deklaracja pakietu rozszerza przestrzeń nazw programu i pozwala na lepsze zarządzanie programem wielomodułowym. Jeżeli moduł Ÿródłowy nie zawiera deklaracji pakietu, to należy on do tzw. pakietu domyœlnego (pakietu bez nazwy). Np. zadeklarowana wczeœniej klasa Hello, umieszczona w pliku Hello.java należy do pakietu domyœlnego.

Pakiety są œciœle powiązane z katalogami, w których umieszcza się moduły Ÿródłowe i pliki wynikowe. Załóżmy np., że w katalogu C:\javaprog (Win'95 DOS) umieszczono główny plik Ÿródłowy aplikacji Student.java, który importuje z katalogu C:\javaprog\myprog\pakiet1 klasy HiGrade oraz LoGrade, umieszczone w plikach HiGrade.java i LoGrade.java. Deklaracje importu mogą mieć postać:

import myprog.pakiet1.HiGrade;

import myprog.pakiet1.LoGrade;

Powyższe dwie deklaracje importu można zastapić jedną:

import myprog.pakiet1.*

public

class Student {

int i = 10;

public static void main(String args[])

{

System.out.println(Hello, I am here!);

HiGrade highgrade = new HiGrade();

highgrade.printgrade();

LoGrade lowgrade = new LoGrade();

lowgrade.printgrade();

}

}

Plik zawiera definicję klasy Student, poprzedzoną deklaracją importu klas HiGrade i LoGrade. Plik ten może zostać skompilowany wywołaniem kompilatora javac z katalogu nadrzędnego w stosunku do katalogu javaprog\myprog\pakiet1; jeżeli plik Student.java umieszczono w katalogu javaprog, to wywołanie będzie miało postać: javac Student.java. Jeżeli pliki HiGrade.java i LoGrade.java mają postać:

package myprog.pakiet1;

public class HiGrade {

int i = 10;

public void printgrade()

{

System.out.println(My grades are higher than + i);

}

}

class Empty{}

oraz

package myprog.pakiet1;

public class LoGrade {

int i = 3;

public void printgrade()

{

System.out.println(My grades are lower than + i);

}

}

to zostaną utworzone cztery pliki wynikowe: “Student.class” w katalogu javaprog oraz “HiGrade.class”, “LoGrade.class” i “Empty.class”, a wywołanie interpretatora java Student spowoduje wyprowadzenie na ekran napisu:

Hello, I am here!

My grades are higher than 10

My grades are lower than 3