background image

 
 
 
 
 
 
 

Język Java: Podstawy, Programowanie, Zastosowania 

 
 
 

 

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

 

background image

Java 

3

1. Język 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ę: 
1.  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. 

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

3.  Specyfikacje maszyny wirtualnej Java (JVM – Java Virtual Machine) i plików klas. 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. 

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

Program w języku Java

Java API

Maszyna wirtualna (JVM)

Platforma sprzętowo-programowa

a)

b)

Środowisko wykonawcze

Klasa System

MyProgram.java

 

 

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 na 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 4-1. 

background image

 

Języki obiektowe 

4 

 

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 4-2. 
 

PC - Windows 95/NT

SUN - Solaris

MyProgram.java

Edytor

Kompilator

Interpretator

Interpretator

MyProgram.class

 

 

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, którego klasa 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 

background image

Java 

5

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: abstractpublicfinal, 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) 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) {/* ... */ }). 

background image

 

Języki obiektowe 

6 

 

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 

background image

Java 

7

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; }. 
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 */ 

background image

 

Języki obiektowe 

8 

 

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. 
W związku z tym w definicji interfejsu zabrania się używania specyfikatorów private i protected, zaś 
użycie specyfikatorów abstract i public jest zbyteczne. 
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. 
 

background image

Java 

9

Boat

Plane

SeaPlane

Vehicle

interface

PlaneLike

interface

BoatLike

Vehicle

Boat

Plane

SeaPlane

a)

b)

 

 

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 4-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 

background image

 

Języki obiektowe 

10 

 

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. 

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:\mike (Win’95 DOS) umieszczono główny plik źródłowy 
aplikacji Student.java o postaci: 

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 mike\myprog\pakiet1; jeżeli plik Student.java umieszczono w katalogu mike, to 
wywołanie będzie miało postać: javac Student.java. Jeżeli pliki HiGrade.java i LoGrade.java 
mają postać: 

background image

Java 

11

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 

)

 Uwaga. Deklaracja importu nie oznacza włączania do pliku Student.java tekstu zawartego w 

plikach HiGrade.java i LoGrade.java. Natomiast pozwala ona użytkownikowi klasy Student używać 
skrótowych nazw: np. zamiast pisać myprog\pakiet1\HiGrade highgrade = new 
mike\myprog\pakiet1\HiGrade(); mogliśmy napisać krótko: HiGrade highgrade = new HiGrade();. 
Gdybyśmy chcieli używać również klasy Empty (lub innych klas pakietu pakiet1), to deklaracja 
importu miałaby postać: import mike.myprog.pakiet1.*. W języku Java ważna jest także kolejność 
deklaracji:najpierw deklaracja pakietu, po niej deklaracje importu, po czym definicje klas. 

 
Zauważmy,  że gdyby umieścić w pliku LoGrade.java definicję pakietowej funkcji wystąpienia, na 
przykład 

void msg() { System.out.print("This is a package method\n"); } 
to funkcję tę można byłoby wywołać z ciała klasy HiGrade, na przykład w funkcji pf: 

public void pf() { 

 LoGrade lg = new LoGrade(); 

 lg.msg(); 

 } 
Fukcja pf jest publiczna, zatem jest dostępna nie tylko dla klas pakietu myprog.pakiet1, lecz także 
poza tym pakietem. Z funkcji main klasy Student można ją, przykładowo, wywołać poleceniem: 
highgrade.pf();. 
Zauważmy też, że deklarując msg jako funkcję klasy LoGrade 

static void msg(){System.out.print("This is a package method\n");} 
moglibyśmy ją wywołać z ciała klasy HiGrade bez tworzenia referencji do obiektu klasy LoGrade, np. 

public void pf() { LoGrade.msg(); } 

1.5. Polimorfizm 

Polimorfizm możemy określić jako wirtualizację operacji; jest to możliwość dynamicznego (późnego, 
realizowanego w fazie wykonania) wiązania nazwy operacji do wielu implementacji (metod) tej 
operacji w różnych klasach pozostających w relacji dziedziczenia. Wiązaniu towarzyszy mechanizm 
wyboru konkretnej implementacji. Wybór implementacji zależy od nazwy metody oraz od typu 
dynamicznego tego obiektu, dla którego została wywołana operacja, a nie od typu zmiennej, 
wskazującej ten obiekt. 

background image

 

Języki obiektowe 

12 

 

Zauważmy,  że przeciążanie operacji nie prowadzi do polimorfizmu; w tym przypadku wiązanie, 
dopasowanie parametrów wywołania operacji do określonej sygnatury i wybór implementacji są 
statyczne, ponieważ  są wykonywane w fazie kompilacji. Muszą wówczas istnieć różnice w 
sygnaturach operacji (w typie wyniku i/lub w typach parametrów wejściowych), a kryterium wyboru 
implementacji zależy od tych różnic. 
W języku Java wszystkie deklaracje metod, które nie są metodami klasy, ani metodami finalnymi, 
można traktować jako operacje wirtualne. Tak więc definicja metody o danej sygnaturze w superklasie 
może być przesłonięta przez definicję metody o tej samej sygnaturze w podklasie. W rezultacie 
kompilator nie może związać nazwy metody z jej implementacją (istnieją co najmniej dwie 
implementacje o takich samych sygnaturach). Wiązanie może się odbyć dopiero w fazie wykonania; 
wówczas interpretator określa typ dynamiczny obiektu na którym zostaje wywołana operacja i wiąże 
tę operację z właściwą dla danego obiektu implementacją. Ilustrację wywołania polimorficznego 
pokazuje poniższy prosty przykład. 
//Plik Polimorf.java 

class Base { 

public void msg() 

{  

 System.out.println("Base class method"); 

 } 

class Derived extends Base { 

public void msg() 

{  

 System.out.println("Derived class method"); 

public class Polimorf { 

    int i = 10; 

    public static void main(String args[]) 

    { 

     Base b1 = new Base(); 

     b1.msg(); 

     b1 = new Derived(); 

     if(b1 instanceof Derived)  

     System.out.println("Derived"); 

     b1.msg(); 

    } 


Metoda msg() ma dwie implementacje: w klasie Base i w klasie Derived. Odnośnik (referencja) 
b1 do klasy Base jest najpierw inicjowana obiektem klasy Base, a więc instrukcja b1.msg(); 
wywoła metodę tej klasy. Następnie odnośnikowi b1 zostaje przypisany obiekt klasy Derived, co 
spowoduje, że następna instrukcja b1.msg(); wywoła implementację metody msg() zdefiniowaną 
w klasie Derived. Instrukcja if wykorzystująca operator instanceof służy do sprawdzenia, jaki 
jest typ dynamiczny obiektu b1. Wynikiem wykonania programu będzie następujący wydruk: 

Base class method 
Derived 
Derived class method 

1.5. Klasy zagnieżdżone 

Klasy, jak już powiedziano, mogą należeć do pakietów nazwanych lub nienazwanych. Począwszy od 
wersji 1.1 języka Java wprowadzono możliwość definiowania klas wewnętrznych (inner classes) jako 
elementów składowych innych klas; można je definiować albo lokalnie wewnątrz ciała klasy 
zewnętrznej, albo (anonimowo) jako wyrażenie w bloku. Takie zagnieżdżone klasy posiadają kilka 
własności, które czynią je użytecznymi: 
•  Nazwy klasy wewnętrznej nie można użyć na zewnątrz jej zasięgu, za wyjątkiem nazwy 

kwalifikowanej. Pomaga to w strukturalizacji klas w obrębie pakietu. 

background image

Java 

13

•  Kod klasy wewnętrznej może wykorzystywać proste nazwy z zasięgów otaczających, w tym 

zarówno składowe klasy, jak i składowe wystąpienia klas otaczających oraz zmienne lokalne 
otaczających bloków. 

Klasy wewnętrzne są elementami kombinacji struktury blokowej i programowania opartego o klasy; 
wprowadzono je po raz pierwszy w języku Beta. Wykorzystanie struktury blokowej oraz klas 
wewnętrznych ułatwia programiście wiązać ze sobą obiekty Javy, ponieważ klasy mogą być 
definiowane w pobliżu obiektów, którymi manipulują, i mogą bezpośrednio używać nazw, których 
potrzebują. Ponadto programista może definiować klasę wewnętrzną jako statyczny element dowolnej 
klasy otaczającej. Jednak klasa wewnętrzna nie może deklarować  żadnej ze swoich składowych ze 
słowem kluczowym static, ponieważ ciało klasy wewnętrznej jest w zasięgu klasy otaczającej. W 
rezultacie zmienne statyczne dla klasy wewnętrznej muszą być umieszczane w klasie otaczającej. Z 
tego samego powodu nie można w klasie wewnętrznej deklarować statycznych inicjatorów dla pól 
klasy. 
Klasy zewnętrzne oraz statyczne klasy wewnętrzne nazywa się klasami wysokiego poziomu. Różnią 
się one od niestatycznych klas wewnętrznych tym, że mogą  używać bezpośrednio jedynie swoje 
własne zmienne wystąpienia. 
Jeżeli klasa wewnętrzna posiada nazwę,  może być deklarowana ze słowami kluczowymi private
protected,  final, lub abstract. Natomiast klasy anonimowe są prywatne w bloku, w którym są 
zadeklarowane, ponieważ nie mogą być wykorzystane na zewnątrz tego bloku. 
Zagnieżdżanie klas w ten właśnie sposób pozwala dowolnej klasie wysokiego poziomu – dla logicznie 
powiązanych klas poziomu niższego – uzyskać podobną do pakietu organizację, w której wszystkie 
klasy mają pełny dostęp do pól prywatnych. 
W podanym niżej przykładzie klasa wewnętrzna została zadeklarowana jako klasa niestatyczna 
Inner z własnym konstruktorem i własną zmienną składową y. 
//Plik Outer1.java 

public class Outer1 { 

 public int x; 

 public Outer1(int i) { x = i; } 

 class Inner { 

  public int y; 

  public Inner(String s, int j)  { 

  y = j; 

  System.out.println(s+j); 

  }; 

 }//end Inner 

 

 public static void main(String args[]) { 

    Outer1 ref1 = new Outer1(10); 

    System.out.println("ref1.x = " + ref1.x); 

    Outer1.Inner ref2 = ref1.new Inner("ref2.y = ", 20); 

    System.out.println("ref2.y = " + ref2.y);     

 } 

}//end Outer1 
Wprowadzenie klas zagnieżdżonych nie spowodowało zmian w przetwarzaniu plików z B-kodem 
przez maszynę wirtualną Javy, tj. przez interpretator, ani w standardowych bibliotekach klas, a nowa 
cecha języka została uwzględniona w nowych wersjach kompilatorów. Uwzględniono ją w ten sposób, 
że nazwy klas wewnętrznych są przekształcane tak, aby uniknąć konfliktów z innymi nazwami w 
różnych zasięgach. Nazwy są kodowane przez kompilator, który bierze ich postać  źródłową, 
kwalifikuje je kropkami, po czym zmienia każdą kropkę po nazwie klasy na znak dolara (‘$’). Tak 
więc po kompilacji pliku źródłowego “Outer1.java” otrzymamy dwa pliki z B-kodem: “Outer1.class” 
oraz “Outer1$Inner.class”. Oczywiście interpretacji poddamy plik “Outer1.class”. 
 
Niestatyczną klasę wewnętrzną wykorzystuje się wtedy, gdy tworzony jest obiekt klasy zewnętrznej; 
jak pokazano wyżej, argumentem operatora new jest jest konstruktor klasy wewnętrznej, wywoływany 
na odnośniku do obiektu klasy zewnętrznej. 
Następny przykład ilustruje deklarację statycznej klasy wewnętrznej: 

background image

 

Języki obiektowe 

14 

 

//plik Outer2.java 

public class Outer2 { 

public int x; 

public Outer2(int i) { x = i; } 

static class Inner { 

public int y; 

public Inner(int j)  { y = j; }; 

 }//end Inner 

 

public static void main(String args[]) { 

  Outer2 ref1 = new Outer2(10); 

  System.out.println("ref1.x = " + ref1.x); 

  Outer2.Inner ref2 = new Outer2.Inner(20); 

  System.out.println("ref2.y = " + ref2.y); 

}//end Outer2 
W tym przypadku argumentem operatora new jest jest konstruktor klasy wewnętrznej, wywoływany 
na składowej statycznej (Outer2.Inner) klasy zewnętrznej. Kompilator, podobnie jak w 
poprzednim przykładzie, utworzy dwa pliki z B-kodem: “Outer2.class” oraz “Outer2$Inner.class”. 
 
Jeżeli nie ma potrzeby tworzenia obiektu klasy zewnętrznej, wówczas zarówno klasę wewnętrzną, jak 
i funkcje składowe klasy zewnętrznej można zadeklarować jako statyczne. Konstrukcje tego typu 
wykorzystywane są w Javie prawie wszędzie tam, gdzie klasa wewnętrzna implementuje interfejs. 
Ilustracją tego stwierdzenia jest kolejny przykład. 
//Plik Outer3.java 

interface InnerInt{ 

 String wyswietl(); 

}//end of InnerInt interface 

 

public class Outer3 { 

 public static int x=10; 

 

static class Inner implements InnerInt{ 

  private int y; 

  public Inner(int j)  { 

   y=j; 

   System.out.println(wyswietl()); 

  }//end of ctor 

  public String wyswietl(){ 

   return (new String("Inner.y = "+y)); 

  } 

 }//end Inner 

 

 public static InnerInt foo(int i){ 

  return (new Inner(i)); 

 } 

 

 public static void main(String args[]) { 

     System.out.println("Outer3.x = " + Outer3.x); 

     Outer3.foo(20); 

 } 

}//koniec public class Outer3 
W bloku main publiczna, statyczna funkcja foo() klasy zewnętrznej, wywoływana z argumentem 
20, zwraca odnośnik typu interfejsowego InnerInt, inicjowany odnośnikiem do tworzonego w 
instrukcji  return obiektu klasy Inner. Mogliśmy tak uczynić, ponieważ klasa Inner jest nie 
tylko odrębnym typem, lecz także implementuje typ InnerInt. W rezultacie kompilator utworzy 
trzy pliki z B-kodem: “Outer3.class”, “Outer3$Inner.class” oraz “InnerInt.class”. 
 

background image

Java 

15

Ponieważ typ Inner jest wykorzystywany w definicji klasy zewnętrznej tylko jeden raz, można, dla 
uzyskania bardziej zwięzłego kodu, zrezygnować z wprowadzania jego nazwy do przestrzeni nazw 
programu, a implementację interfejsu InnerInt umieścić bezpośrednio w wyrażeniu new instrukcji 
powrotu z funkcji foo(). Konstrukcja taka, będąc implementacją interfejsu, jest oczywiście klasą; 
ponieważ jednak nie występuje tutaj nazwa klasy, nazywamy ją  klasą anonimową. Poniżej 
zaprezentowano odpowiedni kod, w którym blok, zaczynający się nawiasem klamrowym po 
InnerInt() jest ciałem klasy anonimowej, faktycznie identycznym z ciałem klasy Inner z 
poprzedniego przykładu. 
//plik Outer4.java 

interface InnerInt{ 

 StringBuffer wyswietl(); 

}//end InnerInt 

public class Outer4 { 

 public static int x=10; 

 

 public static InnerInt foo(final int i) { 

  return new InnerInt() { 

  private int y = i; 

  public StringBuffer wyswietl() { 

   return (new StringBuffer("Outer4.foo(20).wyswietl()= " +y)); 

  }//end wyswietl() 

  };//end anonymous 

 }//end foo 

 

 public static void main(String args[]) { 

     System.out.println("Outer4.x = " + Outer4.x); 

     System.out.println(Outer4.foo(20).wyswietl()); 

 }//end main() 

}//end Outer4 
Zwróćmy uwagę na ostatnią instrukcję w funkcji main(). Argument metody println() jest wyrażeniem 
otrzymanym w wyniku wywołania na klasie Outer4 funkcji foo(), która zwraca obiekt typu InnerInt. 
Na obiekcie tym jest wywoływana funkcja wyswietl(), która zwraca wynik typu 
StringBuffer, a więc typu akceptowanego przez metodę println(). 
Zdefiniowana tutaj klasa anonimowa ma następujące własności: 
•  Może mieć inicjatory pól, ale nie może mieć konstruktora. 
•  Jeżeli klasa anonimowa została wyprowadzona z interfejsu I, to faktyczną jej superklasą jest 

Object, a klasa tylko implementuje interfejs I, bez żadnych rozszerzeń. 

Przekształcanie nazw przez kompilator spowoduje teraz utworzenie następujących plików z B-kodem: 
“Outer4.class”, “Outer4$1.class” oraz “InnerInt.class”. Plik “Outer4$1.class” odpowiada klasie 
anonimowej. W jego nazwie liczba 1 (numer klasy anonimowej) została dodana przez kompilator dla 
otrzymania unikatowej nazwy, która zostanie przekazana do konsolidatora. 
 
Istnienie klas zagnieżdżonych wymaga pewnej zmiany w nazwach kwalifikowanych przy 
definiowaniu zmiennych typu klasy wewnętrznej oraz hierarchii dziedziczenia. Istotne zmiany to: 
–  Inicjowanie zmiennych odnośnikowych typu klasy wewnętrznej słowem kluczowym this

odpowiadającym bieżącemu obiektowi klasy zewnętrznej. 

–  Kwalifikowanie dziedziczenia od klasy wewnętrznej jej nazwą, umieszczaną po nazwie klasy 

zewnętrznej i kropce. 

Ilustracją tego sposobu kwalifikacji nazw jest podany niżej przykład. 
 

class Vehicle { 

  class Wheel { 

   String hubcapType; 

   float radius; 

   }//end class Wheel 

 

  Wheel leftWheel = this. new Wheel(); 

background image

 

Języki obiektowe 

16 

 

  Wheel rightWheel = this. new Wheel(); 

  Wheel extra; 

 

  static void thirdWheel(Vehicle car) { 

  if (car.extra == null) { 

   car.extra = car. new Wheel(); 

  }//end if 

}//end thirdWheel() 

 

public static void main(String args[]) { 

 System.out.println("Vehicle = " ); 

 }//end main() 

 

}//end class Vehicle 

//Koło ze szprychami  

class WireRimWheel extends Vehicle.Wheel { 

 WireRimWheel(Vehicle car, float wireGauge) { 

 car. super(); 

 }//end of constructor WireRimWheel() 

}//end class WireRimWheel 

 

class Auto extends Vehicle { } 

W wyniku kompilacji zostaną utworzone cztery pliki z B-kodem: “Vehicle.class”, 
“Vehicle$Wheel.class”, “WireRimWheel.class” oraz “Auto.class”. 

1.6. Obsługa wyjątków 

Wyjątki pozwalają zachować kontrolę nad przebiegiem wykonania funkcji (metod), a także 
pojedynczych instrukcji zawartych w funkcjach. Wyjątek jest zdarzeniem, które pojawia się podczas 
wykonania i rozrywa normalną kolejność wykonania instrukcji. 
W języku Java istnieje bardzo rozbudowana hierarchia (drzewo) predefiniowanych klas wyjątków, 
których superklasą jest klasa Throwable, a głównymi gałęziami drzewa są klasy Error i Exception. 
Część wyjątków należy do grupy tzw. wyjątków weryfikowalnych (checked exceptions): kompilator 
sprawdza, czy program zawiera procedury obsługi dla każdego wyjątku z tej grupy. Natomiast wyjątki 
klasy Error i jej podklas należą do grupy wyjątków nieweryfikowalnych (unchecked exceptions), 
ponieważ mogą one wystąpić w wielu punktach programu i powrót z nich jest trudny lub wręcz 
niemożliwy. Do grupy wyjątków nieweryfikowalnych należą też wyjątki klasy RuntimeException 
(podklasa Exception) i jej podklas, ponieważ zadeklarowanie w programie takich wyjątków nie 
mogłoby znacznie pomóc w ustaleniu (przez kompilator) poprawności programów. 
Dla obsługi wyjątków weryfikowalnych wprowadzono cztery słowa kluczowe: throw,  throws,  try
catch i finally. Słowo kluczowe throw służy do jawnego zgłaszania wyjątków nieweryfikowalnych i 
występuje w instrukcji throw o składni throw wyrażenie;gdzie wyrażenie musi oznaczać zmienną lub 
wartość typu referencyjnego do klasy Throwable lub jej podklas. Zgłoszenie wyjątku w instrukcji 
throw spowoduje natychmiastowe opuszczenie bloku lub funkcji zawierającego instrukcję throw i 
znalezienie instrukcji try, której fraza catch przechwyci zgłoszony wyjątek. Jeżeli nie ma takiej 
instrukcji try, zostanie wywołana metoda UncaughtException i wykonanie programu (lub wątku) 
zostanie zakończone. 
Fraza:throws klasa_wyjątków 
może wystąpić w nagłówku funkcji (metodzie wystąpienia, konstruktorze, funkcji klasy), np. 

public staic void main(String args[]) throws Exception {/*...*/} 

void printNumber(int number) throws WrongNumberException {/*...*/} 
Fraza throws klasa_wyjątków oznacza, że dana funkcja może zgłaszać jedynie wyjątki podanej klasy. 
Jeżeli wykonanie pewnej instrukcji programu może spowodować powstanie wyjątkowego zdarzenia, 
to musi ona być ujęta w blok instrukcji try, po którym muszą wystąpić procedury obsługi wyjątku 
mające postać frazy catch i bezpośrednio po catch (opcjonalnie) frazy finally. Składnia tej konstrukcji 
ma postać: 
try {I}catch(arg1 e1) {I} catch(arg2 e2) {I} ... catch(argn en) {I} ... finally {I} 
gdzie I oznacza instrukcje, arg1 .. argn – klasy wyjątków, e1 ... en – odnośniki do tych klas. 
Blok catch należy traktować jako ciało procedury obsługi wyjątku należącego do klasy argi. 

background image

Java 

17

Jeżeli funkcja/metoda zawiera instrukcję try, to wyjątki mogą być zgłaszane wyłącznie w bloku try. Po 
zgłoszeniu wyjątku sterowanie opuszcza kod, który zgłosił wyjątek i przechodzi do pierwszej w 
kolejności procedury catch; jeżeli ta nie obsługuje wyjątku zgłoszonej klasy, jest on przekazywany do 
następnej. Blok frazy finally (jeśli występuje) jest wykonywany zawsze gdy kończy się wykonanie 
instrukcji try i to nawet wtedy, gdy wykonanie try zostaje gwałtownie przerwane. 
Prosty program, który jedynie ilustruje składnię zgłaszania i obsługi wyjątków może wyglądać 
następująco: 
//plik TestEx.java 

import java.io.*; 

public class TestEx { 

public TestEx() {} 

final int CONSTANT = 10; 

 void ff() throws IOException 

 try 

  {  

   System.out.println("I was here, within try statement."); 

   if(CONSTANT > 0) 

    throw new IOException("CONSTANT should be negative"); 

  } 

   catch(IOException exc) 

   { 

    System.err.println("Caught IOException: "+ exc.getMessage()); 

   } 

    finally 

     { 

      System.out.println("And now I am in finally blok"); 

     } 

}//end ff 

 

public static void main(String args[]) throws IOException 

 TestEx te = new TestEx(); 

 te.ff(); 

}//end main 

}//end class TestEx 
Podany niżej przykład definiuje klasę ListOfNumbers, wywołującą z pakietów Javy dwie metody klas, 
które mogą zgłosić wyjątki. 

import java.io.*; 

import java.util.Vector; 

 

public class ListOfNumbers { 

private Vector victor; 

private static final int size = 10; 

public ListOfNumbers () { 

 victor = new Vector(size); 

 for (int i = 0; i < size; i++) 

  victor.addElement(new Integer(i)); 

public void writeList() { 

 PrintWriter p = null; 

  try { 

   System.out.println("Entering try statement"); 

   p = new PrintWriter(new FileOutputStream("OutFile.txt")); 

   for (int i = 0; i < size; i++) 

    p.println("Value at: " + i + " = " + victor.elementAt(i)); 

   } catch (ArrayIndexOutOfBoundsException e) { 

      System.err.println("Caught ArrayIndexOutOfBoundsException: " + 

background image

 

Języki obiektowe 

18 

 

 e.getMessage()); 

     } catch (IOException e) { 

        System.err.println("Caught IOException: " + e.getMessage()); 

       } finally { 

          if (p != null) { 

           System.out.println("Closing PrintWriter"); 

           p.close(); 

          } else { 

             System.out.println("PrintWriter not open"); 

            } 

        } 

    } 

 

 public static void main(String args[])   { 

 ListOfNumbers lst = new ListOfNumbers(); 

 lst.writeList(); 

}//end main 

 

}//end ListOfNumbers 
 
Konstruktor ListOfNumbers() tworzy obiekt klasy Vector o dziesięciu elementach będących 
wartościami od 0 do 9. W klasie ListOfNumbers zdefiniowano także metodę writeList(), która 
zapisuje ten ciąg liczb do pliku tekstowego OutFile.txt. Metoda writeList() wywołuje dwie metody, 
które mogą zgłosić wyjątki: 
–  konstruktor klasy FileOutputStream, który zgłasza IOException jeżeli z jakiegoś powodu nie może 

otworzyć pliku: 

p = new PrintWriter(new FileOutputStream("OutFile.txt")); 

–  metodę elementAt() klasy Vector, która zgłasza wyjątek ArrayIndexOutOfBoundsException jeżeli 

przekazana do niej wartość indeksu jest zbyt mała (liczba ujemna) lub zbyt duża (większa niż 
liczba elementów zawartych aktualnie w obiekcie klasy Vector). 

Instrukcje System.err.println wykorzystują komunikaty generowane przez metodę getMessage klasy 
Throwable (lub jej podklasę), która podaje dodatkowe informacje o zaistniałym błędzie. 
Jeżeli na dysku jest wystarczająco dużo miejsca na zapisanie pliku OutFile.txt, to program założy taki 
plik z zawartością: 
Value at: 0 = 0 
... 
Value at 9 = 9 
oraz wyprowadzi na ekran dwa wiersze tekstu: 
Entering try statement 
Closing PrintWriter 

1.7. Zarządzanie pamięcią 

Język Java jest wyposażony w mechanizm zbierania nieużytków (garbage collection). System 
wykonawczy Javy automatycznie zwraca przydzielony obiektowi obszar pamięci gdy stwierdzi, że do 
danego obszaru nie odnosi się już żadna referencja. 

1.8. Współbieżność 

Program Javy jest z reguły wykonywany w obrębie jednego procesu, którego stos może być 
wykorzystywany przez wiele współbieżnych wątków programu (wątkiem nazywa się sekwencyjny 
przepływ sterowania w procesie, który wykonuje dany program). Nawet najprostsza aplikacja, która 
wyświetlała napis "Hello, World!", miała dwa wątki wykonania: wątek główny (main thread) 
wykonujący kod tej aplikacji oraz kolektor nieużytków. 
Jeżeli maszyna wirtualna jest wieloprocesorowa, wątki mogą być wykonywane współbieżnie; na 
komputerze jednoprocesorowym współbieżność może być emulowana (np. w systemie Windows’95) 
przez przydzielanie poszczególnym wątkom pewnej liczby kwantów czasu procesora. W tym drugim 
przypadku jest realizowana tzw. wielowątkowość wywłaszczeniowa (preemptive), która nie dopuszcza 

background image

Java 

19

do “zagłodzenia” (starving) wątków o niskich priorytetach. Priorytet jest liczbą z przedziału 1..10; 
jeżeli priorytet nie zostanie ustawiony jawnie (funkcją  setPriority klasy Thread), to nowy wątek 
przejmie priorytet wątku, który go utworzył. 

)

 Uwaga. Zagłodzenie może się zdarzyć wtedy, gdy jeden lub więcej wątków w programie jest 

blokowanych przed dostępem do pewnego zasobu i wskutek tego nie mogą biec dalej. Krańcową 
postacią zagłodzenia jest zakleszczenie lub impas (deadlock). Impas pojawia się wtedy, gdy dwa 
lub więcej wątków czeka na warunek, który nie może być spełniony; typowym przykładem jest 
sytuacja, gdy istnieją dwa  wątki i każdy z nich czeka na wykonanie czegoś przez partnera. 

Wykonanie metody start (public synchronized void start()) na rzecz utworzonego wcześniej obiektu 
klasy  Thread powoduje utworzenie wątku. Przebieg wykonania wątku zależy od implementacji 
metody  run (public void run()), wywoływanej niejawnie przez  system tuż po utworzeniu wątku. 
Zakończenie wykonania metody run() powoduje niejawne wywołanie metody stop (public static final 
void stop()), która niszczy wątek. Stanami wątków można sterować przez wywołania finalnych metod 
wystąpienia  suspend i resume (zawieszenie i wznowienie wątku zawieszonego), wait i notify 
(wstrzymanie i uwolnienie wątku wstrzymanego), wywołania finalnych metod synchronizowanych 
join() i join(long millisec), które powodują wstrzymanie wykonywania wątku aż do zniszczenia  go 
przez inny wątek oraz przez wywołania metod klasy: public static void sleep(long millisec), która 
powoduje uśpienie wątku na podany okres czasu i public static void yield(), która oddaje dostęp do 
procesora innemu wątkowi (o ile taki istnieje). Ponadto można wywołać metodę public final boolean 
isAlive() dla stwierdzenia, czy wątek istnieje. 

)

 Uwaga. Ze względu na zdarzające się błędy przy blokowaniu i zwalnianiu zasobów oraz możliwe 

zakleszczenia wątków w obowiązującej obecnie wersji JDK1.2 nie zaleca się stosowania metod 
resume(), stop() i stop(Throwable obj). 

Wątek Javy może być tworzony na dwa sposoby: albo jako obiekt podklasy, która dziedziczy od klasy 
Thread (klasa java.lang.Thread zawiera metody, które kontrolują i synchronizują poszczególne wątki), 
albo jako obiekt klasy, która implementuje interfejs Runnable (java.lang.Runnable). W obu 
przypadkach należy podać implementację metody run() zadeklarowanej w interfejsie Runnable. 
Pierwszy sposób ilustruje poniższy przykład. 
//plik Thread1.java 

public class Thread1 extends Object { 

public static void main(String args[]) { 

 

Thread x = new MyThread("Fastthread"); 

 

Thread y = new MyThread("Slow thread"); 

 x.setPriority(Thread.MAX_PRIORITY); 

 x.start(); 

 y.start(); 

 }//end 

main() 

}//end Thread1 

class MyThread extends Thread { 

protected String name = "not initialized"; 

public MyThread(String nameString) 

{ name = nameString; } 

 

public void run() { 

 

for(int i =1;i<=10;i++) { 

        try { 

    Thread.currentThread().sleep(300); 

   }//end try 

   catch(InterruptedException e) { 

   } //end catch 

 

 System.out.println(name+"continues... "+i); 

        }//end for      

 

System.out.println(name+" is DONE!! "); 

}//end run() 

}//end MyThread 

background image

 

Języki obiektowe 

20 

 

W przykładzie utworzono dwa wątki,  x i y, przy czym jeden z nich (x )ustawiono na najwyższy 
osiągalny priorytet. Każdy wątek będzie wykonywany oddzielnie, ale wątek  x zakończy działanie 
wcześniej. 
Drugi sposób implementuje interfejs Runnable: 
//plik TwoThreads 

public class TwoThreads extends Object { 

public static void main(String args[]) { 

 

MyClass xx = new MyClass(); 

 

MyClass yy = new MyClass(); 

 

Thread x = new Thread(xx); 

 

Thread y = new Thread(yy); 

 x.setPriority(Thread.MAX_PRIORITY); 

 x.start(); 

 y.start(); 

 }//end 

main() 

}//end TwoThreads 

class MyClass implements Runnable { 

public void run() 

 { 

  for(int I=0;I<=10;I++) 
  { System.out.println("Thread progress="+I); } 
  System.out.println("Thread completed"); 

 } 

1.9 Synchronizacja wątków 

W podanych wyżej przykładach wątki były niezależne i asynchroniczne. Inaczej mówiąc, każdy wątek 
zawierał wszystkie dane i metody potrzebne dla jego wykonania i nie wymagał żadnych zewnętrznych 
zasobów lub metod. Ponadto przebieg każdego wątku miał  własny rytm, niezależny od stanu, czy 
aktywności drugiego, biegnącego współbieżnie.  
Istnieje jednak wiele sytuacji, gdy oddzielne, współbieżnie wykonywane wątki współdzielą pewne 
dane i muszą uwzględniać stany i aktywności innych wątków. Powszechnie znanym modelem 
programistycznym takich sytuacji jest model producent/konsument, w którym producent generuje 
strumień danych pobieranych następnie przez konsumenta. Przykładem praktycznym może być 
program, w którym jeden wątek (producent) zapisuje dane do pliku, podczas gdy drugi wątek czyta 
dane z tego samego pliku. Innym przykładem może być pisanie znaków na klawiaturze: wątek 
producenta umieszcza naciśnięcia klawisza w kolejce zdarzeń, a wątek konsumenta odczytuje 
zdarzenia z tej samej kolejki. Jeszcze innym przykładem może być wysyłanie znaków do tego samego 
strumienia (np. do System.out) z kilku współbieżnie wykonywanych wątków. Widzimy, że podane 
przykłady wykorzystują współbieżne wątki, które dzielą wspólny zasób: w pierwszym współdzielą 
plik, a w drugim kolejkę zdarzeń, w trzecim ten sam strumień. Ponieważ wątki współdzielą wspólny 
zasób, muszą być w jakiś sposób synchronizowane (np. w trzecim przykładzie przy braku 
synchronizacji łańcuchy znaków pochodzące z różnych źródeł mogą być bezsensownie przemieszane). 
Segmenty kodu programu, które żądają dostępu do tego samego obiektu z dwóch oddzielnych, 
współbieżnych wątków, nazywa się sekcjami krytycznymi. W języku Java sekcją krytyczną może być 
blok lub metoda; identyfikuje się je słowem kluczowym synchronized
W przypadku bloku mamy instrukcję synchronized o składni 
 synchronized 

Wyrażenie ) Blok 

w której Wyrażenie musi być typu referencyjnego, a Blok jest instrukcją grupującą, objętą nawiasami 
klamrowymi. 
Instrukcja synchronized przejmuje wzajemnie wykluczającą blokadę na rzecz wykonywanego wątku, 
wykonuje  Blok, po czym zwalnia blokadę. Inaczej mówiąc, wykonanie instrukcji synchronized 
powoduje przydzielenie wątkowi podanego Bloku jako sekcji krytycznej, a po wykonaniu go na rzecz 
obiektu identyfikowanego przez Wyrażenie, zwolnienie sekcji. 
Ilustracją wykorzystania instrukcji synchronized może być poniższy prosty program: 
//plik TestSynchro 

background image

Java 

21

class TestSynchro { 

public static void main(String[] args) { 

TestSynchro t = new TestSynchro(); 

synchronized(t) { 

synchronized(t) { 

System.out.println("made it!"); 

}//end internal synchronized statement 

}//end external synchronized statement 

}//end main 

}//end TestSynchro 
który wydrukuje napis 
made it! 
 
Metoda jest synchronizowana jeżeli w jej nagłówku umieszczono słowo kluczowe synchronized
Synchronizowana metoda operująca na obiekcie pewnej klasy automatycznie nakłada blokadę na ten 
obiekt przed wykonaniem jego ciała (funkcji, metod) i automatycznie zwalnia blokadę przy powrocie, 
podobnie jak instrukcja synchronized. Tak więc segment kodu 

class Test { 

int count; 

synchronized void bump() { count++; } 

static int classCount; 

static synchronized void classBump() { 

classCount++; 

  }//end synchronized method 

}//end class Test 
jest równoważny segmentowi 

class BumpTest { 

int count; 

void bump(){ 

synchronized (this){ 

count++; 

  }//end synchronized statement 

 }//end bump() 

static int classCount; 

static void classBump() { 

try { 

synchronized (Class.forName("BumpTest")) { 

classCount++; 

}// end synchronized statement  

}//end try 

 catch (ClassNotFoundException e) { 

 //... 

 }//end catch 

}//end classBump 

}//end BumpTest 
 
 
Wymieniona “blokada” (lock) jest w programie wielowątkowym związana z obiektem, na którym 
wątek ma wykonywać pewne operacje; jest ona często nazywana monitorem obiektu. Do czasu 
zwolnienia monitora, zostanie zablokowane wykonanie każdego innego wątku, który podejmie próbę 
wywołania (na rzecz tego samego obiektu), dowolnej metody sychronizowanej danej klasy. 
Zauważmy, że w klasie Test metoda classBump() jest statyczna (jest metodą klasy). Zatem, mimo iż 
metoda ta jest synchronizowana, może być jednocześnie wywoływana na rzecz wielu obiektów, a więc 
blokada nie będzie efektywna. Dla uniknięcia możliwości jednoczesnego wykonywania pewnej 
operacji na tym samym obiekcie (w szczególności zawierającym metody statyczne) przez dwa różne 
wątki dobrą praktyką jest definiowanie klas tak, aby były przygotowane na użycie współbieżne, jak 
ilustruje to poniższy kod: 
//plik Box.java 

background image

 

Języki obiektowe 

22 

 

public class Box { 

private Object boxContents; 

public synchronized Object get() { 

Object contents = boxContents; 

boxContents = null; 

return contents; 

     }//end get 

public synchronized boolean put(Object contents) { 

if (boxContents != null) 

return false; 

boxContents = contents; 

return true; 

     }//end put 


Każde wystąpienie klasy Box ma pewną zawartość zmiennej wystąpienia, która utrzymuje referencję 
do dowolnego obiektu. W rezultacie można włożyć obiekt do pudełka (Box) wywołaniem metody 
put(), która zwróci false jeżeli pudełko jest już pełne. Podobnie można wyjąć obiekt z pudełka 
wywołaniem metody get(), która zwróci null jeżeli pudełko jest puste. Gdyby put() i get() nie były 
synchronizowane i dwa wątki wykonywałyby te metody na tym samym obiekcie klasy Box w tym 
samym czasie, wtedy wykonanie programu dwuwątkowego mogłoby przebiegać w sposób przez nas 
nieoczekiwany. 

1.10. Obiekty sieciowe 

Komputery w sieci Internet komunikują się ze sobą albo poprzez TCP (Transport Control Protocol) 
albo poprzez User Datagram Protocol (UDP). 

)

 Uwaga. Nazwą TCP określa się zwykle protokół (zbiór zasad według których odbywa się 

komunikacja w sieci komputerowej); TCP/IP (gdzie IP jest akronimem od Internet Protocol)  jest 
warstwowym zestawem protokołów i odpowiada siedmiowarstwowemu modelowi ISO/OSI (Open 
Systems Interconnection) z warstwami: fizyczną(1), łącza danych(2), sieciową(3), transportową(4), 
sesji(5), prezentacji(6) i aplikacji(7). Np. Protokół IP jest implementacją warstwy (3), zaś TCP i 
UDP implementują warstwę (4) modelu ISO/OSI, jak pokazano w tabeli 1.1. 

 

Tabela 1.1 Zależność pomiędzy modelem ISO/OSI a TCP/IP 

Warstwa ISO/OSI 

TCP/IP 

Aplikacji 

Aplikacji: SMTP, HTTP, FTP, RPC, TELNET, 

Prezentacji 

RLOGIN, inne usługi 

5 Sesji 

DNS, 

LDAP 

Transportowa 

Transportowa: TCP/UDP, IGMP 

3 Sieciowa 

Międzysieciowa: IP, ICMP, protokoły rutingu 

Łącza danych 

Interfejs sieciowy: ARP, RARP, LLC 802.2, Ethernet 802.3 

Fizyczna 

Fizyczna dla różnych mediów 

 
Pisząc program “sieciowy” w języku Java nie musimy deklarować, czy żądamy komunikacji via TCP 
czy UDP, ponieważ zdefiniowane w pakiecie java.net klasy dostarczają  środki dla niezależnej od 
systemu komunikacji w sieci; klasy URL, URLConnection, Socket i ServerSocket korzystają z TCP, a 
klasy DatagramPacket, DatagramSocket i MulticastSocket korzystają z UDP. 
Tym niemniej warto pamiętać,  że powszechnie używane protokoły: Telnet i wirtualnego terminala 
odpowiadają warstwie 5 i częściowo 6, zaś FTP (File Transfer Protocol) odpowiada warstwom 6 i 7 
modelu ISO/OSI. Zauważmy też, że HTTP (Hypertext Transfer Protocol), jest aplikacją (warstwa (7) 
modelu ISO/OSI. Gdy używamy HTTP do czytania z pewnego lokalizatora URL (Uniform Resource 
Locator) danych z pliku  w formacie HTML (HyperText Markup Language), otrzymywane dane 
muszą wystąpić w tej samej kolejności, w której były przesyłane, co wymaga niezawodnego kanału 
komunikacji punkt-punkt, jaki zapewnia protokół połączeniowy TCP. Z kolei UDP, bezpołączeniowy 
protokół przesyłania niezależnych pakietów danych (datagramów) pomiędzy dwoma aplikacjami w 

background image

Java 

23

sieci, jest wystarczający  dla takich aplikacji, jak np. program ping, czy też programu podającego 
aktualny czas. 
Komputery w sieci są identyfikowane przez 32-bitowe adresy IP, zaś biegnące na danym komputerze 
sieciowym procesy – poprzez 16-bitowe numery portów (port można uważać za utrzymywaną przez 
system kolejkę danych, które mają być dostarczane do danego procesu wykonującego pewien 
program). Na przykład programowi wykorzystującemu protokół HTTP przydziela się zwyczajowo 
port o numerze 80. 
Programy odbierają lub wysyłają dane poprzez wiązane z numerami portów gniazda (sockets). 
Gniazdo, definiowane przez warstwę transportową modelu ISO/OSI, reprezentuje punkt końcowy 
połączenia pomiędzy programami wykonywanymi na komputerach sieciowych w systemie dostawca-
odbiorca (klient-server); jest to interfejs programowy umożliwiający aplikacjom dostęp do protokołów 
TCP i UDP i wymianę danych poprzez sieć pracującą pod kontrolą protokołów TCP/IP. Program 
serwera wykonywany na  konkretnej maszynie ma przypisane do pewnego numeru portu gniazdo, 
poprzez które “nasłuchuje” ewentualnego żądania nawiązania łączności przez klienta. Jeżeli klient zna 
adres komputera sieciowego, na którym jest wykonywany serwer oraz numer portu, do którego serwer 
jest dołączony, to może przesłać takie żądanie. Serwer akceptuje połączenie, a następnie tworzy dla 
klienta nowe gniazdo, związane z innym numerem portu, ponieważ na pierwotnym gnieździe musi 
nadal prowadzić “nasłuch” żądań połączenia. Po stronie klienta, gdy uzyska potwierdzenie połączenia, 
tworzone jest odpowiednie gniazdo (związane z lokalnym numerem portu na maszynie klienta), 
poprzez które może prowadzić komunikację z serwerem. 
W pakiecie java.net klasy Socket i ServerSocket służą do komunikacji w oparciu o protokół 
połączeniowy TCP, zaś klasy DatagramSocket, DatagramPacket oraz MulticastSocket są 
wykorzystywane do komunikacji UDP. Klasy: URL oraz URLConnection i URLEncoder służą do 
nawiązania  łączności z World Wide Web (WWW). Komunikacja, która wykorzystuje te klasy, 
reprezentuje wyższy poziom abstrakcji, ponieważ korzystają one między innymi z implementacji 
gniazd. 
Poniżej pokazano przykład aplikacji, która wykorzystuje klasy URL i URLConnection dla dostępu do 
zasobu sieciowego (pliku tekstowego “abc”) WWW zlokalizowanego pod adresem 
www.task.gda.pl/java/. 
//plik URLTest.java 

import java.net.*; 

import java.io.*; 

//import java.util.*; 

public class URLTest { 

public static void main(String[] args) { 

 URL url; 

 try { 

      url =  new URL("http://www.task.gda.pl/java/abc"); 

      URLConnection uc = url.openConnection(); 

      BufferedReader d = new BufferedReader(new 

      InputStreamReader(uc.getInputStream())); 

      //DataInputStream dis = new  

      //DataInputStream(uc.getInputStream()); 

      String line = d.readLine(); 

      System.out.println(line); 

     }//end try 

      catch (Exception e)  { e.printStackTrace(); } 

}// end main 

}// end URLTest 
W programie wykorzystano konstruktor URL(String). Klasa URL jest wyposażona w cztery publiczne 
konstruktory: 
public URL(String spec) throws MalformedURLException – tworzy obiekt URL z podanej 
reprezentacji obiektu klasy String, lub zgłasza wyjątek, jeżeli  łańcuch “spec” podaje nieznany 
protokół. Dla danego protokołu (http, file, ftp) jest przyjmowany domyślny numer portu. Numer portu 
można też podać jawnie, np. http://www.task.gda.pl:80/java/abc. 

background image

 

Języki obiektowe 

24 

 

public URL(URL context, String spec) throws MalformedURLException – tworzy obiekt URL przez 
parsing (wydzielenie) specyfikacji “spec” w obrębie zadanego kontekstu. 
public URL(String protocol, String host, int port, String file) throws MalformedURLException – 
tworzy obiekt URL z podanego protokołu, nazwy komputera, numeru portu i nazwy pliku. 
public URL(String protocol, String host, String file) throws MalformedURLException – tworzy 
absolutny obiekt URL z podanego protokołu, nazwy komputera i nazwy pliku; przyjmuje port 
domyślny dla danego protokołu. 

2. Aplety - programy Javy na stronach WWW. 

Aplet jest niewielkim programem Javy przeznaczonym do uruchamiania w obrębie innej aplikacji - 
przeglądarki WWW lub Java Applet Viewer. Najprostszy aplet drukujący jedno zdanie na ekranie 
może mieć następującą postać: 
 
HelloWorld.java 

 

import java.applet.*; 

import java.awt.*; 

 

public class HelloWorld extends Applet { 

public void init() { 

add(new Label("Hello World!")); 

 
Podobnie jak aplikację, kompilujemy aplet poleceniem: 

 javac 

HelloWorld.java 

w wyniku czego otrzymamy plik ze skompilowaną klasą o nazwie HelloWorld.class
Następnie tworzymy prostą stronę HTML zawierającą znacznik <applet> wywołujący aplet Javy i 
otwieramy tę stronę przeglądarką WWW. 

 
HelloWorld.html 

 

<html> 

<head> 

<title>HelloWorld</title> 

</head> 

<body> 

<h1>Aplet HelloWorld</h1> 

<applet code=HelloWorld.class width=200 height=50> 

</applet> 

</body> 

</html> 
 
Wynik działania apletu możemy również obejrzeć używając przeglądarki apletów dostarczanej przez 
Suna razem ze środowiskiem JDK: 

 appletviewer 

HelloWorld.html 

Każdy aplet tworzony jest jako podklasa klasy java.applet.Applet, dostarczającej interfejs do 
środowiska, w obrębie którego jest on uruchamiany. 
 

background image

Java 

25

java.awt.Component

java.lang.Object

java.awt.Container

java.awt.Panel

java.applet.Applet

 

 

Rys. 2-1. Hierarchia dziedziczenia dla klasy Applet. 

 
W cyklu życia apletu występują cztery istotne zdarzenia. Gdy one następują, wywoływane są 
odpowiednie metody klasy Applet, które mogą być przesłonięte przez metody zdefiniowane w jej 
podklasie, jak w poniższym przykładzie: 
 

public class MyApplet extends Applet { 

. . . 

public void init() { . . . } 

public void start() { . . . } 

public void stop() { . . . } 

public void destroy() { . . . } 

. . . 


 
Metody te są wywoływane przez środowisko, w którym uruchamiany jest aplet, w momentach 
wystąpienia następujących zdarzeń: 
init - po załadowaniu apletu (lub jego przeładowaniu) w celu jego zainicjowania, 
start  - w momencie uruchamiania apletu, po jego załadowaniu lub po ponownym odwiedzeniu 
strony zawierającej aplet, 
stop - w momencie zatrzymywania pracy apletu, gdy opuszczana jest strona lub następuje wyjście z 
przeglądarki, 
destroy - przed wyjściem z przeglądarki. 
 

init

start

stop

destroy

 

 

Rys. 2-2. Cykl życia apletu. 

 

background image

 

Języki obiektowe 

26 

 

Aplet nie musi przesłaniać wszystkich z tych metod, może tylko niektóre albo żadnej. W metodzie 
init powinno się zamieszczać kod, który normalnie powinien być zawarty w konstruktorze klasy. 
Jednak w momencie wykonywania konstruktora klasy apletu nie ma gwarancji, że jest już 
zdefiniowane całe jego środowisko. Dlatego takie zadania jak na przykład utworzenie wątków apletu 
czy jednokrotne załadowanie obrazów wykorzystywanych przez aplet powinny być zawarte właśnie w 
metodzie init. W metodach start  i  stop  można na przykład odpowiednio uruchamiać i 
zatrzymywać animację tak, żeby w czasie gdy użytkownik nie ogląda strony z apletem niepotrzebnie 
nie zużywać zasobów komputera. 
Aplet dziedziczy metody interfejsu użytkownika z nadrzędnych klas AWT (Abstract Window Toolkit
Component, Container i Panel. 
Z klasy Component  dziedziczy metody paint  i  update, które odpowiadają za graficzną 
reprezentację apletu na stronie przeglądarki. Metody te aplet może przesłonić: 
 

class MyApplet extends Applet { 

. . . 

public void paint(Graphics g) { . . . } 

. . . 

 
Aplet jest podklasą klasy Container, może więc zawierać inne obiekty klasy Component, takie 
jak klawisze, etykiety, listy wyboru itd. Jak w innych obiektach klasy Container  wzajemne 
rozłożenie komponentów kontrolowane jest poprzez klasę LayoutManager. 
Z klasy Component  aplet dziedziczy również metody obsługi zdarzeń. Zdarzenia są generowane 
głównie przez komponenty interfejsu graficznego użytkownika. Komponenty będące źródłem zdarzeń 
rejestrują jedną lub więcej klas delegowanych do obsługi zdarzenia: 

someComponent.addActionListener(instanceOfMyClass); 

W deklaracji klasy obsługującej zadarzenie specyfikujemy, że implementuje ona przynajmniej jeden z 
interfejsów pochodnych od EventListner (np. ActionListner, MouseListner, 
KeyListner, WindowListner itd.): 

public class MyClass implements ActionListener { 

a w jej definicji implementujemy metodę obsługującą zdarzenie: 

public void actionPerformed(ActionEvent e) { 

... // kod reagujący na zdarzenie typu ActionEvent  


Każde  źródło zdarzeń może mieć wiele klas delegowanych do jego obsługi. Jedna klasa może też 
obsługiwać wiele różnych źródeł zdarzeń. 
Aplety są uruchamiane w środowisku przeglądarki, która narzuca im pewne ograniczenia związane z 
zachowaniem bezpieczeństwa. Różnią się one nieco pomiędzy różnymi przeglądarkami. Aplet 
ściągnięty do przeglądarki poprzez sieć: 
•  nie może czytać i pisać do plików znajdujących się na komputerze, który go wykonuje, 
•  nie może tworzyć połączeń sieciowych do komputerów innych niż ten, z którego został ściągnięty, 
•  nie może uruchamiać żadnych programów na komputerze, który go wykonuje, 
•  może czytać tylko wybrane parametry systemowe, 
•  nie może ładować bibliotek ani definiować metod w kodzie maszynowym. 
Ograniczeniem (choć nie związanym z bezpieczeństwem) jest również graficzny interfejs użytkownika 
apletu. Aplet może przedstawiać swoją reprezentację graficzną tylko w obrębie prostokąta na stronie 
WWW o z góry zadanych wymiarach, bądź w postaci generowanych okienek, które różnią się od tych 
generowanych przez aplikacje. 
 
Pliki składające się na aplet: klasy, pliki z grafikami, dźwiękami i inne pliki pomocnicze mogą być 
połączone w jeden plik o formacie Java Archive (JAR), co pozwala uzyskać wiele korzyści: 
•  bezpieczeństwo - zawartość pliku JAR można podpisać cyfrowo; dzięki temu mogą być 

złagodzone niektóre z wcześniej wymienionych ograniczeń związane z dostępem do lokalnych 
zasobów dyskowych oraz z pełnym dostępem do sieci, 

background image

Java 

27

•  skrócenie czasu ładowania - cały aplet może  ściągnięty przez przeglądarkę w jednej transakcji 

HTTP bez konieczności otwierania nowego połączenia dla każdego pliku, 

•  kompresja - pliki są kompresowane zgodnie z formatem ZIP. 
Do obsługi plików JAR służy standardowe narzędzie  jar  zawarte w pakiecie JDK. Sposób jego 
użycia jest analogiczny do programu tar z systemu Unix. 
 

Operacja Polecenie 

Tworzenie pliku JAR 

jar cf jar-file input-file(s) 

Oglądanie zawartości pliku JAR 

jar tf jar-file 

Rozpakowywanie pliku JAR 

jar xf jar-file 

Uruchamianie apletu spakowanego w postaci 
pliku JAR 

<applet 

   code=AppletClassName.class 

   archive="JarFileName.jar" 

   width=width height=height> 

</applet> 

 
)  Uwaga. Internet Explorer używa własnego standardu kompresji - CAB. Netscape Communicator 
obsługuje format JAR.. 
 
Znacznik  <applet>  na stronie HTML może mieć więcej parametrów niż podane wcześniej w 
prostym przykładzie. Bardziej złożone wywołanie może mieć następującą postać: 
 

<applet code=AppletSubclass.class codebase=”directory/” 

width=anInt height=anInt align=left> 

<param name=parameter1Name value=aValue> 

<param name=parameter2Name value=anotherValue> 
Wymagana jest przeglądarka obsługująca aplety Javy! 

</applet> 
 
Domyślnie przeglądarka szuka klas apletu oraz plików skompresowanych w tym samym katalogu, z 
którego został ściągnięty plik HTML zawierający znacznik <applet>. Można jednak wskazać inne 
miejsce podając parametr codebase. Wartością parametru może być ścieżka względna wobec strony 
HTML, ścieżka bezwzględna lub w ogólności dowolny URL. Klasy apletu mogą więc być ściągane z 
innego serwera niż strona HTML. Opcje width i height określają rozmiary prostokąta na stronie 
przeglądarki, w obrębie którego wizualizowany jest aplet. Opcja align  określa położenie apletu, 
podobnie jak dla znacznika <img>: left, right, top, texttop, middle, 
absmiddle, baseline, bottom, absbottom. Do apletu można przekazywać parametry 
definiując znaczniki <param> w obrębie znacznika <applet>. Wartości tych parametrów można 
następnie odczytywać w kodzie apletu wywołując metodę klasy Applet  

public String getParameter(String name) 

Opcjonalny tekst w obrębie znacznika <applet>  jest pisany w okienku przeglądarki, gdy ta nie 
potrafi obsługiwać Javy lub obsługa została wyłączona. 
Kod apletu wykonywany jest przez maszynę wirtualną Javy zaimplementowaną w przeglądarce. 
Jednym ze stałych niedomagań tego modelu dystrybucji kodu jest to, że wersje Javy wspierane przez 
przeglądarki nie nadążają za najnowszymi wersjami wprowadzanymi przez firmę Sun. Aby częściowo 
rozwiązać ten problem Sun opracował moduł rozszerzający (ang. plug-in) przeglądarki (obecnie tylko 
dla Netscape Navigator i Internet Explorer pod Windows), który zapewnia wykorzystanie najnowszej 
wersji bibliotek języka Java. Na skutek niejednolitego sposobu wywołania takiego modułu ze strony 
HTML i jego złożonej postaci najwygodniej jest skorzystać z programu HTMLConverter, który 
zamienia wystąpienia zanacznika <applet> na odpowiadający mu kod wywołujący moduł 
rozszerzający Javy. 

background image

 

Języki obiektowe 

28 

 

3. Standardowe klasy Javy. 

W skład JDK 1.2 wchodzą następujące pakiety: java.applet, java.awt, java.beans, 

java.io, java.lang, java.math, java.net, java.rmi, java.security, 

java.sql, java.text, java.util, javax.accessibility, javax.swing, 
org.omg. Zostaną one omówione pokrótce, najpierw te najczęściej wykorzystywane. 
 
W pakiecie java.lang  zdefiniowana jest klasa Object, która jest klasą nadrzędną wobec 
wszystkich innych klas Javy.  
Pakiet zawiera także klasy opakowujące (kopertowe) zmienne podstawowych typów języka Java takie 
jak Boolean, Byte, Integer, Long, Double, Character itd., definiując użyteczne 
metody operujące na zmiennych tych typów, metody konwersji pomiędzy typami, zamianę na 
łańcuchy znaków i inne.  
Klasa  Math zawiera metody wykonujące podstawowe operacje numeryczne takie jak funkcje 
eksponencjalne, logarytmiczne, trygonometryczne.  
W pakiecie java.lang  zawarte są dwie klasy obsługujące  łańcuchy znaków: String  i 
StringBuffer. Klasa String  używana jest do przechowywania i wykonywania operacji na 
stałych łańcuchach; po utworzeniu obiektu tej klasy nie można zmienić jego wartości. Klasa ta zawiera 
metody do sprawdzania poszczególnych znaków, porównywania łańcuchów, wyodrębniania 
podłańcuchów, tworzenia kopii z zamianą na małe albo duże litery. Klasa StringBuffer 
implementuje łańcuchy znaków, które mogą być zmieniane. Podstawowe metody tej klasy to append 
dodająca znaki na końcu bufora i insert wstawiająca znaki w określonym miejscu. Każdy obiekt 
przydziela bufor na przechowywany łańcuch znaków. Jeżeli całkowita długość  łańcucha wzrośnie 
powyżej rozmiaru bufora, automatycznie przydzielany jest większy.  
Dostęp do zasobów systemowych można uzyskać poprzez niezależne od systemu API zdefiniowane w 
finalnej klasie System oraz zależne od systemu API zawarte w Runtime. Można uzyskać dostęp do 
parametrów systemowych, standardowych strumieni: wyjściowego, wejściowego i błędów, metod 
ładowania bibliotek, odczytu czasu komputera, szybkiego kopiowania tablic itd. 
Klasa Thread definiuje wątki programu Javy pozwalające na jego współbieżną pracę. 
 
Pakiet  java.io  definiuje klasy implementujące operacje wejścia-wyjścia. W pakiecie tym (jak 
również i w innych) występują klasy i metody pochodzące z JDK 1.0 równolegle z nowymi klasami 
wprowadzonymi wraz z wejściem JDK 1.1. Pozostawiono stare klasy w celu zapewnienia zgodności 
dla dawniej pisanych programów. Często przestarzałe (deprecated) konstrukcje  są odradzane i 
proponowane są ich udoskonalone odpowiedniki.  
Podstawowymi klasami pozwalającymi czytać i pisać z plików, łańcuchów znaków i innych strumieni, 
są: 

Reader wraz z podklasami BufferedReader, CharArrayReader, 

InputStreamReader, FileReader, StringReader oraz Writer wraz z podklasami 

BufferedWriter, CharArrayWriter, OutputStreamWriter, FileWriter, 
PrintWriter, StringWriter. Ich starsze odpowiedniki to InputStream i 
OutputStream wraz z ich podklasami.  
Strumień wejściowy znaków może być dzielony na jednostki logiczne - tokeny, poprzez 
skonstruowanie na nim obiektu klasy StreamTokenizer. Można zdefiniować składnię, według 
której wyróżniane będą tokeny typu słowo, liczba, znaczniki końca linii i pliku oraz pomijane 
komentarze. 
Zdefiniowane są również klasy wspierające obsługę struktury drzewa plików w systemie - File oraz 
czytanie i pisanie do plików o dostępie swobodnym - RandomAccessFile. 
 
W pakiecie java.util  można znaleźć m.in. szereg klas definiujących różne struktury danych 
przechowujące inne obiekty. Klasa Vector implementuje tablicę obiektów, która może rosnąć lub 
zmniejszać się w miarę jak obiekty są dodawane lub usuwane. Tak jak w zwykłej tablicy dostęp do jej 
elementów może odbywać się poprzez całkowity indeks, można również szukać wystąpienia obiektu 
w wektorze, którego wartość jest równa podanemu. Wszystkie elementy wektora najwygodniej jest 
przeglądać wykorzystując interfejs Enumeration: 

background image

Java 

29

 

Vector v; 

for(Enumeration e = v.elements(); e.hasMoreElements() ;) 

System.out.println(e.nextElement()); 
 
Każdy wektor stara się optymalizować zarządzanie rozmiarem zajmowanej pamięci. Gdy wektor 
zajmuje całą przydzieloną pamięć, przed dodaniem kolejnego elementu jego rozmiar jest 
automatycznie zwiększany o wartość capacityIncrement. Program może jednak sam zwiększyć 
rozmiar wektora przed wstawieniem dużej porcji danych, aby uniknąć wielu realokacji. Podklasą klasy 
Vector jest Stack realizujący kolejkę LIFO obiektów z metodami push i pop. 
Klasa  Dictionary jest abstrakcyjnym rodzicem dowolnej klasy implementującej odwzorowanie  
kluczy na wartości. Jej podklasą jest Hashtable, której konstrukor posiada dwa parametry 
initialCapacity i loadFactor. Gdy liczba elementów w tablicy mieszającej (hash table
osiąga wartość iloczynu tych parametrów, rozmiar tablicy jest automatycznie zwiększany i przeliczane 
są pozycje elementów. Większy współczynnik wypełnienia pozwala oszczędniej gospodarować 
pamięcią kosztem dłuższego czasu potrzebnego do wyszukiwania elementów. Efektywniej jest 
również zawczasu przydzielić odpowiedniej wielkości tablicę, niż potem zdać się na automatyczne 
przeładowywania. Poniżej przedstawiono przykład tablicy mieszającej obiektów typu Integer 
posiadających nazwy jako klucze: 
 

Hashtable numbers = new Hashtable(); 

numbers.put("one", new Integer(1)); 

numbers.put("two", new Integer(2)); 

numbers.put("three", new Integer(3)); 
 
Aby wydobyć element z tablicy można posłużyć się następującym kodem: 
 

Integer n = (Integer)numbers.get("two"); 

if(n != null) 

System.out.println("two = " + n); 
 
Obiekty przechowywane w tablicy mieszającej muszą implementować metody hashCode i equals 
dziedziczone z klasy java.lang.Object. 
Przy użyciu klasy BitSet można tworzyć zbiory bitów i wykonywać na nich operacje logiczne. 
Klasa StringTokenizer pozwalająca dzielić łańcuchy znaków na tokeny jest znacznie prostsza w 
użyciu niż klasa java.io.StreamTokenizer. Określa się w niej tylko jakie znaki rozdzielają 
kolejne tokeny, domyślnie są to znaki z łańcucha ” \t\n\r”, na przykład: 
 

StringTokenizer st = new StringTokenizer("To jest tylko test"); 

while (st.hasMoreTokens()) 

System.out.println(st.nextToken()); 
 
Można również znaleźć klasy obsługujące daty - Date  i  Calendar, ustawiające parametry 
specyficzne dla kraju, regionu lub języka - Locale, TimeZone. 
W pakiecie java.util.zip znajdują się klasy pozwalające tworzyć i czytać pliki skompresowane 
w formatach ZIP i GZIP. 
 
Pakiet java.net zawiera klasy realizujące połączenia sieciowe zarówno na poziomie gniazd, jak i 
adresów URL wskazujących zasoby w WWW. Podstawowe klasy to Socket, URL, 
URLConection. Poniższy przykład pokazuje jak z apletu można w prosty sposób przejść do 
wybranej strony WWW: 
 

try { 

   URL task = new URL("http://www.task.gda.pl/"); 

   getAppletContext().showDocument(task); 

background image

 

Języki obiektowe 

30 

 

} catch (MalformedURLException e) {}     // new URL() failed 
 
Aplety i aplikacje Javy komunikują się z użytkownikiem wykorzystując klasy z pakietu java.awt 
składające się na graficzny interfejs użytkownika AWT (Abstract Window Toolkit). AWT dostarcza 
typowe komponenty graficzne takie, jak klawisze, pola do wprowadzania tekstu, listy wyboru itd. 
poprzez klasy Button, Checkbox, Choice, Label, List, Menu, Scrollbar, 
TextArea, TextField będące pochodnymi klasy Component. Wykorzystując klasę Canvas, 
można rysować dowolne obrazy graficzne na ekranie, a po dodaniu odpowiedniej obsługi zdarzeń 
można zdefiniować dowolny własny komponent.  
Wraz z JDK 1.1 został wprowadzony nowy model obsługi zdarzeń, który jest znacznie bardziej 
elastyczny i wydajny w porównaniu z modelem z JDK 1.0. Stary model dla zachowania zgodności jest 
również obsługiwany, choć jego użycie jest odradzane. W modelu 1.1 AWT zdarzenia są generowane 
przez  źródła, którymi mogą być komponenty interfejsu użytkownika, myszka, klawiatura itd. Może 
być wydelegowany jeden lub więcej odbiorców zdarzenia pochodzącego od określonego źródła, który 
jest obiektem dowolnej klasy implementującej przynajmniej jeden z interfejsów obsługi zdarzeń 
takich, jak ActionListener, KeyListener czy MouseListener. Zasadę działania tego 
modelu można prześledzić w poniższym przykładzie: 
 

import java.applet.*; 

import java.awt.*; 

import java.awt.event.*; 

 

public class URLButton extends Applet implements ActionListener { 

   public void init() { 

      Button button = new Button("TASK Home"); 

      add(button); 

      button.addActionListener(this); 

   } 

   public void actionPerformed(ActionEvent event) { 

      . . . 

   } 


 
W skład JDK 1.2 został  włączony pakiet javax.swing nazywany również  Java Foundation 
Classes
 (JFC), znacznie rozszerzający możliwości funkcjonalne interfejsu graficznego użytkownika. 
Pakiet ten zawiera znacznie więcej komponentów graficznych, pozwala dynamicznie imitować znane 
środowiska graficzne (np. Windows, CDE/Motif), umożliwia korzystanie z urządzeń pomocniczych w 
odczytywaniu informacji (jak np. czytniki ekranu, wyświetlacze Braille’a), zawiera bogatą bibliotekę 
do tworzenia dwuwymiarowej grafiki, wspomaga technikę “przeciągnij i upuść” pomiędzy 
aplikacjami Javy i aplikacjami w danym systemie operacyjnym. 
 
Zastosowanie klas Applet i AppletContext  z pakietu java.applet  było już 
demonstrowane we wcześniejszych przykładach. Definiują one środowisko, w którym uruchamiane są 
aplety. Pozwalają również na komunikację pomiędzy apletami znajdującymi się na tej samej stronie. 
 
Środowisko budowania niezależnych od platformy, modyfikowalnych wizualnych komponentów 
JavaBeans API zawarte jest w pakiecie java.beans. Wykorzystując przeznaczone do tego 
narzędzia programistyczne, można wizualnie z komponentów konstruować aplikacje, aplety, servlety 
lub komponenty złożone. Narzędzie tego typu pozwala wybrać z zestawu potrzebny nam komponent, 
wstawić go do okienka programu, zmodyfikować jego wygląd i zachowanie, zdefiniować interakcję z 
innymi komponentami; wszystko to nie pisząc ani jednej linii kodu. 
 
JDBC API (Java DataBase Conectivity) zdefiniowane w pakiecie java.sql wprowadza jednolity 
standard dostępu do dowolnych relacyjnych baz danych. Klasy z tego pakietu implementują 
połączenia z bazą danych, zapytania SQL, wyniki tych zapytań itp. Dostęp do bazy odbywa się za 

background image

Java 

31

pośrednictwem drivera, który może być napisany całkowicie w Javie i wtedy może być np. ściągnięty 
jako część apletu, może być też napisany z wykorzystaniem kodu maszynowego określonej platformy, 
aby uzyskać dostęp do istniejących już bibliotek dostępu do baz danych. Drivery JDBC można 
podzielić na cztery kategorie: 
1.  Pomost JDBC-ODBC, który zapewnia dostęp do bazy poprzez binarne drivery ODBC. 
2.  Driver w Javie zamieniający odwołania JDBC na odwołania do binarnego drivera dostępowego 

konkretnej bazy danych znajdującego się na lokalnym komputerze. 

3.  Całkowicie w Javie napisany driver komunikujący się poprzez sieć z oprogramowaniem 

pośredniczącym w dostępie do bazy (middleware). Protokół dostępowy zależy od producenta tego 
oprogramowania, producent ten jest też najczęściej dostawcą odpowiedniego drivera JDBC. 

4.  Całkowicie w Javie napisany driver komunikujący się poprzez sieć bezpośrednio z serwerem bazy 

danych. Dostawcą drivera często jest producent bazy danych. 

 
RMI  (Remote Method Invocation) zawarty w java.rmi umożliwia tworzenie rozproszonych 
aplikacji w Javie. Aplikacja taka złożona jest z serwera, tworzącego obiekty, których metody mogą 
być wywoływane zdalnie; po utworzeniu obiektów aplikacja udostępnia odnośniki do nich i czeka na 
wywołania ich metod przez klientów; klient ściąga odnośniki do zdalnych obiektów i wywołuje ich 
metody. RMI zapewnia mechanizm, poprzez który odbywa się komunikacja pomiędzy serwerem i 
klientem oraz przesyłane są dane w obie strony. 
W pakietach org.omg Javy 1.2 wsparto powszechnie przyjęty standard modelu rozproszonych 
obiektów – CORBA (Common Object Request Brokerage Architecture). Pozwala on na komunikację 
pomiędzy obiektami bez względu na platformę systemu operacyjnego, ani użyty języka 
programowania. 
 
W pakiecie java.math znajdują się klasy BigInteger i BigDecimal pozwalające na tworzenie 
i operacje na liczbach całkowitych dowolnej precyzji. BigInteger implementuje operacje 
arytmetyki modularnej, obliczanie największego wspólnego podzielnika, generator liczb pierwszych, 
operacje na bitach itd. Z liczb zdefiniowanych w tej klasie korzysta pakiet java.security 
wprowadzający jednolity model kryptografii i ochrony danych. Znajdują się tam abstrakcyjne klasy 
definiujące kody zapewniające integralność danych (message digests) oparte na jednokierunkowych 
funkcjach mieszających (one-way hash functions), podpisy cyfrowe oparte na parze kluczy jawnego i 
tajnego, zarządzanie kluczami i certyfikaty. Jest to tylko ramowy szkielet koncepcji 
kryptograficznych, pod który producenci oprogramowania mogą podłączać konkretne algorytmy i 
metody. Razem z JDK Sun dostarcza jedynie klasy do cyfrowego podpisywania dokumentów 
opartego na algorytmie DSA oraz obliczania kodów dokumentów metodami MD5 i SHA-1. 
 
W pakiecie java.text znajdziemy klasy obsługujące różne formaty danych tekstowych, np. 
DateFormat obsługujący daty w różnych standardach czy NumberFormat tworzący i analizujący 
różne formaty liczb. 

4. Servlety - programy Javy na serwerze WWW. 

Servlety są modułami, które są uruchamiane wewnątrz serwerów przetwarzających zapytania i 
generujących odpowiedzi, takich jak np. rozszerzone o obsługę Javy serwery WWW. Rozszerzają one 
funkcjonalność tych serwerów. Można w uproszczeniu powiedzieć, że servlety dla serwerów są tym, 
czym aplety dla przeglądarek. 
Servlety stanowią alternatywę dla skryptów CGI, umożliwiając łatwą metodę dynamicznego tworzenia 
dokumentów HTML. Są one łatwiejsze do pisania dzięki wykorzystaniu środowiska Javy. Są również 
szybciej wykonywane, gdyż wywołanie servletu odbywa się nie poprzez uruchomienie nowego 
procesu, co jest kosztowne ze względu na czas procesora i zasoby pamięciowe, lecz jako wątek. Co 
więcej, kod wykonywalny dla servletu jest ładowany do serwera tylko raz, gdy po raz pierwszy żądana 
jest usługa oferowana przez dany servlet lub automatycznie, gdy zostanie zmieniony kod servleta. 
Potem servlet pozostaje w pamięci serwera i może równolegle obsługiwać wiele zapytań z 
możliwością komunikacji pomiędzy nimi. 

background image

 

Języki obiektowe 

32 

 

Klasy implementujące funkcje servletów znajdują się w pakiecie javax.servlet dostępnego z 
firmy Sun jako Java Servlet Development Kit. Zawierają one abstrakcyjną klasę GenericServlet 
oraz interfejs Servlet, które definiują podstawowe własności ogólnych servletów, czyli modułów 
przetwarzających zapytania i generujących odpowiedzi. Jednak najczęściej korzysta się z podklas 
znajdujących się w pakiecie javax.servlet.http definiujących servlety HTTP. Klasa 
HttpServlet posiada m.in. takie metody jak doGet i doPost obsługujące typowe zapytania 
CGI, czy uniwersalną metodę service obsługującą wszystkie rodzaje zapytań. Najprostszy servlet 
może wyglądać następująco: 
 
HelloWorldServlet.java 

 

import java.io.*; 

import javax.servlet.*; 

import javax.servlet.http.*; 

 

public class HelloWorldServlet extends HttpServlet { 

   public void doGet (HttpServletRequest req,  

      HttpServletResponse res) 

      throws ServletException, IOException { 

 

      res.setContentType("text/html"); 

      ServletOutputStream out = res.getOutputStream(); 

      out.println("<html>"); 

      out.println("<head><title>Hello World</title></head>"); 

      out.println("<body>"); 

      out.println("<h1>Hello World</h1>"); 

      out.println("</body></html>"); 

   } 


 
Większość użytecznych servletów czyta parametry wejściowe przekazane im z przeglądarki poprzez 
metody GET lub POST i na ich podstawie tworzy odpowiedni dokument HTML. Prosty przykład 
takiego servletu przedstawiono poniżej. 
 
Hello.java 

 

import java.io.*; 

import javax.servlet.*; 

import javax.servlet.http.*; 

 

public class Hello extends HttpServlet { 

   public void service(HttpServletRequest req,  

      HttpServletResponse res) 

      throws ServletException, IOException { 

 

      res.setContentType("text/html"); 

      PrintWriter toClient = res.getWriter(); 

      toClient.println("<html><head>"); 

      toClient.println("<title>Hello</title>"); 

      toClient.println("</head>"); 

      toClient.println("<h1>Hello " +  

         req.getParameterValues("name")[0] + "!</h1>"); 

      toClient.println("</body></html>"); 

      toClient.close(); 

   } 


 
Powyższy servlet można wywołać na kilka sposobów. Można podać w przeglądarce URL 

background image

Java 

33

 http://www.task.gda.pl/servlet/Hello?name=Darek 
i wywołać servlet przekazując parametry metodą GET. Można na stronie HTML umieścić formularz: 

 

<form action=http://www.task.gda.pl/servlet/Hello method=POST> 
Podaj swoje imię: <input type=text name=name> 
<input type=submit value=Wyślij> 

</form> 
 
i wywołać servlet przekazując parametry metodą POST. Można również wykorzystać znacznik 
<servlet> na stronie HTML zapisanej w pliku z rozszerzeniem shtml
 
Hello.shtml 
 

... 

<servlet code=Hello> 

<param name=name value=Darek> 

</servlet> 

... 
 
Wtedy serwer WWW rozszerzony o funkcjonalność servletów zastąpi na stronie HTML powyższy 
znacznik wynikiem generowanym przez servlet Hello i tak przetworzoną stronę prześle do 
przeglądarki. 
Dostępnych jest już wiele serwerów WWW obsługujących servlety Javy. Przykładami mogą być Java 
Web Server firmy Sun lub darmowy dla celów niekomercyjnych JRun firmy Live Software. Ten 
ostatni może pracować jako samodzielny serwer WWW, bądź jako moduł rozszerzający 
funkcjonalność innych znanych serwerów WWW np. Apache, Microsoft IIS, Netscape. 

 

5. Podsumowanie 

Zalety języka Java: 
•  “ożywienie” WWW - swego rodzaju “lingua franca” Internetu 
•  język pozwala łatwo pisać aplikacje sieciowe 
•  przenośność - niezależność od platformy dzięki JVM; Java poprzez swój B-kod staje się najbliższą 

aproksymacją (zbudowaną raczaej z software niż z hardware, chociaż anonsowane są już “Java 
chips”) jednego z najstarszych marzeń przemysłu komputerowego: prawdziwie uniwersalna 
maszyna  wirtualna 

•  prawie czysta implementacja paradygmatu obiektowego 
•  usunięcie arbitralnego pojęcia wskaźnika jak w C++ 
•  wsparcie dla zbierania nieużytków 
•  brak samodzielnych (tj. definiowanych poza klasami) funkcji zewnętrznych 
•  weryfikacja kodu w fazie kompilacji i w fazie wykonania na zgodność ze standardem języka 
•  dynamiczne ładowanie klas (w locie) 
•  biblioteki klas dla GUI, sieciowe i WWW 
 
Wady: 
•  Brak definiowanych przez użytkownika przeciążonych operatorów 
•  złamanie zasad obiektowości w standardowych bibliotekach klas: w klasach opakowań 

(kopertowych), w klasach kolekcji i w klasach łańcuchów 

•  Komunikaty są rzadko polimorficzne i rzadko trafiają na oczekiwaną strukturę dziedziczenia 
•  brak klas parametryzowanych (genericity, templates) 
•  dziedziczenie tylko pojedyncze 
•  usunięcie instrukcji assert znanej w C i C++ 

background image

 

Języki obiektowe 

34 

 

•  pogmatwana (zawikłana) struktura modularna z trzema wpływającymi wzajemnie na siebie 

koncepcjami (klasy, pakiety zagnieżdżone, pliki źródłowe). 

 

6. Bibliografia 

[1]  Cargill T. An Overview of Java for C++ Programmers. C++ Report, vo. 8/No. 2, pp. 46-49, 1996. 
[2]  Lorenz M. Java as an Object-Oriented Language. SIGS Books & Multimedia, New York 1996. 
[3]  Martin R. C++ and Java: A Critical Comparison. C++ Report, vo. 9/No. 1, pp. 42-49, 1997. 
[4]  Jain P. and Schmidt D.C. Experiences Converting a C++ Communication Software Framework to 

Java. C++ Report, vo. 9/No. 1, pp. 51-66, 1997. 

[5]  Gosling J., Joy B, Steele G. The Java Language Specification. Addison-Wesley, 1996, 

http://java.sun.com/docs/books/jls/. 

[6]  The Java Tutorial. http://java.sun.com/docs/books/tutorial/, 1998. 
[7]  JDK1.2 Documentation. http://java.sun.com/products/jdk/1.2/docs/, 1998. 
[8]  Servlet Tutorial. http://www.task.gda.pl/java/ServletTutorial.html, 1998. 
 
 
 
 

7. Programy - ćwiczenia 

1. Plik Squares.java 
class Squares { 
    /** Print out the squares of integers from 1 to 10 */ 
    public static void main(String[] args) { 
 

int x = 1; 

 
 

System.out.println("Squares of integers from 1 to 10:"); 

 

while (x <= 10) { 

 

    System.out.println(x * x); 

// print x squared 

 

    x = x + 1; 

 

 

// add 1 to x 

 

} // end while 

    } // end main 
} // end Squares 
 
2. Plik StringDemo.java 
class StringsDemo { 
    static public void main(String[] args) { 
 

String   myName = "Archibald"; 

 
 

myName = myName + " Tuttle"; 

 

System.out.println("Name = " + myName); 

    } 

 
3. Plik Child1.java 
class Parent {  //accessible within package 
static int i = 10; //accessible within package 
public static void fp() 

 System.out.println("fp of Parent class"); 
}  //end fp 
}  //end Parent 

background image

Java 

35

 
public class Child1 extends Parent {  
private static String st = "I am Child1"; 
public static void main(String args[]) 

 fp();//call class function; same result as 
 Parent.fp();//Only class name; same result as 
 System.out.println(Parent.i); 
 System.out.println(st); 
// super.fp(); // Doesn't work. Why? 
}//end main 
}//end Child1 
 
4. Plik Inherit1.java 
class GrandFather { 
private int i = 10; 
void fp() 

 System.out.print("fp of Parent class\n"+"i= "+i); 
}//end fp 
}//end GrandFather 
 
class Father extends GrandFather {  
}//end Father 
 
class GrandSon extends Father { 
String st = "I am a grandson"; 
} // end GrandSon 
 
public class Inherit1 { 
public static void main(String args[]) 

 GrandSon gs = new GrandSon();  
 gs.fp(); 
 System.out.println("\n"); 
 if(gs instanceof GrandSon) 
 System.out.println(gs.st); 
}//end main 
}//end Inherit1 
 
5. Plik Inherit2.java 
class Parent {  
private int i = 10; 
void fp() 

 System.out.print("fp of Parent class\n"+"i= "+i); 
}//end fp 
}//end Parent 
 
class Descendant extends Parent { 
public void fp() 

 System.out.println("fp of Descendant class"); 
} //end fp 
public void fd() 

background image

 

Języki obiektowe 

36 

 


 super.fp(); //call to Parent's fp  
} // end fd 
} // end Descendant 
 
public class Inherit2 { 
public static void main(String args[]) 

 Descendant ds = new Descendant(); 
 ds.fp(); 
 ds.fd(); 
}//end main 
}//end Inherit2 
 
6. Plik Inherit3.java 
class Parent {  
private int i = 10; 
void fp() 

 System.out.print("fp of Parent class\n"+"i= "+i); 
}//end fp 
}//end Parent 
 
class Descendant extends Parent { 
public void fp() 

 System.out.println("fp of Descendant class"); 
} //end fp 
public void fd() 

 super.fp(); //call to Parent's fp  
} // end fd 
} // end Descendant 
 
class Kin extends Descendant { 
public void fp() 

 System.out.println("fp of Kin class"); 
} //end fp 
public void fk() 

 super.fd(); // call to Descendant's fp 
} // end fk 
} // end Kin 
 
public class Inherit3 { 
public static void main(String args[]) 

 Kin kn = new Kin(); 
 kn.fp(); 
 kn.fk(); 
}//end main 
}//end Inherit2 
 
7. Plik PascalTriangle.java 

background image

Java 

37

import java.io.*; 
 
public class PascalTriangle { 
 
    private int data[][]; 
 
    /** Create a Pascal's triangle to specified depth. */ 
    public PascalTriangle(int rows) { 
        data = new int[rows][]; 
        for (int row = 0; row < rows; row++) { 
   data[row] = new int[row + 1]; 
   if (row == 0) 
       data[row][0] = 1; 
   else 
       for (int col = 0; col <= row; col++) { 
  data[row][col] = 0; 
  // if not on right edge, add node up and right 
  if (col < row) 
      data[row][col] += data[row - 1][col]; 
  // if not on left edge, add node up and left 
  if (col > 0) 
      data[row][col] += data[row - 1][col - 1]; 
       } 
        } 
    } 
 
    /** Print this Pascal's triangle to given stream. */ 
    public void print(PrintStream ps) {         
        for (int i = 0; i < data.length; i++) { 
   int[] row = data[i]; 
   for (int j = 0; j < row.length; j++) 
       ps.print(row[j] + " "); 
   ps.println(); 
        } 
    } 
 
    /** Create a Pascal's triangle of depth 12 and print it. */ 
    public static void main(String[] args) 
    { 
        PascalTriangle pt = new PascalTriangle(12); 
        pt.print(System.out); 
    } 

 
8. Plik EasyIn.java 
// Simple input from the keyboard for all primitive types. ver 1.0 
// Not thread safe, not high performance, and doesn't tell EOF. 
// It's intended for low-volume easy keyboard input. 
// An example of use is: 
// EasyIn easy = new EasyIn(); 
// int i = easy.readInt();   // reads an int from System.in 
// float f = easy.readFloat();   // reads a float from System.in 
 
import java.io.*; 
import java.util.*; 

background image

 

Języki obiektowe 

38 

 

 
class EasyIn { 
  static InputStreamReader is = new InputStreamReader( System.in ); 
  static BufferedReader br = new BufferedReader( is ); 
  StringTokenizer st; 
  StringTokenizer getToken() throws IOException { 
      String s = br.readLine(); 
      return new StringTokenizer(s); 
    } 
 
    boolean readBoolean() { 
       try { 
 st = getToken(); 
 return new Boolean(st.nextToken()).booleanValue(); 
       } catch (IOException ioe) { 
 System.err.println("IO Exception in EasyIn.readBoolean"); 
 return false; 
       } 
    } 
 
    byte readByte() { 
       try { 
         st = getToken(); 
         return Byte.parseByte(st.nextToken()); 
       } catch (IOException ioe) { 
 System.err.println("IO Exception in EasyIn.readByte"); 
 return 0; 
       } 
    } 
 
    short readShort() { 
       try { 
         st = getToken(); 
         return Short.parseShort(st.nextToken()); 
       } catch (IOException ioe) { 
 System.err.println("IO Exception in EasyIn.readShort"); 
 return 0; 
       } 
    } 
 
    int readInt() { 
       try { 
         st = getToken(); 
         return Integer.parseInt(st.nextToken()); 
       } catch (IOException ioe) { 
 System.err.println("IO Exception in EasyIn.readInt"); 
 return 0; 
       } 
    } 
 
    long readLong() { 
       try { 
         st = getToken(); 
         return Long.parseLong(st.nextToken()); 
       } catch (IOException ioe) { 

background image

Java 

39

 System.err.println("IO Exception in EasyIn.readLong"); 
 return 0L; 
       } 
    } 
 
    float readFloat() { 
       try { 
         st = getToken(); 
         return new Float(st.nextToken()).floatValue(); 
       } catch (IOException ioe) { 
 System.err.println("IO Exception in EasyIn.readFloat"); 
 return 0.0F; 
       } 
    } 
 
    double readDouble() { 
       try { 
         st = getToken(); 
         return new Double(st.nextToken()).doubleValue(); 
       } catch (IOException ioe) { 
 System.err.println("IO Exception in EasyIn.readDouble"); 
 return 0.0; 
       } 
    } 
 
    char readChar() { 
       try { 
         String s = br.readLine(); 
         return s.charAt(0);   
       } catch (IOException ioe) { 
 System.err.println("IO Exception in EasyIn.readChar"); 
 return 0; 
       } 
    } 
 
    String readString() { 
       try { 
         return br.readLine();  
       } catch (IOException ioe) { 
 System.err.println("IO Exception in EasyIn.readString"); 
 return ""; 
       } 
    } 
 
// This method is just here to test the class 
   public static void main (String args[]){ 
       EasyIn easy = new EasyIn(); 
 
       System.out.print("enter char: "); System.out.flush(); 
       System.out.println("You entered: " + easy.readChar() ); 
 
       System.out.print("enter String: "); System.out.flush(); 
       System.out.println("You entered: " + easy.readString() ); 
 
       System.out.print("enter boolean: "); System.out.flush(); 

background image

 

Języki obiektowe 

40 

 

       System.out.println("You entered: " + easy.readBoolean() ); 
 
       System.out.print("enter byte: "); System.out.flush(); 
       System.out.println("You entered: " + easy.readByte() ); 
 
       System.out.print("enter short: "); System.out.flush(); 
       System.out.println("You entered: " + easy.readShort() ); 
 
       System.out.print("enter int: "); System.out.flush(); 
       System.out.println("You entered: " + easy.readInt() ); 
 
       System.out.print("enter long: "); System.out.flush(); 
       System.out.println("You entered: " + easy.readLong() ); 
 
       System.out.print("enter float: "); System.out.flush(); 
       System.out.println("You entered: " + easy.readFloat() ); 
 
       System.out.print("enter double: "); System.out.flush(); 
       System.out.println("You entered: " + easy.readDouble() ); 
   } 
 

 
9. Plik Heritage.java 
 
/**Program krotko demonstruje mechanizm korzystania z interfejsow*/ 
/**Glowna klasa programu, tworzy pozostale*/ 
public class Heritage 

 public static void main (String[] args) { 
  /*tworzymy obiekty i wywolujemy odpowiednie metody*/ 
  Client our_client = new Client(); 
  our_client.startingReport(); 
  Server our_server= new Server (our_client); 
  our_server.execute(5); 
 } 

 
/**Klasa klienta implementuje interfejs Message*/ 
class Client implements Message { 
    int counter=0; 
    /**zeruje licznik i wyswietla komunikat poczatkowy*/ 
    public void startingReport() 
    { 
        counter=0; 
        System.out.println ("To jest raport poczatkowy klienta"); 
        System.out.println ("Counter="+counter); 
    } 
 
/**Zwieksza licznik i wyswietla jego wartosc - podajac, ze klasa implementuje interfejs Message; 
zobowiazalismy sie, ze metoda o nazwie giveReport bedzie istniala*/ 
    public void giveReport() 
    { 
        counter++; 
        System.out.println ("Counter="+counter); 

background image

Java 

41

    } 

 
/**Klasa serwera korzysta z obiektu majacego zaimplementowany interfejs Message*/ 
class Server 

    /*Zwrocmy uwage, ze typem obiektu jest nazwa interfejsu*/ 
    Message object; 
 
/**Konstuktor. Jako parametr podajemy dowolny obiekt majacy zaimplementowany interfejs 
Message; w naszym przypadku bedzie to Client*/ 
    Server (Message object) 
    { 
        this.object=object; 
    } 
 
/** Wywolujemy metode giveReport obiektu z interfejsem Message times razy */ 
    public void execute (int times) 
    { 
        for (int k=0; k<times; k++) 
   object.giveReport(); 
    } 
     

 
/**Interfejs Message, kazda klasa ktora go zaimplementuje musi posiadac metode giveReport*/ 
interface Message 

/**Tylko zobowiazanie, ze kazda klasa ktora implementuje ten interfejs bedzie miala metode 
giveReport - jej dzialanie bedzie zalezec od danej klasy*/ 
    void giveReport(); 

 
10. Plik Compare.java 
/**Program krotko demonstruje mechanizm wyjatkow w Java*/ 
import java.io.*; 
 
/**Klasa porownuje dwa pliki i zwraca rezultat porowanania na ekranie*/ 
public class Compare {    
/**glowna i jedyna metoda aplikacji*/ 
 public static void main (String[] args) { 
/*nazwy plikow*/ 
 String first_file = args[0]; 
 String second_file = args[1]; 
/*obiekty reprezentujace pliki*/ 
 RandomAccessFile first =null; 
 RandomAccessFile second =null; 
/*Zawartosc plikow bedzie umieszczona w tych buforach*/ 
 byte[] first_buffer; 
 byte[] second_buffer;         
/*Dlugosci plikow*/ 
 int first_size = -1; 
 int second_size = -1; 
 try { 
 System.out.println ("Ten program porowna dwa pliki"); 

background image

 

Języki obiektowe 

42 

 

 System.out.println ("plik 1: "+first_file+" plik2: "+second_file); 
 try { 
 first = new RandomAccessFile(first_file, "r"); 
 second = new RandomAccessFile(second_file, "r"); 
/*blok, w ktorym porownujemy*/ 
 prawie_koniec:{ //etykieta 
  first_size = (int)first.length(); 
  second_size = (int)second.length(); 
/*najpierw porownujemy dlugosci*/ 
 if (first_size!=second_size) 
 { 
  System.out.println ("Rozne pliki"); 
  break prawie_koniec; 
 } 
/*jesli dlugosci identyczne, wczytujemy pliki do pamieci*/ 
 first_buffer = new byte [first_size]; 
 second_buffer = new byte [second_size];        
 first.readFully (first_buffer, 0, first_size); 
 second.readFully (second_buffer, 0, second_size); 
/*porownujemy zawartosc plikow*/ 
 for (int i=0; i<first_size; i++) 
 { 
 
  if (first_buffer[i]!=second_buffer[i]) 
  { 
   System.out.println ("Rozne pliki"); 
   break prawie_koniec; 
  } 
 }   
 System.out.println ("Pliki identyczne"); 
 } 
/*tutaj przechodzi break prawie_koniec*/    
 System.out.println ("---------------"); 
 } 
/*przechwytujemy wyjatki zwiazane z operacjami I/O*/ 
  catch (IOException e) 
  { 
   System.out.println ("Blad I/O"); 
  } 
/*to sie zawsze wykona*/ 
  finally 
  { 
   try 
    { 
     first.close(); 
     second.close(); 
    } 
/*podczas zamykania plikow ignorujemy wyjatki*/ 
  catch (Exception ignored) { } 
  System.out.println ("Koniec czesci wewnetrznej"); 
  } 
 } 
/*tu przechwytujemy wyjatki nie obsluzone w czesci wewnetrznej*/ 
  catch (Exception e) 
  { 

background image

Java 

43

   System.out.println ("Wystapil nastepujacy wyjatek przechwycony w main"); 
   System.out.println (e); 
   System.out.println ("Uzycie programu: Compare pierwszy_plik drugi_plik"); 
  } 
/*na koniec zawsze wyswietlamy krotki komunikat*/ 
   finally 
    { 
     System.out.println ("Koniec dzialania programu"); 
    } 
 
} //end main 
}// end Compare 
 
11. Plik FileInfo.java 
import java.io.*; 
public class FileInfo { 
public static void main(String[] args) { 
for (int i = 0; i < args.length; i++) 
 { 
   String path = args[i]; 
   if (args.length > 1) System.out.println("Regarding \"" + path + "\":"); 
   File file = new File(path); 
   if(file.exists()) 
   { 
    System.out.println("This file exists."); 
    System.out.println("It can" + (file.canRead() ? "" : "not") + 
                                    " be read."); 
    System.out.println("It can" + (file.canWrite() ? "" : "not") + 
 

 

                   " be written."); 

    System.out.println("It is " + file.length() + " bytes long."); 
   if(file.isFile()) 
    { System.out.println("It is a normal file."); }  
     else 
     { 
      System.out.println("It is a directory."); 
      String[] contents = file.list(); 
      if (contents.length == 0) System.out.println("It is empty."); 
        else 
        { 
         System.out.println("It contains the following files:"); 
         for (int j = 0; j < contents.length; j++) 
         System.out.println("\t" + contents[j]); 
        }//end else that belongs to if(contents.length==0) 
    }// End else that belongs to if(file.isFile()) 
   }// End if(file.exists()) 
   else { 
    System.out.println("This file does not exist."); 
   } 
// End else that belongs to if(file.exists()) 
    System.out.println(); 
 }// End for 
}//End main 
}// End FileInfo