Jan Bielecki, Java od Podstaw

background image

1

Jan Bielecki















Java

od

podstaw

background image

2







































Moim przyjaciołom

Hanne i Rainerowi

Langmaack

background image

3

Spis treści

Od Autora

Część I

Język Java

Aplikacje i aplety
Modyfikowanie apletu
Obsługiwanie zdarze
ń
Wykonywanie w
ątków
Obsługiwanie wyj
ątków

Część II Środowisko rozwojowe

Programowanie manualne
Programowanie wizualne

Część III Podstawy programowania

Leksemy
Zmienne
Instrukcje
Tablice
Obiekty
Strumienie

Część IV Programowanie obiektowe

Hermetyzacja
Dziedziczenie
Interfejsy
Konwersje
Polimorfizm
Kolekcje

Część V Programowanie zdarzeniowe

Klasa zdarzeniowa
Obsługiwanie zdarze
ń
Odtwarzanie pulpitu

Część VI Programowanie współbieżne

Stany wątków

background image

4

Priorytety
W
ątek główny
Tworzenie w
ątków
Synchronizowanie w
ątków
Instrukcja synchronizuj
ąca
Procedury synchronizowane
Monitor
Impas

Część VII Programowanie apletów

Opis
Wykonanie
Otoczenie
Grafika
Klawiatura
Myszka
Kursory
Obrazy
Rozkłady
Sterowniki
Pojemniki
Kontrolki
D
źwięki
Animacje
Przesyłanie
Komunikacja
Przeł
ączenia


Literatura

Słownik terminów

Dodatki

A Priorytety operatorów
B Parametry zdarze
ń
C Klasa uruchomieniowa
D Styl programowania
E Typy predefiniowane
F Operacje i operatory




background image

5

background image

6

Od Autora


Nie ulega wątpliwości, że to co się dzieje wokół Javy jest rewolucją. Po długim okresie przestoju, w którym
aktywność w zakresie języków programowania ograniczała się do porządkowania tego, co było już znane od
kilkunastu lat, pojawił się nowy język, który niemal z dnia na dzień zyskał aprobatę zarówno specjalistów, jak i
zwykłych użytkowników Internetu.

Swój sukces zawdzięcza Java temu, iż wyszła spod ręki zawodowego programisty, że wykorzystano w niej
najlepsze pomysły zawarte w językach C++, Objective C i Smalltalk oraz że wbudowując w nią mechanizmy
programowania strukturalnego, obiektowego, zdarzeniowego i współbieżnego przygotowano grunt pod
programowanie rozproszone.

Dzięki odważnej decyzji implementowania Javy jako języka interpretowanego, którego kod wynikowy jest
wykonywany na Maszynie Wirtualnej, zapewniono przenośność programów między odmiennymi platformami
sprzętowymi i systemowymi, i to nie tylko na poziomie kodu źródłowego, ale również na poziomie kodu
binarnego.

Powszechne zaakceptowanie Javy przekroczyło najśmielsze oczekiwania jej twórcy Jamesa Goslinga. W ślad
za wsparciem udzielonym jej przez przez wiodące firmy komputerowe: Sun, Microsoft, IBM, Apple, Oracle,
Corel, Netscape, Symantec, Borland, Hewlett Packard, Silicon Graphics, Toshiba, poszły liczne implementacje
wizualnych Środowisk Rozwojowych, w tym Cafe i Visual Cafe (Symantec), Open JBuilder (Borland),
SuperCede (Asymetrix), Parts for Java (ParcPlace), Visual J++ (Microsoft), Java WorkShop (Sun) i
CodeWarrior (Metrowerks).

Opracowano wiele wyspecjalizowanych bibliotek języka, opublikowano specyfikację komponentów
JavaBeans, implementowano konfigurowalną przeglądarkę HotJava Views oraz wprowadzono na rynek
komputery sieciowe JavaStation wyposażone w system operacyjny JavaOS. W dążeniu do pełnego
upowszechnienia Javy podjęto produkcję układu scalonego PicoJava, umożliwiającego wyposażenie w nią
takich miniaturowych urządzeń konsumenckich jak komórki, faksy i kalkulatory.


Niniejszą książkę, zapowiedzianą w przedmowie do Java po C++ napisałem dla tych, którzy mieli już
styczność z programowaniem, ale nie znają C++ ani nawet języka C. W tej postaci przedkładam ją
informatykom zafascynowanym nowymi narzędziami Internetu oraz tym wszystkim, którzy w ślad za
wiodącymi ośrodkami akademickimi porzucą Turbo Pascal, Delphi i Visual Basic, decydując się na
zainwestowanie swego czasu w poznanie narzędzia następnego wieku.

Lektura tekstu, którego kolejnymi częściami są: Język Java, Środowisko rozwojowe, Podstawy
programowania
, Programowanie obiektowe, Programowanie zdarzeniowe, Programowanie współbieżne,
Programowanie apletów i Słownik terminów oraz bardzo ważne dla pełnego poznania języka Dodatki,
umożliwia łagodne wejście w tematykę programowania w Javie, przygotowując do lektury mojej następnej
książki pt. Java - programowanie wizualne, poświęconej współczesnej metodyce programowania, wstępnie
przedstawionej tu w rozdziale o środowiskach rozwojowych

Jestem przekonany, że niniejszy podręcznik w pełni zaspokoi potrzeby tych, którzy chcą poznać Javę od
podstaw
oraz że dostarczy obszernego materiału praktycznego nie tylko słuchaczom moich wykładów w
Politechnice Warszawskiej i Polsko-Japońskiej Wyższej Szkole Technik Komputerowych, ale również tym
wszystkim, którzy podzielą pogląd, że gruntowne poznanie Javy jest bardzo dobrą inwestycją.

Jan Bielecki

background image

7

Część I

Język Java


Java jest nowym językiem programowania opartym na uznanych językach starszej generacji. We wczesnej fazie
powstawania była projektowana jako narzędzie do programowania komunikujących się ze sobą konsumenckich
urządzeń elektronicznych, ale wkrótce przekształcono ją w narzędzie do programowania aplikacji w sieciach
korporacyjnych i w Internecie.

Główną zaletą Javy jest przenośność programów na poziomie kodu binarnego. Dzięki temu program
uruchomiony w pewnym środowisku operacyjnym (np. Windows) może być wykonany w dowolnym innym
(np. Unix i MacOS). Tę unikalną właściwość zawdzięcza Java temu, iż jest językiem interpretowanym, a nie
kompilowanym.

Program źródłówy w Javie przygotowuje się za pomocą edytora, a następnie poddaje się kompilacji,
uruchomieniu i testowaniu. Produktem końcowym jest B-kod Maszyny Wirtualnej (JavaVM) umieszczony w
pliku z rozszerzeniem .class.

Warunkiem wykonania B-kodu jest wyposażenie platformy programistycznej w emulator Maszyny Wirtualnej:
układ scalony, albo program napisany w rodzimym języku platformy. Inwestycja ta jest niewielka i została już
wykonana we wszystkich liczących się systemach (Windows, Unix, MacOS).


_________________________________________________________________________________________

Aplikacje i aplety


Program napisany w Javie jest aplikacją lub apletem. Aplikacja jest programem samodzielnym, a aplet jest
programem zanurzonym w aplikacji, na przykład w przeglądarce Netscape.

Ponieważ przeglądarka przydziela apletowi wydzielony obszar okna, nazywany zazwyczaj pulpitem, więc
określenie aplet utożsamia się często nie z samym jego programem, ale z tym co ten program wykreśli. Z tego
powodu mają sens i mogą być używane takie określenia jak: napisanie apletu (utworzenie kodu programu) i
wyświetlenie apletu (uwidocznienie wyników wykonania kodu).

Struktura programu


Każdy program składa się z co najmniej jednego modułu źródłowego umieszczonego w pliku. Tekst modułu
zapisuje się w układzie swobodnym, z dowolnie dodanymi odstępami (spacjami, tabulacjami i zakończeniami
wierszy
).

Odstępy można umieszczać poza jednostkami leksykalnymi programu (słowami kluczowymi, identyfikatorami,
operatorami i ogranicznikami).

Szczególną odmianę odstępu stanowi komentarz wierszowy. Zaczyna się on od pary znaków // (skośnik,
skośnik), a kończy w miejscu zakończenia wiersza. Cały taki napis jest traktowany jak jedna spacja, a więc jest
ignorowany.

Aplikacje


Aplikacją jest program składający się z zestawu definicji klas, z których przynajmniej jedna jest publiczna i
zawiera publiczną i statyczną funkcję główną.

Nagłówek funkcji głównej ma postać

public static void main(String args[])


Wykonanie aplikacji kończy się w chwili, gdy w funkcji głównej wykona się instrukcję powrotu

background image

8

return;


albo gdy z dowolnej procedury (w tym z funkcji głównej) wywoła się funkcję exit.

Uwaga: Przytoczona instrukcja jest domniemywana przed każdą klamrą kończącą definicję procedury o
rezultacie void, a więc jawne jej użycie jest na ogół zbyteczne.

Przykład Najprostsza aplikacja

public
class Master {
public statatic void main(String args[])
{
}
}


Klasa Master jest publiczna. Zawiera ona publiczną i statyczną funkcję główną.

Tuż przed klamrą kończącą definicję funkcji domniemano instrukcję powrotu.

Aplikacje znakowe


Wyniki aplikacji znakowej są wyprowadzane na konsolę (ekran monitora), a w środowisku graficznym (np.
Windows 95) do okna konsoli.

Następująca aplikacja, pokazana podczas wykonania na ekranie Aplikacja znakowa, wyprowadza do okna
konsoli napis

Hello, I am JanB.


Ekran Aplikacja znakowa

### textapp.gif


Przykład Aplikacja znakowa

public
class Master {
public static void main(String args[])
{
// wyprowadzenie tekstu na konsol

ę

System.out.println("Hello, I am JanB.");
}
}

Dzięki użyciu procedury println, a nie print, zapewniono wyprowadzenie wiersza znaków, a nie tylko znaków
zawartych w cudzysłowach.

Wstrzymanie zakończenia aplikacji

Jeśli aplikacja znakowa jest wykonywana w środowisku graficznym, to w chwili jej zakończenia usuwa się z
ekranu okno konsoli. W celu uniknięcia towarzyszącego temu "zniknięcia" wyników, zaleca się użycie
instrukcji

System.in.read();


Spowoduje to wstrzymanie zamknięcia okna konsoli do chwili wprowadzenia z klawiatury dowolnego znaku
(np. Enter).

W takim jednak wypadku należy nagłówek procedury zawierającej tę instrukcję uzupełnić frazą

background image

9

throws IOException


a aplikację poprzedzić poleceniem

import java.io.IOException

albo

import java.io.*;


Na przykład

import java.io.IOException;

public
class Master {
public static void main(String args[])
{
// wyprowadzenie tekstu na konsol

ę

System.out.println("Hello, I am JanB.");
System.in.read();
}
}


Aplikacje graficzne


Wyniki aplikacji graficznej są wyprowadzane do własnego okna graficznego. Zamknięcie okna nie jest na ogół
wystarczające i w chwili zamknięcia aplikacji musi być wywołana funkcja exit.

Do komunikowania się aplikacji z oknem graficznym jest wykorzystywany wykreślacz dysponujący
informacjami o właściwościach środowiska (np. o rodzaju użytej karty graficznej). Dzięki temu unika się
programowego rozpoznawania środowiska i umożliwia pisanie programów, które mogą być wykonywane na
dowolnej platformie.

Uwaga: Wykreślacz związany z oknem można utworzyć dopiero wówczas, gdy okno jest już wyświetlone na
ekranie (to jest dopiero po wywołaniu metody show).

Następująca aplikacja, pokazana podczas wykonania na ekranie Aplikacja graficzna, wykreśla w oknie napis

Hello, I am JanB.


Ekran Aplikacja graficzna

### graphapp.gif

import java.io.IOException;
import java.awt.*;

public
class Master {
public static void main(String args[])
throws IOException
{
// utworzenie okna graficznego
Frame frame = new MyFrame("Results");
// okre

ś

lenie rozmiarów okna

frame.resize(160, 80);
// wy

ś

wietlenie okna

frame.show();
// utworzenie wykre

ś

lacza

// komunikuj

ą

cego si

ę

z oknem

Graphics gDC = frame.getGraphics();
// wykre

ś

lenie tekstu

// za po

ś

rednictwem wykre

ś

lacza

gDC.drawString("Hello, I am JanB.", 10, 20);
System.out.println("Press Enter to exit!);
System.in.read();
System.exit(0);

background image

10

}
}

class MyFrame extends Frame {
MyFrame(String caption)
{
// przekazanie nazwy okna klasie Frame
super(caption);
}
// obsługa zdarzenia WINDOW_DESTROY
// generowanego w chwili zamkni

ę

cia okna

public boolean handleEvent(Event evt)
{
if(evt.id == Event.WINDOW_DESTROY) {
hide(); // ukrycie okna
dispose(); // zniszczenie okna
return true;
} else
return super.handleEvent(evt);
}
}


Program aplikacji składa się z definicji klas Master i MyFrame. Klasa Master zawiera definicję funkcji
głównej, a klasa MyFrame wywodzi si
ę z klasy bibliotecznej Frame.

Poza klasą Frame, aplikacja posługuje się klasą biblioteczną Graphics. Kod tych klas jest zawarty w pakiecie
java.awt, wł
ączonym do programu za pomocą deklaracji importu

import java.awt.*;


W celu uniknięcia przedwczesnego zamknięcia aplikacji, wywołanie funkcji exit poprzedzono wykonaniem
instrukcji

System.in.read();


Aplety


Apletem jest program składający się z zestawu definicji klas, z których przynajmniej jedna jest publiczna i
wywodzi się (extends) od klasy Applet.

Tak zdefiniowana klasa apletowa zawiera w ogólnym wypadku procedury init, start, paint, stop, destroy
stanowiące jej metody. Wymienione metody są wywoływane przez przeglądarkę w podanej kolejności. Metoda
init oraz metoda destroy jest wywoływana jednokrotnie. Metody start, stop i paint mogą być wywoływane
wielokrotnie.

Uwaga: Program apletu nie musi zawierać takich typowych wywołań System.in.read() i System.exit(0).

W najprostszym przypadku wystarcza zdefiniowanie w aplecie tylko metody paint. Jest ona wywoływana
wkrótce po wykonaniu metody init, a także w każdej sytuacji gdy zaistnieje potrzeba odtworzenia pulpitu
apletu (na przykład po zasłonięciu i odsłonięciu go przez pewne okno).

Aby przeglądarka mogła wyświetlić aplet, należy przekazać jej opis apletu. Uproszczony do minimum ma on
postać

<applet
code = Nazwa.class
width = Szeroko

ść

height = Wysoko

ść

>
</applet>


w której Nazwa jest nazwą klasy apletowej, a Szerokość i Wysokość określają poziomy i pionowy rozmiar
apletu.

background image

11


Następujący aplet, pokazany podczas wykonania na ekranie Pozdrowienie, wykreśla na swoim pulpicie napis

Hello, I am JanB.


Ekran Pozdrowienie

### hello.gif

<applet code=Master.class width=200 height=80>
</applet>
==============================================

import java.applet.*;
import java.awt.*;

public
class Master extends Applet {
Font font; // czcionka
public void init()
{
font = new Font("TimesRoman", // krój
Font.BOLD | Font.ITALIC, // styl
24); // rozmiar
}
public void paint(Graphics gDC)
{
gDC.setFont(font); // wstaw do wykre

ś

lacza

gDC.drawString("Hello, I am JanB.", 10, 50);
}
}


Program apletu składa się z definicji klasy Master wywodzącej się od klasy Applet.

Klasa Master zawiera definicje metod init i paint. Metoda init jest wywoływana jednokrotnie, a metoda paint
jest wywoływana co najmniej jeden raz.

W metodzie init przygotowano czcionkę do wykreślenia tekstu. W metodzie paint jest ona wstawiana do
wykre
ślacza, dzięki czemu zapowiedziany napis jest wykreślany czcionką TimesRoman, pogrubioną (BOLD) i
pochylon
ą (ITALIC), o rozmiarze 24 punktów (1 punkt drukarski to około 1/72 cala).

Aplikacje a aplety


Podział na aplikacje i aplety jest umowny. Następujący program jest zarówno apletem jak i aplikacją.

<applet code=Master.class width=100 height=100>
</applet>
===============================================

import java.applet.*;
import java.awt.*;
import java.io.IOException;

public
class Master extends Applet {
public static void main(String args[])
{
System.out.println("I am an application.");
System.in.read();
System.exit(0);
}
public void paint(Graphics gDC)
{
gDC.drawString("I am an applet.", 10, 10);
}
}

background image

12

W zależności od sposobu potraktowania, program wyprowadza napis

I am an applet.

albo

I am an application.



_________________________________________________________________________________________

Modyfikowanie apletu


Każda klasa definiująca aplet jest opisem rodziny obiektów. Jeśli podklasa (klasa pochodna) wywodzi się od
innej klasy, to jej obiekty składają się ze składników jej nadklasy (klasy bazowej) oraz dodatkowo z własnych
składników podklasy.

Jeśli w podklasie występuje metoda o identycznej sygnaturze jak pewna metoda nadklasy, to przedefiniowuje
ona metodę nadklasy. Dzięki temu, definiując podklasę wystarczy podać w niej tylko metody przedefiniowujące
oraz metody dodatkowe.

Uwaga: Metoda przedefiniowująca nie może być bardziej-prywatna niż metoda przedefiniowywana. W
szczególności, jeśli metoda przedefiniowywana jest publiczna, to metoda przedefiniowująca także musi być
publiczna.

Następujący aplet, pokazany podczas wykonania na ekranie Modyfikowanie, ilustruje zasadę modyfikowania
apletu przez zastosowanie przedefiniowania metod.

Ekran Modyfikowanie

### modify.gif

<applet code=Master.class width=200 height=80>
</applet>
==============================================

// Plik Master.java

import java.applet.*;
import java.awt.*;

public
class Master extends Applet {
Font font; // czcionka
public void init()
{
font = new Font("TimesRoman", // krój
Font.BOLD | Font.ITALIC, // styl
24); // rozmiar
}
public void paint(Graphics gDC)
{
gDC.setFont(font); // wstaw do wykre

ś

lacza

gDC.drawString("Hello, I am JanB.", 10, 50);
}
}

// Plik Master2.java

import java.applet.*;
import java.awt.*;

public
class Master2 extends Master {
public void init()
{
font = new Font("Helvetica", // krój
Font.BOLD, // styl
18); // rozmiar
}

background image

13

}


Klasa Master2 wywodzi się od klasy Master, a więc pośrednio od klasy Applet.

Klasa Master2 zawiera wszystkie składniki apletu Master (w tym jego metod
ę paint), ale przedefiniowuje jego
metod
ę init.

Dlatego wykonanie apletu Master2 powoduje wykreślenie napisu

Hello, I am JanB.


18 pt czcionką Helvetica, a nie 24 pt czcionką TimesRoman.


_________________________________________________________________________________________

Obsługiwanie zdarzeń


Od czasu pojawienia się Javy, można zauważyć powszechne wyposażanie stron WWW w interakcyjne aplety,
reagujące na takie zdarzenia zewnętrzne jak kliknięcie przyciskiem myszki albo naciśnięcie klawisza
klawiatury.

Obsługiwanie zdarzeń zewnętrznych odbywa się za pomocą predefiniowanych metod klas komponentowych
(m.in. Panel, Applet), opisujących takie obiekty oblicza graficznego (Graphical interface) jak etykiety (Label),
przyciski (Button), ramki (Frame), itp.

Obsługa domniemana polega zazwyczaj na zignorowaniu zdarzenia, dlatego w celu dostarczenia własnej
obsługi zdarzenia zewnętrznego, należy dokonać przedefiniowania metody domniemanej.

W szczególności, ponieważ w klasie komponentowej Applet obsługa przeciągania kursora myszki jest
określona przez metodę

public boolean mouseDrag(Event evt, int x, int y)
{
return false; // zignoruj zdarzenie
}


więc w celu dostarczenia własnej obsługi wystarczy przedefiniować metodę mouseDrag.

Następujący aplet, pokazany podczas wykonywania na ekranie Wykreślanie z ręki, ilustruje zasadę
obsługiwania zdarzeń.

Ekran Wykreślanie z ręki

### scribe.gif

<applet code=Master.class width=200 height=80>
</applet>
==============================================

import java.applet.*;
import java.awt.*;

public
class Master extends Applet {
int xPos, yPos;
Graphics gDC;
public void init()
{
gDC = getGraphics();
}
public boolean mouseDown(Event evt, int x, int y)
{
xPos = x;
yPos = y;

background image

14

return true;
}
public boolean mouseDrag(Event evt, int x, int y)
{
gDC.drawLine(xPos, yPos, x, y);
xPos = x;
yPos = y;
return true;
}
public boolean mouseUp(Event evt, int x, int y)
{
repaint();
return true;
}
public void paint(Graphics gDC)
{
gDC.drawString("Drag mouse to draw!", 10, 20);
}
}


Wykonanie metody init powoduje utworzenie wykreślacza związanego z pulpitem apletu.

Wykonanie metody paint powoduje wykreślenie zachęty do wykreślania linii metodą przeciągania kursora
myszki.

Wykonanie metody mouseDown powoduje zapamiętanie współrzędnych punktu naciśnięcia przycisku myszki.

Każdorazowe wykonanie metody mouseDrag powoduje wykreślenie odcinka łączącego punkt poprzedni z
bie
żącym.

Wykonanie metody mouseUp powoduje wywołanie metody repaint.

Wywołanie metody repaint powoduje,
że po zakończeniu wykonywania metody z której ją wywołano (tu metody
mouseUp), nast
ąpi wywołanie metody update (domyślnie wyczyszczenie pulpitu i wywołanie metody paint).


_________________________________________________________________________________________

Wykonywanie wątków


Oprogramowanie ruchomej grafiki wymaga utworzenia co najmniej jednego dodatkowego wątku: niezależnego
od obsługi zdarzeń, przepływu sterowania przez B-kod programu.

W celu ustanowienia wątku należy utworzyć obiekt klasy Thread oraz określić w jakiej klasie znajduje się
metoda run, określająca czynności wątku.

Uwaga: Klasa zawierająca metodę run musi implementować interfejs Runnable. Fakt ten wyraża się za
pomocą słowa kluczowego implements.

Następujący aplet, pokazany podczas wykonywania na ekranie Wykonywanie wątków, ilustruje zasadę
programowania współbieżnego z wykorzystaniem wątków.

Ekran Wykonywanie wątków

### thread.gif

<applet code=Master.class width=100 height=100>
</applet>
===============================================

import java.applet.*;
import java.awt.*;

public
class Master extends Applet {
Thread thread;

background image

15

Graphics gDC;
Color backGround;
Circle circle;
public void init()
{
gDC = getGraphics();
backGround = getBackground();
circle = new Circle(gDC, backGround);
thread = new Thread(circle);
thread.start();
}
}

class Circle implements Runnable{
Graphics gDC;
Color backGround;
Circle(Graphics gdc, Color back)
{
gDC = gdc;
backGround = back;
}
public void run()
{
for(int i = 0; i < 100 ; i++) {
gDC.setColor(Color.black);
gDC.drawOval(i, i, i/2, i/2);
try {
Thread.currentThread().sleep(100);
}
catch(InterruptedException e) {
}
gDC.setColor(backGround);
gDC.drawOval(i, i, i/2, i/2);
}
}
}


Wykonanie instrukcji

circle = new Circle(gDC, backGround);


powoduje utworzenie obiektu klasy Circle, którego składnikiem jest metoda run.

Wykonanie instrukcji

thread = new Thread(circle);


powoduje utworzenie obiektu zarządzającego wątkiem.

Wykonanie instrukcji

thread.start();


powoduje utworzenie wątku realizowanego przez metodę run (tu: wykreślenie okręgu poruszającego się po
przek
ątnej apletu).

Zakończenie wykonywania metody run powoduje zniszczenie wątku.


_________________________________________________________________________________________

Obsługiwanie wyjątków


Podczas wykonywania programu mogą wystąpić sytuacje wyjątkowe: dzielenie przez 0, dostarczenie danej o
złym formacie, albo napotkanie końca pliku.

background image

16

Z miejsca wystąpienia sytuacji wyjątkowej jest wysyłany wyjątek: obiekt szczegółowo opisujący sytuację
wyjątkową. Dzięki temu, że wyjątek może być przechwycony, a sytuacja wyjątkowa obsłużona, stwarza się
szansę wyjścia z powstałej sytuacji wyjątkowej i umożliwia kontynuowanie wykonywania programu.

Do jawnego wysyłania wyjątków służy instrukcja throw, a do przechwytywania i obsługiwania wyjątków
instrukcja try. Jeśli wywołanie pewnej procedury może spowodować wysłanie wyjątku, to wymaga się, aby
zawarto je w bloku instrukcji try, albo aby w nagłówku procedury wywołującej umieszczono frazę throws
wyszczególniającą ten wyjątek.

Uwaga: Wyszczególnienie nazwy wyjątku we frazie throws metody przedefiniowującej jest dozwolone tylko
wówczas, gdy taka nazwa występuje albo może wystąpić we frazie throws metody przedefiniowywanej.

Na przykład

int readByte(String fileName)
throws IOException
{
try {
FileInputStream inp = new FileInputStream(fileName);
try {
return inp.read();
}
catch(IOException e) {
System.out.println("File " + fileName +
" empty or read error");
throw new IOException();
}
}
catch(FileNotFoundException e) {
System.out.println("File " + fileName +
" does not exist");
return -1;
}
}

Podczas wykonania operacji

new FileInputStream(fileName)


może zostać wysłany wyjątek klasy FileNotFoundException albo wyjątek klasy IOException.

Wysłanie pierwszego z nich jest obsłużone przez zewnętrzną frazę catch, a o możliwości wysłania drugiego
informuje fraza throws.

Podczas wykonywania operacji

inp.read()


może być wysłany wyjątek klasy IOException. Wyjątek ten jest obsłużony przez wewnętrzną frazę catch.

Ponieważ w ciele funkcji readByte użyto instrukcji

throw new IOException();


więc w jej nagłówku musi wystąpić fraza throws.

Osłabienie wymagań


Wymaganie użycia frazy throws nie dotyczy wyjątków kategorii RuntimeException, w tym wyjątków

ArithmeticException

(ąd operacji arytmetycznej)

NumberFormatException

(zły format liczby)

background image

17

IndexOutOfBoundsException

(indeks poza zakresem)

NegativeArraySizeException

(ujemny rozmiar tablicy)

NullPointerException

(odwołanie przez odniesienie puste)


oraz wyjątków kategorii Error, w tym wyjątków

OutOfMemoryError

(brak pamięci operacyjnej)

StackOverflowError

(przepełnienie stosu)

ClassFormatError

(ąd reprezentacji klasy)

NoClassDefFoundError

(brak definicji klasy)


ale dotyczy wyjątków kategorii IOException związanych z wykonywaniem operacji wejścia-wyjścia, w tym
m.in. wyjątków

EOFException

(koniec pliku)

FileNotFoundException

(brak pliku)

InterruptedIOException

(przerwanie przesłania)


Przykład Obsługiwanie wyjątków

import java.io.*;

public
class Master {
public static void main(String args[])
throws IOException
{
open("SOURCE", 0);
System.in.read();
}
static void open(String fileName, int value)
{
// nast

ę

pna instrukcja jest poprawna, mimo i

ż

// nie przewidziano dla niej obsługi wyj

ą

tku

// ArithmeticException
System.out.println(1 / value);
// ...
FileInputStream stream;
// nast

ę

pna instrukcja jest bł

ę

dna, poniewa

ż

// nie przewidziano dla niej obsługi wyj

ą

tków

// FileNotFoundException i IOException
stream = new FileInputStream(fileName);
}
}


Mimo iż podczas wykonywania instrukcji

System.out.println(1 / value);


może być wysłany wyjątek ArithmeticException, nie stawia się wymagania obsłużenia go.

Podczas wykonania instrukcji

stream = new FileInputStream(fileName);


może zostać wysłany wyjątek FileNotFoundException oraz wyjątek IOException.

Poniewa
ż żaden z tych wyjątków nie został obsłużony, więc przytoczony program jest ędny.

Jednym ze sposobów poprawienia błędu jest użycie instrukcji try dla wyjątku FileNotFoundException oraz
umieszczenie w nagłówku funkcji open frazy throws wyszczególniaj
ącej wyjątek IOException.

import java.io.*;

background image

18

public
class Master {
public static void main(String args[])
throws IOException
{
open("SOURCE", 0);
System.in.read();
}
static void open(String fileName, int value)
throws IOException
{
try {
System.out.println(1 / value);
}
catch(ArithmeticException e) {
System.out.println("Division by 0");
System.exit(-1); // przerwanie wykonywania
}
// ...
FileInputStream stream;
try {
stream = new FileInputStream(fileName);
}
catch(FileNotFoundException e) {
System.out.println("File" + fileName +
" not found");
System.exit(-2);
}
}
}


Ponieważ z nagłówka funkcji open wynika, że może być z niej wysłany wyjątek klasy IOException, więc
uwzgl
ędniono to w funkcji main. Jako metodę obsługi przyjęto użycie frazy throws wyszczególniającej ten
wyj
ątek.

W celu usunięcia z nagłówka funkcji open frazy throws, należałoby instrukcję

try {
stream = new FileInputStream(fileName);
}
catch(FileNotFoundException e) {
System.out.println("File" + fileName +
" not found");
System.exit(-2);
}


zastąpić na przykład instrukcją

try {
stream = new FileInputStream(fileName);
}
catch(FileNotFoundException e) {
System.out.println("File" + fileName +
" not found");
System.exit(-2);
}
catch(IOException e) {
System.exit(-3);
}

background image

19

Część II

Ś

rodowisko rozwojowe



Do uruchomienia zamieszczonych w książce programów użyto Środowiska Rozwojowego Cafe (wersja 1.51)
składającego się z wbudowanego edytora, kompilatora i uruchamiacza. Środowisko to, opracowane przez firmę
Symantec, uzyskało w kategorii narzędzi do programowania w Internecie tytuł Najlepszego Produktu Roku
(The best product for Web Development). Ponieważ na tytuł ten w pełni zasługuje, a jest znacznie lepsze niż
inne porównywalne produkty (np. Visual J++), więc można je z przekonaniem polecić każdemu, komu zależy
na komfortowym programowaniu w Javie.

Wywołanie środowiska


Po wywołaniu środowiska Cafe pojawia się informacja o jego wersji. Jest ona wyświetlana także po wydaniu
polecenia Help/About. Na ekranie Winieta Cafe pokazano ekran identyfikujący środowisko.

Ekran Winieta Cafe

### welcome.gif


Tworzenie programów


Ś

rodowisko Cafe umożliwia tworzenie programów nie tylko w sposób tradycyjny, to jest metodą manualnego

generowania kodu, ale również zawiera mechanizmy umożliwiające programowanie wizualne, dostępne w
takich środowiskach drugiej generacji jak

Visual Cafe

(Symantec)

Java WorkShop

(Sun)

Open JBuilder

(Borland)

SuperCede

(Asymetrix)

CodeWarrior

(Metrowerks)

Parts for Java

(ParcPlace)


Programowanie wizualne jest bardzo atrakcyjne dla tych, którzy nie chcą nadmiernie zagłębiać się w tajniki
programowania w Javie, ale chcą w krótkim czasie uzyskać poprawnie działający i wcale niebanalny program.


_________________________________________________________________________________________

Programowanie manualne


Programowanie manualne polega na ręcznym przygotowaniu składników programu, a następnie utworzeniu z
nich wykonywalnego programu wynikowego. Przy takim postępowaniu wyposażenie programu w oblicze
graficzne
(graphical interface) jest niekiedy dość uciążliwe, ale czyni program krótszym i wydajniejszym.

Edycja dokumentu


Wydanie polecenia File/New powoduje utworzenie okna edycyjnego, w którym można przygotować moduł
ź

ródłowy albo dokument HTML. Po zakończeniu edycji moduł albo dokument należy zapamiętać w pliku.


Jeśli tekst jest modułem źródłowym, który zawiera klasę publiczną Class, to nazwą pliku musi być Class.java.
Jeśli jest dokumentem HTML, to nazwa pliku powinna mieć rozszerzenie .html.

Uwaga: Jeśli program zawiera tylko jedną klasę publiczna Class, to zaleca się umieszczenie dokumentu HTML
w pliku Class.html.

background image

20

Utworzenie projektu


W celu utworzenia projektu należy wydać polecenie Project/New. Spowoduje to wyświetlenie okna
dialogowego Project Express pokazanego na ekranie Nazwanie projektu.

Ekran Nazwanie projektu

### name.gif


W klatce Project Name należy wówczas podać Nazwę projektu, a przez zapoznanie się z listą Directories
upewnić, że plik projektu zostanie zapamiętany w uprzednio utworzonym katalogu.

Po naciśnięciu przycisku Next wyświetli się okno pokazane na ekranie Określenie typu projektu.

Ekran Określenie typu projektu

### type.gif


W oknie tym należy podać czy tworzony program jest aplikacją czy apletem (lista Target Type), a przez
wybranie nastawy Project Settings zdecydować się na to, czy program wynikowy będzie uruchamiany
(Debug) czy dystrybuowany (Release).

Po naciśnięciu przycisku Next wyświetli się okno pokazane na ekranie Dodaj pliki. W oknie tym należy podać
jakie pliki wchodzą w skład projektu. Odbywa się to przez dwukliknięcie elementu listy File Name, albo przez
zaznaczenie jej elementu i naciśnięcie przycisku Add.

Uwaga: Jeśli projekt dotyczy apletu, to do projektu należy również włączyć plik zawierający dokument HTML.

Ekran Dodaj pliki

### addfile.gif


Po naciśnięciu przycisku Next wyświetli się okno pokazane na ekranie Ustawienia początkowe. W jego klatce
Class Path można podać nazwy katalogów, w których mają być poszukiwane pliki włączone do projektu.

Uwaga: Jeśli klasy programu należą do pakietu domyślnego (w pliku źródłowym nie ma polecenia package),
to klatka Class Path może pozostać pusta).

Ekran Ustawienia początkowe

### settings.gif


Otwarcie projektu


Uprzednio utworzony projekt może być następnie otwarty, a przetwarzanie programu może być wówczas
kontynuowane.

W celu otwarcia projektu należy wydać polecenie Project/Open. Spowoduje to wyświetlenie okna Open
Project
, pokazanego na ekranie Otwarcie projektu. W jego klatce File Name, z uwzględnieniem katalogu
określonego przez listę Folders, należy podać nazwę uprzednio utworzonego projektu.

Ekran Otwarcie projektu

### openprj.gif


Modyfikowanie projektu


W celu zmodyfikowania otwartego (a więc już istniejącego) projektu, należy wydać polecenie Project/Edit
albo Project/Settings. Spowoduje to wyświetlenie znanych już okienek dialogowych, w których można
dokonać modyfikacji projektu (np. przekształcić aplikację w aplet, lub odwrotnie).

background image

21

Konfigurowanie pulpitu


Przed, albo po utworzeniu projektu można skonfigurować pulpit środowiska. Można to wykonać za pomocą
poleceń menu Window. W szczególności wydanie poleceń Views, Build i Debug spowoduje wyświetlenie
okienek pokazanych na ekranie Okienka Środowiska.

Uwaga: Operacje na okienku Views odbywają się przez przeciągnięcie ikony, a operacje na okienku Builds i
Debug odbywają się przez kliknięcie.

Ekran Okienka Środowiska

### desktop.gif


Kompilowanie modułu


W celu skompilowania modułu źródłowego należy w okienku Build kliknąć ikonę Compile. Jeśli moduł
zawiera błędy, to kompilator wyświetli je w odrębnym oknie. Dwukliknięcie wiersza wyszczególniającego
błąd spowoduje wyświetlenie tego fragmentu programu, w którym błąd ten wykryto.

Uwaga: Skompilowanie pojedynczego modułu jest wykonywane rzadko. Zazwyczaj od razu przystępuje się
do zbudowania programu. W takim wypadku Cafe kompiluje tylko te moduły, które zostały zmienione w
okresie od poprzedniej ich kompilacji.

Na ekranie Niepomyślna kompilacja pokazano skutek skompilowania programu zawierającego błędy
składniowe.

Ekran Niepomyślna kompilacja

### badcomp.gif


Budowanie programu


W celu zbudowania programu należy w okienku Build kliknąć ikonę Build albo Rebuild All. Nastąpi wówczas
skompilowanie wszystkich modułów wchodzących w skład projektu i utworzenie B-kodu programu.
Sygnalizowanie ewentualnych błędów składniowych odbywa tak jak podczas kompilowania modułu.

Budowanie może dotyczyć tylko takiego programu, w którym wyróżniono moduł główny (plik zawierający opis
apletu albo program z funkcją main). Ma to istotne znaczenie zwłaszcza wówczas, gdy w skład projektu
wchodzi więcej niż jeden moduł źródłowy albo dokument HTML. W takim wypadku należy w oknie projektu
kliknąć lewym przyciskiem myszki nazwę modułu, a następnie kliknąć ją prawym i wybrać opcję Mark as
Main
. Wykonanie tej operacji pokazano na ekranie Zdefiniowanie modułu głównego.

Ekran Zdefiniowanie modułu głównego

### main.gif


Uwaga: Podczas kompilowania lub budowania programu pojawia się niekiedy komunikat

Access violation


Ten niepokojący komunikat, któremu nie towarzyszy żadna diagnoza o przyczynie błędu, jest najczęściej
wywołany literówką w nazwie klasy, na przykład napisaniem

new DEbug();


zamiast

new Debug();

background image

22

albo doprowadzeniem do wystąpienia niezrównoważonych nawiasów klamrowych.

Na ekranie Zagrożenie bezpieczeństwa pokazano sytuację, która doprowadziła do powstania rozpatrywanego
komunikatu.

Ekran Zagrożenie bezpieczeństwa

### security.gif


Dostarczenie argumentów


Jeśli program jest aplikacją, to zazwyczaj oczekuje argumentów skojarzonych z parametrami funkcji main. W
celu ich dostarczenia, należy wydać polecenie Project/Arguments. Spowoduje to wyświetlenie dialogu Run
arguments
, pokazanego na ekranie Dostarczenie argumentów. W okienku dialogu można podać wymagane
argumenty aplikacji.

Ekran Dostarczenie argumentów

### giveargs.gif


Wykonanie programu


W celu wykonania programu należy w okienku Build kliknąć ikonę Excecute Program. Jeśli program jest
aplikacją, to nastąpi wówczas podjęcie wykonania publicznej i statycznej funkcji main. Jeśli jest apletem, to
nastąpi zinterpretowanie dokumentu HTML wchodzącego w skład projektu i wyświetlenie opisanych w nim
apletów.

Jeśli po aktywowaniu programu nastąpi wyładowanie i załadowanie Cafe, bez możliwości zaobserwowania

czegokolwiek, to zapewne

1)

Program jest aplikacją, ale nie wyposażono go w statyczną i publiczną funkcję main zawartą w klasie,

która jest publiczna.

2)

Program jest aplikacją, która wyprowadza wyniki na konsolę, ale nie zapewniono możliwości

obejrzenia konsoli (zazwyczaj pomaga System.in.read()).

3)

Program jest apletem, ale nie zawiera publicznej nadklasy klasy Applet.

4)

Program jest apletem, ale jego nazwa podana w opisie apletu (np. Master.class) nie wynika z nazwy

klasy apletu (np. Master).

5)

Wykonanie programu doprowadziło do wysłania nieobsłużonego wyjątku.

Jeśli po aktywowaniu apletu zniknie środowisko Cafe, ale nic się nie wyświetli, to najprawdopodobniejszą

tego przyczyną jest błąd w opisie apletu, na przykład brak zamykającego nawiasu kątowego w opisie apletu
albo błędna nazwa parametru (np. lenght zamiast length).

Uwaga: W systemie Windows 95 należy w takim wypadku nacisnąć klawisze Ctrl-Alt-Del, wyszukać program
Appletviewer, a następnie wydać polecenie End Task. Spowoduje to powrót do środowiska Cafe.

Jeśli po wykonaniu aplikacji nie pojawi się oczekiwany napis, to zapewne nie spowodowano wymiecenia

bufora wyjściowego konsoli, tj. zapomniano, że po wykonaniu sekwencji instrukcji

System.out.print(exp);


należy wykonać instrukcję

System.out.println();


Uruchomienie programu

background image

23

Podczas uruchamiania programów posługiwałem się własną klasa uruchomieniową, przytoczoną w Dodatku
Klasa uruchomieniowa
. Okazała się ona bardzo przydatna, dlatego szczerze zachęcam do jej użycia. Ci, który
oczekują większego komfortu mogą użyć uruchamiacza wbudowanego w Cafe. Posługiwanie się nim jest na
tyle proste, że nie wymaga dodatkowych wyjaśnień.

Na ekranie Uruchamianie pokazano wykonanie programu posługującego się klasą Debug. Wyświetlenie jego
modułu źródłowego zapewniono dzięki aktywowaniu dodatkowej kopii środowiska Cafe, w którym otwarto
plik zawierający ten moduł.

Ekran Uruchamianie

### debugger.gif


Przykład Wysłanie nieobsłużonego wyjątku

import java.io.IOException;
import java.awt.*;

public
class Master {
static {
new Debug(); // por. Dodatek Klasa uruchomieniowa
}
public static void main(String args[])
{
div(1, 0);
System.in.read();
}
static void div(int num, int den)
{
System.out.println(num / den);
}
}


Wykonanie programu powoduje wysłanie wyjątku klasy ArithmeticException, a na skutek nie obsłużenia go,
wyprowadzenie do okna konsoli napisu

java.lang.ArithmeticException: / by zero
at Master.div(Master.java:17)
at Master.main(Master.java:12)


Z analizy napisu wynika, że błąd "dzielenie przez 0" (/ by zero) wystąpił w 17. wierszu funkcji sub, wywołanej z
12. wiersza funkcji main.

W wykryciu bł
ędu okazała się pomocna klasa Debug opisana w Dodatku Klasa uruchomieniowa.


________________________________________________________________________________________

Programowanie wizualne


Programowanie wizualne polega na użyciu generatora do wytworzenia kodu obsługującego graficznie
zaprojektowane oblicze programu. Wygenerowanie programu jest łatwe, ale na ogół czyni program dłuższym i
mniej wydajnym.

Następujący aplet, utworzony za pomocą generatora programów, pokazany podczas wykonania na ekranie
Programowanie wizualne, umożliwia zapoznanie się z kolorami o podanych składnikach RGB (red, green,
blue).

Ekran Programowanie wizualne

### visual.gif


Przykład Program wygenerowany

<HTML>

background image

24

<HEAD><TITLE> Master </TITLE></HEAD>
<BODY>
<APPLET CODE="Master.class" WIDTH=283 HEIGHT=190>
</APPLET>
</BODY>
</HTML>
=================================================

import java.applet.*;
import java.awt.*;

public class Master extends Applet {
private int red, green, blue;
public void init()
{
super.init();
//{{INIT_CONTROLS
setLayout(null);
resize(303, 133);
panel = new Panel();
panel.setLayout(null);
add(panel);
panel.reshape(14, 39, 66, 43);
redEdit = new TextField(7);
add(redEdit);
redEdit.reshape(106, 18, 58, 20);
greenEdit = new TextField(7);
add(greenEdit);
greenEdit.reshape(106, 54, 58, 20);
blueEdit = new TextField(7);
add(blueEdit);
blueEdit.reshape(106, 90, 58, 20);
redLabel = new Label("Red");
add(redLabel);
redLabel.reshape(161, 20, 47, 17);
greenLabel = new Label("Green");
add(greenLabel);
greenLabel.reshape(161, 56, 47, 17);
blueLabel = new Label("Blue");
add(blueLabel);
blueLabel.reshape(161,92,47,17);
exitButton = new Button("Exit");
exitButton.setFont(new Font("Helvetica",
Font.BOLD,10));
add(exitButton);
exitButton.reshape(232, 3, 67, 19);
//}}
}
public void paint(Graphics gDC)
{
Rectangle b = panel.bounds();
gDC.drawRect(b.x-1, b.y-1,
b.width+1, b.height+1);
}
public boolean handleEvent(Event evt)
{
if (evt.id == Event.ACTION_EVENT &&
evt.target == redEdit) {
redEnter(evt);
return true;
}
else if (evt.id == Event.ACTION_EVENT &&
evt.target == greenEdit) {
greenEnter(evt);
return true;
}
else if (evt.id == Event.ACTION_EVENT &&
evt.target == blueEdit) {
blueEnter(evt);
return true;
}
else if (evt.id == Event.ACTION_EVENT &&

background image

25

evt.target == exitButton) {
clickedExitButton();
return true;
}
return super.handleEvent(evt);
}

//{{DECLARE_CONTROLS
Panel panel;
TextField redEdit;
TextField greenEdit;
TextField blueEdit;
Label redLabel;
Label greenLabel;
Label blueLabel;
Button exitButton;
//}}
public void clickedExitButton()
{
// to do: put event handler code here.
System.exit(0);
}
public void redEnter(Event evt)
{
// to do: put event handler code here.
showColor(red = rgb(evt), green, blue);
}
public void greenEnter(Event evt)
{
// to do: put event handler code here.
showColor(red, green = rgb(evt), blue);
}
public void blueEnter(Event evt)
{
// to do: put event handler code here.
showColor(red, green, blue = rgb(evt));
}
int rgb(Event evt)
{
return Integer.parseInt((String)evt.arg);
}
void showColor(int r, int g, int b)
{
Color color = new Color(r, g, b);
panel.setBackground(color);
panel.repaint();
}
}


Generowanie programu


W celu wywołania generatora należy wydać polecenie Tools/AppExpress. Spowoduje to wyświetlenie okna
dialogowego pokazanego na ekranie Generator programu.

Bezpośrednio po wywołaniu generatora jest w nim wyróżniony przycisk Application Type oraz związany z nim
zestaw przycisków Applications. Umożliwiają one określenie, czy program ma być apletem czy aplikacją.

Uwaga: Dalszy opis dotyczy generowania apletu zdefiniowanego przez klasę o arbitralnie wybranej nazwie
Master.

Ekran Generator programu

### apptype.gif


Po naciśnięciu przycisku Select Directory wyświetli się okno pokazane na ekranie Wybranie katalogu. Należy
w nim określić nazwę katalogu, w którym ma być wygenerowany program.

Ekran Wybranie katalogu

background image

26

### catalog.gif


Po naciśnięciu przycisku Miscellaneous wyświetli się okno pokazane na ekranie Nazwa projektu. Należy w
nim podać nazwę projektu (Master). Odbywa się to przez wypełnienie klatki Project Name

Ekran Nazwa projektu

### prjname.gif


Po naciśnięciu przycisku Finish nastąpi wygenerowanie programu aplikacji.

Projektowanie oblicza


W celu zaprojektowania oblicza graficznego programu należy wydać polecenie Resource/Open, a w
wyświetlonym wówczas oknie dialogowym wybrać nazwę pliku z rozszerzeniem .rc (Master.rc). Spowoduje
to wyświetlenie okna, w którym po kliknięciu pozycji Form i Master.init pojawi się ramka, pokazana na
ekranie Pulpit apletu.

Ekran Pulpit apletu

### pulpit.gif


Zapełnienie ramki sterownikami odbywa się za pomocą narzędzi zawartych w ramce narzędziowej. Po
kliknięciu narzędzia i przeciągnięciu kursora w obrębie pulpitu następuje wykreślenie sterownika. Zestawy
sterowników mogą być rozmieszczane na pulpicie za pomocą narzędzi wyszczególnionych na pasku
narzędziowym okna zasobów.

Na ekranie Sterowniki apletu pokazano przykładowe rozmieszczenie sterowników Label, TextField i Panel.

Ekran Sterowniki apletu

### controls.gif


Określanie właściwości


Po naniesieniu sterowników i pojemników na pulpit, należy określić ich właściwości: nazwy i opisy (Member,
Caption), wygląd (Look) i reakcje (Events).

Na ekranach Nazwa przycisku, Wygląd przycisku i Reakcja przycisku pokazano sposób określenia właściwości
dla przycisku opatrzonego napisem Exit.

Ekran Nazwa przycisku

### member.gif


Ekran Wygląd przycisku

### look.gif


Ekran Reakcja przycisku

### events.gif


Po wybraniu zdarzenia dotyczącego sterownika (np. clicked) i naciśnięciu przycisku Edit, zostanie wyświetlone
okno Class Editor, pokazane na ekranie Zdefiniowanie obsługi. W oknie tym można określić sposób
reagowania sterownika na zdarzenie.

Ekran Zdefiniowanie obsługi

### handler.gif


Komponowanie programu

background image

27

Po wykonaniu podanych czynności dla wszystkich komponentów pulpitu, a następnie zamknięciu okna do
projektowania zasobów, otrzymuje się gotowy program, który może być poddany dodatkowej ręcznej obróbce
(m.in. skopiowaniu komentarza zawierającego opis apletu do pliku z rozszerzeniem .html).

Wygenerowany wizualnie program należy oczywiście zbudować, uruchomić i wytestować. Czynności te
wykonuje się w identyczny sposób jak dla programu utworzonego ręcznie.

background image

28

Część III

Podstawy programowania


Program źródłowy jest zestawem napisów umieszczonych w modułach źródłowych. Każdy moduł źródłowy
jest umieszczony w pliku.

Moduł źródłowy składa się z komentarzy, deklaracji pakietu, deklaracji importu i definicji klas. Jeśli moduł
ź

ródłowy zawiera definicję więcej niż jednej klasy, to tylko jedna z nich może być publiczna. Klasa publiczna o

nazwie Class musi być umieszczona w pliku o nazwie Class.java.

Po skompilowaniu modułu źródłowego powstaje moduł wynikowy z rozszerzeniem .class. Z takich właśnie
modułów, po uzupełnieniu ich modułami bibliotecznymi pochodzącymi z pliku classes.zip tworzy się program
wynikowy
.


_________________________________________________________________________________________

Leksemy


Moduł źródłowy składa się z odstępów i komentarzy oraz z sekwencji leksemów: słów kluczowych,
identyfikatorów, operatorów i ograniczników.

Słowa kluczowe


Słowo kluczowe jest napisem służącym do zapisywania instrukcji programu. Do najczęściej używanych słów
kluczowych należą

int double

if else while for


Na przykład

int age; // wiek
// ...
if(age >= 18) // je

ś

li sko

ń

czyłe

ś

18 lat

youCanVote(); // mo

ż

esz głosowa

ć

else // w przeciwnym razie
youMustWait(); // musisz zaczeka

ć

Deklaracja zmiennej age zawiera słowo kluczowe int.

Instrukcja warunkowa zawiera słowa kluczowe if i else.

Literały


Literałem jest nazwa zmiennej, której wartość i typ wynikają z zapisu literału. Literałami są liczby całkowite
(np. 12), liczby rzeczywiste (np. 1.2e+2), znaki (np. ('a') i łańcuchy (np. "Yes").

Liczby całkowite


Liczba całkowita dziesiętna składa się z ciągu cyfr dziesiętnych. Liczba całkowita szesnastkowa składa się z
napisu 0x po którym następuje ciąg cyfr szesnastkowych. Wartości liczb typu "int" należą do przedziału

0 .. +9,223,372,036,854,775,807


Na przykład

background image

29

12

liczba całkowita dziesiętna

(wartość: 12)

0xc

liczba całkowita szesnastkowa

(wartość: 12)

-12

wyrażenie całkowite


Liczby rzeczywiste


Liczba rzeczywista składa się z dwóch ciągów cyfr dziesiętnych oddzielonych kropką, po których może
wystąpić litera e z wykładnikiem.

Ciąg cyfr przed albo po kropce może być pominięty. Kropkę można pominąć tylko wówczas, gdy liczbę
zapisano z wykładnikiem albo gdy po liczbie występuje litera f. Wykładnik ma postać liczby całkowitej,
ewentualnie poprzedzonej znakiem + (plus) albo - (minus).

Wartości liczb typu "double" należą do przedziału

0 .. 1.8e308


Na przykład

1.2

liczba rzeczywista

(wartość: 1.2)

3e-2

liczba rzeczywista

(wartość: 0.03)

-1.2

wyrażenie rzeczywiste


Znaki


Literał znakowy ma postać 'c' w której c jest znakiem widocznym (np. 'a'), symbolem znaku (np. '\n'), albo
szesnastkowym kodem znaku (np. '\u0061'). Wartością literału znakowego jest kod opisanego przezeń znaku
(np. wartością literału 'a' jest 97).

W tabeli Symbole znaków wymieniono kilka ważniejszych literałów znakowych.

Tabela Symbole znaków

###

Znak

Interpretacja


'\n'

znak nowego wiersza

'\r'

znak powrotu karetki

'\0'

znak o kodzie 0

'\t'

znak tabulacji

'\\'

znak \

'\''

znak '

(krócej: ''')

'\"'

znak " (krócej: '"')

###

Łańcuchy


Łańcuchem jest napis "cc ... c", w którym każde c jest takim samym napisem jak w literale znakowym. Literał
łańcuchowy składający się z N znaków jest nazwą odnośnika do wektora o N+1 elementach typu "char",
zainicjowanych kodami kolejnych znaków łańcucha oraz dodatkowo kodem znaku '\0' (liczbą 0).

Na przykład

"Yes"

nazwa odnośnika do wektora zainicjowanego

kodami znaków Y, e, s oraz kodem znaku '\0' (liczbą 0)

""

nazwa odnośnika do 1-elementowego wektora

zainicjowanego kodem znaku '\0'

background image

30


Identyfikatory


Identyfikator jest napisem reprezentującym element programu: pakiet, klasę, procedurę albo zmienną.
Identyfikatory mogą składać się tylko z liter i cyfr (dolar i podkreślenie są uznawane za litery), ale nie mogą
zaczynać się od cyfry.

Liczba znaków identyfikatora nie jest ograniczona. Identyfikator nie może składać się z takich samych znaków
jak słowo kluczowe.

Identyfikatorami są m.in. napisy

forSale ForSale forsale For_Sale $forSale


Nie jest identyfikatorem napis

4sale


Operatory


Operatorami są ciągi znaków, służące do zapisywania operacji. Do najczęściej używanych operacji należą
m.in.:

operacje arytmetyczne


+

(dodawanie),

-

(odejmowanie),

*

(mnożenie),

/

(dzielenie),

++ (zwiększenie o 1)

--

(zmniejszenie o 1)

+= (dodanie)

-=

(odjęcie)

*=

(pomnożenie)

/=

(podzielenie)

porównania


== (równe)

!=

(nie równe),

<

(mniejsze)

>

(większe),

<= (mniejsze lub równe)

>= (większe lub równe)

operacje logiczne


!

(negacja)

&& (koniunkcja)

||

(dysjunkcja)

Na przykład

int num = 5, var = 7;
var += -num++ / 2;
System.out.println(var); // 5
System.out.println(num); // 6


Instrukcja

var += -num++ / 2;


zawiera operatory

++ - / +=

background image

31

Wartości kolejnych podwyrażeń są następujące

num

5

num++

5

-num++

-5

-num++ / 2

-2


A zatem do zmiennej var dodaje się -2, czyli nadaje się jej wartość 5.

Ograniczniki


Ogranicznikami są pojedyncze znaki oddzielające albo kończące elementy programu. Najczęściej używanymi
ogranicznikami są przecinki i średniki.

Na przykład

for(int suma = 0, i = 0; i < 100 ; i++)
suma = suma + i;

Przytoczona instrukcja zawiera 6 ograniczników (dwa nawiasy, przecinek i trzy średniki).


_________________________________________________________________________________________

Zmienne


Zmienna jest elementem programu wynikowego. W programie źródłowym występuje pod postacią
identyfikatora (np. var), literału (np. 12, 3.4, 'a', "Hello"), albo wyrażenia (np. a+1).

Zmienne mogą być modyfikowalne albo niemodyfikowalne. Zmiennym modyfikowalnym można, a zmiennym
niemodyfikowalnym nie można przypisywać danych. Zmienne niemodyfikowalne mogą być nazywane stałymi.

Uwaga: Zapis literału uznaje się za niejawną deklarację reprezentowanej przezeń zmiennej. Wszystkie
pozostałe zmienne muszą być zadeklarowane jawnie, albo muszą powstać jako rezultat wykonania operacji na
zmiennych. W szczególności każde wyrażenie jest chwilową nazwą pewnej zmiennej.

Na przykład

int var;
// ...
var = var + 2;


Identyfikator var jest nazwą zmiennej typu "int".

Wyrażenie

var + 2


jest chwilową nazwą rezultatu operacji dodawania.

Wyrażenie

var = var + 2


jest chwilową nazwą rezultatu operacji przypisania.

Identyfikowanie zmiennych

background image

32

Do identyfikowania zmiennych służą ich nazwy. Nazwą zmiennej modyfikowalnej jest identyfikator, a nazwą
zmiennej niemodyfikowalnej literał. Wyrażenie jest nazwą zmiennej modyfikowalnej tylko wówczas, gdy jest l-
nazw
ą.

O tym, czy rezultat operacji jest czy nie jest l-nazwą decyduje opis operacji. Decyzje takie zostały podjęte
arbitralnie przez twórców języka. W szczególności jest l-nazwą każdy identyfikator zmiennej (z wyjątkiem
odnośnika this), ale nie jest nią wyrażenie arytmetyczne (np. 2 + 3), ani nazwa rezultatu przypisania (np. a =
12
)

Uwaga: Określenie l-nazwa bierze się stąd, że tylko taka nazwa zmiennej może wystąpić jako lewy argument
operacji przypisania.

Na przykład

int var = 12;
++++var; // bł

ą

d (++var nie jest l-nazw

ą

)

(var = 2) = 3; // bł

ą

d (var = 2 nie jest l-nazw

ą

)

this = null // bł

ą

d (this nie jest l-nazw

ą

)


Modyfikowanie zmiennych


Modyfikowanie zmiennych odbywa się za pomocą operacji przypisania oraz za pomocą operacji wejścia.

Na przykład

int chr, fix;
fix = 'a';
// ...
chr = System.in.read();

Zmienna fix jest modyfikowana za pomocą operacji przypisania, a zmienna chr jest modyfikowana za pomocą
operacji wej
ścia.

Przekształcanie zmiennych


Do przekształcenia zmiennej w inną zmienną służy konwersja. Konwersja, która może być zastosowana
niejawnie jest konwersją standardową.

Konwersja standardowa jest konwersją dopuszczalną (poprawną składniowo), ale nie musi być konwersją
wykonalną (poprawną semantycznie).

Wyrażenie poddane konwersji zapisuje się w postaci

(Type)exp


w której Type jest nazwą typu docelowego (najczęściej identyfikatorem typu), a exp jest wyrażeniem, które
można poddać konwersji do typu "Type".

Uwaga: Nazwa zmiennej, która jest rezultatem konwersji nie jest l-nazwą.

Na przykład

int num = (int)12.0; // dobrze (jawna konwersja)
double dbl = 12; // dobrze (niejawna konwersja)
int fix = 12.0; // bł

ą

d (brak jawnej konwersji)

(int)fix = 12; // bł

ą

d ((int)Fix nie jest l-nazw

ą

)


Promocje

background image

33


Przed wykonaniem pewnych operacji jednoargumentowych jest wykonywana promocja do typu ogólniejszego
(np. z typu "char" do typu "int"). Takie przekształcenie jest wykonywane niejawnie.

Na przykład

char chr = 'a';
System.out.print(chr); // a
System.out.print((int)chr); // 97
System.out.print(+chr); // 97


Przed wykonaniem operacji +chr dokonano promocji z typu "char" do typu "int".

Typ wspólny


Przed wykonaniem pewnych operacji dwuargumentowych jest wykonywane przekształcenie argumentów do
najwęższego typu ogólniejszego, stanowiącego wówczas typ wspólny argumentów. Przekształcenie takie jest
wykonywane niejawnie.

Na przykład

char chr = 'a'; // 0000 0000 0110 0001
System.out.print((int)chr & 0x21); // 33
System.out.print(chr & 0x21); // 33


Przed wykonaniem operacji chr & 0x21 przekształcono argument chr do typu wspólnego "int".


_________________________________________________________________________________________

Zmienne orzecznikowe


Zmiennymi orzecznikowymi (krótko: orzecznikami) są zmienne typu "boolean". Zmiennym orzecznikowym
można przypisywać orzeczenia reprezentujące prawdę (true) albo fałsz (false).

Na orzecznikach można wykonywać operacje negacji (!), koniunkcji (&&) i dysjunkcji (||). Operacja negacji
jest jednoargumentowa, a operacje koniunkcji i dysjunkcji są dwuargumentowe.

Negacja


Jeśli var jest orzecznikiem o wartości false, to rezultatem operacji !var jest orzecznik o wartości true.

Jeśli var jest orzecznikiem o wartości true, to rezultatem operacji !var jest orzecznik o wartości false.

Na przykład

boolean var = false;
var = !var;


Po opracowaniu deklaracji, orzecznik var ma wartość false.

Po opracowaniu przypisania, orzecznik var ma wartość true.

Koniunkcja


Koniunkcja dwóch argumentów ma wartość true tylko wówczas, gdy każdy z nich ma wartość true. Jeśli lewy
argument ma wartość false, to rezygnuje się z opracowania prawego.

background image

34


Na przykład

boolean one = true,
two = false;
boolean var;
var = one && two;
two = var && fun();


Zmiennej var przypisano warto
ść false.

Podczas wykonywania ostatniej instrukcji nie dojdzie do wykonania funkcji fun.

Dysjunkcja


Dysjunkcja dwóch argumentów ma wartość false tylko wówczas, gdy każdy z nich ma wartość false. Jeśli lewy
argument ma wartość true, to rezygnuje się z opracowania prawego.

Na przykład

boolean one = true,
two = false;
boolean var;
var = one || fun();


Zmiennej var przypisano wartość true.

Podczas wykonywania ostatniej instrukcji nie dojdzie do wykonania funkcji fun.


_________________________________________________________________________________________

Zmienne arytmetyczne


Zmiennymi arytmetycznymi są zmienne całkowite (np. typu "int") i rzeczywiste (np. typu "double").

Na zmiennych arytmetycznych można wykonywać podstawowe działania arytmetyczne: dodawanie,
odejmowanie, mnożenie i dzielenie, operacje zwiększenia i zmniejszenia wartości o 1 oraz operacje porówania.

Zmienne całkowite


Najczęściej używanymi zmiennymi całkowitymi są 4-bajtowe zmienne typu "int". Można w nich
przechowywać dane liczbowe z przedziału

-2,147,483,648 .. 2,147,483,647


Rzadziej są używane zmienne całkowite typu "byte", "short" i "long". Zakresy ich wartości przytoczono w
tabeli Pozostałe zmienne całkowite.

Uwaga: Nie ma literałów typu "byte" i "short". Literał typu "long" ma postać literału typu "int" zakończonego
literą L albo l (np. 12L).

Tabela Pozostałe zmienne całkowite

###

Typ

Rozmiar

Wartości


byte

1-bajt

(od -128 do 127)

short

2-bajty

(od

-32768 do 32767)

long

8-bajtów

(od

-9,223,372,036,854,775,808 do

background image

35

+9,223,372,036,854,775,807)

###


Zmienne rzeczywiste

Najczęściej używanymi zmiennymi rzeczywistymi są 8-bajtowe zmienne typu "double". Można w nich
przechowywać dane liczbowe z przedziału

-1.8e308 .. 1.8e308


Rzadziej są używane 4-bajtowe zmienne typu "float". Można w nich przechowywać dane liczbowe z przedziału

-3.4e38 .. 3.4e38


Uwaga: Literał typu "float" ma postać literału typu "int" albo "double" zakończonego literą F albo f (np. 12f).

Podstawowe działania arytmetyczne


Rezultatem podstawowego działania arytmetycznego jest zmienna arytmetyczna o wartości określonej przez to
działanie. Jeśli operacja jest dwuargumentowa, a jeden z argumentów jest typu "double", to i rezultat operacji
jest typu "double".

Na przykład

int fix = 12;
double dbl = 3.4;
double var;
var = fix + dbl;


Zmiennej var przypisano daną typu "double" o wartości 15.4.

Uwaga: Jeśli zmienna arytmetyczna jest połączona operatorem + (plus) ze zmienną łańcuchową, to zmienna
arytmetyczna jest przekształcana w łańcuchową, a następnie tworzy się zmienną łańcuchową składającą się z
wszystkich znaków obu łańcuchów.

W szczególności, rezultatem operacji

"" + 12


jest "12", rezultatem operacji

"" + 2 + 3


jest "23", a rezultatem operacji

"Wynik: " + (2 + 3)


jest

"Wynik: 5"


Operacje zwiększenia i zmniejszenia


Rezultatem następnikowej operacji zwiększenia (++) jest zmienna o takiej wartości jaką ma argument operacji.
Skutkiem ubocznym operacji zwiększenia jest dodanie do argumentu liczby 1.

Rezultatem następnikowej operacji zmniejszenia (--) jest zmienna o takiej wartości jaką ma argument operacji.
Skutkiem ubocznym operacji zmniejszenia jest odjęcie od argumentu liczby 1.

background image

36

Na przykład

int fix = 12;
int num = fix++;
fix = num--;

Tuż przed wykonaniem ostatniej instrukcji zmienna fix oraz zmienna num ma wartość 12.

Tuż po wykonaniu ostatniej instrukcji zmienna fix ma wartośc 12, a zmienna num ma wartość 11.

Operacje porównania


Rezultatem operacji porównania: == (równe), != (nie równe), < (mniejsze), > (większe), <= (mniejsze lub
równe
), >= (większe lub równe) jest orzecznik o wartości określającej prawdziwość porównania (np. 12 > 10
ma wartość true).

Uwaga: Specjalnej ostrożności wymaga użycie operatora równe. Jeśli zapomni się o tym, że składa się z pary
znaków równości, to faktycznie stanie się operatorem przypisania.

Na przykład

int fix;
// ...
if(fix >= 100 && fix <= 200)
System.out.println("Warto

ść

zmiennej fix " +

"nale

ż

y do przedziału 100 .. 200");

if(fix == 100)
System.out.println("Zmienna fix ma warto

ść

100");


Wykonanie instrukcji warunkowych powoduje wyprowadzenie napisu dotyczącego aktualnej wartości zmiennej
fix.

Gdyby w ostatniej instrukcji relacj
ę

fix == 100


omyłkowo zastąpiono operacją przypisania

fix = 100


to niezależnie od wartości zmiennej fix wyprowadzono by napis

Zmienna fix ma warto

ść

100



_________________________________________________________________________________________

Zmienne znakowe



Zmienne znakowe są 16-bitowymi zmiennymi arytmetycznymi. Mogą być używane w taki sam sposób jak
pozostałe zmienne arytmetyczne, ale ich głównym przeznaczeniem jest reprezentowanie kodów znaków,
zarówno znaków europejskich jak i znaków używanych w krajach azjatyckich.

Dane znakowe są znakami Unikodu. Jego pierwszych 256 znaków jest identycznych ze znakami kodu ASCII
Latin-1
. Wartości wszystkich znaków Unikodu nieujemne.

Na przykład

char chr;
// ...

background image

37

if(chr >= 'a' && chr <= 'z')
System.out.println("Kod przypisany zmiennej chr " +
"jest kodem litery a .. z");
if(chr == 'm')
System.out.println("Kod przypisany zmiennej chr " +
"jest kodem litery m");


Wykonanie instrukcji warunkowych powoduje wyprowadzenie napisu dotyczącego aktualnej wartości zmiennej
chr.


_________________________________________________________________________________________

Zmienne odnośnikowe


Zmienne odnośnikowe służą do identyfikowania zmiennych obiektowych, m.in. zmiennych predefiniowanego
typu łańcuchowego "String".

Identyfikowanie odbywa się za pomocą odniesień przypisywanych odnośnikom. Każda taka dana zawiera
informację o adresie i typie identyfikowanego przez nią obiektu

Uwaga: Odnośnikowi dowolnego typu można przypisać odniesienie puste reprezentowane przez słowo
kluczowe null. Po wykonaniu takiego przypisania odnośnik nie identyfikuje żadnego obiektu.

Identyfikowanie zmiennych


Zmienna odnośnikowa typu "Class" może identyfikować zmienne klasy Class oraz zmienne dowolnej jej
podklasy.

W szczególności, ponieważ klasa Applet jest podklasą klasy Panel, więc odnośnik ref zadeklarowany za
pomocą instrukcji

Panel ref;


może identyfikować zarówno zmienne klasy Panel jak i zmienne klasy Applet.

Na odnośnikach nie wolno wykonywać innych operacji niż porównanie odnośników i przypisanie odniesienia.
Jeśli dwa odnośniki identyfikują tę samą zmienną, to rezultat operacji == (równe) ma wartość true, a rezultat
operacji != (nie równe) ma wartość false.

Na przykład

String h = "Hello";
String w = "World";
System.out.println(h + " " + w);


Odno
śnik h identyfikuje obiekt zainicjowany łańcuchem Hello, a odnośnik w identyfikuje obiekt zainicjowany
ła
ńcuchem World.

Rezultatem operacji

h + " " + w


jest odnośnik identyfikujący obiekt zainicjowany łańcuchem

Hello World



_________________________________________________________________________________________

Zmienne łańcuchowe

background image

38


Zmienną łańcuchową jest zmienna obiektowa predefiniowanego typu "String" albo "StringBuffer". Podobnie
jak obiekty innych typów, zmienne łańcuchowe są identyfikowane przez odnośniki.

Ponieważ porównanie odnośników polega jedynie na sprawdzeniu, czy identyfikują one te same obiekty,
porównanie wartości zmiennych łańcuchowych może być wykonane tylko za pomocą specjalnych metod, takich
jak equals i compareTo.

Uwaga: Zasada porównywania łańcuchów jest następująca: 1) jeśli łańcuchy składają się z takich samych
znaków, to uznaje się je za równe, 2) w przeciwnym ignoruje się początkowe znaki łańcuchów (jeśli są parami
identyczne), a porównanie łańcuchów zastępuje porównaniem pierwszej pary znaków nieidentycznych (znak o
mniejszym kodzie uznaje się za mniejszy).

public boolean equals(Object obj)

Rezultatem metody equals użytej w wyrażeniu

str1.equals(str2)


jest orzecznik o wartości true jeśli odnośniki str1 i str2 identyfikują zmienne łańcuchowe zainicjowane takimi
samymi łańcuchami. W przeciwnym razie orzecznik ma wartość false.

Na przykład

String str = "$";
String one = str + "100";
String two = str + "100";
System.out.println(one == two); // false
System.out.println(one.equals(two)); // true


public int compareTo(String str)

Rezultatem metody compareTo użytej w wyrażeniu

str1.compareTo(str2)


jest zmienna typu "int" o wartości ujemnej, zero, albo dodatniej, odpowiednio do tego czy odnośniki str1 i
str2 identyfikują zmienne zainicjowane takimi łańcuchami, że pierwszy jest mniejszy od drugiego, oba są
równe, albo pierwszy jest większy od drugiego.


Na przykład

String one;
String two;
// ...
int res = one.compareTo(two);
if(res < 0)
System.out.println("Pierwszy ła

ń

cuch jest mniejszy");

if(res == 0)
System.out.println("Ła

ń

cuchy s

ą

równe");

if(res > 0)
System.out.println("Pierwszy ła

ń

cuch jest wi

ę

kszy");

Jeśli tuż przed użyciem metody compareTo wykonano instrukcje

one = "ABC";
two = "Ab";


to ponieważ kod litery b (98) jest większy od kodu litery B (42), więc nastąpi wyprowadzenie napisu

Pierwszy ła

ń

cuch jest mniejszy

background image

39


_________________________________________________________________________________________

Instrukcje


Instrukcja jest opisem czynności. Każda instrukcja może być poprzedzona etykietą (albo więcej niż jedną
etykietą). Etykieta ma postać identyfikatora, a od instrukcji (albo od etykiety) oddziela ją znak : (dwukropek).

Na przykład

void sub(int par)
{
Lab: // etykieta
while(true)
if(par++ < 0)
break Lab; // odwołanie do etykiety
}


Instrukcja deklaracyjna


Instrukcja deklaracyjna ma postać

spec dcl, dcl, ... , dcl;


w której spec jest zestawem specyfikatorów, a każde dcl jest deklaratorem zmiennej albo deklaratorem z
inicjatorem.

Specyfikatory

Specyfikatory służą do określenia typu zmiennych i rezultatów procedur, a ponadto określają dodatkowe
atrybuty zmiennych, procedur i klas, takie jak

dostępność

private, protected, public

statyczność

static

rodzimość

native

ustaloność

final

synchroniczność

synchronized

ulotność

volatile

nietrwałość

transient


Na przykład

public final
class Master {
public static final double Pi = 3.14;
// ...
}


Klasa Master jest publiczna (dostępna z wszystkich klas) i ustalona (nie może być podklasą innej klasy).

Zmienna Pi jest publiczna (dostępna z wszystkich klas), statyczna (istnieje od chwili załadowania do chwili
wyładowania klasy) i ustalona (nie mo
że być modyfikowana).

Deklaratory

Deklarator służy do określenia nazwy zmiennej. Jeśli zmienna jest odnośnikiem do tablicy, to deklarator
zawiera informację o liczbie jej wymiarów.

Uwaga: Sposób rozmieszczenia par nawiasów kwadratowych określających liczbę wymiarów tablicy jest
dowolny. W obrębie nawiasów kwadratowych nie może wystąpić żaden napis.

background image

40


Na przykład

int var;
int vec[];
String[] arr[]; // String arr[][]; String [][]arr;
int mtx[2][] // bł

ą

d (liczba w obr

ę

bie nawiasów)


Deklaratorami są var, vec[] i []arr[]. Identyfikator vec jest nazwą zmiennej typu "int", a identyfikatory vec i arr
s
ą nazwami odnośników do tablic zmiennych, odpowiednio 1-wymiarowej i 2-wymiarowej (wynika to z liczby
par nawiasów kwadratowych).

Inicjatory

Inicjator deklaratora może mieć jedną z następujących postaci

= exp

inicjator wyra

ż

eniowy

= { phi, phi, ... , phi }

inicjator klamrowy

w których exp jest wyrażeniem a każde phi jest wyrażeniem, albo frazą inicjującą o postaci

{ phi, phi, ... , phi }


Opracowanie instrukcji deklaracyjnej powoduje zadeklarowanie wymienionych w niej zmiennych oraz
ewentualne zainicjowanie ich wartościami wyrażeń wymienionych w inicjatorach.

Uwaga: Inicjator klamrowy może być użyty tylko do inicjowania tablic.

Przykład Instrukcje deklaracyjne

int varA;
int varB = 10;
int var1, var2 = 20, var3;
int lotto[] = { 12, 45, 6, 32, 18, 5 };
String monolog[] = { "To", "be", "or", "not", "to", "be" };
int matrix[][] = {
{ 10, 20, 30, 40 },
{ 20, 30, 40, 50 },
{ 30, 40, 50, 60 }
};
int var5(50); // bł

ą

d (niewła

ś

ciwy inicjator)

int varC = { 10 }; // bł

ą

d (niewła

ś

ciwy inicjator)


Zmienna matrix jest odnośnikiem do 2-wymiarowej tablicy o 2 wierszach i 4 kolumnach.

Zakres deklaracji


W punkcie bezpośrednio za deklaratorem zaczyna się zakres deklaracji zmiennej. Kończy się on w miejscu
zakończenia najwęższego bloku, w którym wystąpiła ta deklaracja.

Uwaga: Blokiem jest fragment programu zawarty między parą odpowiadających sobie nawiasów klamrowych.

Na przykład

import java.io.IOException;

public
class Master {
public static void main(String args[])
throws IOException
{
if(args.length > 0) {

background image

41

String allArgs = "";
for(int i = 0; i < args.length ; i++) {
System.out.println(
"Argument #" + i + ": " + args[i]
);
allArgs += args[i] + ' ';
}
System.out.println("All arguments: " + allArgs);
} else
System.out.println("Program was called " +
"with no arguments!");
System.in.read();
}
}


W programie występują 4 wzajemnie zawierające się bloki.

Wykonanie programu powoduje wyszczególnienie jego argumentów, na przykład

Argument #0: Ewa

Argument #1: Jan

All arguments: Ewa Jan


albo wyprowadzenie napisu

Program was called with no arguments!


Kolidowanie deklaracji


W zakresie deklaracji zmiennej nie może wystąpić deklaracja zmiennej oznaczonej takim samym
identyfikatorem.

Przykład Kolidowanie deklaracji

void sub(int age)
{
System.out.println(age);
{
int age = 12; // bł

ą

d (kolizja z parametrem)

// ...
{
int age = 20; // bł

ą

d (dodatkowa kolizja)

String name = "Bob";
// ...
}
String name = "Tom";
// ...
for(int i = 0; i < 10 ; i++)
// ...
for(int i = 10; i > 0 ; i--)
// ...
}
}


Odnośnik name identyfikujący obiekt zainicjowany łańcuchem "Bob" nie koliduje z odnośnikiem name
identyfikuj
ącym obiekt zainicjowany łańcuchem "Tom".

Nie występuje kolizja między identyfikatorami i występującymi w instrukcjach for.

Instrukcja pusta


Instrukcja pusta ma postać

;

background image

42


Jej wykonanie nie wywołuje żadnych skutków.

Przykład Instrukcja pusta

void fun()
{
int i = 10000;
JustLabel:
; // instrukcja pusta
while(i-- != 0)
; // instrukcja pusta
Fin: // bł

ą

d (brak instrukcji pustej)

}


Instrukcja grupująca


Instrukcja grupująca ma postać

{ Ins Ins ... Ins }

w której każde Ins jest dowolną instrukcją.

Wykonanie instrukcji grupującej składa się z sekwencyjnego wykonania zawartych w niej instrukcji Ins.

Uwaga: Użycie instrukcji grupującej ma na celu utworzenie z sekwencji instrukcji dokładnie jednej instrukcji.

Przykład Instrukcje grupujące

int i = -100;
do {
System.out.print(i++);
i++;
} while(i < 0);

do
System.out.print(i++);
i++; // bł

ą

d (brak instrukcji grupuj

ą

cej)

while(i < 100);

Między pierwszą parą słów kluczowych do i while występuje instrukcja grupująca.

Instrukcja wyrażeniowa


Instrukcja wyrażeniowa ma postać

exp;


w której exp jest wyrażeniem.

Wykonanie instrukcji wyrażeniowej składa się z opracowania wyrażenia exp, a następnie zignorowania
rezultatu tego opracowania.

Uwaga: Wykonanie instrukcji wyrażeniowej powinno pociągać za sobą skutki uboczne, takie jak
przypisywanie i przesyłanie danych. W przeciwnym razie użycie jej jest zbyteczne.

Przykłady Instrukcje wyrażeniowe

int var;
// ...
var = 10; // przypisanie

background image

43

var1 = var2 = 10; // przypisanie
var = System.in.read(); // przesłanie
System.in.read(); // przesłanie
System.out.println(var); // przesłanie


Instrukcja warunkowa


Instrukcja warunkowa ma postać

if(exp) InsT

albo

if(exp) InsT else InsF


w których exp jest wyrażeniem orzecznikowym, a InsT oraz InsF jest instrukcją.

Jeśli InsT jest instrukcją warunkową, to obowiązuje zasada, że każdej frazie else odpowiada najbliższa z lewej,
poprzedzająca ją fraza if.

W szczególności, instrukcja

if(exp1) if(exp2) Ins1 else Ins2


jest równoważna instrukcji

if(exp1) { if(exp2) Ins1 else Ins2 }


a nie instrukcji

if(exp1) { if(exp2) Ins1 } else Ins2


Wykonanie instrukcji warunkowej zaczyna się od wyznaczenia wartości wyrażenia exp. Jeśli wyrażenie ma
wartość true, to jest wykonywana instrukcja InsT, a w przeciwnym razie instrukcja InsF (o ile występuje). Po
wykonaniu tych czynności wykonanie instrukcji warunkowej uznaje się za zakończone.

Przykład Instrukcje warunkowe

if(var < 5 && var != 0)
System.out.println(var);

if(flag || var >= 5)
var = 0;
else
System.out.println(var);

if(var >= 0) {
if(var <= 9)
System.out.println(var);
} else
var = -1;

Instrukcja grupująca została użyta po to, aby przed frazą else nie wystąpiła instrukcja grupująca bez frazy else.

Gdyby pominięto nawiasy klamrowe, to rozpatrywana instrukcja przybrałaby postać

if(var >= 0)
if(var <= 9)
System.out.println(var);
else
var = -1;


równoważną

if(var >= 0) {

background image

44

if(var <= 9)
System.out.println(var);
else
var = -1;
}


a zatem o istotnie różnej semantyce.

Instrukcje pętli


Instrukcje pętli umożliwiają cykliczne wykonywanie objętych nimi instrukcji. Instrukcja pętli powinna być
skonstruowana w taki sposób, aby istniała gwarancja zakończenia jej wykonywania.

Instrukcja while


Instrukcja while ma postać

while(exp) Ins


w której exp jest wyrażeniem orzecznikowym, a Ins jest instrukcją.

Wykonanie instrukcji while składa się z cyklicznego wykonywania następujących czynności

1. Opracowania i wyznaczenia wartości wyrażenia exp.

2. Jeśli wyrażenie expC ma wartość true, wykonania instrukcji Ins.


Jeśli wyrażenie ma wartość false, to wykonanie instrukcji while uznaje się za zakończone.

Uwaga: Jeśli pierwszą wartością exp jest false, to instrukcja Ins w ogóle nie będzie wykonana.

Przykład Instrukcja while

int val = 3;
while(val-- != 0)
System.out.println(val); // 2 1 0


Opracowanie wyrażenia

val-- != 0


ma skutek uboczny w postaci zmniejszenia wartości zmiennej val.

Dzięki temu, po wykonaniu trzech obrotów pętli, nastąpi zakończenie wykonywania instrukcji while.

Instrukcja for


Instrukcja for ma postać

for(Ins0 expC ; expI) Ins


w której Ins0 jest instrukcją wyrażeniową albo deklaracyjną, expC jest wyrażeniem orzecznikowym, expI jest
wyrażeniem, a Ins jest instrukcją.

Wykonanie instrukcji for składa się z jednokrotnego wykonania instrukcji Ins0, a następnie z cyklicznego
wykonywania następujących czynności

1. Opracowania i wyznaczenia wartości wyrażenia expC.

background image

45

2. Jeśli wyrażenie expC ma wartość true, wykonania instrukcji Ins oraz instrukcji wyrażeniowej

utworzonej z wyrażenia expI.


Jeśli wyrażenie expC ma wartość false, to wykonanie instrukcji for uznaje się za zakończone.

Uwaga: Warto odnotować, że wykonanie instrukcji

for(Ins0 expC ; expI)

Ins


ma na ogół taki sam skutek jak wykonanie instrukcji

Ins0

while(expC) {

Ins
expI;
}

Przykład Instrukcja for

for(int var = 0; var < 3 ; var++)
System.out.println(var); // 0 1 2
int var;
for(var = 3; var > 0 ; var--)
System.out.println(var); // 3 2 1


Instrukcja do


Instrukcja do ma postać

do

Ins

while(exp);


w której Ins jest instrukcją, a exp jest wyrażeniem orzecznikowym.

Wykonanie instrukcji do składa się z cyklicznego wykonywania następujących czynności

1. Wykonania instrukcji Ins.

2. Opracowania i wyznaczenia wartości wyrażenia exp.

3. Jeśli wyrażenie exp ma wartość true, ponownego wykonania podanych czynności.


Jeśli wyrażenie exp ma wartość false, to wykonanie instrukcji do uznaje się za zakończone.

Uwaga: Instrukcja Ins jest wykonywana co najmniej jeden raz.

Przykład Instrukcja do

int val = 3;
do
System.out.println(val); // 3 2 1 0
while(val-- != 0);


Ponieważ badanie warunku odbywa się dopiero po wykonaniu instrukcji zawartej między do i while, więc liczba
"obrotów" p
ętli do jest o 1 większa niż liczba obrotów podobnej do niej instrukcji while.

Instrukcje zaniechania i kontynuowania


We wnętrzu pętli mogą być użyte instrukcje

background image

46

break;

oraz

continue;


Wykonanie instrukcji zaniechania (break) powoduje zakończenie wykonywania pętli, natomiast wykonanie
instrukcji kontynuowania (continue) powoduje wykonanie takich czynności, jakby zakończyło się
wykonywanie wszystkich instrukcji objętej pętlą (co spowoduje kontynuowanie wykonywania pętli).

Przykład Instrukcje break i continue

int sum = 0;
for(int i = 1; i <= 9 ; i++) {
if(i < 3) {
sum--;
continue;
} else if(sum > 7)
break;
sum = sum + 2*i;
}
System.out.println("Sum = " + sum);

Zmienna sum przyjmuje kolejno wartości: -1, -2, 4, 12.

Zako
ńczenie wykonywania pętli for następuje znacznie wcześniej niż wynikałoby z rozpatrzenia jej pierwszego
wiersza.

Wykonanie podanego wycinka programu powoduje wyprowadzenie napisu

Sum = 12


Użycie etykiet


Instrukcje zaniechania i kontynuowania mogą mieć również postać

break Lab;

oraz

continue Lab;


w których Lab jest etykietą.

W takim wypadku wykonanie instrukcji zaniechania powoduje zakończenie wykonywania pętli poprzedzonej
podaną etykietą, a wykonanie instrukcji kontynuowania powoduje kontynuowanie wykonania pętli
poprzedzonej podaną etykietą.

Przykład Instrukcja zaniechania z etykietą

int arr[][] = {
{ 1, 2, 3 },
{ 4, 0, 5 },
{ 1, 1, 0 }
};
int sum = 0;
Loop:
for(int i = 0; i < 3 ; i++)
for(int j = 0; j < 3 ; j++)
if(arr[i][j] == 0)
break Loop;
else
sum += arr[i][j];
System.out.println(sum);


Pętla sumuje kolejne wiersze tablicy, ale kończy się w chwili napotkania elementu o wartości 0.

background image

47


Wykonanie programu powoduje wyprowadzenie liczby 10.

Gdyby z instrukcji zaniechania usunięto etykietę, nastąpiłoby wyprowadzenie liczby 12.

Instrukcja wyboru


Instrukcja wyboru ma na ogół postać

switch(exp0) {

Case Case ... Case Default

}


w której każde Case jest frazą o postaci

case exp: Ins Ins ... break;


a Default jest frazą o postaci

default: Ins Ins ... Ins


W takim zapisie, exp0 jest wyrażeniem całkowitym, każde exp jest wyrażeniem stałym całkowitym (zazwyczaj
literałem albo symbolem), a każde Ins jest instrukcją, albo jest napisem pustym.

Na przykład

void sub(int par)
throws IllegalArgumentException
{
switch(par) {
case 0:
System.out.print("0");
break;
case -1:
System.out.print("-1");
break;
case +1:
System.out.print("+1");
break;
default:
System.out.print("Wrong value");
throw new IllegalArgumentException();
}
System.out.println();
}

Wykonanie instrukcji wyboru zaczyna się od opracowania i wyznaczenia wartości wyrażenia exp0. Następnie
wartość tego wyrażenia jest porównywana z wartościami wyrażeń exp zawartych w kolejnych frazach Case, aż
do stwierdzenia równości.

W takim wypadku są wykonywane instrukcje danej frazy. W przeciwnym razie są wykonywane instrukcje frazy
Default (domniemana fraza Default składa się z jednej instrukcji break;). Po zakończeniu tych czynności
wykonanie instrukcji wyboru uznaje się za zakończone.

Uwaga: Jeśli frazy Case nie kończy instrukcja break;, to bezpośrednio po wykonaniu jej instrukcji Ins
wykonywane instrukcje następnych fraz, aż do napotkania instrukcji zaniechania (break) bądź powrotu
(return), albo do końca instrukcji wyboru.

Instrukcje powrotu


Instrukcja powrotu ma postać

background image

48

return;

albo

return exp;


w której exp jest wyrażeniem.

Wykonanie instrukcji powrotu powoduje zakończenie wykonywania zawierającej ją procedury. Jeśli procedura
jest rezultatowa (jest typu różnego od "void"), to jej rezultat jest inicjowany wartością wyrażenia exp (po
ewentualnej konwersji do typu rezultatu).

Przykład Instrukcja powrotu

static double fun(int par)
{
if(par > 0)
return par * par; // return (double)(par * par);
else
return 0.0;
}


Jeśli funkcja fun zostanie wywołana w instrukcji

System.out.print(fun(2));


to wartością jej rezultatu będzie 4.0.

Instrukcja obsługi wyjątków


Instrukcja do obsługi wyjątków ma postać

try

Block

Catch

Finally


w której Block jest instrukcją grupującą, Catch jest niepustym zestawem fraz

catch(Dcl)

Block


w których Dcl jest deklaracją parametru anonimowej funkcji do obsługiwania wyjątków, a Finally jest
nieobowiązkową frazą

finally

Block


Na przykład

int len = 3, vec[];
try {
vec = new int [len];
// ...
}
catch(OutOfMemoryError e) {
// ...
}
catch(Exception e) {
// ...
}
finally {
vec = null;
// ...

background image

49

}


Wykonanie instrukcji try składa się z wykonania instrukcji grupującej występującej bezpośrednio po frazie try.
Jeśli podczas jej wykonywania wystąpi sytuacja wyjątkowa (spowodowana na przykład brakiem pamięci na
stercie), to wykonanie instrukcji grupującej uzna się za zakończone, a wysłany wówczas wyjątek (obiekt klasy
wyjątku) zostanie odebrany i obsłużony przez tę pierwszą frazę catch, której parametr można skojarzyć z
wysłanym wyjątkiem.

Niezależnie od tego jaki był przebieg wykonania instrukcji try, ale bezpośrednio przed jej zakończeniem, jest
wykonywany blok frazy finally.

Jeśli żadna z fraz catch instrukcji try nie jest w stanie odebrać wyjątku, to jest on wysyłany do najwęższej
dynamicznie
, obejmującej ją, instrukcji try. Jeśli takiej nie ma, to domyślnie następuje zakończenie
wykonywania programu.

Uwaga: Jeśli wystąpienie sytuacji wyjątkowej spowoduje zaniechanie dalszego wykonywania jakiegokolwiek
bloku programu, to nastąpi niejawne zniszczenie wszystkich jeszcze nie zniszczonych jego zmiennych
lokalnych.

Na przykład

import java.io.IOException;

public
class Master {
public static void main(String args[])
throws IOException
{
String str = fun("Hello");
// ...
System.in.read();
}
static String fun(String str)
{
try {
int fix = 12;
// ...
return getStr(str);
}
catch(OutOfMemoryError e) {
System.out.println("Buy more RAM!");
System.exit(0);
return null; // wymagane!
}
}
static String getStr(String str)
throws OutOfMemoryError
{
String strRef;
try {
strRef = new String(str);
System.out.println(strRef.charAt(0));
}
catch(NullPointerException e) {
System.exit(1);
return null;
}
return strRef;
}
}


Jeśli podczas wykonywania instrukcji

strRef = new String(str);


zostałby wysłany wyjątek klasy OutOfMemoryError, to ponieważ nie mógłby zostać odebrany przez frazę

background image

50

catch(NullPointerException e)


wchodzącą w skład instrukcji try funkcji getStr, więc zostałby wysłany do dynamicznie obejmującej ją instrukcji
try funkcji fun, gdzie zostałby przechwycony przez fraz
ę

catch(OutOfMemoryError e)


Należy zwrócić uwagę, że tuż po rozpatrzeniu fraz catch instrukcji try należącej do funkcji getStr zostanie
zniszczona zmienna strRef, a tu
ż przed rozpatrzeniem fraz catch instrukcji try należącej do funkcji fun zostanie
zniszczona zmienna fix.


________________________________________________________________________________________

Tablice


Tablica jest wektorem elementów, z których każdy jest takiego samego typu. Elementami tablicy mogą być
tylko zmienne typu podstawowego oraz odnośniki do tablic i obiektów.

Z każdym elementem tablicy jest związany indeks, określający pozycję elementu w obrębie tablicy. Indeks
pierwszego elementu ma wartość 0.

Przetwarzanie elementów tablicy odbywa się za pośrednictwem odnośnika identyfikującego tablicę. Jeśli nazwą
odnośnika jest ref, to wyrażenie

ref[exp]


w którym exp jest wyrażeniem całkowitym, jest nazwą elementu o indeksie równym wartości wyrażenia exp,
zaś wyrażenie

ref.length


jest nazwą zmiennej o wartości równej liczbie elementów tablicy.

Elementy podstawowe


Jeśli Type jest nazwą typu podstawowego (np. "int" albo "double"), to deklaracja

Type ref[]


oznajmia, że ref jest odnośnikiem do wektora elementów typu podstawowego, z których każdy jest typu
"Type".

Podczas opracowania takiej deklaracji, odnośnikowi ref jest przypisywane odniesienie puste. Odniesienie
niepuste można przypisać na dwa sposoby: albo za pomocą takiego inicjatora jak

= { 10, 20, 30 }


określającego wartości elementów, na przykład

int vec[] = { 10, 20, 30 };

albo za pomocą wyrażenia fabrykującego

new Type [size]


dostarczającego odnośnik do size-elementowego wektora, którego elementy mają wartości 0, na przykład

new int [width * height]

background image

51


A zatem, wykonanie następującej instrukcji

int arr[] = { 10, 20, 30 };


może być zastąpione wykonaniem instrukcji

int arr[];
arr = new int [3];
for(int i = 0; i < arr.length ; i++)
arr[i] = 10 * (i+1);


Przykład Sumowanie elementów

import java.io.IOException;

public
class Master {
public static void main(String args[])
throws IOException
{
int vec[] = { 10, 20, 30 };
int sum = 0;
for(int i = 0; i < vec.length ; i++)
sum = sum + vec[i];
System.out.println("Sum = " + sum);
System.in.read();
}
}

Wykonanie programu powoduje wyprowadzenie napisu

Sum = 60


Elementy odnośnikowe


Jeśli Type jest nazwą typu odnośnikowego (np. "String" albo "StringBuffer"), to deklaracja

Type ref[]


oznajmia, że ref jest odnośnikiem do wektora odnośników, z których każdy jest typu "Type".

Podczas opracowania takiej deklaracji, odnośnikowi ref jest przypisywane odniesienie puste. Odniesienie
niepuste można przypisać na dwa sposoby: albo za pomocą takiego inicjatora klamrowego jak

= { "Hello", "World" }


określającego wartości elementów, na przykład

String vec[] = { "Hello", "World" };

albo za pomocą wyrażenia fabrykującego

new Type [size]


dostarczającego odnośnik do size-elementowego wektora, którego elementy mają wartości null, na przykład

new String [rows * cols]



A zatem, wykonanie następującej instrukcji

String arr[] = { "Hello", "World" };

background image

52


może być zastąpione wykonaniem instrukcji

String arr[];
arr = new String [2];
arr[0] = "Hello";
arr[1] = "World";


Przykład Wyznaczenie średniej argumentów

import java.io.IOException;

public
class Master {
public static void main(String args[])
throws IOException, NumberFormatException
{
if(args.length == 0)
System.out.println("Please supply arguments");
System.in.read();
return;
}
int sum = 0, count = args.length;
for(int i = 0; i < count ; i++) {
String arg = args[i];
int val = Integer.parseInt(arg);
sum = sum + val;
}
System.out.println(sum / count);
System.in.read();
System.exit(0);
}
}


Jeśli program Master zostanie wywołany za pomocą polecenia

Master 10 20 30


to wykonanie programu spowoduje wyprowadzenie liczby 20.

Wykonanie instrukcji

String arg = args[i];


powoduje przypisanie odnośnikowi arg odniesienia do obiektu łańcuchowego zainicjowanego kolejnym
argumentem programu.

Wykonanie instrukcji

int val = Integer.parseInt(arg);


powoduje przekształcenie obiektu łańcuchowego w zmienną o wartości reprezentowanej przez łańcuch.

Ponieważ wymienione przekształcenie może spowodować wysłanie wyjątku

NumberFormatException


więc w nagłówku funkcji main, występuje wymagana fraza throws.

Reprezentowanie tablic

background image

53

Na rysunku Reprezentowanie tablic pokazano sposób przechowywania w pamięci operacyjnej tablic jedno- i
dwuwymiarowych o elementach typu podstawowego oraz tablic o elementach typu obiektowego "String",
zadeklarowanych i zainicjowanych za pomocą następujących instrukcji.

int vecI[] = { 10, 20, 30 };

int arrI[][] = { { 10, 20, 30 },
{ 40, 50, 60 } };

String vecS[] = { "Java", "is", "cool" };

String arrS[][] = { { "Java", "is", "much" },
{ "cooler", "than", "C++" } };

background image

54


Rysunek Reprezentowanie tablic

###

vecI

10

20

30

arrI

arrI[0]

10

20

30

arrI[1]

40

50

60

vecS

vecS[0]

Java

vecS[1]

is

vecS[2]

cool

arrS

arrS[0]

arrS[0][0]

Java

arrS[0][1]

is

arrS[0][2]

much

arrS[1]

arrS[1][0]

cooler

arrS[1][1]

than

arrS[1][2]

C++

###



_________________________________________________________________________________________

Obiekty


Obiektem jest zmienna strukturalna stanowiąca egzemplarz klasy. Podczas definiowania klasy wyszczególnia
się składniki jej obiektów: pola, konstruktory i metody.

Pole jest składnikiem, który określa właściwości zmiennej wchodzącej w skład obiektu.

Konstruktor jest składnikiem, który określa sposób inicjowania obiektu klasy.

Metoda jest składnikiem, który określa operację jaką można wykonać na obiekcie klasy.

Uwaga: Definicja klasy może ponadto zawierać definicje funkcji i zmiennych statycznych. Zmienne i funkcje
statyczne nie wchodzą w skład poszczególnych obiektów klasy, ale są wspólne wszystkim jej obiektom.

Przykład Klasa Cplx

class Cplx {
double re = 0, // pole
im = 0; // pole
static int count = 0; // zmienna
Cplx(int rePar, double imPar) // konstruktor
{
re = rePar;
im = imPar;
count++;
}
void outCplx() // metoda
{

background image

55

System.out.println("(" + re + "," + im + ")");
}
static void outCount() // funkcja
{
System.out.println(count);
}
}


Klasa Cplx opisuje rodzinę obiektów reprezentujących liczby zespolone o części rzeczywistej re i urojonej im.

W każdym obiekcie klasy Cplx występują zmienne opisane przez pola re i im oraz jeden konstruktor i jedna
metoda.

Zmienna count oraz funkcja outCount jest wspólna wszystkim obiektom klasy Cplx. Składniki te występują poza
wszystkimi obiektami klasy.

Tworzenie obiektów


Utworzenie obiektu odbywa się za pomocą wyrażenia fabrykującego

new Class(arg, arg, arg)


w którym Class jest nazwą klasy obiektu, a każde arg jest argumentem kojarzonym z odpowiadającym mu
parametrem konstruktora obiektu.

Bezpośrednio po utworzeniu obiektu jest wywoływany konstruktor klasy Class. W jego ciele jest dostępny
odnośnik this, identyfikujący właśnie utworzony obiekt. Rezultatem operacji new jest odnośnik zainicjowany
kopią odnośnika this.

Jeśli w klasie obiektu zdefiniowano pole field, to nazwą zmiennej wchodzącej w skład sfabrykowanego
obiektu, a opisanej przez to pole, jest this.field. Nazwę tę można uprościć do field tylko wówczas, gdy w
miejscu jej wystąpienia, deklaracja pola jest widoczna (nie przesłonięta przez inną deklarację).

Uwaga: Jeśli klasa Class nie zawiera definicji ani jednego konstruktora, to jest niejawnie uzupełniana definicją
konstruktora domyślnego

public Class()

{

super();

}



Przykład Odnośnik this

import java.io.IOException;

class Cplx {
double re = 0, im = 0;
Cplx(int re, double im)
{
this.re = re;
this.im = im;
}
void out()
{
System.out.println("(" + re + "," + im + ")");
}
}

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

background image

56

{
Cplx ref = new Cplx(3, 4);
ref.out(); // (3,4)
System.in.read();
}
}


Opracowanie wyrażenia fabrykującego

new Cplx(3, 4)


powoduje utworzenie obiektu składającego się z 2 zmiennych typu "double", utworzenia odnośnika this
zainicjowanego odniesieniem do obiektu, a nast
ępnie wywołania konstruktora klasy Cplx z argumentami 3 i 4.

Ponieważ odnośnik this identyfikuje właśnie utworzony obiekt, więc this.re jest nazwą zmiennej reprezentującej
jego cz
ęść rzeczywistą, a this.im jest nazwą zmiennej reprezentującej jego część urojoną.

W ciele konstruktora zmiennym tym są przypisywane dane o wartościach 3 i 4, określonych przez argumenty
konstruktora.

Ponieważ w miejscu wystąpienia napisu this.re nie jest widoczna deklaracja pola re (gdyż jest przesłonięta
przez deklaracj
ę parametru re), więc uproszczenie this.re do re nie jest dozwolone.

Niszczenie obiektów


Niszczenie obiektów jest automatyczne. Zniszczenie obiektu odbywa się nie wcześniej, niż w chwili, gdy
istnieje pewność, że ani jednemu odnośnikowi nie jest przypisane odniesienie do obiektu.

Przykład Niszczenie obiektów

import java.io.IOException;

class Master {
public static void main(String args[])
throws IOException
{
{
Cplx ref = new Cplx(1, 2);
}
Cplx ref = new Cplx(3, 4);
ref = new Cplx(5, 6);
ref.out(); // (5, 6)
ref = null;
ref.out(); // bł

ą

d (NullPointerException)

System.out.println("Done");
System.in.read();
}
}


Ponieważ po wykonaniu instrukcji

{
Cplx ref = new Cplx(1, 2);
}


nie istnieje już odnośnik identyfikujący obiekt Cplx(1, 2), więc w dowolnej chwili obiekt ten może być
zniszczony.

Ponieważ po wykonaniu instrukcji

ref = new Cplx(5, 6);

background image

57

nie istnieje już odnośnik identyfikujący obiekt Cplx(3, 4), więc w dowolnej chwili obiekt ten może być
zniszczony.

Analogicznie, po wykonaniu instrukcji

ref = null;


może być zniszczony obiekt Cplx(5, 6).

W miejscu wykonania instrukcji

ref.out();


zostanie wysłany wyjątek klasy NullPointerException. Ponieważ wyjątek nie jest obsłużony (brak instrukcji
try), wi
ęc nastąpi zakończenie wykonywania programu, bez wykonania instrukcji

System.out.println("Done");


Fabrykowanie obiektów


Obiekty można fabrykować za pomocą operacji new oraz za pomocą metod fabrykujących, jak na przykład
newInstance. Metoda newInstance jest wywoływana na rzecz obiektu opisującego klasę, której obiekt ma
zostać sfabrykowany.

public Class getClass()

Metoda zwraca odnośnik do obiektu opisującego klasę tego obiektu, na rzecz którego ją wywołano.

public static Class forName(String className)
throws ClassNotFoundException

Metoda zwraca odnośnik do obiektu opisującego klasę o nazwie identyfikowanej przez className.

public Object newInstance()
throws InstantiationException,
IllegalAccessException

Metoda zwraca odnośnik do obiektu klasy opisanej przez obiekt klasy Class na rzecz którego ją wywołano.
Sfabrykowany obiekt jest inicjowany przez konstruktor domyślny.


Przykład Fabrykowanie obiektów

import java.io.IOException;

public
class Master {
public static void main(String args[])
throws IOException,
ClassNotFoundException,
InstantiationException,
IllegalAccessException
{
Class childObj = Class.forName("Child");
Object child = childObj.newInstance();
Child isa = (Child)child;
System.out.println(isa.getName() + " is " +
isa.getAge());
System.in.read();
}
}

class Child {
private String name = "Isabel";
private int age;

background image

58

public Child()
{
age = 13;
}
public String getName()
{
return name;
}
public int getAge()
{
return age;
}
}


Wykonanie programu powoduje wyprowadzenie napisu

Isabel


Wywoływanie procedur


Wywołaniami procedur są wywołania konstruktorów, metod i funkcji. Konstruktor wywołuje się na rzecz
obiektu utworzonego podczas opracowania wyrażenia fabrykującego albo z wnętrza innego konstruktora, na
rzecz właśnie inicjowanego obiektu. Metodę wywołuje się na rzecz obiektu identyfikowanego przez odnośnik.
Funkcję wywołuje się w oderwaniu od jakiegokolwiek obiektu.

Jeśli w ciele klasy zdefiniowano więcej niż jedną procedurę o takiej samej nazwie, to jest wywoływana ta, do
której parametrów najlepiej pasują podane argumenty wywołania.

W szczególności, jeśli klasa zawiera więcej niż jeden konstruktor, to wywołuje się ten, którego liczba
parametrów jest równa liczbie argumentów wywołania.

Na przykład

String isa = "Isabel";
System.out.print(isa.toUpperCase()); // ISABEL


Metodę toUpperCase wywołano na rzecz obiektu identyfikowanego przez odnośnik isa.

Inicjowanie parametrów i rezultatów


W ramach wywołania procedury odbywa się kojarzenie jej parametrów z argumentami wywołania. Każde
skojarzenie polega na zainicjowaniu parametru wartością kojarzonego z nim argumentu.

Jeśli rezultat procedury jest typu różnego od "void", to w chwili wykonania instrukcji powrotu, inicjuje się go
wartością wyrażenia podanego w tej instrukcji.

Uwaga: Parametr jest lokalną zmienną procedury, a zmiana wartości parametru nie ma żadnego wpływu na
wartość skojarzonego z nim argumentu.

Przykład Inicjowanie parametrów i rezultatu

import java.io.IOException;

class Child {
String name;
int age = 0;
Child(String name, int age)
{
this.name = name;
this.age = age;
}

background image

59

Child(String name)
{
this.name = name;
}
void setAge(int age)
{
this.age = age;
}
int getAge()
{
return age;
}
void show()
{
if(age != 0)
System.out.println(name + " is " + age);
else
System.out.println(name + "! How old are you?");
}
}

public
class Master {
public static void main(String args[])
throws IOException
{
Child tom = new Child("Tom");
tom.show();
tom.setAge(12);
tom.show();
Child isa = new Child("Isa", 13);
isa.show();
System.in.read();
}
}


Podczas wykonania instrukcji

tom.setAge(12);


następuje skojarzenie parametru age metody setAge z argumentem 12, a w ramach tej czynności niejawne
wykonanie instrukcji

age = 12


Podczas wykonania instrukcji

return age;


następuje skojarzenie rezultatu metody getAge ze zmienną reprezentowaną przez wyrażenie age, a w ramach tej
czynno
ści niejawne wykonanie instrukcji

res = age;


w której res reprezentuje rezultat funkcji.

Wykonanie programu powoduje wyprowadzenie napisu

Tom! How old are you?

Tom is 12

Isa is 13


Wywoływanie konstruktorów


Konstruktor jest wywoływany podczas opracowywania wyrażenia fabrykującego

background image

60

new Class(arg, arg, ... , arg)


w którym Class jest nazwa klasy, a każde arg jest argumentem wywołania.

Ponadto, ale tylko w ciele konstruktora i jako jego pierwsza instrukcja wyrażeniowa, mogą być użyte
wywołania

this(arg, arg, ... , arg)


oraz

super(arg, arg, ... , arg)


Pierwsze z nich jest wówczas wywołaniem innego konstruktora tej samej klasy, a drugie wywołaniem
konstruktora jej nadklasy.

Przykład Wywoływanie konstruktorów

import java.io.IOException;

class Person {
String name;
int age;
Person(String name)
{
this.name = name;
age = -1; // this.age = -1;
}
Person(String name, int age)
{
this.name = name;
this.age = age;
}
}

class Woman extends Person {
Person husband = null;
Woman(String name, int age)
{
super(name, age);
}
Woman(String name, int age, Person husband)
{
this(name, age);
this.husband = husband;
}
}

class Master {
public static void main(String args[])
throws IOException
{
Person john = new Person("John Smith", 40);
Woman mary = new Woman("Mary Smith", 30, john);
System.in.read();
}
}


Wykonanie instrukcji

super(name, age);


powoduje wywołanie dwuparametrowego konstruktora klasy Person.

Wykonanie instrukcji

this(name, age);

background image

61


powoduje wywołanie dwuparametrowego konstruktora klasy Woman.

Wywoływanie metod


Wywołanie metody ma postać

ref.name(arg, arg, ... , arg)

albo

super.name(arg, arg, ... , arg)


w której ref jest odnośnikiem identyfikującym obiekt, name jest nazwą metody widocznej w klasie obiektu,
słowo kluczowe super określa, że chodzi o metodę nadklasy, a każde arg jest argumentem wywołania.

Uwaga: Jeśli ref jest odnośnikiem this, a w miejscu wywołania jest widoczna definicja metody name, to
wywołanie można uprościć do

name(arg, arg, ..., arg)


Takie samo uproszczenie można zastosować do wywołania ze słowem kluczowym super.

W chwili wywołania metody kojarzy się każdy z jej parametrów z odpowiadającym mu argumentem, a
następnie tworzy odnośnik this i inicjuje go odniesieniem do obiektu na rzecz którego odbywa się wywołanie.
Po wykonaniu tych czynności wykonuje się ciało metody name.

Uwaga: Jeśli odnośnik ref jest typu "Class", to w klasie Class musi być widoczna metoda name. Podczas
wykonania programu typ odnośnika jest nieistotny: wywołuje się metodę name widoczną w klasie obiektu
identyfikowanego przez odniesienie przypisane odnośnikowi ref.

Przykład Wywoływanie metod

import java.io.IOException;

class Person {
String name;
int age;
Person(String name, int age)
{
this.name = name;
this.age = age;
}
void setAge(int age)
{
System.out.println("In Person.setAge");
this.age = age;
}
void showAge()
{
System.out.println(age);
}
}

class Woman extends Person {
Person husband = null;
Woman(String name, int age, Person husband)
{
super(name, age);
this.husband = husband;
}
void setAge(int age)
{
System.out.println("In Woman.setAge");
super.setAge(age);
}

background image

62

}

class Master {
public static void main(String args[])
throws IOException
{
Woman mary = new Woman("Mary Smith", 30, null);
Person ref = mary;
ref.setAge(32);
ref.showAge(); // 32
System.in.read();
}
}


Odnośnik mary jest typu "Woman". Przypisano mu odniesienie do obiektu klasy Woman.

Odnośnik Person jest typu "Person". Przypisano mu odniesienie do obiektu klasy Woman.

Ponieważ wywołanie metody setAge odbywa się na rzecz obiektu mary klasy Woman, a nie na rzecz obiektu
klasy Person, wi
ęc wykonanie instrukcji

ref.setAge(32);


powoduje wywołanie metody setAge klasy Woman, a nie metody setAge klasy Person.

Natomiast wykonanie w ciele metody Woman.setAge instrukcji

super.setAge(age);


powoduje wywołanie metody Person.setAge.

Wykonanie programu powoduje wyprowadzenie napisu

In Woman.setAge

In Person.setage

32


Wywoływanie funkcji


Wywołanie funkcji klasy Class ma postać

Class.name(arg, arg, ..., arg)


w której name jest nazwą funkcji widocznej w klasie Class, a każde arg jest argumentem wywołania. Jeśli
wywołanie znajduje się w zakresie deklaracji funkcji name, to można je uprościć do

name(arg, arg, ..., arg)


W chwili wywołania funkcji kojarzy się każdy z jej parametrów z odpowiadającym mu argumentem, a
następnie wykonuje ciało funkcji name.

Dla funkcji nie tworzy się odnośnika this, a więc w każdym zawartym w niej odwołaniu do niestatycznego
składnika klasy musi wystąpić jawny odnośnik.

Uwaga: Z powodu braku odnośnika this, odwołanie do statycznego składnika klasy jest zazwyczaj
kwalifikowane nazwą klasy. Jest to zbyteczne jeśli w punkcie odwołania składnik jest widoczny.

Przykład Wywoływanie funkcji

import java.io.IOException;

public

background image

63

class Master {
static void printLine(String line)
{
System.out.println(line);
}
void printStars(int count)
{
for(int i = 1; i <= count ; i++)
System.out.print("*");
System.out.println();
}
public static void main(String args[])
throws IOException
{
String name = "John";
Master.printLine(name); // printLine(name)
Master refObj = new Master();
refObj.printStars(3);
printLine(name); // Master.printLine(name)
System.in.read();
}
}

Ponieważ wywołanie

Master.printLine(name)


znajduje się w zakresie deklaracji statycznej funkcji printLine, więc można je uprościć do

printLine(name)


Wywołanie

refObj.printStars(3)


nie może być uproszczone do

printStars(3)


ani zastąpione wywołaniem

Master.printStars(3)


ponieważ składnik printStars nie jest statyczny.

Wykonanie programu powoduje wyprowadzenie napisu

John

***

John

Wywołanie rekurencyjne


Wywołanie metody i funkcji może być rekurencyjne. Rekurencja polega na tym, że jeszcze przed
zakończeniem wykonania pewnej procedury, jest ona wywoływana ponownie. Taki sposób postępowania może
znakomicie uprościć zakodowanie procedury, jednak w pewnych warunkach może spowodować wydłużenie
czasu jej wykonywania lub zwiększenie ilości wymaganych przez nią zasobów.

Przykład Wyznaczenie potęgi

Rozwiązanie iteracyjne

int powerOf2(int pow)
{

background image

64

int power = 1;
while(pow-- != 0)
power += power;
return power;
}



Rozwiązanie rekurencyjne

int powerOf2(int pow)
{
if(pow == 0)
return 1;
else {
int halfPower = powerOf2(pow-1);
return halfPower + halfPower;
}
}


Przetwarzanie obiektów


Przetwarzanie obiektów polega na ich tworzeniu oraz na wywoływaniu metod na rzecz obiektów.
Przetwarzanie obiektów za pomocą funkcji wymaga dostarczenia im odnośników do obiektów. W ciele funkcji
taki odnośnik może być użyty do wywołania metody na rzecz obiektu.

Następujący program ilustruje zasadę utworzenia stosu. Elementami stosu są obiekty klasy Item zawierające
zmienne typu "int".

Uwaga: Stosem jest kolekcja, do której można dokładać elementy, ale z której można je wyjmować tylko w
kolejności odwrotnej do dokładania.

Przykład Projektowanie stosu

import java.io.IOException;

class Item {
Item refNext = null;
int value;
Item(int val)
{
value = val;
}
}

class Stack {
Item refHead = null;
void push(int val)
{
Item refNew = new Item(val);
refNew.refNext = refHead;
refHead = refNew;
}
int pop()
{
if(refHead == null)
throw new RuntimeException();
else {
int res = refHead.value;
refHead = refHead.refNext;
return res;
}
}
}

public
class Master {
public static void main(String args[])

background image

65

throws IOException
{
int vec[] = { 10, 20, 30, 40 };
Stack stackA = new Stack(),
stackB = new Stack();
for(int i = 0; i < vec.length ; i++)
stackA.push(vec[i]);
for(int i = 0; i < vec.length ; i++) {
int val = stackA.pop();
System.out.print(val + " ");
stackB.push(val);
}
System.out.println();
for(int i = 0; i < vec.length ; i++)
System.out.print(stackB.pop() + " ");
System.out.println();
System.in.read();
}
}

Wykonanie programu powoduje wyprowadzenie napisu

40 30 20 10

10 20 30 40


Przetwarzanie klas


Warunkiem utworzenia obiektu jest istnienie definicji jego klasy. Definicje klas są ładowane dynamicznie w
chwili gdy po raz pierwszy zaistnieje potrzeba utworzenia obiektu. Ponieważ ładowanie może odbywać się z
sieci rozległej, co jest obarczone ryzykiem załadowania klasy naruszającej bezpieczeństwo systemu ładującego,
stosuje się szereg zabezpieczeń eliminujących to zagrożenie.

W celu wdrożenia zabezpieczeń ładowanie klas odbywa się pod nadzorem zarządcy ładowania (Class Loader),
a wykonywanie B-kodu pod nadzorem zarządcy ochrony (Security Manager).

Zarządca ładowania dokonuje weryfikacji klasy oraz sprawdza poprawność jej powiązań z uprzednio
załadowanymi klasami.

Zarządca ochrony przeprowadza dynamiczną kontrolę uprawnień programu do wykonywania niebezpiecznych
dla Systemu czynności (np. apletowi zezwala na wykonywanie operacji wejścia-wyjścia tylko w odniesieniu do
komputera z którego załadowano jego klasę).

Oba te mechanizmy łącznie uniemożliwiają wprowadzenie do systemu programów, które mogłyby wymknąć się
spod jego kontroli.

Uwaga: Zainstalowanie zarządcy ochrony jest wykonywane jednokrotnie dla danej aplikacji. Jeśli jest nią
przeglądarka (np. Netscape), to nie może być zmienione przez wrogi aplet ładowany podczas nawigowania po
stronach WWW.

Weryfikacja


Weryfikacja polega na sprawdzeniu, czy B-kod klasy jest właściwie uformowany, czy zawiera odpowiednią
tablicę symboli oraz czy spełnia wymagania syntaktyczne określone w specyfikacji języka, w tym czy zawiera
wyłącznie dopuszczalne kody operacji, przeniesienia sterowania tylko do początku instrukcji oraz właściwe
sygnatury procedur.

Przygotowanie

background image

66

Przygotowanie polega na przydzieleniu pamięci dla zmiennych statycznych oraz na przypisaniu im wartości
domyślnych (np. odnośnikom wartości null, a zmiennym arytmetycznym wartości 0).

Związanie


Związanie polega na zastąpieniu odwołań symbolicznych do klas, interfejsów, zmiennych i pól odwołaniami
bezpośrednimi, wykorzystującymi adresy Maszyny Wirtualnej. Podczas związywania bada się również, czy
załadowana klasa zawiera wszystkie wymagane składniki oraz czy są respektowane prawa dostępu do
składników.

Zainicjowanie


Zainicjowanie składa się z przypisania wartości początkowych zmiennym statycznym klasy oraz z wykonania
kodu jej inicjatorów.

Uwaga: Zainicjowanie klasy jest wykonywane dopiero po zweryfikowaniu i załadowaniu nadklasy danej
klasy. Opracowywanie inicjatorów zmiennych statycznych oraz inicjatorów klasy odbywa się w kolejności ich
wystąpienia w klasie.

Zmienne statyczne

Zmienną statyczną jest zmienna zadeklarowana ze specyfikatorem static. Taka zmienna nie jest elementem
obiektu, ale jest wspólna wszystkim obiektom klasy. Istnieje nawet wówczas gdy nie utworzono ani jednego
obiektu jej klasy.

Na przykład

class Master {
static int var = 12;
// ...
}


Zmienna statyczna var jest inicjowana podczas inicjowania klasy.

Inicjatory klasy

Inicjatorem klasy jest fraza

static Block


w której Block jest instrukcją grupującą.

Opracowanie inicjatora składa się z wykonania zawartej w niej instrukcji grupującej.

Na przykład

import java.io.IOException;

class Master {
public static void main(String args[])
throws IOException
{
System.in.read();
}
static int var;
static {
var = 12;
System.out.println("Variable var initialized");
}
// ...

background image

67

static {
var++;
System.out.println("Variable var reinitialized");
}
}


Wykonanie programu powoduje wyprowadzenie napisu

Variable var initialized

Variable var reinitialized


Aktywne użycie


Klasa, której załadowanie jest niezbędne do dalszego wykonania programu jest uznawana za aktywnie użytą.
Strategia ograniczenia się do ładowania klas aktywnie użytych, dobrze nadaje się do programowania w
Internecie, ponieważ wyklucza ładowanie klas, które podczas pewnych wykonań programu są dla niego
całkowicie zbędne.

Uwaga: Za poprawną uznaje się implementację, w której wszystkie związania klas, są dokonywane jeszcze
przed podjęciem wykonywania programu.

Przykład Aktywne użycie

import java.io.IOException;

class Point {
int x, y;
static int count = 0;
static {
System.out.println("Point initialized");
}
Point()
{
x = y = -1;
}
}

class Point3d extends Point {
static {
System.out.println("Point3d initialized");
}
int z;
}

public
class Master {
public static void main(String args[])
throws IOException
{
Point3d spacePoint;
spacePoint = new Point3d();
// ...
System.in.read();
}
}



Wykonanie instrukcji

Point3d spacePoint;


powoduje utworzenie zmiennej spacePoint, ale nie wymaga załadowania klasy Point3d, ponieważ nie istnieje
jeszcze potrzeba utworzenia obiektu tej klasy.

Opracowanie operacji

background image

68

new Point3d()


wymaga załadowania klasy Point3d.

W ramach ładowania klasy Point3d jest wykonywana jej weryfikacja, przygotowanie i związanie. Tuż przed
zainicjowaniem klasy jest ładowana jej nadklasa Point.

W ramach ładowania klasy Point są wykonywane identyczne czynności jak dla klasy Point3D, ale przed jej
zainicjowaniem jest ładowana i inicjowana jej nadklasa Object.

Po załadowaniu i zainicjowaniu klasy Object jest inicjowana klasa Point. Powoduje to przypisanie zmiennej
var warto
ści początkowej 0 oraz wykonanie inicjatora

static {
System.out.println("Point initialized");
}


W analogiczny sposób jest inicjowana klasa Point3D.

Wykonanie programu powoduje wyprowadzenie napisu

Point initialized

Point3d initialized



_________________________________________________________________________________________

Strumienie


Strumieniem wejściowym jest sekwencja danych pochodząca z pliku, z pamięci operacyjnej, albo z urządzenia.
Dane mają zazwyczaj postać sekwencji bajtów, ale za pomocą metod takich predefiniowanych klas
wejściowych jak StreamTokenizer i StringTokenizer mogą być interpretowane jako ciągi leksemów.

Strumieniem wyjściowym jest sekwencja danych wysyłana do pliku, do pamięci operacyjnej, albo do
urządzenia. Wysyłane dane mają postać sekwencji bajtów, ale za pomocą predefiniowanej klasy wyjściowej
PrintStream mogą być przekształcane w ciągi znaków.

Klasa FileInputStream


Klasa FileInputStream umożliwia wykonywanie operacji wprowadzania danych na obiektach
reprezentujących strumienie wejściowe.

Wykonanie operacji

new FileInputStream(fileName)


w której fileName jest wyrażeniem typu "String" określającym nazwę pliku, powoduje utworzenie obiektu
reprezentującego strumień związany z plikiem o podanej nazwie.

Uwaga: Podczas wykonania operacji może być wysłany wyjątek klasy FileNotFoundException albo
IOException.

Klasa FileOutputStream


Klasa FileOutputStream umożliwia wykonywanie operacji wyprowadzania danych na obiektach
reprezentujących strumienie wyjściowe.

background image

69

Wykonanie operacji

new FileOutputStream(fileName)


w której fileName jest wyrażeniem typu "String" określającym nazwę pliku, powoduje utworzenie obiektu
reprezentującego strumień związany z plikiem o podanej nazwie.

Uwaga: Podczas wykonania operacji może być wysłany wyjątek klasy IOException.

Przykład Kopiowanie pliku

import java.io.*;

public
class Master {
static String fileName = "c:\\autoexec.bat";
public static void main(String args[])
throws IOException
{
FileInputStream inp;
try {
inp = new FileInputStream(fileName);
}
catch(FileNotFoundException e) {
System.out.println("File " + fileName +
" not found");
System.in.read();
return;
}
FileOutputStream out =
new FileOutputStream("auto.bat");
int chr, count = 0;
while((chr = inp.read()) != -1) {
out.write(chr);
count++;
}
System.out.println("Done! " + count +
"chars copied");
System.in.read();
}
}


Wykonanie programu powoduje skopiowanie pliku c:\autoexec.bat do pliku auto.bat. Plik auto.bat zostanie
umieszczony w katalogu bie
żącym, to jest w tym katalogu, z którego pochodzi B-kod aplikacji.

Badanie końca pliku identyfikowanego przez odnośnik inp należy wykonać przez sprawdzenie, czy
wprowadzony kod znaku jest ró
żny od -1 (a nie przez sprawdzenie, czy został wysłany wyjątek klasy
IOException). Z tego powodu zmienna chr musi by
ć typu "int".

Klasa StreamTokenizer


Klasa StreamTokenizer umożliwia wprowadzanie leksemów ze strumienia wejściowego skojarzonego z
plikiem.

Wykonanie operacji

new StreamTokenizer(fileStream)


w której fileStream jest wyrażeniem typu "FileInputStream", powoduje utworzenie obiektu, za pośrednictwem
którego można ze strumienia pobierać kolejne leksemy.

Uwaga: W celu przygotowania się do pobierania leksemów ze standardowego strumienia wejściowego
(domyślnie: z klawiatury) należy wykonać operację

background image

70

new StreamTokenizer(System.in)


a w celu przekazania informacji o końcu strumienia wejściowego nacisnąć klawisz Ctrl-Z.

public int nextToken()

Metoda dostarcza symbol albo kod kolejnego leksemu. Jeśli dostarczy symbol TT_NUMBER, to leksem jest
liczbą umieszczoną w zmiennej nval. Jeśli dostarczy symbol TT_WORD, to leksem jest słowem, odnośnik do
którego jest umieszczony w zmiennej sval. Symbole TT_EOF i TT_EOL oznaczają odpowiednio: koniec pliku
i koniec wiersza.

public void eolIsSignificant(boolean itIs)

Metoda określa, że mają być rozpoznawane i dostarczane znaki końca wiersza (w przeciwnym razie znak
nowego wiersza jest traktowany tak jak inne odstępy).

public void parseNumbers()

Metoda określa, że mają być rozpoznawane i dostarczane liczby rzeczywiste (to jest napisy składające się z
ewentualnego znaku minus oraz cyfr i kropki (np. -4.25).

public void quoteChar(int quote)

Metoda określa kod znaku, który ma być użyty jako obustronny ogranicznik łańcucha znaków zawierającego
odstępy (domyślnie nie ma takiego ogranicznika). Po wprowadzeniu takiego łańcucha metoda nextToken
dostarcza kod ogranicznika, a odnośnik do łańcucha jest przypisywany sval.


Przykład Wprowadzanie leksemów

import java.io.*;

public
class Master {
static String fileName = "c:\\source";
public static void main(String args[])
throws IOException
{
FileInputStream inp;
try {
inp = new FileInputStream(fileName);
}
catch(FileNotFoundException e) {
System.out.println("File " + fileName +
" not found");
System.in.read();
return;
}
StreamTokenizer tokens;
tokens = new StreamTokenizer(inp);
int what,
EOF = StreamTokenizer.TT_EOF,
EOL = StreamTokenizer.TT_EOL,
NUMBER = StreamTokenizer.TT_NUMBER,
WORD = StreamTokenizer.TT_WORD;
tokens.eolIsSignificant(true);
tokens.parseNumbers();
tokens.quoteChar('#');
int lineNo = 1;
while((what = tokens.nextToken()) != EOF) {
if(what == EOL)
System.out.println("\nEnd of line #" +
lineNo++ + '\n');
else if(what == NUMBER)
System.out.print(tokens.nval + " ");
else if(what == WORD)
System.out.print(tokens.sval + " ");
else if(what == '#')
System.out.print(tokens.sval + " ")

background image

71

else
System.out.print(what + " ");
}
System.in.read();
}
}


Program analizuje podany plik znakowy, a następnie wyprowadza zawarte w nim leksemy.

Jeśli plik c:\source zawiera napis

12 Isa -4.2e2 #John & Mary# 127

Isa#bell#13 $%&


to zostanie wyprowadzony napis

12 Isa -4.2 e2 John & Mary 127

End of line #1

Isa bell 13 $ % &

End of line #2


Klasa StringTokenizer


Klasa StringTokenizer umożliwia wprowadzanie leksemów z obiektu łańcuchowego.

Wykonanie operacji

new StringTokenizer(string)


w której string jest wyrażeniem typu "String", powoduje utworzenie obiektu, za pośrednictwem którego można
z łańcucha pobierać kolejne leksemy.

public StringTokenizer(String str, String dlm)

Argumentami konstruktora są: łańcuch dzielony na leksemy (str) oraz łańcuch składający się z ograniczników
leksemów (dlm).

public int countTokens()

Metoda dostarcza liczbę leksemów w łańcuchu.

public boolean hasMoreTokens()

Metoda dostarcza wartość true, jeśli łańcuch zawiera jeszcze co najmniej jeden leksem.

public String nextToken() throws NoSuchElementException

Metoda dostarcza kolejny leksem.


Przykład Sortowanie imion

import java.io.*;
import java.util.*;

public
class Master {
static String string = " Jan Ewa Iza ";
static String names[];
public static void main(String args[])
throws IOException
{
StringTokenizer tokens;
tokens = new StringTokenizer(string, " \t\n");
int count = tokens.countTokens();
names = new String[count];

background image

72

int pos = 0;
while(tokens.hasMoreTokens()) {
Object obj = tokens.nextToken();
String str = (String)obj;
names[pos++] = str;
}
show(names);
sort(names);
show(names);
System.in.read();
}
static void sort(String names[])
{
boolean sorted = false;
while(!sorted) {
for(int i = 0; i < names.length-1 ; i++) {
sorted = true;
String one = names[i],
two = names[i+1];
if(one.compareTo(two) > 0) {
names[i] = two;
names[i+1] = one;
sorted = false;
}
}
}
}
static void show(String names[])
{
for(int i = 0; i < names.length ; i++)
System.out.print(names[i] + " ");
System.out.println();
}
}


Ogranicznikami leksemów są: spacja, tabulacja i koniec wiersza.

Wykonanie programu powoduje wyprowadzenie napisu

Jan Ewa Iza

Ewa Iza Jan


Klasa PrintStream


Klasa PrintStream umożliwia wyprowadzanie ciągów znaków do strumienia wyjściowego.

Wykonanie operacji

new PrintStream(stream)


w której stream jest odnośnikiem do obiektu klasy FileOutputStream identyfikującego plik wyjściowy,
powoduje utworzenie obiektu, za pośrednictwem którego można do tego pliku wysyłać ciągi znaków.

public void print(boolean b)
public void print(char c)
public void print(int i)
public void print(long l)
public void print(float f)
public void print(double d)
public void print(String s)
public void print(Object o)

Metoda wyprowadza wartość typu podstawowego albo obiektowego po przetworzeniu jej na ciąg znaków.

background image

73

Uwaga: Zastąpienie identyfikatora print identyfikatorem println powoduje dodatkowo wyprowadzenie znaku
końca wiersza.

public void println()

Metoda wyprowadza znak końca wiersza.

public void flush()

Metoda wymiata bufor wyjściowy.

public void close()

Metoda zamyka strumień.

Uwaga: Zamknięcie strumienia jest wykonywane domyślnie w chwili zakończenia wykonywania programu.


Przykład Utworzenie pliku tekstowego

import java.io.*;

public
class Master {
public static void main(String args[])
throws IOException
{
PrintStream out = new PrintStream(
new FileOutputStream(
"c:\\auto.bat"
)
);
out.println("c:\\mouse.exe /q");
out.println("nc");
out.close(); // zb

ę

dne

System.in.read();
}
}


Wykonanie programu powoduje utworzenie pliku c:\auto.bat zawierającego napis

c:\mouse.exe /q

nc


Klasa plikowa


Równie często jak przesyłanie danych, występuje potrzeba wykonania operacji na pliku albo katalogu. Do tego
celu doskonale nadaje się klasa plikowa File.

Metody klasy File umożliwiają realizowanie zapytań o właściwości plików i katalogów. Jedna z metod
umożliwia usunięcie pliku, ale żadna nie pozwala na usunięcie katalogu.

Uwaga: Jeśli nazwa pliku zawiera separatory "\" albo "/", to niezależnie od użytego systemu operacyjnego,
każdy z nich jest uznawany za poprawny.

public File(String name)

Konstruktor tworzy obiekt plikowy opisujący plik albo katalog o podanej nazwie.

public boolean exists()

Metoda sprawdza, czy istnieje plik albo katalog identyfikowany przez obiekt plikowy.

public boolean isFile()

Metoda sprawdza, czy obiekt plikowy identyfikuje plik.

background image

74

public boolean isDirectory()

Metoda sprawdza, czy obiekt plikowy identyfikuje katalog.

public boolean canRead()

Metoda sprawdza, czy plik albo katalog może być odczytany.

public boolean canWrite()

Metoda sprawdza, czy do pliku albo katalogu można dokonać zapisu.

public boolena mkDir()

Metoda tworzy katalog (wraz z nadkatalogami) o nazwie określonej przez obiekt plikowy.

public boolean renameTo(File trg)

Metoda zmienia nazwę pliku albo katalogu na podaną.

public boolean delete()

Metoda usuwa plik.


Przykład Klasa plikowa

import java.io.*;

public
class Master {
public static void main(String args[])
throws IOException
{
String path, name;
if(args.length != 2) {
System.out.println("Please supply Path & Name");
return;
}
path = args[0];
name = args[1];
String pathName = path + name;
File file = new File(pathName);
if(!file.exists())
throw new FileNotFoundException(pathName);
if(file.isFile()) {
if(!file.canRead()) {
System.out.println(
"File " + pathName + " is not readable"
);
return;
} else {
FileInputStream source =
new FileInputStream(pathName);
byte buffer[] = new byte[1024];
while(true) {
int byteCount = source.read(buffer);
if(byteCount == -1)
break;
System.out.write(buffer, 0, byteCount);
}
}
} else {
System.out.println(
"Directory " + pathName + " contains\n");
String list[] = file.list();
for(int i = 0; i < list.length ; i++) {
String fileName = list[i];
System.out.print(fileName);
File fullName = new File(pathName + "/" + fileName);
if(fullName.isDirectory())
System.out.print("\t(directory)");
System.out.println();
}
}

background image

75

System.in.read();
}
}


Wykonanie programu powoduje wyprowadzenie zawartości podanego pliku albo katalogu.

Jeśli program zostanie wywołany z parametrami określającymi ścieżkę (np. c:\) i nazwę pliku albo katalogu
(np. psp311), to wyprowadzi na przykład napis

Directory c:\psp311 contains

PAINT311.ARJ

PAINT311.A01

PAINT311.A02

Paint311 (directory)

background image

76

Część IV

Programowanie obiektowe




Programowanie obiektowe polega na modelowaniu zjawisk, przedmiotów i pojęć za pomocą obiektów klas. Od
strony kodowania programowanie obiektowe polega na łącznym wykorzystaniu hermetyzacji, dziedziczenia i
polimorfizmu.

Podczas analizy problemu, który ma być rozwiązany techniką programowania obiektowego wyodrębnia się
rzeczowniki opisujące modelowane zagadnienie, a następnie, dla każdego z nich, tworzy odrębną definicję
klasy
.

W szczególności, jeśli zamierza się modelować związki między pojazdami, samochodami i silnikami, zaleca się
utworzyć definicje klas Pojazd (Vehicle), Samochód (Car) i Silnik (Motor), a następnie, posługując się
hermetyzacją, dziedziczeniem i polimorfizmem, wyrazić wymagane powiązania za pomocą zestawu definicji
klas.

Przykład Projektowanie klas

import java.io.IOException;

public
class Master {
public static void main(String args[])
throws IOException
{
Vehicle vehicle = new Car("Bugatti", 400);
String maker = vehicle.theMaker();
int cuInch = vehicle.getCU();
System.out.println(maker + " " + cuInch);
// ...
System.in.read();
}
}

abstract
class Vehicle {
abstract public String theMaker();
abstract public int getCU();
// ...
}

class Car extends Vehicle {
private String maker;
private Motor motor;
public Car(String maker, int cuInch)
{
this.maker = maker;
motor = new Motor(cuInch);
}
public String theMaker()
{
return maker;
}
public int getCU()
{
return motor.getCU();
}
// ...
}

class Motor {
private int cuInch;
public Motor(int cuInch)
{
this.cuInch = cuInch;
}

background image

77

public int getCU()
{
return cuInch;
}
// ...
}


Ponieważ Samochód jest Pojazdem, a Pojazd ma Silnik, więc związek "jest" wyrażono za pomocą dziedziczenia
klas, a zwi
ązek "ma" za pomocą zawarcia w klasie.


_________________________________________________________________________________________

Hermetyzacja


Hermetyzacja polega na odseparowaniu klas i składników klas od innych klas i ich składników. Może dotyczyć
całych klas oraz pól, konstruktorów, metod i funkcji.

Hermetyzacja klas


Każda klasa, a wraz nią jej składniki, należy do pewnego pakietu. Do określenia nazwy pakietu służy
deklaracja

package name;


w której name jest nazwą pakietu.

Deklaracja pakietu musi być pierwszą deklaracją modułu źródłowego. Jeśli jej nie użyto, to przyjmuje się, że
wszystkie klasy modułu należą do pakietu domyślnego.

Nazwy klas


Klasy mogą być tylko publiczne albo pakietowe. Klasa publiczna jest zadeklarowana ze specyfikatorem public
i jest dostępna wszędzie. Klasa pakietowa jest deklarowana bez specyfikatora dostępności. Jest ona wówczas
dostępna w dowolnym module należącym do jej pakietu.

Jeśli klasa Class należy do pakietu package, to jej nazwą jest package.Class. W wypadku odwołania się do
takiej nazwy, definicja klasy jest poszukiwana w katalogach wymienionych w zmiennej środowiskowej
CLASSPATH, w kolejności ich wystąpienia.

W szczególności, jeśli w środowisku Windows 95 wykonano polecenie

set CLASSPATH=c:\cafe;d:\debug\tests


a w programie występuje instrukcja

janb.pkg Chain chain = new janb.pkg.Chain();


to skompilowana definicja klasy Chain jest poszukiwana w katalogu c:\cafe\janb\pkg, a jeśli się jej tam nie
ma, to w katalogu d:\debug\tests\janb\pkg.

Uwaga: Gdyby w rozpatrywanym programie użyto polecenia importu

import janb.pkg.Chain;


albo ogólniejszego polecenia

import janb.pkg.*.;

background image

78

to przytoczoną instrukcję można by uprościć do

Chain chain = new Chain();


Przykład Hermetyzacja klas

package janb.pkg;

public
class Master {
// ...
}

class Slave {
// ...
}

Klasy Master i Slave należą do pakietu janb.pkg. Klasa Master jest dostępna wszędzie, natomiast klasa Slave
jest dost
ępna tylko w obrębie klas należących do pakietu janb.pkg.

Hermetyzacja składników


Hermetyzacja składników jest określona przez specyfikatory: private (prywatny), public (publiczny) i
protected (chroniony).

Składnik prywatny jest dostępny tylko w jego klasie macierzystej. Składnik publiczny jest dostępny wszędzie.

Składnik chroniony jest dostępny w całym pakiecie jego klasy macierzystej oraz w podklasie jego klasy
macierzystej (niezależnie od tego, w jakim umieszczono ją pakiecie).

Składnik pakietowy (zadeklarowany bez specyfikatora dostępności) jest dostępny w całym pakiecie, do którego
należy jego klasa.

Przykład Hermetyzacja składników

package janb.pkg;

class Person {
private String name;
private int age;
public Person(String name, int age)
{
// ...
}
protected void setAge(int age)
{
// ...
}
static void fun()
{
// ...
}
}

Pola name i age są prywatne, konstruktor Person jest publiczny, metoda setAge jest chroniona, a funkcja fun
jest pakietowa (dost
ępna w całym pakiecie janb.pkg).


_________________________________________________________________________________________

Dziedziczenie


Dziedziczenie wyraża się za pomocą frazy extends. Jest ono zawsze publiczne. Każda klasa jest podklasą tylko
jednej nadklasy.

background image

79


U podstawy hierarchii dziedziczenia znajduje się klasa Object. Ponieważ każda klasa różna od Object ma
dokładnie jedną nadklasę, więc struktura klas jest drzewem.

Przykład Dziedziczenie klas

class Person {
private String name;
private int age;
public Person(String name, int age)
{
this.name = name;
this.age = age;
}
}

class Woman extends Person {
boolean isMarried;
public Woman(String name, int age,
boolean isMarried)
{
super(name, age);
this.isMarried = isMarried;
// ...
}
}


Klasa Person jest nadklasą klasy Woman. Klasa Woman jest podklasą klasy Person. Nadklasą klasy Person
oraz Woman jest klasa Object.

Obiekt klasy Person zawiera dwie zmienne opisane przez pola name i age. Obiekt klasy Woman zawiera takie
same zmienne jak obiekt klasy Person oraz dodatkowo, zmienn
ą opisaną przez pole isMarried.


_________________________________________________________________________________________

Klasy abstrakcyjne


Klasą abstrakcyjną jest klasa zadeklarowana ze specyfikatorem abstract. Jeśli pewna klasa zawiera deklarację
metody abstrakcyjnej, to jest metody zadeklarowanej ze specyfikatorem abstract, to musi być jawnie
zadeklarowana jako abstrakcyjna.

Klasa abstrakcyjna różni się od klasy nieabstrakcyjnej tym, że nie można tworzyć jej obiektów bezpośrednio,
to jest za pomocą operacji new albo za pomocą metody fabrykującej. Dlatego klasy abstrakcyjne są z reguły
dziedziczone przez klasy nieabstrakcyjne.

Uwaga: Jeśli podklasa klasy abstrakcyjnej nie przedefiniuje wszystkich metod abstrakcyjnych jej nadklasy, to
sama staje się klasą abstrakcyjną.

public abstract
class Shape { // klasa abstrakcyjna
float x, y;
Shape(float x, float y)
{
this.x = x;
this.y = y;
}
public abstract
double getArea(); // metoda abstrakcyjna
float getX() // metoda nieabstrakcyjna
{
return x;
}
float getY()
{
return y;

background image

80

}
}


Klasa Shape zawiera deklarację metody abstrakcyjnej getArea oraz definicje dwóch metod nieabstrakcyjnych.
Nie wolno pomin
ąć specyfikatora abstract występującego w nagłówku definicji klasy.


_________________________________________________________________________________________

Metody abstrakcyjne


W miejscu ciała metody abstrakcyjnej występuje średnik. W jednej z podklas klasy abstrakcyjnej musi dla
każdej metody abstrakcyjnej być dostarczona metoda nieabstrakcyjna o identycznej sygnaturze, której ciałem
jest instrukcja grupująca.

Uwaga: Dwie procedury mają identyczne sygnatury, jeśli mają takie same identyfikatory, a ich listy deklaracji
parametrów (po usunięciu z nich identyfikatorów) składają się z takich samych jednostek leksykalnych.

W szczególności, dwie następujące procedury, mimo iż są istotnie różne (pierwsza jest funkcją, a druga metodą)
mają identyczne sygnatury

static String proc(int one, int[] two[])
String proc(int uno, int[][] due)

abstract
class Shape {
float x, y;
Shape(float x, float y)
{
this.x = x;
this.y = y;
}
public abstract
double getArea(); // metoda abstrakcyjna
float getX()
{
return x;
}
float getY()
{
return y;
}
}

public
class Circle extends Shape {
static final double Pi = Math.PI;
private float radius;
Circle(float x, float y, float radius)
{
super(x, y);
this.radius = radius;
}
public double getArea() // metoda nieabstrakcyjna
{
return Pi * radius * radius;
}
}


W klasie Circle przedefiniowano metodę abstrakcyjną getArea klasy Shape. Gdyby klasa Circle nie zawierała
definicji metody getArea, to byłaby klas
ą abstrakcyjną.

Zastosowania

background image

81

Następujący program, implementujący stos o elementach niejednorodnych ilustruje zasady posługiwania się
klasami i metodami abstrakcyjnymi.

Przykład Projektowanie stosu

import java.io.IOException;

abstract
class Item {
void out()
{
System.out.println("Please override me!");
}
}

class IntItem extends Item {
private int value;
public IntItem(int val)
{
value = val;
}
void out()
{
System.out.print(value + " ");
}
}

class DblItem extends Item {
private double value;
public DblItem(double val)
{
value = val;
}
void out()
{
System.out.print(value + " ");
}
}

class Stack {
private Item vec[];
private int size;
private int top = 0;
public Stack(int size)
{
this.size = size;
vec = new Item[size];
}
void push(Item item)
throws StackException
{
vec[top++] = item;
if(top == size)
throw new StackException("Stack overflow");
}
Item pop()
throws StackException
{
top--;
if(top < 0)
throw new StackException("Stack underflow");
return vec[top];
}
}

class StackException extends Exception {
public StackException(String cause)
{
super(cause);
}
}

background image

82

public
class Master {
public static void main(String args[])
throws IOException
{
Stack stack = new Stack(5);
try {
stack.push(new IntItem(10));
stack.push(new DblItem(2.5));
stack.push(new IntItem(20));
}
catch(Exception e) {
}
while(true) {
Item item;
try {
item = stack.pop();
}
catch(StackException e) {
break;
}
item.out();
}
System.out.println();
System.in.read();
}
}

Wykonanie programu powoduje wyprowadzenie napisu

20 2.5 10



_________________________________________________________________________________________

Interfejsy


Każda klasa (różna od klasy Object) ma tylko jedną nadklasę, ale może implementować dowolnie wiele
interfejsów.

Interfejs jest w istocie klasą abstrakcyjną, zawierającą tylko definicje statycznych pól ustalonych (static i
final) oraz metod abstrakcyjnych (abstract).

Uwaga: Jeśli w definicji interfejsu pominie się specyfikatory static, final i abstract, to zostaną one
domniemane.

Przykład Definicja interfejsu

interface Drawable {
static final Color red = Color.red;
final Color green = Color.green;
Color blue = Color.blue;
abstract void draw(Graphics gDC);
Graphics getDC();
}


Wszystkie 3 pola są statyczne i ustalone. Obie metody są abstrakcyjne.

Implementowanie interfejsu


Implementowanie interfejsu wyraża się za pomocą słowa kluczowego implements. Każdemu odnośnikowi typu
interfejsowego można przypisać odniesienie do obiektu klasy implementującej ten interfejs.

Interfejs może zawierać tylko deklaracje metod oraz definicje statycznych zmiennych ustalonych (final).
Każdy z tych składników klasy jest domyślnie publiczny.

background image

83


Klasa, która implementuje interfejs musi dostarczyć definicje wszystkich jego metod. W przeciwnym razie
staje się klasą abstrakcyjną.

Przykład Implementowanie interfejsu

import java.io.IOException;

interface Taxable {
double amount();
}

interface Eligible {
boolean isMarried();
}

class Person {
private String name;
private int age;
protected double taxes;
public Person(String name, int age)
{
// ...
}
// ...
}

class Woman extends Person
implements Taxable, Eligible {
private Person husband;
public Woman(String name, int age, Person husband)
{
super(name, age);
this.husband = husband;
}
public double amount()
{
return taxes;
}
public boolean isMarried()
{
return husband != null;
}
// ...
}

public
class Master {
public static void main(String args[])
{
Taxable taxRef;
Person john = new Person("John Smith", 40);
Woman mary = new Woman("Mary Smith", 20, john);
taxRef = mary;
double taxes = taxRef.amount();
// ...
System.in.read();
}
}


Klasa Woman dziedziczy klasę Person oraz implementuje interfejsy Taxable i Eligible.

Odnośnikowi interfejsowemu taxRef przypisano odniesienie do obiektu klasy Woman implementującej interfejs
Taxable.

Metod
ę amount wywołano poprzez odnośnik typu "Taxable".

background image

84

Zastosowania


Następujący program, implementujący stos o elementach niejednorodnych, ilustruje zasadę posługiwania się
interfejsami.

Przykład Projektowanie stosu

import java.io.IOException;

interface Stackable {
void out();
}

class IntItem implements Stackable {
private int value;
public IntItem(int val)
{
value = val;
}
void out()
{
System.out.print(value + " ");
}
}

class DblItem implements Stackable {
private double value;
public DblItem(double val)
{
value = val;
}
void out()
{
System.out.print(value + " ");
}
}

class Stack {
private Stackable vec[];
private int size;
private int top = 0;
public Stack(int size)
{
this.size = size;
vec = new Stackable[size];
}
void push(Stackable item)
throws StackException
{
vec[top++] = item;
if(top == size)
throw new StackException("Stack overflow");
}
Stackable pop()
throws StackException
{
top--;
if(top < 0)
throw new StackException("Stack underflow");
return vec[top];
}
}

class StackException extends Exception {
public StackException(String cause)
{
super(cause);
}
}

public

background image

85

class Master {
public static void main(String args[])
throws IOException
{
Stack stack = new Stack(5);
try {
stack.push(new IntItem(10));
stack.push(new DblItem(2.5));
stack.push(new IntItem(20));
}
catch(Exception e) {
}
while(true) {
Stackable item;
try {
item = stack.pop();
}
catch(StackException e) {
break;
}
item.out();
}
System.out.println();
System.in.read();
}
}

Wykonanie programu powoduje wyprowadzenie napisu

20 2.5 10



_________________________________________________________________________________________

Konwersje


Konwersja obiektowa jest przekształceniem z jednego typu obiektowego do innego. Jest ona wyrażana przez
operator konwersji.

(Type)


w którym Type jest nazwą typu docelowego (np. "Object" albo "String []").

Rezultatem poprawnej konwersji odnoœnikowej jest odnoœnik zainicjowany wartoœci¹ argumentu.

Na przykład

import java.awt.*;
import java.applet.*;

public
class Master {
// ...
Applet fun(Object obj)

{

return (Applet)(Panel)obj;
}
}


Wyrażenie obj poddano konwersji do typu "Panel", a następnie do typu "Applet".

Konwersje poprawne


Konwersja odnoœnikowa jest poprawna tylko wówczas gdy jest dopuszczalna i wykonalna.

background image

86

Konwersja jest dopuszczalna gdy jest to¿samoœciowa (np. z typu "Vector" do "Vector"), albo gdy polega na
przekszta³ceniu z klasy do nadklasy (np. z "Vector" do "Object"), z klasy do podklasy (np. z "Object" do
"Vector") albo z klasy do implementowanego przez ni¹ interfejsu (np. z "Vector" do "Cloneable").

Konwersja jest wykonalna, gdy jest dopuszczalna, a wyra¿enie

exp instanceof Type


(por. opis operatora instanceof) ma wartoϾ true.

Na przyk³ad

String city = "Warsaw";
Object obj = (Object)city;
city = (String)obj;
Vector vec = (Vector)obj; // bł

ą

d (konwersja niewykonalna)


Ponieważ klasa String jest podklasą klasy Object, więc konwersja

(String)obj


jest dopuszczalna.

Jest ona wykonalna, poniewa
ż jest dopuszczalna, a wyrażenie

obj instanceof String


ma wartość true.

Konwersja odno
śnikowa

(Vector)obj


jest dopuszczalna, ale nie jest wykonalna. A zatem jest błędna.

Konwersje standardowe


Konwersją standardową jest każda konwersja, która może być wykonana niejawnie. Do tej kategorii należą
konwersje arytmetyczne, łańcuchowe i obiektowe.

Konwersje arytmetyczne

Standardową konwersją arytmetyczną jest przekształcenie z typu o węższym zakresie wartości do typu o
szerszym zakresie wartości (np. z typu "int" do typu "long").

Na przykład

byte b = 12;
int i = 12;
i = b; // i = (int)b;
b = i; // bł

ą

d (brak konwersji)


Konwersje łańcuchowe

Standardową konwersją łańcuchową jest przekształcenie z dowolnego typu podstawowego oraz obiektowego
do typu "String".

background image

87

Jeśli operacja dotyczy zmiennej typu podstawowego, to jest używana metoda append klasy StringBuffer, a
jeśli dotyczy zmiennej typu obiektowego, to sposób wykonania konwersji jest określony przez jej metodę
toString.

Uwaga: Metoda toString jest zdefiniowana zawsze, ponieważ występuje w klasie Object.

Na przykład

Thread thread = Thread.currentThread();
String var = 4 + "sale" + thread;


Operacja

4 + "sale" + thread


jest wykonywana tak jak operacja

new StringBuffer().append(4).

append("sale").

append(thread.toString()).

toString()


Konwersje obiektowe

Standardową konwersją obiektową jest przekształcenie odnośnika klasy na odnośnik jego nadklasy oraz
odnośnika klasy na odnośnik jej interfejsu. W szczególności taką konwersją jest przekształcenie każdego
odnośnika na odnośnik klasy Object.

Na przykład

import java.io.IOException;

class Primary implements Runnable {
// ...
public void run()
{
// ...
}
}

class Derived extends Primary {
// ...
}

public
class Master {
public static void main(String args[])
throws IOException
{
Primary prm;
prm = (Primary)new Derived();
prm = new Derived(); // identyczne z poprzednim

Runnable rnb;
rnb = (Runnable)prm;
rnb = prm; // identyczne z poprzednim;

rnb = (Runnable)new Derived();
rnb = new Derived(); // identyczne z poprzednim

prm = (Primary)rnb;
prm = rnb; // bł

ą

d (brak konwersji)


args = (String [3])args; // bł

ą

d (podano rozmiar)

// ...
System.in.read();
}

background image

88

}


Ponieważ klasa Primary implementuje interfejs Runnable, więc albo musi być abstrakcyjna, albo musi
zawiera
ć definicję metody run (wybrano to drugie).

ąd w operacji przypisania

prm = rnb


wynika z tego,
że nie istnieje konwersja standardowa z typu "Runnable" do typu "Primary".

ąd w operacji

(String [3])args


polega na tym, że użyto rozmiaru tablicy.

Konwersje statyczne


Każda konwersja odnośnika na odnośnik jego nadklasy albo podklasy jest konwersją poprawną statycznie, ale
nie musi być konwersją poprawną dynamicznie (tj. podczas wykonania programu).

Przykład Konwersje statyczne

String str = "Hello";
Object obj = str; // Object obj = (Object)str;
str = (String)obj;


Ponieważ klasa String jest podklasą klasy Object, więc wyrażenie str typu "String" może być przekształcone w
wyra
żenie typu "Object", a wyrażenie obj typu "Object" może być przekształcone w wyrażenie typu "String".

Konwersje dynamiczne


Konwersja odnośnika na inny odnośnik jest poprawna dynamicznie tylko wówczas, gdy klasa odniesienia
przypisanego odnośnikowi poddawanemu konwersji jest klasą albo podklasą odnośnika docelowego. Jeśli
wymaganie to nie jest spełnione, to z miejsca konwersji jest wysłany wyjątek klasy ClassCastException.

Przykład Konwersje dynamiczne

import java.io.IOException;
import java.awt.*;
import java.applet.*;

public
class Master {
public static void main(String args[])
{
Panel panel;
Applet applet;
panel = new Applet();
applet = (Applet)panel;
panel = new Panel();
applet = (Applet)panel; // bł

ą

d dynamiczny

// ...
System.in.read();
}
}


Ponieważ klasa Applet jest podklasą klasy Panel, więc obie instrukcje

applet = (Applet)panel;

background image

89


s
ą poprawne statycznie.

Poniewa
ż podczas wykonywania drugiej instrukcji

applet = (Applet)panel;


odnośnik panel nie identyfikuje obiektu klasy Applet, więc podczas próby wykonania konwersji

(Applet)panel


zostanie wysłany wyj
ątek klasy ClassCastException.

Operator pochodzenia


Rozstrzygnięcie o tym, czy konwersja doprowadzi, czy nie doprowadzi do wysłania wyjątku klasy
ClassCastException umożliwia operator pochodzenia.

Operacja pochodzenia ma postać

exp instanceof Class


w której exp jest wyrażeniem odnośnikowym, a Class jest identyfikatorem klasy.

Rezultatem operacji pochodzenia jest orzecznik, który ma wartość true tylko wówczas, gdy odnośnik exp
identyfikuje obiekt klasy Class albo jej podklasy.

Przykład Operator pochodzenia

static Applet anApplet(Panel panel)
{
if(panel instanceof Applet)
return (Applet)panel;
return null;
}


Jeśli podczas wykonywania funkcji fun, odnośnik panel nie identyfikuje obiektu, który jest klasy Applet albo jej
podklasy, to funkcja zwraca null.


_________________________________________________________________________________________

Polimorfizm


Każde wywołanie metody jest polimorficzne. Oznacza to, że jest wywoływana metoda widoczna w klasie
obiektu identyfikowanego przez odniesienie przypisane odnośnikowi, a nie metoda widoczna w klasie
odnośnika.

Przykład Wywołania polimorficzne

interface Taxable {
void showTaxes();
}

class Person {
private double taxes;
public Person(String name, int age)
{
// ...
}
double getTaxes()
{

background image

90

return taxes;
}
void showTaxes()
{
// ...
}
}

class Woman extends Person implements Taxable {
public Woman(String name, int age)
{
super(name, age);
}
public void showTaxes()
{
// ...
}
// ...
}

class Master {
void Sub(Person person, Woman woman, Taxable taxes)
{
person = new Woman("Mary Smith", 20);
person.showTaxes();
// ...
woman = (Woman)person;
taxes = woman;
taxes.showTaxes();
// ...
double amount;
amount = person.getTaxes();
}
}


Wykonanie instrukcji

person.showTaxes();

powoduje wywołanie metody showTaxes klasy Woman (a nie metody showTaxes klasy Person).

Wykonanie instrukcji

taxes.showTaxes()


powoduje wywołanie metody showTaxes klasy Woman (a nie metody showTaxes interfejsu Taxable).

Wykonanie instrukcji

amount = person.getTaxes();


powoduje wywołanie metody getTaxes widocznej w klasie Woman, to jest metody getTaxes klasy Person.


_________________________________________________________________________________________

Klasy


Zasady programowania obiektowego można zilustrować projektami klas do przechowywania łańcuchów
znaków (CString) i dynamicznych wektorów (CVector).

Klasa CString


Klasa CString umożliwia tworzenie obiektów identyfikujących łańcuchy znaków o dowolnym rozmiarze. W
odróżnieniu od predefiniowanej klasy String, której obiekty identyfikują wektory o elementach 2-bajtowych,

background image

91

obiekty klasy CString identyfikują wektory o elementach 1-bajtowych, a więc zapewniają oszczędniejszą
implementację.

class CString implements Cloneable {
private int len;
private byte ref[];
public CString(String str)
{
len = str.length();
ref = new byte [len+1];
for(int i = 0; i < len ; i++)
ref[i] = (byte)str.charAt(i);
ref[len] = 0;
}
public char charAt(int i)
throws IndexOutOfBoundsException
{
if(i < 0 || i >= len)
throw new IndexOutOfBoundsException();
return (char)ref[i];
}
public String toString()
{
String str = "";
for(int i = 0; i < len ; i++)
str += (char)ref[i];
return str;
}
public int compareTo(CString str)
{
return toString().compareTo(str.toString());
}
public boolean equals(Object obj)
{
if(obj instanceof String) {
String str = ((String)obj).toString();
return toString().compareTo(str) == 0;
} else
return false;
}
public Object clone()
throws CloneNotSupportedException
{
Object clone = super.clone();
String str = toString();
CString string = new CString(str);
((CString)clone).ref = string.ref;
return clone;
}
public void set(CString str)
throws CloneNotSupportedException
{
CString string = (CString)str.clone();
len = string.len;
ref = string.ref;
}
public void set(String str)
throws CloneNotSupportedException
{
CString string = new CString(str);
set(string);
}
}


Przykład Użycie klasy CString

import java.io.IOException;

public
class Master {
public static void main(String args[])

background image

92

throws IOException, CloneNotSupportedException
{
CString greet = new CString("Hello world");
System.out.println(greet);
int i = 0;
while(true) {
try {
char chr = greet.charAt(i++);
System.out.print(chr);
}
catch(IndexOutOfBoundsException e) {
break;
}
}
System.out.println();
CString string = (CString)greet.clone();
System.out.println(string.equals(greet.toString()));
greet = new CString("Hello");
System.out.println(greet);
System.in.read();
}
}


Wykonanie programu powoduje wyprowadzenie napisu

Hello world

Hello world

true

Hello


Klasa CVector


Klasa CVector umożliwia tworzenie obiektów identyfikujących dynamiczne wektory o elementach
pomocniczej klasy CDouble. Klasa ta stanowi otoczkę dla zmiennych typu "double".

public
class CVector {
private int size = 1;
private int len = 0;
private CDouble ref[];
public CVector()
{
ref = new CDouble [size];
}
CDouble refAt(int i)
{
return ref[i];
}
CVector append(double val)
{
if(len >= size) {
CDouble ref[] = new CDouble [2 * size];
for(int i = 0; i < size ; i++)
ref[i] = this.ref[i];
size *= 2;
this.ref = ref;
}
ref[len++] = new CDouble(val);
return this;
}
}

class CDouble {
private double value;
public CDouble(double val)
{
value = val;
}
public double get()

background image

93

{
return value;
}
public void set(double val)
{
value = val;
}
}


Przykład Użycie klasy CVector

import java.io.IOException;
import java.util.*;

public
class Master {
private static final int size = 5;
public static void main(String args[])
throws IOException
{
CVector vec = new CVector();
for(int i = 0; i < size ; i++)
vec.append(10 * (i+1));
for(int i = 0; i < size ; i++)
vec.refAt(i).set(-vec.refAt(i).get());
for(int i = 0; i < size ; i++)
System.out.print(vec.refAt(i).get() + " ");
System.out.println();
System.in.read();
}
}


Wykonanie programu powoduje wyprowadzenie napisu

-10 -20 -30 -40 -50



_________________________________________________________________________________________

Kolekcje


Następujący program, w którym zdefiniowano interfejs Measurable, kolekcję Shapes oraz iterator
ShapesEnumerator ilustruje zasady programowania obiektowego.

Wymienione klasy umożliwiają przechowywanie obiektów podklas klasy Shape, bezpośrednio albo pośrednio
implementujących interfejs Measurable.

Przykład Projektowanie kolekcji


interface Measurable {
double getArea(); // wyznaczenie pola
String kindOf(); // dostarczenie nazwy
}

class Shapes {
private int noOfSlots = 1; // liczba miejsc
private int freeSlot = 0; // pierwsze wolne miejsce
Measurable vec[]; // odno

ś

niki do obiektów

public Shapes()
{
vec = new Measurable [noOfSlots];
}
public int getFreeSlot()
{
return freeSlot;
}
int getNoOfSlots()
{
return noOfSlots;

background image

94

}
Measurable getAt(int pos)
{
return vec[pos];
}
int addShape(Measurable shape) // dodaj obiekt
{
if(freeSlot >= noOfSlots) {
Measurable vecOld[] = vec;
int noOfSlotsOld = noOfSlots;
noOfSlots <<= 1; // podwój pojemno

ść

vec = new Measurable [noOfSlots];
System.arraycopy(vecOld, 0,
vec, 0, vecOld.length);
}
vec[freeSlot] = shape;
return freeSlot++;
}
Measurable remShape(int pos) // usu

ń

obiekt

throws IndexOutOfBoundsException
{
Measurable shape = vec[pos];
vec[pos] = null;
return shape;
}
double getArea() // wyznacz pole
{
double total = 0;
for(int i = 0; i < freeSlot ; i++)
if(vec[i] != null)
total += vec[i].getArea();
return total;
}
void showAll() // poka

ż

zawarto

ść

{
for(int i = 0; i < freeSlot ; i++) {
Shape shape = (Shape)vec[i];
if(shape != null)
System.out.println(
"\t" + shape.kindOf() +
" At(" +
shape.getX() +
"," +
shape.getY() +
")"
);
}
}
public final synchronized Enumeration elements()
{
return new ShapesEnumerator(this);
}
}

class ShapesEnumerator implements Enumeration {
private Shapes shapes;
private int pos = 0;
public ShapesEnumerator(Shapes shapes)
{
this.shapes = shapes;
}
public boolean hasMoreElements()
{
if(pos < shapes.getFreeSlot())
return true;
pos = 0;
return false;
}
public Object nextElement()
{
if(hasMoreElements())
return shapes.getAt(pos++);
throw new NoSuchElementException("ShapesEnumerator");

background image

95

}
}


Zdefiniowane powyżej klasy kolekcyjne i iteracyjne zostały użyte w następującym programie. W programie tym,
podklasami klasy Shape, implementuj
ącymi interfejs Measurable są Circle i Square.


import java.io.*;
import java.util.*;

abstract
class Shape implements Measurable {
private int x = 0, y = 0; // współrz

ę

dne

ś

rodka

Shape()
{
}
Shape(int x, int y)
{
this.x = x;
this.y = y;
}
int getX()
{
return x;
}
int getY()
{
return y;
}
public abstract double getArea(); // powierzchnia obiektu
public String kindOf() // nazwa obiektu
{
return getClass().toString();
}
}

class Circle extends Shape { // klasa okr

ę

gów

public static final double Pi = Math.PI;
private int radius;
public Circle(int x, int y, int radius)
{
super(x, y);
this.radius = radius;
}
public String kindOf()
{
return "Circle";
}
public double getArea()
{
return Pi * radius * radius;
}
}

class Square extends Shape { // klasa kwadratów
private int side;
public Square(int x, int y, int side)
{
super(x, y);
this.side = side;
}
public String kindOf()
{
return "Square";
}
public double getArea()
{
return (double)side * side;
}
}

public
class Master {

background image

96

public static void main(String args[])
throws IOException
{
Circle aCircle = new Circle(10, 10, 1), // aC
bCircle = new Circle(20, 20, 2), // bC
cCircle = new Circle(30, 30, 3); // cC
Square aSquare = new Square(40, 40, 5), // aS
bSquare = new Square(50, 50, 5); // bS

Shapes shapes = new Shapes();
int pos;
// obiekty w kolekcji
// ==========
shapes.addShape(aCircle); // aC
pos = shapes.addShape(bCircle); // aC, bC
shapes.remShape(pos); // aC
pos = shapes.addShape(cCircle); // aC, cC
shapes.addShape(aSquare); // aC, cC, aS
shapes.remShape(pos); // aC, aS
pos = shapes.addShape(bSquare); // aC, aS, bS
shapes.remShape(pos); // aC, aS

System.out.println("Shapes in collection:\n");
shapes.showAll();
System.out.println("\nTotal area: " + shapes.getArea());
System.out.println("\nCleaning the collection:\n");
ShapesEnumerator shapesEnum =
new ShapesEnumerator(shapes);
pos = 0;
while(shapesEnum.hasMoreElements()) {
Shape shape = (Shape)shapesEnum.nextElement();
if(shape != null)
System.out.println(
"\tDeleting " +
shape.kindOf() +
", Area: " +
shape.getArea()
);
shapes.remShape(pos++);
}
System.out.println("\nDone!");
System.in.read();
}
}


Program wyprowadza napis pokazany w tabeli Wyniki wykonania.

Tabela Wyniki wykonania

###

Shapes in collection:


Circle At(10,10)

Square At(40,40)


Total area: 28.1416


Cleaning the collection:


Deleting Circle, Area: 3.14159

Deleting Square, Area: 25


Done!

###

background image

97

Część V

Programowanie zdarzeniowe


Programowanie zdarzeniowe polega na dostarczeniu zestawu procedur, z których po zajściu zdarzenia
zewn
ętrznego (kliknięciu przycisku myszki, naciśnięciu klawisza klawiatury), jest wybierana do wykonania ta
procedura, która została przewidziana do obsługi zdarzenia.

Podstawowymi procedurami zdarzeniowymi, w które wyposaża się komponenty i pojemniki komponentów (np.
przyciski, aplety, ramki) są metody paint i handleEvent.

Metoda paint jest wywoływana przez System po utworzeniu pojemnika oraz każdorazowo gdy zawartość
pojemnika powinna zostać odtworzona. Z wnętrza programu może być wywołana za pomocą metody repaint.

Uwaga: Metoda repaint wywołuje metodę update, która tuż przez wywołaniem metody paint czyści obszar
pojemnika (wypełnia go kolorem tła).

Metoda handleEvent jest wywoływana każdorazowo po zajściu zdarzenia zewnętrznego. Jeśli w klasie
pojemnika nie zostanie przedefiniowana, to w zależności od rodzaju zdarzenia wywoła takie metody jak action
(np. reakcja na kliknięcie przycisku), mouseDown (reakcja na naciśnięcie przycisku myszki), mouseUp
(reakcja na zwolnienie przycisku myszki), itp.

Uwaga: Jeśli zdarzenie dotyczące sterownika (komponentu albo pojemnika) zostało w pełni obsłużone przez
metodę (np. handleEvent, action, itp.), to potwierdzeniem tego jest zwrócenie przez nią wartości true. W
przeciwnym razie metoda zwraca wartość false. Umożliwia to obsłużenie zdarzenia przez pojemnik sterownika.

public void paint(Graphics gDC)

Metoda jest wywoływana przez System, gdy zachodzi potrzeba odtworzenia zawartości pulpitu.

public void repaint()
public void repaint(int x, int y, int w, int h)

Metoda jest apelem o niejawne wywołanie metody update w odniesieniu do całego pulpitu, albo do jego
prostokątnej części określonej łącznie przez (x,y) i w x h.

public boolean handleEvent(Event evt)

Metoda jest wywoływana przez System, bezpośrednio po zajściu zdarzenia zewnętrznego.


Następujący program, pokazany podczas wykonywania na ekranie Propagacja zdarzeń, ilustruje zasadę
obsługiwania zdarzeń.

Ekran Propagacja zdarzeń

### propag.gif


Przykład Propagacja zdarzeń

import java.io.IOException;
import java.awt.*;

public
class Master extends Frame {
static Frame frame;
static Button button;
public Master(String caption)
{
super(caption);
}
public static void main(String args[])
throws IOException
{
frame = new Master("Frame window");
button = new MyButton("Click me", frame);
frame.setLayout(new FlowLayout());

background image

98

frame.add(button);
frame.resize(120, 80);
frame.show();
System.out.println("Press Enter to close all");
System.in.read();
System.exit(0);
}
public boolean action(Event evt, Object arg)
{
Graphics gDC = frame.getGraphics();
gDC.drawString("Frame clicked!", 10, 70);
return true;
}
}

class MyButton extends Button {
Frame frame;
MyButton(String caption, Frame frame)
{
super(caption);
this.frame = frame;
}
public boolean action(Event evt, Object arg)
{
Graphics gDC = frame.getGraphics();
gDC.drawString("Button clicked!", 10, 50);
return false;
}
}


Kliknięcie przycisku powoduje wywołanie metody action klasy MyButton.

Ponieważ metoda zwraca false, więc zdarzenie propaguje do pojemnika przycisku, którym jest okno aplikacji.

Pojemnik zwraca wartość true, co oznacza, że zdarzenie w pełni obsłużono.


_________________________________________________________________________________________

Klasa zdarzeniowa


W ciele metody handleEvent jest dostępny odnośnik do obiektu klasy Event, zawierającego szczegółowy opis
zaistniałego zdarzenia.

Wybrany fragment definicji klasy Event przytoczono w tabeli Klasa zdarzeniowa.

Tabela Klasa zdarzeniowa

###
public
class Event extends Object {
public int id; // rodzaj zdarzenia
public Object target; // odno

ś

nik do sterownika

public Object arg; // cecha sterownika
public long when; // chwila zaj

ś

cia zdarzenia

public int x; // punkt klikni

ę

cia (x)

public int y; // punkt klikni

ę

cia (y)

public int key; // kod klawisza
public int modifiers; // Ctrl, Shift, Alt, Meta
public int clickCount; // licznik klikni

ęć

public Event evt; // zdarzenie
// ...
}
###


Następujący program, pokazany podczas wykonania na ekranie Pozdrowienie, wyświetla przyciski Greet i
Clear. Po kliknięciu przycisku jest wywoływana metoda handleEvent. Jeśli kliknięto przycisk Greet, to
wyświetla się napis Hello, a jeśli kliknięto przycisk Clear to następuje wyczyszczenie pulpitu ramki.

background image

99


Ekran Pozdrowienie

### sayhello.gif

import java.io.IOException;
import java.awt.*;

public
class Master extends Frame {
static private Button greetButton, clearButton;
String msg = "";
static Frame frame;
public static void main(String args[])
throws IOException
{
frame = new Master();
frame.setLayout(new FlowLayout());
frame.resize(160, 100);
frame.show();
System.in.read();
System.exit(0);
}
Master()
{
super("Hello");
greetButton = new Button("Greet");
clearButton = new Button("Clear");
add(greetButton);
add(clearButton);
}
public void paint(Graphics gDC)
{
gDC.drawString(msg, 15, 50);
}
public boolean handleEvent(Event evt)
{
if(evt.id == Event.ACTION_EVENT) {
if(evt.target == greetButton) {
msg = "Hello";
repaint(); // "wywołuje" paint
return true;
} else if(evt.target == clearButton) {
msg = "";
repaint(); // "wywołuje" paint
return true;
}
}
return false;
}
}


Ponieważ przedefiniowano metodę handleEvent, więc nie są wywoływane takie metody jak action,
mouseDown, mouseUp, itp.

Wywołanie metody repaint jest apelem o wywołanie metody paint. System uwzględni ten apel w najbliższym
dogodnym momencie, ale dopiero po zako
ńczeniu wykonywania procedury wywołującej.


_________________________________________________________________________________________

Obsługiwanie zdarzeń


Przedefiniowanie procedury handleEvent stanowi najogólniejszy sposób obsługi zdarzeń. Nie zawsze jest to
jednak konieczne, ponieważ w wielu wypadkach wystarczy przedefiniować procedury obsługi wywoływane
przez predefiniowaną metodę handleEvent.

Fragment klasy Component zawierającej definicję metody handleEvent przytoczono w tabeli Metoda
handleEvent
.

background image

100


Tabela Metoda handleEvent

###
public abstract
class Component implements ImageObserver {
// ...
public boolean handleEvent(Event evt)
{
switch (evt.id) {
case Event.ACTION_EVENT:
return action (evt, evt.arg);
case Event.MOUSE_DOWN:
return mouseDown (evt, evt.x, evt.y);
case Event.MOUSE_UP:
return mouseUp (evt, evt.x, evt.y);
case Event.MOUSE_MOVE:
return mouseMove (evt, evt.x, evt.y);
case Event.MOUSE_DRAG:
return mouseDrag (evt, evt.x, evt.y);
case Event.MOUSE_ENTER:
return mouseEnter(evt, evt.x, evt.y);
case Event.MOUSE_EXIT:
return mouseExit (evt, evt.x, evt.y);
case Event.KEY_PRESS:
case Event.KEY_ACTION:
return keyDown (evt, evt.key);
case Event.KEY_RELEASE:
case Event.KEY_ACTION_RELEASE:
return keyUp (evt, evt.key);
case Event.GOT_FOCUS:
return gotFocus (evt, evt.arg);
case Event.LOST_FOCUS:
return lostFocus(evt, evt.arg);
}
return false;
}
// ...
public boolean action(Event evt, Object arg)
{
return false;
}
// ...
}
###


W metodzie handleEvent są rozpoznawane i wywoływane zdarzenia związane z kliknięciami przycisków
(ACTION_EVENT), zdarzenia związane z myszką i z klawiaturą (np. MOUSE_DOWN i KEY_PRESS) oraz
zdarzenia związane z przemieszczeniem wyróżnienia sterownika (GOT_FOCUS i LOST_FOCUS).

Uwaga: W danej chwili może być wyróżniony tylko tylko jeden sterownik. Przemieszczenie wyróżnienia może
być wykonane z programu, albo za pomocą klawiszy Tab i Shift-Tab.

Po rozpoznaniu zdarzenia jest wywoływana pomocnicza metoda obsługi (np. action, mouseDown, keyPress,
gotFocus). Jej przedefiniowanie umożliwia dostarczenie własnej obsługi zdarzenia.

public boolean action(Event evt, Object arg)

Metoda action reaguje na akcję wykonaną na sterowniku (kliknięcie przycisku, wybranie polecenia menu,
wybranie elementu listy, naciśnięcie klawisza Enter po zakończeniu edycji klatki tekstowej, itp.), w tym na
akcję wykonaną za pomocą myszki i klawiatury.

public boolean mouseDown (Event evt, int x, int y)
public boolean mouseUp (Event evt, int x, int y)
public boolean mouseMove (Event evt, int x, int y)
public boolean mouseDrag (Event evt, int x, int y)
public boolean mouseEnter(Event evt, int x, int y)
public boolean mouseExit (Event evt, int x, int y)

background image

101

Metody reagują na akcję wykonaną za pomocą myszki. Wykonanie kliknięcia jest rozbite na dwie podakcje:
naciśnięcie przycisku myszki (mouseDown) oraz zwolnienie go (mouseUp). Ponadto umożliwiono obsługę
przesuwania i przeciągania kursora myszki (mouseMove, mouseDrag) oraz obsługę przemieszczenia kursora
myszki do wnętrza i na zewnątrz obszaru sterownika (mouseEnter, mouseExit).

public boolean keyDown(Event evt, int key)
public boolean keyUp (Event evt, int key)

Metody reagują na naciśnięcie klawisza (keyDown) oraz na zwolnienie go (keyUp).

public boolean gotFocus (Event evt, Object arg)
public boolean lostFocus(Event evt, Object arg)

Metody reagują na otrzymanie/utratę wyróżnienia.

public void requestFocus()

Metoda przyznaje sterownikowi wyróżnienie.


Następujący program, pokazany podczas wykonywania na ekranie Wyświetlanie znaków, wykreśla w punkcie
kliknięcia ostatni znak wprowadzony z klawiatury.

Ekran Wyświetlanie znaków

### drawkey.gif

import java.io.IOException;
import java.applet.*;
import java.awt.*;

public
class Master extends Frame {
Font font;
String chr = " ";
static Frame frame;
Master()
{
super("PressKey & Click");
font = new Font("TimesRoman", Font.BOLD, 24);
}
public static void main(String args[])
throws IOException
{
frame = new Master();
frame.resize(160, 100);
frame.show();
System.in.read();
System.exit(0);
}
public boolean keyUp(Event evt, int key)
{
chr = String.valueOf((char)key);
return true;
}
public boolean mouseUp(Event evt, int x, int y)
{
Graphics gDC = getGraphics();
gDC.setFont(font);
gDC.drawString(chr, x, y);
return true;
}
public boolean handleEvent(Event evt)
{
if(evt.id == Event.WINDOW_DESTROY) {
hide(); // ukrycie ramki
dispose(); // zniszczenie ramki
return true;
} else
return super.handleEvent(evt);

background image

102

}
}


Zajście zdarzenia zewnętrznego powoduje wywołanie podanej metody handleEvent przedefiniowującej metodę
domnieman
ą.

W metodzie przedefiniowującej rozpoznaje się tylko zdarzenie WINDOW_DESTROY generowane przez System
w chwili r
ęcznego zamknięcia ramki utworzonej przez aplikację.

Pozostałe zdarzenia są obsługiwane przez predefiniowana metodę handleEvent wywoływaną w instrukcji

return super.handleEvent(evt);


Metody keyUp i mouseUp są wywoływane przez tę właśnie metodę handleEvent.


_________________________________________________________________________________________

Odtwarzanie pulpitu


Podczas programowania w środowisku graficznym należy tak definiować metodę paint, aby po każdym
wywołaniu jej przez System, nastąpiło pełne odtworzenie obsługiwanego przez nią pulpitu.

Następujący program właściwie dostosowano do warunków obsługiwania zdarzeń w dynamicznym i
wielookienkowym środowisku graficznym.

import java.io.IOException;
import java.applet.*;
import java.awt.*;
import java.util.*;

public
class Master extends Frame {
Font font;
String chr = " ";
Vector chars = new Vector();
static Frame frame;
Master()
{
super("PressKey & Click");
font = new Font("TimesRoman", Font.BOLD, 24);
}
public static void main(String args[])
throws IOException
{
frame = new Master();
frame.resize(160, 100);
frame.show();
System.in.read();
System.exit(0);
}
public boolean keyUp(Event evt, int key)
{
chr = String.valueOf((char)key);
return true;
}
public boolean mouseUp(Event evt, int x, int y)
{
Graphics gDC = getGraphics();
gDC.setFont(font);
gDC.drawString(chr, x, y);
Item item = new Item(chr, x, y);
chars.addElement(item);
return true;
}
public void paint(Graphics gDC)
{
Font oldFont = gDC.getFont();

background image

103

gDC.setFont(font);
Enumeration charSet = chars.elements();
while(charSet.hasMoreElements()) {
Object item = charSet.nextElement();
((Item)item).draw(gDC);
}
gDC.setFont(oldFont);
}
}

class Item {
private String chr;
private int x, y;
public Item(String chr, int x, int y)
{
this.chr = chr;
this.x = x;
this.y = y;
}
public void draw(Graphics gDC)
{
gDC.drawString(chr, x, y);
}
}


W programie użyto predefiniowanej klasy Vector implementującą kolekcję tablicową o nieograniczonej liczbie
elementów oraz interfejsu Enumeration definiuj
ącym metody do iterowania kolekcji.

Zgodnie z zasadami programowania obiektowego, każdy element przewidziany do wyświetlenia jest odrębnym
obiektem (tu klasy Item).

W klasie Item zdefiniowano nie tylko pola, ale również metodę do wykreślania obiektu.

Rezultatem metody elements wywołanej w instrukcji

Enumeration charSet = chars.elements();


jest odnośnik do iteratora, którego klasa zawiera metody hasMoreElements i nextElement. Umożliwiają one
dokonanie przegl
ądu wszystkich elementów wstawionych do kolekcji chars.


background image

104

Część VI

Programowanie współbieżne



Istota programowania współbieżnego polega na wykorzystaniu możliwości jednoczesnego wykonania
współpracujących i komunikujących się ze sobą procesów. W obrębie ustalonego programu takimi procesami
wątki.

Wątkiem jest sekwencyjny przepływ sterowania przez kod wynikowy programu. Program jest napisany
wielowątkowo, jeśli podczas wykonywania go w środowisku wieloprocesorowym można stwierdzić, że są takie
przedziały czasu, kiedy w co najmniej dwóch procesorach występuje współbieżny (tj. równoczesny) przepływ
sterowania przez kod wynikowy programu.

W komputerach jednoprocesorowych, współbieżność wątków jest tylko emulowana, a w każdej chwili
przepływ sterowania odbywa się w tylko jednym wątku. Ale nawet w takim środowisku, program
wielowątkowy powinien być napisany tak, jakby miał być wykonany przez wiele procesorów.

Najprostszym sposobem utworzenia programu wielowątkowego jest utworzenie obiektu klasy Thread,
przekazanie mu odnośnika do obiektu klasy zawierającej metodę run, a następnie wykonanie na rzecz
pierwszego z tych obiektów metody start. Spowoduje to dla danego wątku aktywowanie metody run. Po jej
zakończeniu wątek przestanie istnieć.

Następujący program, pokazany podczas wykonywania na ekranie Pulsujące okręgi, ilustruje zasadę
programowania wielowątkowego. Tworzy on okna, które są obsługiwane przez niezależne wątki.

Ekran Pulsujące okręgi

### pulsars.gif

import java.io.IOException;
import java.awt.*;

public
class Master extends Frame implements Runnable {
static final int count = 5;
Thread thread;
Master(int frameNo)
{
super("Frame #" + frameNo);
resize(160, 100);
show();
thread = new Thread(this); // utworzenie obiektu
thread.start(); // utworzenie w

ą

tku

}
public static void main(String args[])
throws IOException
{
Frame frame[] = new Frame[count];
for(int i = 0; i < count ; i++)
frame[i] = new Master(i);
System.in.read();
System.exit(0);
}
public void run()
{
int xC = size/2, yC = xC;
Graphics gDC = getGraphics();
while(true) {
for(int i = 0; i < 70 ; i++)
gDC.drawOval(0, 0, i, i);
repaint();
}
}
public boolean handleEvent(Event evt)
{
if(evt.id == Event.WINDOW_DESTROY) {
hide(); // ukrycie okna

background image

105

dispose(); // zniszczenie okna
thread.stop(); // zniszczenie w

ą

tku

return true;
} else
return super.handleEvent(evt);
}
}


Utworzono count niezależnych wątków, z których każdy posługuje się odrębnym oknem.

Kod wątków jest wspólny i określony przez metodę run.

Zamknięcie okna powoduje zniszczenie posługującego się nim wątku.


_________________________________________________________________________________________

Stany wątków


Każdy wątek wymaga istnienia kontrolującego go obiektu klasy Thread. Wykonanie na rzecz obiektu
kontrolującego metody start powoduje utworzenie wątku, a wykonanie metody stop jego zniszczenie.

W okresie między utworzeniem i zniszczeniem wątek istnieje, ale nie musi być czynny. Istniejący wątek
wykonuje się, śpi, jest zawieszony albo czeka.

O przebiegu wykonania wątku decyduje metoda run wywołana niejawnie przez System tuż po utworzeniu
wątku. Metoda run należy do klasy implementującej interfejs Runnable. Miejsce jej wystąpienia jest określone
podczas tworzenia obiektu kontrolującego wątek. Zakończenie wykonywania metody run powoduje
zniszczenie wątku.

public Thread(Runnable runClass)

Argument konstruktora identyfikuje obiekt klasy zawierającej definicję metody run.

public void start() throws IllegalThreadStateException

Wywołanie metody powoduje utworzenie wątku.

public void stop()

Wywołanie metody powoduje zniszczenie wątku.

public static void sleep(long millis)

Wywołanie metody powoduje uśpienie wątku na podany okres czasu, wyrażony w milisekundach.

public void suspend()

Wywołanie metody powoduje zawieszenie wątku.

public void resume()

Wywołanie metody powoduje odwieszenie wątku.

public void wait() throws InterruptedException,
IllegalMonitorStateException

Wywołanie metody powoduje wstrzymanie wykonywania wątku z jego własnej inicjatywy.

public void notify() throws IllegalMonitorStateException

Wywołanie metody powoduje uwolnienie jednego dobrowolnie wstrzymanego wątku.

public static void yield()

Wywołanie metody powoduje podzielenie się przez wątek dostępem do procesora. Po wywołaniu tej metody
zostanie podjęte wykonywanie innego wątku (o ile taki istnieje). Nie wyklucza to jednak zagłodzenia wątku
jeśli jest ich więcej niż dwa.

public void join() throws InterruptedException

background image

106

Wywołanie metody powoduje powstrzymanie wykonywania wątku aż do zniszczenia go przez inny wątek.

public boolean isAlive()

Wywołanie metody umożliwia stwierdzenie czy wątek istnieje, to jest czy na rzecz obiektu kontrolującego
wątek wywołano już metodę start, ale nie wywołano jeszcze metody stop.


_________________________________________________________________________________________

Priorytety


Z każdym wątkiem jest związany priorytet, określający jego rangę wśród innych wątków. Priorytet jest liczbą z
przedziału 1..10. Jeśli nie poda się go jawnie, to nowy wątek przejmie priorytet wątku który go utworzył.

Jako zasadę przyjmuje się, że wątek o wyższym priorytecie uzyskuje większy przydział procesora niż wątek o
niższym priorytecie. Nie oznacza to jednak, że jeśli są wykonywane wątki o różnych priorytetach, to ten o
najwyższym zmonopolizuje procesor (chociaż może się tak stać!).

Uwaga: Poleganie na priorytetach wątków nie daje gwarancji co do kolejności ich wykonania i dlatego musi
być uznane za przejaw złego stylu sterowania wykonaniem wątków.


_________________________________________________________________________________________

Wątek główny


W chwili rozpoczęcia wykonywania programu istnieje tylko wątek główny należący do grupy wątków main.
Wątek ten oraz każdy inny, może tworzyć inne wątki.

Każdy wątek może być uczyniony demonem. Wykonywanie programu kończy się bezpośrednio po tym, gdy
zakończy się wykonywanie ostatniego wątku, który nie jest demonem.

Przykład Wątek główny

import java.io.IOException;

public
class Master {
public static void main(String args[])
throws IOException
{
Thread thread = Thread.currentThread();
System.out.println(thread);
System.in.read();
}
}


Wykonanie programu powoduje wyprowadzenie napisu

Thread[main,5,main]



_________________________________________________________________________________________

Tworzenie wątków


W celu utworzenia wątku należy utworzyć kontrolujący go obiekt klasy Thread. Obiektowi temu należy
przekazać odnośnik do obiektu klasy implementującej metodę run interfejsu Runnable.

Uwaga: Metodę run można zadeklarować w tej samej klasie, do której należy metoda tworząca wątek. Klasa
tej metody musi implementować interfejs Runnable.

import java.io.IOException;

background image

107


public
class Master implements Runnable {
public static void main(String args[])
throws IOException
{
System.out.println("Hello from MainThread");
Master myThis = new Master();
new Thread(myThis, "OtherThread").start();
System.in.read();
}
public void run()
{
System.out.println("Hello from OtherThread");
}
}


Podany program napisano wielowątkowo. Jest on wykonywany dwuwątkowo od chwili wywołania metody start

new Thread(this, "OtherThread").start();


to jest do chwili zakończenia wykonywania funkcji main albo metody run (co zajdzie wcześniej).

Wykonanie programu powoduje wyprowadzenie napisu

Hello from MainThread

Hello from OtherThread



_________________________________________________________________________________________

Synchronizowanie wątków


Jeśli dwa, lub więcej, wątków dzieli wspólny zasób (na przykład pamięć operacyjną), to szczególną troską
należy otoczyć dostęp do tego zasobu.

Aby się przekonać o konieczności synchronizowania dostępu, wystarczy rozpatrzyć sytuację, gdy rolę wątków
pełnią dwaj kasjerzy, którzy mają nie-synchronizowany dostęp do wspólnej bazy danych.

Jeśli na wspólnym koncie dwóch osób jest na przykład $100, a każda z nich wpłaca w osobnym okienku $20, to
może zaistnieć następująca sytuacja

Pierwszy kasjer sprawdza konto i odnotowuje jego stan ($100), ale coś odrywa go do telefonu.

Drugi kasjer sprawdza konto, odnotowuje jego stan ($100), dodaje $20 i aktualizuje konto (do $120).

Pierwszy kasjer ko
ńczy rozmowę, dodaje $20 do odnotowanej sumy i aktualizuje bazę (do $120).

W nast
ępstwie nie-synchronizowanego dostępu do bazy danych, następuje zwiększenie konta nie o $40, ale o
$20.

Następujący program ilustruje sposób rozwiązania przedstawionego problemu. Dzięki synchronizacji dostępu
do bazy dataBase, funkcja updateSavings może być w danej chwili wykonywana tylko przez co najwyżej
jeden wątek.

import java.io.IOException;

class Savings {
private float savings;
// ...
void add(float amount)
{
savings += amount;
}
}

background image

108


class Transaction {
int account;
float amount;
Transaction(int account, int amount)
{
// ...
}
// ...
}

public
class Master {
static float savingsData[] =
{ 100, 500, 300, 400, 200 };
static Savings dataBase[] =
new Savings[savingsData.length];
static int noOfTellers = 2;
public static void main(String args[])
throws IOException, InterruptedException
{
loadDataBase(dataBase, savingsData);
Transaction
setOne[] = {
new Transaction(2, 10),
new Transaction(4, 20) },
setTwo[] = {
new Transaction(0, 30),
new Transaction(2, 10)
};

// ===================
// 100 500 300 400 200 Baza danych
// 10 20 John
// 30 10 Bill
// ===================
// 130 500 320 400 220 results

Teller john = new Teller("John", setOne, dataBase),
bill = new Teller("Bill", setTwo, dataBase);

Thread tellerOne = new Thread(john),
tellerTwo = new Thread(bill);

showDataBase(dataBase);
tellerOne.start();
tellerTwo.start();
// ...
Thread.currentThread().sleep(1000);
// ...
showDataBase(dataBase);
System.in.read();
}
// ...
static void loadDataBase(Savings dataBase[],
float savings[])
{
// ...
}
static void showDataBase(Savings dataBase[])
{
// ...
}
}

class Teller implements Runnable {
private String name;
private Transaction set[];
private Savings dataBase[];
Teller(String name, Transaction set[],
Savings dataBase[])
{
this.name = name;

background image

109

this.set = set;
this.dataBase = dataBase;
}
private void updateSavings(int i)
{
int account = set[i].account;
float amount = set[i].amount;
System.out.println(name + " " +
account + " " + amount);
dataBase[account].add(amount);
}
public void run()
{
for(int i = 0; i < set.length ; i++)
synchronized(dataBase)
updateSavings(i);
Master.setDone();
}
}


Podany program wyprowadza napis pokazany w tabeli Transakcje. Kolejność wierszy środkowej części napisu
może się zmieniać od wykonania do wykonania. Uogólnienie programu na dowolną liczbę kasjerów i transakcji
jest trywialne.

Tabela Transakcje

###

Account #1 $100

Account #1 $500

Account #2 $300

Account #3 $400

Account #4 $200


John 2 10

Bill 0 30

John 4 20

Bill 2 10


Account #1 $130

Account #1 $500

Account #2 $320

Account #3 $400

Account #4 $220

###



_________________________________________________________________________________________

Instrukcja synchronizująca


Do synchronizowania wątków służy instrukcja

synchronized(exp)Block


w której exp jest nazwą odnośnika, a Block jest instrukcją grupującą.

Wykonanie instrukcji synchronizującej powoduje przydzielenie wątkowi bloku sekcji krytycznej związanej z
obiektem identyfikowanym przez odnośnik, a po wykonaniu instrukcji bloku, zwolnienie sekcji.

Uwaga: Jeśli podczas wykonywania sekcji krytycznej związanej z pewnym obiektem, jakikolwiek inny wątek
podejmie próbę wykonania tej samej sekcji krytycznej związanej z tym samym obiektem, to jego wykonanie
wątku zostanie zablokowane do chwili zwolnienia sekcji.

W następującej funkcji, wykonanie sekcji krytycznej wyznaczonej przez instrukcję synchronized, może być w
danej chwili, na rzecz tego samego obiektu point, realizowane tylko przez co najwyżej jeden wątek. Dzięki
temu wywołanie metody incPoint powoduje zwiększenie obu pól (x, y) o tę samą wartość.

class Master {

background image

110

// ...
public Point incPoint(Point point)
{
synchronized(point) {
point.incXY();
return point;
}
}
}

class Point {
int x, y;
void incXY()
{
this.x = x + 1;
this.y = y + 1;
}
// ...
}



Uwaga: Gdyby zrezygnowano z użycia instrukcji synchronized, a metodę incPoint wywołano z dwóch
różnych wątków, to mogłoby się zdarzyć, że po takich operacjach współrzędne x i y punktu nie byłyby równe.


_________________________________________________________________________________________

Procedury synchronizowane


Procedurą synchronizowaną jest procedura zadeklarowana ze specyfikatorem synchronized.

class Counter {
private int counter = 0;
public synchronized void Counter()
{
counter += 1;
}
}


Uwaga: Semantyka procedur synchronizowanych zostanie wyjaśniona przez powołanie się na instrukcję
synchronized

Metody synchronizowane


Wywołanie w instrukcji

ref.met(arg, arg, ... , arg);


metody synchronizowanej met, na rzecz obiektu identyfikowanego przez odnośnik ref jest równoważne
wywołaniu

synchronized(ref) {

ref.met2(arg, arg, ... , arg);

}


identycznej z nią metody niesynchronizowanej met2.

Funkcje synchronizowane


Wywołanie w instrukcji

Class.fun(arg, arg, ... , arg);

background image

111


funkcji synchronizowanej fun należącej do klasy Class jest równoważne wywołaniu

try {

synchronized(Class.forName(Class)) {

Class.fun2(arg, arg, ... , arg);

}

}

catch(ClassNotFoundException e) {

}


identycznej z nią funkcji niesynchronizowanej fun2.


_________________________________________________________________________________________

Monitor


Z każdym obiektem klasy jest skojarzony monitor. Jeśli metoda synchronizowana klasy zostanie wywołana na
rzecz obiektu, to wywołujący ją wątek zajmie monitor tego 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 funkcji sychronizowanej danej klasy.

Wątek zajmujący monitor może posługiwać się metodami wait i notify. Biorąc pod uwagę pojęcie monitora,
ich opisy przybierają następującą postać:

public void wait() throws InterruptedException,
IllegalMonitorStateException

Wywołanie metody powoduje zwolnienie monitora przez wątek i dobrowolne wstrzymanie wykonywania
wątku. Umożliwi to innemu wątkowi ubieganie się o przydzielenie monitora.

public void notify() throws IllegalMonitorStateException

Wywołanie metody powoduje uwolnienie jednego dobrowolnie wstrzymanego wątku. Umożliwi to
uwolnionemu wątkowi ubieganie się o zajęcie monitora, ale nie wcześniej niż monitor zostanie zwolniony
przez wątek wywołujący metodę notify.

Produkcja i konsumpcja


Następujący program ilustruje użycie monitora do oprogramowana klasycznego problemu Producent-
Konsument
. Producent wkłada liczby do pudełka, a konsument je wyjmuje. Jeśli pudełko jest puste, to czeka
konsument, a jeśli jest pełne, to czeka producent.

Przykład Produkcja-Konsumpcja


import java.io.IOException;
import java.io.*;

public
class Master {
static Box box = new Box();
public static void main(String args[])
throws IOException
{
new Producer(box);
new Consumer(box);
}
}

class Producer implements Runnable {

background image

112

Box box;
Producer(Box box)
{
this.box = box;
new Thread(this).start();
}
public void run()
{
for(int number = 0; number < 5 ; number++)
box.putInto(number+1);
box.setDone();
}
}

class Consumer implements Runnable {
Box box;
Consumer(Box box)
{
this.box = box;
new Thread(this).start();
}
public void run()
{
int gotFrom;
while(true)
try {
gotFrom = box.getFrom();
}
catch(IOException e) {
}
}
}

class Box {
int contents;
boolean boxEmpty = true;
static boolean producerDone = false; // flaga ko

ń

ca

public Box()
{
}
synchronized void setDone()
{
producerDone = true;
}
synchronized int getFrom()
{
if(boxEmpty) {
if(producerDone) {
System.in.read();
System.exit(0);
} else
try {
wait();
}
catch(InterruptedException e) {
}
}
int number = contents;
System.out.println("Number " + number +
" from Box");
boxEmpty = true;
notify();
return number;
}
synchronized void putInto(int number)
{
if(!boxEmpty)
try {
wait();
}
catch(InterruptedException e) {
}
contents = number;

background image

113

System.out.println("Number " + number +
" to Box");
boxEmpty = false;
notify();
}
}


Producent i konsument operują na wspólnym zasobie jakim jest pudełko box.

Wykonanie programu powoduje wyprowadzenie napisu

Number 1 to Box

Number 1 from Box

Number 2 to Box

Number 2 from Box

Number 3 to Box

itd.

Po wstawieniu do programu bardziej szczegółowych poleceń informujących o przebiegu wydarzeń można
otrzyma
ć sekwencję napisów pokazaną w tabeli Produkcja-konsumpcja.

Tabela Produkcja-konsumpcja

###

koniec wykonania funkcji main

ale wykonanie trwa

startuje producent

zaczyna działa

ć

producent wywołuje putInto(1)

producent wstawia 1 w pudełka

producent wywołuje putInto(2)

startuje konsument

dopiero teraz

konsument wywołuje getFrom

konsument dostaje 1 z pudełka

konsument wywołuje getFrom

producent wstawia 2 do pudełka

konsument dostaje 2 z pudełka

producent wywołuje putInto(3)

konsument wywołuje getFrom

producent wstawia 3 do pudełka

producent wywołuje putInto(4)

konsument dostaje 3 z pudełka

producent wstawia 4 do pudełka

konsument wywołuje getFrom

producent wywołuje putInto(5)

konsument dostaje 4 z pudełka

producent wstawia 5 do pudełka

konsument wywołuje getFrom

producent ko

ń

czy wykonywanie

ustawia flag

ę

ko

ń

ca

konsument dostaje 5 z pudełka

konsument wywołuje getFrom

konsument ko

ń

czy wykonywanie

po zbadaniu flagi

###



_________________________________________________________________________________________

Impas


O ile synchronizacja nie jest zaprojektowana prawidłowo, może powstać impas. Występuje on wówczas, gdy w
zestawie współdziałających wątków każdy jest zawieszony albo dobrowolnie wstrzymany, ale nie istnieje
możliwość odwieszenia ani uwolnienia przynajmniej jednego wątku.

Następujący program monitoruje pracę dwóch osób. Każda z nich informuje o postępach swoich i konkurenta.

import java.io.IOException;

public
class Master {
static Worker tom, bob;
public static void main(String args[])

Komentarz [JB1]:

background image

114

throws IOException
{
tom = new Worker("Tom");
bob = new Worker("Bob");
new Thread(tom.setOther(bob)).start();
new Thread(bob.setOther(tom)).start();
System.in.read();
}
static synchronized void sendMessage(String string)
{
System.out.println(string);
}
}

class Worker implements Runnable {
private long counter = 0;
String name;
Worker other;
Worker(String name)
{
this.name = name;
}
Worker setOther(Worker other)
{
this.other = other;
return this;
}
synchronized void showStatus()
{
Master.sendMessage(name + " " + counter +
", other: " + other.peep());
}
synchronized long peep()
{
return counter;
}
public void run()
{
while(true) {
counter++; // wykonanie pracy
showStatus(); // komunikat
}
}
}


Podczas wykonywania programu może (ale nie musi!) dojść do impasu. Dojdzie do niego na przykład przy
następującym splocie wydarzeń:

1. Wątek Tom wywołuje metodę showStatus.
2. Ponieważ showStatus jest metodą synchronizowaną, więc wątkowi Tom przydziela się monitor związany

ze zmienną Master.tom.


Niech w tym miejscu zostanie przerwane wykonywanie wątku Tom.


3. Zaczyna się wykonywać wątek Bob i wywołuje metodę showStatus.
4. Ponieważ showStatus jest metodą synchronizowaną, więc wątkowi Bob przydziela się monitor związany

ze zmienną Master.bob.

5. Metoda showStatus wywołuje metodę synchronizowaną peep.
6. Ponieważ peep jest metodą synchronizowaną, więc wątek Bob spodziewa się przydzielenia monitora

związanego ze zmienną Master.tom (zmienna other jest odnośnikiem do tej właśnie zmiennej). Monitor
jest już jednak przydzielony, więc wątek Bob zawiesza się, w oczekiwaniu na zwolnienie monitora.


Niech w tym miejscu nastąpi wznowienie wykonywania wątku Tom.


7. Wątek Tom wywołuje metodę peep.
8. Ponieważ peep jest metodą synchronizowaną, więc wątek Tom spodziewa się przydzielenia monitora

związanego ze zmienną Master.bob (zmienna other jest odnośnikiem do tej właśnie zmiennej).

background image

115

9. Monitor jest już jednak przydzielony, więc wątek Tom zawiesza się, w oczekiwaniu na zwolnienie

monitora.


Wystąpił impas. Żaden z rozpatrywanych wątków nie wykonuje się.

Zgodnie z oczekiwaniami, wykonanie programu w systemie Windows 95 dość szybko doprowadzało do impasu.
Niemal każde wykonanie dawało inną sekwencję wyjściową. Najkrótszą z uzyskanych, ale dłuższą od pustej
(sic!), przytoczono w tabeli Wyniki przed impasem. Najbardziej interesujący jest niej drugi wiersz, z którego
wynika, że Tom wykonał już 2 operacje, podczas gdy informował o wykonaniu tylko jednej!

Tabela Wyniki przed impasem

###

Tom 1, other: 0

Bob 1, other: 2

Tom 2, other: 1

Tom 3, other: 1

Tom 4, other: 1

Bob 2, other: 4

Tom 5, other: 3

###

background image

116

Część VII

Programowanie apletów


W odróżnieniu od aplikacji, która jest programem samodzielnym, aplet jest programem wbudowanym w inną
aplikację (np. w przeglądarkę Netscape). Aplikacja ta zapewnia wykonanie i wizualizację apletu.

Wraz z B-kodem apletu należy dostarczyć przeglądarce opis apletu. Opis jest zawarty w pliku z rozszerzeniem
.html. Jego nazwa jest zazwyczaj identyczna z nazwą pliku zawierającego program źródłowy apletu.

Następujący aplet, pokazany podczas wykonania na ekranie Wachlarz, wyświetla obracający się odcinek.

Ekran Wachlarz

### fan.gif


Przykład Wachlarz

<applet code=Master.class width=0 height=0>
</applet>
===========================================

import java.applet.*;
import java.awt.*;

public
class Master extends Applet implements Runnable {
private boolean stopped = false;
private final int rad = 50;
private Graphics gDC;
private Thread thread = new Thread(this);
private int size, xC, yC;
public void init()
{
gDC = getGraphics();
size = 2*rad + 10;
xC = yC = size/2;
resize(size, size);
thread.start();
}
public boolean mouseUp(Event evt, int x, int y)
{
stopped = !stopped;
return true;
}
public void run()
{
double angle = 0;
int i = 0;
while(!stopped) {
if(i++ == 40) {
repaint();
i = 0;
angle = 0;
}
int x = (int)(rad * Math.cos(angle)),
y = (int)(rad * Math.sin(angle));
gDC.drawLine(xC + x, yC - y, yC - x, yC + y);
try {
Thread.currentThread().sleep(100);
}
catch(InterruptedException e) {
thread.stop();
}
angle = angle + Math.PI / 36;
}
}
}


Rozmiary apletu określono nie w jego opisie, ale w w metodzie init programu.

background image

117

Kliknięcie lewym przyciskiem myszki powoduje zakończenie wykreślania.


_________________________________________________________________________________________

Opis


Z każdym apletem przewidzianym do wykonania w przeglądarce musi być związany dokument HTML
zawierający opis apletu.

Opis apletu musi zawierać parametry code, width i height. Argumenty zawarte we frazach param są dostępne
w programie za pomocą metody getParameter.

Składnię opisu apletu zamieszczono w tabeli Opis apletu. Wymieniony w niej TekstZastępczy jest wyświetlany
przez przeglądarkę, która nie rozpoznaje opisu apletu.

Tabela Opis apletu

###

<applet

codebase =

Ś

cie

ż

ka

code = Plik

alt = Tekst

name = Oznaczenie

width = Szeroko

ść

height = Wysoko

ść

align = Wyrównanie

vspace = Uskok

hspace = Wci

ę

cie

>

<param name = Nazwa value = Warto

ść

>

<param name = Nazwa value = Warto

ść

>

... j.w.

TekstZast

ę

pczy

</applet>

###

codebase =

Ś

cie

ż

ka

Parametr określa ścieżkę do katalogu zawierającego plik z B-kodem apletu. Ścieżka względna, np. subdir\code
jest odnoszona do katalogu, w którym znajduje się opis apletu. Ścieżką domyślną jest . (kropka).

code = Plik

Parametr podaje nazwę pliku z rozszerzeniem .class, zawierającego B-kod apletu. Przyjmuje się, że nazwę
ś

cieżki do pliku określa parametr codebase.

alt = Tekst

Parametr podaje tekst, który zostanie wyświetlony zamiast apletu przez taką przeglądarkę, która rozpoznaje
opis apletu, ale nie może wyświetlić apletu.

name = Oznaczenie

Parametr podaje nazwę apletu, odróżniającą go od innych apletów tej samej strony WWW. W celu odwołania się
do apletu przez nazwę należy w programie użyć instrukcji podobnej do

Applet myApplet = getAppletContext().getApplet(Oznaczenie);

width = Szeroko

ść

Parametr podaje początkową szerokość apletu na stronie WWW (wyrażoną w pikselach).

height = Wysoko

ść

Parametr podaje początkową wysokość apletu na stronie WWW (wyrażoną w pikselach).

align = Wyrównanie

Parametr podaje sposób usytuowania apletu na stronie WWW. Dopuszczalnymi wartościami Wyrównania są:
left, right, top, texttop, middle, absmiddle, baseline, bottom, absbottom.

background image

118

vspace = Uskok

Parametr podaje pionowy odstęp powyżej i poniżej apletu (wyrażony w pikselach).

hspace = Wci

ę

cie

Parametr podaje poziomy odstęp przed i po aplecie (wyrażony w pikselach).

<param name = Nazwa value = Warto

ść

>

Parametr umożliwia określenie parametru apletu. Parametrów param może być w opisie apletu dowolnie
wiele
. Każdy z nich określa Nazwę i Wartość argumentu (małe i duże litery są uznawana za różne). Wartości,
które zawierają odstępy i znaki specjalne, należy ująć w cudzysłowy.

W celu użycia w programie parametru apletu należy użyć instrukcji

String Value = getParameter("Name");


a następnie dokonać analizy i konwersji łańcucha Value, na przykład

int Fixed = Integer.parseInt(Value);


albo

double Real = Double.valueOf(Value).doubleValue();


Uwaga: Jeśli oczekiwany argument nie istnieje, to metoda getParameter zwraca odniesienie puste (null).

Następujący aplet, pokazany podczas wykonania na ekranie Kolory, wyświetla napis

Hello


czcionką o rozmiarze 48 punktów, w kolorze karmazynowym.

Uwaga: Kolor karmazynowy ma składniki RGB(255,0,255).

Ekran Kolory

### colors.gif

<applet
codebase = d:\applets
code = Master.class
width = 300
height = 80>
<param name = Size value = 48>
<param name = Color value = "255,0,255">
</applet>
=========================================

import java.applet.*;
import java.awt.*;

public
class Master extends Applet {
private static Color color;
private static Font font;
public void init()
{
String sizeString = getParameter("Size");
if(sizeString == null)
throw new IllegalArgumentException("No Size");
int size;
try {
size = Integer.parseInt(sizeString);
}
catch(NumberFormatException e) {
throw new IllegalArgumentException("Size");
}

background image

119

String colorString = getParameter("Color");
if(colorString == null)
throw new IllegalArgumentException("No Color");
color = getColor(colorString);
font = new Font("Courier", Font.ITALIC, size);
}
public void paint(Graphics gDC)
{
gDC.setColor(color);
gDC.setFont(font);
gDC.drawString("Hello", 100, 60);
}
Color getColor(String triplet)
{
String string = triplet + ",.", tmpString;
int rgb[] = new int [3];
for(int i = 0; i < 3 ; i++) {
int index = string.indexOf(",");
if(index < 0)
throw new IllegalArgumentException("Color");
tmpString = string.substring(0, index);
try {
rgb[i] = Integer.parseInt(tmpString);
}
catch(NumberFormatException e) {
throw new IllegalArgumentException("Color");
}
string = string.substring(index+1);
}
if(!string.equals("."))
throw new IllegalArgumentException("Color");
return new Color(rgb[0], rgb[1], rgb[2]);
}
}


Dokonano pełnej kontroli poprawności parametrów apletu. Zabieg taki jest kosztowny, ale mimo to zalecany.


_________________________________________________________________________________________

Wykonanie


W chwili rozpoznania opisu apletu, przeglądarka tworzy obiekt klasy apletu, a następnie wywołuje na jego
rzecz metodę init. Tuż przed zniszczeniem obiektu jest wywoływana metoda destroy.

W czasie istnienia obiektu są dodatkowo wywoływane metody start, stop, update i paint. Z dowolnej
procedury apletu mogą być ponadto wywoływane metody repaint, resize i reshape.

public void init()

Metoda init jest wywoływana jednokrotnie, tuż po pierwszym załadowaniu strony WWW zawierającej aplet.
Przejście na inną stronę WWW i powrót do strony zawierającej aplet nie powoduje ponownego wywołania
metody init.

public void start()

Metoda start jest wywoływana za każdym razem, gdy strona WWW zawierająca aplet staje się stroną bieżącą.

public void stop()

Metoda stop jest wywoływana za każdym razem gdy strona WWW zawierająca aplet przestaje być stroną
bieżącą.

public void update(Graphics gDC)

Metoda update jest wywoływana za każdym razem, gdy zachodzi potrzeba wykreślenia apletu. Na przykład
tuż po wykonaniu metody start oraz po wywołaniu takich metod jak repaint i resize. Domniemana metoda
update czyści pulpit apletu aktualnym kolorem tła, a następnie wywołuje metodę paint.

background image

120

public void paint(Graphics gDC)

Wywołanie metody paint służy do wykreślenia apletu. Każdy aplet, który trwale modyfikuje swój pulpit
powinien dostarczyć własną definicję metody paint. Domniemana metoda paint nie robi nic. Kilka kolejnych
wywołań metody paint może być zastąpione przez System tylko jednym jej wykonaniem.

public void repaint()
public void repaint(long time)
public void repaint(int x, int y, int width, int height)

Wywołanie metody repaint jest apelem, aby System w miarę szybko, ale w wybranej przez siebie chwili,
wywołał metodę update (która z kolei wywoła paint). O ile nie przedefiniowano tych metod, doprowadzi to do
wyczyszczenia i odtworzenia pulpitu. Metoda repaint może być wywołana z argumentami określającymi
prostokątny obszar obcinania ograniczający obszar odtwarzania pulpitu. Jeśli zostanie wywołana z
argumentem time, a System nie zdoła uwzględnić apelu w podanym czasie, to apel ma być zignorowany.

public void resize(int width, int height)

Wywołanie metody resize powoduje zmianę rozmiarów apletu. Bezpośrednio po uwzględnieniu nowych
rozmiarów, System wywołuje metodę repaint.

public void destroy()

Metoda destroy jest wywoływana jednokrotnie, tuż przed jawnym zniszczeniem apletu (na przykład w chwili
zamknięcia przeglądarki). Metoda destroy jest zazwyczaj wykorzystywana do zwolnienia zasobów
przydzielonych apletowi.


Następujący aplet, pokazany podczas wykonania na ekranie Obsługa apletu, wyświetla w odrębnym oknie
informacje o wywoływanych zewnętrznie metodach jego klasy.

Ekran Obsługa apletu

### methods.gif

<applet code=Master.class width=160 height=80>
</applet>
=============================================

import java.applet.*;
import java.awt.*;

public
class Master extends Applet {
Frame frame;
int yPos = 20;
Graphics gDC;
public void init()
{
frame = new MyFrame("Msg");
frame.resize(100, 200);
frame.show();
gDC = frame.getGraphics();
sendMsg("init");
}
void sendMsg(String msg)
{
gDC.drawString(msg, 10, yPos);
yPos = yPos + 20;
}
public void start()
{
sendMsg("start");
}
public void stop()
{
sendMsg("stop");
}
public void update(Graphics gDC)
{
sendMsg("update");
super.update(gDC);

background image

121

}
public void paint(Graphics gDC)
{
gDC.drawString("Click me!", 10, 20);
sendMsg("paint");
}
public boolean mouseUp(Event evt, int x, int y)
{
sendMsg("mouseUp");
return true;
}
}

class MyFrame extends Frame {
MyFrame(String caption)
{
super(caption);
}
public void update(Graphics gDC)
{
}
}


Gdyby instrukcję

frame = new MyFrame("Msg");


zastąpiono instrukcją

frame = new Frame("Msg");


to po wywołaniu predefiniowanej metody update klasy Frame (co nastąpiłoby po wykonaniu metody start),
nast
ąpiłoby wyczyszczenie napisu

init


już wysłanego do okna.

Wynika stąd wniosek, że posługując się odrębnymi oknami tworzonymi przez aplet, należy unikać wykreślania w
nich tekstów i grafiki podczas wykonywania metod init i start.


_________________________________________________________________________________________

Otoczenie


Każdy aplet ma otoczenie, które może rozpoznać i z którym może się komunikować. Podstawowymi
metodami do rozpoznania otoczenia apletu są getCodeBase i getDocumentBase. Wysyłanie krótkich
komunikatów do otoczenia odbywa się za pomocą metody showStatus.

Lokalizator


Lokalizowanie zasobów otoczenia odbywa się za pomocą lokalizatora zasobów (Uniform Resource Locator).
W ogólnym wypadku, parametrami lokalizatora są: nazwa protokołu komunikacyjnego, najczęściej HTTP
(Hypertext Transfer Protocol), nazwa komputera-gospodarza (host), numer portu (dla HTTP zwyczajowo 80)
oraz nazwa pliku.

Informacje o lokalizatorze są przechowywane w obiekcie klasy URL (Uniform Resource Locator) inicjowanym
na przykład za pomocą argumentu

http://www.microsoft.com:80/index.html


background image

122

public URL getCodeBase()

Metoda dostarcza odnośnik do obiektu klasy URL identyfikującego katalog, w którym znajduje się B-kod
apletu.

public URL getDocumentBase()

Metoda dostarcza odnośnik do obiektu klasy URL identyfikującego katalog, w którym znajduje się dokument
HTML z opisem apletu.

public void showStatus(String msg)

Wywołanie metody powoduje wyświetlenie komunikatu o stanie apletu.


Uwaga: Ujawnienie okna konsoli w przeglądarce Netscape wymaga wydania w niej polecenia Options/Show
Java Console
.

Następujący aplet, pokazany podczas wykonania na ekranie Pęcherzyki, wyświetla obraz zawarty w pliku. Plik
znajduje się w tym samym katalogu co B-kod apletu.

Ekran Pęcherzyki

### bubbles.gif

<applet code=Master.class width=160 height=100>
</applet>
===============================================

import java.applet.*;
import java.awt.*;
import java.net.*;

public
class Master extends Applet {
Image myImage;
public void init()
{
URL codeBase = getCodeBase();
myImage = getImage(codeBase, "Bubbles.gif");
}
public void paint(Graphics gDC)
{
gDC.drawImage(myImage, 10, 10, this);
}
}


Wykonanie programu powoduje wyświetlenie obrazu znajdującego się w pliku Bubbles.gif.


_________________________________________________________________________________________

Grafika



Dołączona do języka biblioteka AWT umożliwia wykreślanie podstawowych obiektów graficznych: tekstów,
odcinków, prostokątów, elips, okręgów, łuków i wielokątów. Wykreślanie odbywa się za pomocą metod klasy
abstrakcyjnej Graphics.

public void drawString(String str, int x, int y)

Wykreślenie tekstu str w prostokącie, którego lewy-dolny narożnik ma współrzędne (x,y).

public void drawLine(xA, yA, xZ, yZ)

Wykreślenie odcinka łączącego punkty o współrzędnych (xA, yA) i (xZ, yZ).

public void drawRect(int x, int y, int w, int h)

background image

123

Wykreślenie prostokąta o współrzędnych lewego-górnego wierzchołka (x,y) i rozmiarach w x h pikseli (w -
szerokość, h - wysokość).

public void drawOval(int x, int y, int w, int h)

Wykreślenie owalu (okręgu albo elipsy) wpisanego w niewidoczny prostokąt, o współrzędnych lewego-górnego
wierzchołka (x,y) i rozmiarach w x h pikseli.

public void drawArc(int x, int y, int w, int h,
int f, int t)

Wykreślenie łuku wpisanego w niewidoczny prostokąt, o współrzędnych lewego-górnego wierzchołka (x,y) i
rozmiarach w x h pikseli, od kąta początkowego f do kąta końcowego t.

public void drawPolygon(int x[], int y[], int n)

Wykreślenie linii łamanej łączącej n punktów o współrzędnych (x[i], y[i]).

public void fillRect(int x, int y, int w, int h)

Wykreślenie wypełnionego prostokąta (por. drawRect).

public void fillOval(int x, int y, int w, int h)

Wykreślenie wypełnionego owalu (por. drawOval).

public void fillPolygon(int x[], int y[], int n)

Wykreślenie wypełnionego wielokąta (por. drawPolygon).

Kontekst


Z każdym dającym się wykreślić komponentem jest związany kontekst graficzny. Informacje o kontekście
znajdują się w obiekcie wykreślacza. Odnośnik do wykreślacza jest dostępny poprzez parametr metody paint.
Można go także otrzymać za pomocą metody getGraphics.

public Graphics getGraphics()

Metoda zwraca odnośnik do wykreślacza związanego z komponentem, albo odniesienie null, jeśli komponent
nie może pojawić się na ekranie.


Uwaga: Zaleca się, aby po umieszczeniu w wykreślaczu pewnej informacji (np. rodzaju czcionki albo koloru
pióra) przywrócono jego stan pierwotny. Dzięki temu uniknie się kolizji wynikających z użycia tego samego
wykreślacza przez różne komponenty.

Na przykład

public void draw(Graphics gDC)
{
Font oldFont = gDC.getFont();
Color oldColor = gDC.getColor();
// ...
Font font = new Font("TimesRoman", Font.BOLD, 80);
gDC.setFont(font);
gDC.setColor(Color.red);
gDC.drawString("Hello", 10, 20);
// ...
gDC.setColor(oldColor);
gDC.setFont(oldFont);
}


Po zakończeniu wykonywania metody draw, wykreślacz identyfikowany przez odnośnik gDC znajduje się w
takim samym stanie jak w chwili rozpocz
ęcia wykonywania tej metody.

Następujący aplet, pokazany podczas wykonania na ekranie Konteksty graficzne, ilustruje zasady posługiwania
się wykreślaczami.

background image

124


Ekran Konteksty graficzne

### getgdc.gif

<applet code=Master.class width=160 height=100>
</applet>
===============================================

import java.applet.*;
import java.awt.*;

public class Master extends Applet {
public void paint(Graphics gDC)
{
gDC.drawString("Press to draw a Circle", 20, 20);
}
public boolean mouseDown(Event evt, int x, int y)
{
Graphics gDC = getGraphics();
gDC.drawOval(x-10, y-10, 20, 20); // wykre

ś

lenie

return true;
}
public boolean mouseUp(Event evt, int x, int y)
{
Graphics gDC = getGraphics();
update(gDC); // wyczyszczenie
return true;
}
}


Wywołanie metody getGraphics powoduje dostarczenie odnośnika do wykreślacza związanego z pulpitem
apletu.

Naciśnięcie klawisza myszki powoduje wykreślenie okręgu o środku w punkcie, w którym znajduje się kursor, a
zwolnienie przycisku powoduje usuni
ęcie okręgu z ekranu.

Współrzędne


Układ współrzędnych pojemnika (np. ramki apletu) ma punkt początkowy w lewym-górnym narożniku. Odcięte
(x) zwiększają się prawo, a rzędne (y) do dołu.

Podczas wykreślania obiektu podaje się lewą-górną współrzędną oraz rozmiary: poziomy i pionowy.

public void paint(Graphics gDC)
{
gDC.drawRect(10, 20, 100, 200); // (x, y, w, h)
}


Wyjątek od tej zasady dotyczy wykreślania napisów. W tym wypadku podaje się współrzędne lewego-dolnego
narożnika niewidocznej linii na której spoczywa napis.

Następujący aplet, pokazany podczas wykonania na ekranie Współrzędne komponentów, ilustruje zasadę
określania współrzędnych napisów i grafiki.

Ekran Współrzędne komponentów

### over.gif

<applet code=Master width=160 height=60>
</applet>
=========================================

import java.applet.*;
import java.awt.*;

public

background image

125

class Master extends Applet {
public void paint(Graphics gDC)
{
gDC.drawString("Text over rectangle", 10, 20);
gDC.drawRect(10, 20, 80, 5);
}
}


Mimo iż w obu przypadkach podano te same współrzędne narożnika, niewidoczny prostokąt, w który wpisano
napis nie pokrywa si
ę z prostokątem wykreślonym jawnie.

Pulpit


Ta część obszaru pojemnika, na której odbywa się wykreślanie grafiki (odcinków, owali, prostokątów, itp.) jest
jego pulpitem. Podczas wstawiania komponentów do pojemnika używa się współrzędnych pojemnika, ale
podczas wykreślania grafiki oraz obsługiwania zdarzeń związanych z operacjami wykonywanymi za pomocą
myszki używa się współrzędnych pulpitu.

Wcięcia


Obszar pulpitu jest wcięty względem obszaru pojemnika. Rozmiary pojemnika można określić za pomocą
metody size, a rozmiary wcięć za pomocą metody insets.

Uwaga: Domniemane wcięcia dla apletu mają wartość 0. A zatem obszar i pulpit apletu pokrywają się. W
systemie Windows 95 domniemane wcięcia dla ramki mają wartości 4, 23, 4, 4.

public Dimension size()

Metoda zwraca odnośnik do obiektu, którego pola width i height określają poziomy i pionowy rozmiar obszaru
pojemnika.

public Insets insets()
Metoda zwraca odnośnik do obiektu, którego pola left (lewe), top (górne), right (prawe) i bottom (dolne)
określają wcięcia krawędzi pulpitu względem krawędzi obszaru pojemnika.


Następujący aplet, pokazany podczas wykonania na ekranie Pulpit apletu i ramki, ilustruje zasady
uwzględniania wcięć.

Ekran Pulpit apletu i ramki

### insets.gif

<applet code=Master width=160 height=100>
</applet>
=========================================

import java.applet.*;
import java.awt.*;

public
class Master extends Applet {
Frame frame;
Button hello, world;
int w, h;
public void init()
{
setLayout(null);
add(hello = new Button("Hello"));
hello.reshape(1, 1, 60, 40);

frame = new MyFrame("Frame View");
Insets ins = frame.insets();

background image

126

frame.add(world = new Button("World"));
int l = ins.left, // 4
t = ins.top, // 23
r = ins.left, // 4
b = ins.bottom; // 4
world.reshape(l+1, t+1, 60, 40);
Dimension size = size(); // 160 x 100
int w = size.width + (l + r),
h = size.height + (t + b);
frame.resize(w, h);
}
public void paint(Graphics gDC)
{
Dimension dim = size();
Insets ins = insets();
int w = dim.width - (ins.left + ins.right),
h = dim.height - (ins.top + ins.bottom);
gDC.drawRect(0, 0, w-1, h-1);
}
public boolean mouseUp(Event evt, int x, int y)
{
Dimension dim = size();
Insets ins = insets();
int w = dim.width,
h = dim.height;
Graphics gDC = getGraphics();
int xC = w/2,
yC = h/2;
gDC.drawLine(x, y, xC, yC);
return true;
}
}

class MyFrame extends Frame {
MyFrame(String caption)
{
super(caption);
show();
}
public void paint(Graphics gDC)
{
Dimension dim = size();
Insets ins = insets();
int w = dim.width - (ins.left + ins.right),
h = dim.height - (ins.top + ins.bottom);
gDC.drawRect(0, 0, w-1, h-1);
}
public boolean mouseUp(Event evt, int x, int y)
{
Dimension dim = size();
Insets ins = insets();
int w = dim.width - (ins.left + ins.right),
h = dim.height - (ins.top + ins.bottom);
Graphics gDC = getGraphics();
int xC = w/2,
yC = h/2;
gDC.drawLine(x, y, xC, yC);
return true;
}
}


Program napisano w taki sposób, aby rozmiar pulpitu ramki był identyczny z rozmiarem pulpitu apletu. W celu
uwidocznienia pulpitów wykre
ślono na nich prostokąty o maksymalnych rozmiarach.

Jak wynika z rozpatrzenia kodu programu, umieszczenie przycisku na pulpicie ramki oraz wykreślenie odcinka
ł
ączącego punkt kliknięcia ze środkiem ramki wymaga uwzględnienia wcięć. Problemy te nie występują dla
apletów.

Obcinanie

background image

127


Jeśli wykres nie mieści się na pulpicie apletu, to jest obcinany. Dzięki temu nic nie stoi na przeszkodzie
"wykreślania" obiektów, które nie mieszczą się na pulpicie.

Domyślnym obszarem obcinania jest pulpit apletu. Za pomocą metody clipRect wywołanej na rzecz
wykreślacza, można jako obszar obcinania określić dowolny inny prostokąt.

public void clipRect(int x, int, y, int width, int height)

Wywołanie metody powoduje określenie jako obszaru obcinania, prostokąta o współrzędnych narożnika (x,y) i
rozmiarach w x h..

W następującym programie, pokazanym podczas wykonywania na ekranie Obszar obcinania, wykreślono
prostokąt, a następnie zajęty przezeń obszar zdefiniowano jako obszar obcinania. Dlatego wykres jednego z
okręgów jest obcięty do wybranego prostokąta.

Ekran Obszar obcinania

### clip.gif


Przykład Obcinanie

<applet code=Master width=160 height=80>
</applet>
=========================================

import java.applet.*;
import java.awt.*;

public
class Master extends Applet {
public void paint(Graphics gDC)
{
gDC.drawRect(25, 25, 50, 50);
gDC.clipRect(25, 25, 50, 50);
gDC.drawOval(0, 25, 50, 50);
Dimension size = this.size();
int w = size.width, h = size.height;
gDC.clipRect(0, 0, w, h);
gDC.drawOval(50, 25, 50, 50);
}
}


Przez ustawienie jako obszaru obcinania całego pulpitu apletu, przywrócono obcinanie domyślne.

Czcionki


Czcionkę charakteryzuje rozmiar, krój i styl. Na przykład tytuł niniejszego podrozdziału jest napisany 14-
punktow
ą czcionką TimesRoman, w stylu Bold.

Mając na względzie przenośność wyglądu czcionki zaleca się posługiwać tylko czcionkami o nazwach
podanych w tabeli Czcionki (dla porównania podano ich odpowiedniki w systemie Windows).

Uwaga: Czcionka domyślna ma rozmiar 12 punktów i styl zwykły.

Tabela Czcionki

###

Java

Windows


TimesRoman

Times New Roman

Helvetica

Arial

Courier

Courier New

Dialog

MS Sans Serif

Symbols

WingDing

background image

128

###


Do zapisania stylu można posłużyć się symbolami podanymi w tabeli Style czcionki.

Tabela Style czcionki

###

Styl

Symbol


zwykły

Font.PLAIN

kursywa

Font.ITALIC

pogrubiony

Font.BOLD

pogrubiona kursywa

Font.BOLD + Font.ITALIC

###



Czcionka jest reprezentowana przez obiekt klasy Font. W wywołaniu jej konstruktora określa się krój, styl i
rozmiar czcionki.

public Font(String name, int style, int size)

Konstruktor tworzy obiekt czcionki o podanym kroju, stylu i rozmiarze. Jeśli w danym Systemie taka czcionka
nie istnieje, to zostanie utworzona czcionka maksymalnie zbliżona do wymaganej.


Następujący program, pokazany podczas wykonania na ekranie Kroje i style, wyświetla zestaw 12 pt czcionek
o różnych krojach i stylach.

Ekran Kroje i style

### typeface.gif

<applet code=Master.class width=160 height=200>
</applet>
===============================================

import java.applet.*;
import java.awt.*;

public
class Master extends Applet {
String string;
String typeFace[] = {
"TimesRoman",
"Helvetica",
"Courier"
};
int style[] = {
Font.PLAIN,
Font.BOLD,
Font.ITALIC,
Font.BOLD + Font.ITALIC
};
String styleName[] = {
" plain",
" bold",
" italic",
" bold-italic"
};
public void paint(Graphics gDC)
{
for(int n = 1, i = 0; i < typeFace.length ; i++) {
String fontName = typeFace[i];
for(int j = 0; j < style.length ; j++) {
int fontStyle = style[i];
Font font = new Font(fontName, fontStyle, 12);
gDC.setFont(font);
string = fontName + styleName[j];
gDC.drawString(string, 10, 15 * n++);
}

background image

129

}
}
}


Metryka


Kompletny zestaw właściwości czcionki opisuje jej metryka. Metody operujące na metryce należą do klasy
abstrakcyjnej FontMetrics.

Uwaga: Badanie metryki może dotyczyć tylko czcionki takiego komponentu, który może być wykreślony na
ekranie.

Ważnymi parametrami metryki są

uniesienie

(ascent)

odległość między liną bazową, a szczytem znaku,
jak w literze Ý,

obniżenie

(descent)

odległość między linią bazową, a podstawą znaku,
jak w literze g,

ś

wiatło

(leading)

odstęp poniżej podstawy znaku,

wysokość

(height)

suma uniesienia, obniżenia i światła.


Uwaga: Linią bazową (base line) jest linia na której spoczywają znaki, np. jak w Hello.

public int getAscent()

Metoda zwraca uniesienie w pikselach.

public int getDescent()

Metoda zwraca obniżenie w pikselach.

public int getLeading()

Metoda zwraca światło w pikselach.

public int getHeight()

Metoda zwraca wysokość czcionki w pikselach.


Następujący program, pokazany podczas wykonania na ekranie Metryka czcionki, ilustruje posługiwanie się
ważniejszymi parametrami czcionki.

Ekran Metryka czcionki

### metrics.gif

<applet code=Master.class width=300 height=150>
</applet>
===============================================

import java.applet.*;
import java.awt.*;

public
class Master extends Applet {
Font font = new Font("Arial", Font.BOLD, 80);
static String string = "ÝfgA";
static int xBase = 10, yBase;
int strAscent, strDescent, strHeight, strLeading;
static int strWidth;
public void init()
{
Toolkit toolKit = Toolkit.getDefaultToolkit();
FontMetrics mtx = toolKit.getFontMetrics(font);
strWidth = mtx.stringWidth(string);
strAscent = mtx.getAscent();

background image

130

strDescent = mtx.getDescent();
strHeight = mtx.getHeight();
strLeading = strHeight - (strAscent + strDescent);
yBase = strHeight + 10;
}
public void paint(Graphics gDC)
{
gDC.setFont(font);
gDC.drawString(string, xBase + 50, yBase);
draw(gDC, "BaseLine", 0);
draw(gDC, "AscentLine", strAscent);
draw(gDC, "DescentLine", -strDescent);
draw(gDC, "", -strDescent-strLeading);
}
static void draw(Graphics gDC, String str, int h)
{
int xBase = Master.xBase,
yBase = Master.yBase;
gDC.drawLine(xBase, yBase-h,
xBase+ 50 + 10 + strWidth, yBase-h);
Font oldFont = gDC.getFont(),
newFont = new Font("TimesRoman", Font.ITALIC, 10);
gDC.setFont(newFont);
gDC.drawString(str, xBase, yBase-h);
gDC.setFont(oldFont);
}
}


Kolory


Wzorcowym modelem koloru jest RGB. W modelu RGB każdy kolor można przedstawić jako trójkę
składników

RGB


w której R (red), G (green), B (blue) określają ile w kolorze jest składnika czerwonego, zielonego i
niebieskiego.

W szczególności, kolor RGB(255,255,0), w którym występuje maksymalna ilość czerwieni i zieleni, ale nie ma
składnika niebieskiego, jest kolorem żółtym.

W programie wynikowym kolor jest reprezentowany przez czwórkę bajtów

aRGB


w której a jest składnikiem alfa, reprezentującym przeźroczystość koloru, a R, G, B są składnikami koloru o
wartościach z przedziału 0..255 włącznie.

Jeśli składnik alfa ma wartość 0, to kolor jest całkowicie przeźroczysty (transparent), a jeśli ma wartość 255, to
jest całkowicie nieprzeźroczysty (opaque). Domniemaną wartością alfa jest 255.

Kilkanaście typowych kolorów ma oznaczenia symboliczne podane w tabeli Symbole kolorów.

Tabela Symbole kolorów

###

Color.black

Color.blue

Color.cyan

Color.darkGray

Color.gray

Color.green

Color.lightGray

Color.magenta

Color.orange

Color.pink

Color.red

Color.white

Color.yellow

###


background image

131

Kolor bieżący


Z każdym kontekstem graficznym jest związany kolor bieżący. Ustawienie i pobranie koloru bieżącego odbywa
się za pomocą procedur setColor i getColor.

public void setColor(Color color)

Metoda ustawia kolor bieżący na kolor podany.

public Color getColor()

Metoda zwraca odnośnik do odrębnego obiektu opisującego kolor bieżący.


Następujący program, pokazany podczas wykonywania na ekranie Model kolorów, ilustruje sposób
przekazywania informacji o kolorze z opisu apletu

Ekran Model kolorów

### rgbcolor.gif

<applet code=Master.class width=160 height=60>
<param name=Color value=ff00ff>
</applet>
==============================================

import java.applet.*;
import java.awt.*;

public
class Master extends Applet {
Color color;
int rgbColor = 0;;
public void init()
{
String color = getParameter("Color");
try {
rgbColor = Integer.parseInt(color, 16);
}
catch(NumberFormatException e) {
showStatus("Wrong format");
}
this.color = new Color(rgbColor);
}
public void paint(Graphics gDC)
{
gDC.setColor(color);
gDC.drawString("Hello", 20, 20);
}
}


Wykonanie programu powoduje wyświetlenie napisu Hello w kolorze purpurowym.

Gdyby parametrowi Color nadano wartość 0xff00ff, to spowodowałoby to wysłanie wyjątku klasy
NumberFormatException, a napis zostałby wy
świetlony w kolorze czarnym.

Kolor lica i kolor tła


Z każdym komponentem są związane dwa kolory: kolor lica (foreground) i kolor tła (background). Do
zarządzania nimi służą metody getForeground i setForeground oraz getBackground i setBackground.

public void setForeground(Color color)

Metoda ustawia kolor lica komponentu na podany.

public void setBackground(Color color)

background image

132

Metod ustawia kolor tła komponentu na podany.

public Color getForeground()

Metoda zwraca kolor lica komponentu.

public Color getBackground()

Metoda zwraca kolor tła komponentu.


Następujący aplet, pokazany podczas wykonania na ekranie Kolory lica i tła, ilustruje zasadę określania koloru
komponentów.

Ekran Kolory lica i tła

### foreback.gif

<applet code=Master width=160 height=100>
</applet>
=========================================

import java.applet.*;
import java.awt.*;

public
class Master extends Applet {
Button button;
public void init()
{
add(button = new Button("OK"));
setBackground(Color.white);
setForeground(Color.red);
}
public void paint(Graphics gDC)
{
gDC.drawOval(10, 10, 80, 80);
}
public boolean action(Event evt, Object arg)
{
boolean flag = evt.target == button;
if(flag)
button.setBackground(Color.black);
return flag;
}
}


Kliknięcie przycisku powoduje zmianę jego koloru na czarny.

Tryb XOR


Często istnieje potrzeba wykreślenia i wytarcia wykreślonego obiektu, ale bez naruszenia tła. Do tego celu
dobrze nadaje się wykreślanie w trybie XOR. Ma ono tę właściwość, że dwukrotne wykreślenie tego samego
obiektu przywraca pierwotny stan tła.

public void setXORMode(Color color)

Metoda przygotowuje wykreślacz do wykreślania w trybie XOR z kolorem color. Powoduje to, że piksel
wykreślony w kolorze bieżącym zachowa ten kolor na tle o podanym kolorze, a na tle w swoim kolorze
przyjmie podany kolor. Zmiana pozostałych kolorów nie jest przewidywalna, ale po ponownym wykreśleniu
nastąpi przywrócenie kolorów pierwotnych.


Następujący aplet, pokazany podczas wykonania na ekranie Odtwarzanie tła, umożliwia przeciąganie okręgu
bez naruszenia kolorowego napisu stanowiącego tło.

background image

133

Ekran Odtwarzanie tła

### xormode.gif


Przykład Odtwarzanie tła

<applet code=Master width=400 height=100>
</applet>
=========================================

import java.applet.*;
import java.awt.*;

public
class Master extends Applet {
Font font = new Font("TimesRoman", Font.BOLD, 60);
final int r = 20;
int xOld, yOld;
public void init()
{
setBackground(Color.green);
}
public void paint(Graphics gDC)
{
gDC.setFont(font);
gDC.setColor(Color.red);
gDC.drawString("Drag over me!", 10, 60);
}
public boolean mouseDown(Event evt, int x, int y)
{
drawRing(xOld = x, yOld = y);
return true;
}
public boolean mouseDrag(Event evt, int x, int y)
{
drawRing(xOld, yOld);
drawRing(xOld = x, yOld = y);
return true;
}
public boolean mouseUp(Event evt, int x, int y)
{
drawRing(x, y);
return true;
}
void drawRing(int x, int y)
{
Graphics gDC = getGraphics();
gDC.setColor(Color.red);
gDC.setXORMode(Color.green);
int l = x-r, t = y-r, w = 2*r, h = 2*r;
for(int i = 0; i < 5 ; i++)
gDC.drawOval(l+i, t+i, w-2*i, h-2*i);
}
}


W celu uproszczenia kodu apletu nie obsłużono sytuacji kiedy przeciąganie zakończy się poza pulpitem apletu.
W takim przypadku przeci
ągany pierścień nie zostanie usunięty z ekranu.


_________________________________________________________________________________________

Myszka


W typowych przypadkach nie jest istotne, który z przycisków myszki został użyty do wykonania akcji. Jednak
zbadanie stanu modyfikatorów Alt (przycisk środkowy) i Meta (przycisk prawy) umożliwia dokonanie takiego
rozstrzygnięcia.

background image

134

Jeśli myszka jest wyposażona w przycisk związany z modyfikatorem Alt albo Meta, to jest on ustawiany
niejawnie. W przeciwnym razie ustawienie modyfikatora musi być zasymulowane przez naciśnięcie klawisza
Alt albo Meta.

W szczególności, jeśli myszka jest dwu-przyciskowa, to użycie prawego przycisku powoduje niejawne
ustawienie modyfikatora Meta, ale w celu zasymulowania środkowego przycisku (i ustawienia modyfikatora
Alt) należy wraz z użyciem przycisku myszki, dodatkowo nacisnąć klawisz Alt.

Uwaga: Opisany efekt można zaobserwować wykonując aplet pod przeglądarką Netscape. W środowisku Cafe
naciśnięcie klawisza Alt powoduje wyróżnienie menu Applet, a więc nie może być rozpoznane przez badanie
modyfikatora.

W tabeli Maski klawiszy wymieniono maski związane z modyfikatorami Alt i Meta oraz dodatkowo maski
klawiszy Shift i Ctrl.

Tabela Maski klawiszy

###

Event.SHIFT_MASK

naci

ś

ni

ę

to klawisz Shift

Event.CTRL_MASK

naci

ś

ni

ę

to klawisz Ctrl

Event.ALT_MASK

naci

ś

ni

ę

to klawisz Alt

Event.META_MASK

naci

ś

ni

ę

to klawisz Meta

###


Następujący aplet, pokazany podczas wykonania na ekranie Przyciski myszki, wyświetla nazwę naciśniętego
klawisza myszki.

Ekran Przyciski myszki

### mouse.gif

<applet code=Master.class width=160 height=100>
</applet>
===============================================

import java.applet.*;
import java.awt.*;

public
class Master extends Applet {
public void paint(Graphics gDC)
{
gDC.drawString("Press mouse button!", 10, 20);
}
public boolean mouseDown(Event evt, int x, int y)
{
int mods = evt.modifiers;
String button;
if((mods & Event.ALT_MASK) != 0)
button = "MIDDLE";
else if((mods & Event.META_MASK) != 0)
button = "RIGHT";
else
button = "LEFT";
Graphics gDC = getGraphics();
gDC.drawString("You pressed " + button +
" button!", x, y);
return true;
}
public boolean mouseUp(Event evt, int x, int y)
{
repaint();
return true;
}
}


Jeśli zostanie naciśnięty prawy klawisz myszki, to w punkcie kliknięcia wyświetli się napis

You pressed RIGHT button!

background image

135



_________________________________________________________________________________________

Klawiatura


Zdarzenia KEY_PRESS i KEY_RELEASE dotyczą zwykłych klawiszy (wszystkie znaki drukowalne, a
ponadto: Enter, Tab, Esc, Backspace, Del), a zdarzenia KEY_ACTION i KEY_ACTION_RELEASE dotyczą
klawiszy funkcyjnych.

Klawisze funkcyjne mają oznaczenia podane w tabeli Klawisze funkcyjne.

Tabela Klawisze funkcyjne

###

Event.HOME

klawisz Home

Event.END

klawisz End

Event.PGUP

klawisz PgUp

Event.PGDOWN

klawisz PgDn

Event.UP

klawisz Up (strzalka w gór

ę

)

Event.DOWN

klawisz Dn (strzałka w dół)

Event.LEFT

klawisz Lt (strzałka w lewo)

Event.RIGHT

klawisz Rt (strzałka w prawo)


Event.F1

klawisz F1

Event.F2

klawisz F2

...

...

Event.F12

klawisz F12

###


Badanie, czy wraz z naciśnięciem klawisza naciśnięto jeden z klawiszy modyfikujących: Shift, Ctrl, Alt, Meta
odbywa się za pomocą masek.

Następujący aplet, pokazany podczas wykonania na ekranie Nazwy klawiszy, wyświetla nazwę naciśniętego
klawisza.

Ekran Nazwy klawiszy

### keys.gif

<applet code=Master.class width=360 height=80>
</applet>
==============================================

import java.applet.*;
import java.awt.*;

public
class Master extends Applet {
Font font = new Font("Arial", Font.BOLD, 60);
String keyName = "";
public void paint(Graphics gDC)
{
gDC.setFont(font);
gDC.drawString(keyName, 20, 60);
}
public boolean keyDown(Event evt, int key)
{
if(evt.id == Event.KEY_PRESS)
keyName = getModKeys(evt) + getRegKeyName(evt);
else if(evt.id == Event.KEY_ACTION)
keyName = getModKeys(evt) + getFunKeyName(evt);
repaint();
return true;
}
String getModKeys(Event evt)
{
int flags = evt.modifiers;
boolean notEnter = evt.key != 10;
String string = "";

background image

136

if(evt.shiftDown() && notEnter)
string += "Shift-";
if((flags & Event.ALT_MASK) != 0 && notEnter)
string += "Alt-";
if(evt.metaDown() && notEnter)
string += "Meta-";
if(evt.controlDown() && notEnter)
string += "Ctrl-";
return string;
}
String getRegKeyName(Event evt)
{
if(evt.key == 10)
return "Enter";
char chr = (char)evt.key;
if(evt.controlDown() && chr < ' ')
return "" + (char)(chr + '`');
else switch(chr) {
case '\t':
return "Tab";
case '\33':
return "Esc";
case '\10':
return "Backspace";
}
if(chr == '\177')
return "Delete";
if(chr == ' ')
return "Space";
return "" + chr;
}
String getFunKeyName(Event evt)
{
int key = evt.key;
switch(key) {
case Event.HOME:
return "Home";
case Event.END:
return "End";
case Event.PGUP:
return "PgUp";
case Event.PGDN:
return "PgDn";
case Event.UP:
return "Up";
case Event.DOWN:
return "Down";
case Event.LEFT:
return "Lt";
case Event.RIGHT:
return "Rt";
default:
int offF1 = key - Event.F1;
return "F" + (1 + offF1);
}
}
}



_________________________________________________________________________________________

Kursory


Podczas wykonywania operacji graficznych w oknach można posługiwać się zestawem standardowych
kursorów.

Kształt kursora identyfikują liczby całkowite, które można wyrażać za pomocą symboli podanych w tabeli
Kursory. Wygląd kursora zależy od użytego systemu. W Windows 95 kursor oczekiwania (delay) ma wygląd
klepsydry, a w MacOS ma wygląd zegarka.

background image

137

Tabela Kursory

###

Kursor

Wygląd

Frame.DEAFAULT_CURSOR

wska

ź

nik

Frame.TEXT_CURSOR

karetka

Frame.WAIT_CURSOR

oczekiwanie

Frame.CROSSHAIR_CURSOR

krzy

ż

ak

Frame.HAND_CURSOR

dło

ń

###


public int getCursorType()

Metoda zwraca liczbę identyfikującą kursor (np. Frame.TEXT_CURSOR).

public void setCursor(int shape)

Metoda określa kształt kursora jaki ma obowiązywać w oknie.


Następujący aplet, pokazany podczas wykonania na ekranie Kształt kursora, umożliwia wykreślanie prostokąta
za pomocą kursora w kształcie krzyżaka (za sprawą programu PaintShopPro, użytego do zdejmowania
ekranów, krzyżak nie jest widoczny na przytoczonej ilustracji).

Ekran Kształt kursora

### cursor.gif

<applet code=Master.class width=160 height=60>
</applet>
==============================================

import java.applet.*;
import java.awt.*;

public
class Master extends Applet {
Frame frame;
public void init()
{
frame = new MyFrame("Draw a box");
}
public void paint(Graphics gDC)
{
gDC.drawString("Drag in FRAME", 10, 25);
}
}

class MyFrame extends Frame {
Graphics gDC;
MyFrame(String caption)
{
super(caption);
resize(150, 120);
show();
gDC = getGraphics();
}
int xAnchor, yAnchor,
xOld, yOld;
Color backColor;
public boolean mouseDown(Event evt, int x, int y)
{
xAnchor = xOld = x;
yAnchor = yOld = y;
backColor = getBackground();
return true;
}
int abs(int par)
{
if(par < 0)

background image

138

return -par;
return par;
}
int min(int one, int two)
{
if(one < two)
return one;
return two;
}
public boolean mouseDrag(Event evt, int x, int y)
{
Color color = gDC.getColor();
gDC.setColor(backColor);
int w = xOld - xAnchor,
h = yOld - yAnchor;
int l = min(xAnchor, xOld),
t = min(yAnchor, yOld);
gDC.drawRect(l, t, abs(w), abs(h));
gDC.setColor(color);
w = (xOld = x) - xAnchor;
h = (yOld = y) - yAnchor;
l = min(xAnchor, x);
t = min(yAnchor, y);
gDC.drawRect(l, t, abs(w), abs(h));
return true;
}
public boolean mouseUp(Event evt, int x, int y)
{
repaint();
return true;
}
}


Wykreślany prostokąt znika w chwili zwolnienia przycisku myszki.

_________________________________________________________________________________________

Obrazy


Obrazy są ładowane z plików. W celu załadowania obrazu, znajdującego się w pliku sieci globalnej, należy
podać jego lokalizator URL.

Ponieważ często zdarza się, że ładowany obraz znajduje się w tym samym katalogu co kod albo opis apletu,
dobrze jest pamiętać o metodach getCodeBase i getDocumentBase.

Rozpoznawane są tylko obrazy zapisane w formatach GIF i JPEG. Inne formaty wymagają własnego
dekodowania.

Format GIF stosuje kompresję bezstratną, a format JPEG stosuje transformację z utratą elementów obrazu.
Dlatego format GIF jest przydatny do przekazywania tekstów i zrzutów ekranu, a format JPEG do
przekazywania fotografii.

Ładowanie obrazów odbywa się za pomocą metod getImage, a ich wykreślanie za pomocą metod drawImage.
Jeśli aplet podejmie próbę załadowania obrazu z innego miejsca niż sam pochodzi, to zostanie wysłany wyjątek
klasy SecurityException.

public Image getImage(URL whereURL)

Zapoczątkowanie pobrania obrazu z miejsca określonego przez lokalizator whereUrl.

public Image getImage(URL baseURL, String fileName)

Zapoczątkowanie pobrania obrazu z pliku fileName znajdującego się w katalogu określonym przez baseURL.

public boolean drawImage(Image img, int x, int y,
ImageObserver imgObs)

background image

139

Zapoczątkowanie wykreślania obrazu img, pod nadzorem obserwatora imgObs, w prostokącie, którego lewy-
górny narożnik znajduje się w punkcie (x, y).

public boolean drawImage(Image img, int x, int y,
int width, int height,
ImageObserver imgObs)

Zapoczątkowanie wykreślania przeskalowanego obrazu img, pod nadzorem obserwatora imgObs, w
prostokącie o rozmiarach width x height, którego lewy-górny narożnik znajduje się w punkcie (x, y).

public boolean drawImage(Image img, int x, int y,
int width, int height,
Color backColor,
ImageObserver imgObs)

Zapoczątkowanie wykreślania przeskalowanego obrazu img, pod nadzorem obserwatora imgObs, w
prostokącie o rozmiarach width x height, którego lewy-górny narożnik znajduje się w punkcie (x, y) i z kolorem
tła backColor (istotne podczas wykreślania obrazów z przeźroczystymi pikselami).


Wykonanie następującego programu, pokazanego podczas wykonania na ekranie Obraz WinZip, powoduje
wyświetlenie obrazu z pliku WinZip.gif, znajdującego się w tym samym katalogu co plik zawierający opis
apletu.

Ekran Obraz WinZip

### grab.gif

<applet code=Master.class width=420 height=100>
<param name=Grab value=WinZip.gif>
</applet>
===============================================

import java.applet.*;
import java.awt.*;
import java.net.*;

public
class Master extends Applet {
private static Image myImage;
int width, height;
public void init()
{
String what = getParameter("Grab");
URL where = getDocumentBase();
myImage = getImage(where, what);
width = myImage.getWidth(this);
height = myImage.getHeight(this);
}
public void paint(Graphics gDC)
{
gDC.drawImage(myImage, 0, 0, width, height,
Color.black, this);
}
}


Informacje o rozmiarach apletu pobrano z jego opisu.

Obraz przeskalowano w taki sposób, aby zajął cały pulpit apletu.

Obserwacja


Mimo mylącej nazwy, wykonanie metody getImage jedynie zapoczątkowuje ściągnięcie obrazu z sieci.
Dopiero wykonanie metody drawImage powoduje sukcesywne, w miarę postępującej transmisji, wyświetlanie
kolejnych fragmentów obrazu.

background image

140

To sukcesywne wyświetlanie jest nadzorowane przez obserwatora, którym jest obiekt klasy implementującej
interfejs ImageObserver.

Interfejs ImageObserver implementuje m.in. klasa Component, a więc i klasa Applet. Dlatego w wywołaniu
metod drawImage może wystąpić odnośnik this identyfikujący obserwatora.

Uwaga: Interfejs ImageObserver zawiera deklarację tylko jednej metody: imageUpdate.

public boolean imageUpdate(Image img, int flags,
int x, int y,
int width, int height)

Metoda imageUpdate jest wywoływana przez System wielokrotnie, w miarę postępującego ładowania obrazu.
Jeśli zwróci wartość true, to będzie wywołana ponownie, a jeśli zwróci false, to dla danej operacji nie będzie
już wywoływana.


W ciele metody imageUpdate można odwoływać się m.in. do następujących flag bitowych

WIDTH

Jest dostępna szerokość obrazu, określona przez parametr width.

HEIGHT

Jest dostępna wysokość obrazu, określona przez parametr height.

SOMEBITS

Jest dostępna następna porcja bitów obrazu. Ograniczający ją prostokąt jest określony łącznie przez parametry
(x,y) i width x height.

ALLBITS

Jest dostępny cały obraz.

ERROR

Wystąpił błąd. Inne informacje nie będą już dostarczone. Obrazu nie da się wykreślić.


Następujący aplet, pokazany podczas wykonania na ekranie Obserwowanie ładowania, ilustruje zasadę
posługiwania się obserwatorem ładowania obrazu.

Ekran Obserwowanie ładowania

### observer.gif

<applet code=Master.class width=160 height=100>
<param name=Photo value=Duke.gif>
</applet>
===============================================

import java.applet.*;
import java.awt.*;
import java.awt.image.*;
import java.net.*;

public
class Master extends Applet implements ImageObserver {
boolean done;
private static Image myImage;
public void init()
{
String what = getParameter("Photo");
URL where = getDocumentBase();
myImage = getImage(where, what);
}
public void paint(Graphics gDC)
{

background image

141

done = gDC.drawImage(myImage, 0, 0, this);
}
public boolean imageUpdate(Image img, int flags,
int x, int y,
int width, int height)
{
if((flags & ImageObserver.ALLBITS) != 0) {
showStatus("Image fully loaded");
Graphics gDC = getGraphics();
gDC.drawImage(img, x, y, null); // wykre

ś

lenie

return false;
} else {
if((flags & ImageObserver.HEIGHT) == 0)
showStatus("Loading ... ");
else
showStatus("Loaded " + height);
return true;
}
}
}


Przebieg wykreślania obrazu jest obserwowany. Komunikaty o przebiegu ładowania są wyświetlane w wierszu
stanu apletu.

Nadzór


Nadzorowanie ładowania większej liczby obrazów zapewnia nadzorca mediów. Jego rolę pełni obiekt klasy
MediaTracker.

Dzięki nadzorcy mediów można zainicjować ładowanie większej liczby obrazów, na przykład zestawu kadrów
animacji, a do czasu ich ściągnięcia z sieci wykonywać inne czynności.

Każda operacja zlecona nadzorcy jest rejestrowana i opatrywana unikatowym identyfikatorem. Zapytania o
przebieg wykonania operacji wymagają podania identyfikatora.

Kilka metod nadzorcy mediów zwraca flagi bitowe określające postęp ładowana obrazów. Do ich badania
można użyć symboli wymienionych w tabeli Flagi ładowania.

Tabela Flagi ładowania

###

LOADING

Trwa ładowania

ABORTED

Ładowanie/ładowania zarzucono

ERRORED

Ładowanie/ładowania nie powiodły się

COMPLETE

Ładowanie/ładowania zakończono

###


boolean waitForID(int id)

Metoda wstrzymuje wykonanie wątku do chwili zakończenia ładowania wszystkich obrazów opatrzonych
podanym identyfikatorem.

boolean waitForAll()

Metoda wstrzymuje wykonanie wątku do chwili zakończenia ładowania wszystkich obrazów.

int statusId(int id, boolean load)

Metoda zwraca flagi opisujące przebieg ładowania obrazu o podanym identyfikatorze.

int statusAll(boolean load)

Metoda zwraca flagi opisujące ładowanie wszystkich obrazów.

background image

142

Następujący aplet, pokazany podczas wykonania na ekranie Nadzorowanie ładowania, ilustruje użycie
nadzorcy mediów.

Ekran Nadzorowanie ładowania

### tracker.gif

<applet code=Master.class width=220 height=100>
<param name=Count value=3>
<param name=Prefix value=Photo>
<param name=Photo1 value=Duke1.gif>
<param name=Photo2 value=Duke2.gif>
<param name=Photo3 value=Duke3.gif>
</applet>
===============================================

import java.applet.*;
import java.awt.*;
import java.net.*;
import java.util.*;

public
class Master extends Applet {
int count;
Image photos[];
static MediaTracker tracker;
int width, height;
public void init()
{
tracker = new MediaTracker(this);
URL where = getCodeBase();
try {
String count = getParameter("Count");
this.count = Integer.parseInt(count);
}
catch(NumberFormatException e)
{
this.count = 0;
}
photos = new Image [count+1];
String prefix = getParameter("Prefix");
for(int i = 1; i < count+1 ; i++) {
String parName = prefix + i,
fileName = getParameter(parName);
photos[i] = getImage(where, fileName);
tracker.addImage(photos[i], i);
}
int failCount = 0;
for(int i = 1; i < count+1 ; i++) {
try {
tracker.waitForID(i);
}
catch(InterruptedException e) {
}
if(tracker.isErrorID(i))
failCount++;
}
if(failCount != 0) {
showStatus("No. of failures: " + failCount);
}
width = photos[1].getWidth(this);
height = photos[1].getHeight(this);
}
public void paint(Graphics gDC)
{
Thread thisThread = Thread.currentThread();
for(int i = 1; i < count+1 ; i++) {
gDC.drawImage(photos[i], width*(i-1), 0,
width, height,
Color.black, this);
try {
thisThread.sleep(200);

background image

143

}
catch(InterruptedException e) {
}
}
}
}


Sprawdza się, że obrazy zostały pomyślnie załadowane, a dopiero po tym przystępuje się do ich wyświetlenia.


_________________________________________________________________________________________

Rozkłady



Sposób rozmieszczenia komponentów w pojemniku: w aplecie, w panelu albo w ramce określa zarządca
rozkładu
(Layout Manager). Do najczęściej używanych należą zarządcy FlowLayout, BorderLayout i
GridLayout.

W chwili utworzenia pojemnika jest mu przypisywany zarządca domyślny (np. dla apletu FlowLayout, a dla
ramki BorderLayout). Nic jednak nie stoi na przeszkodzie, aby zarządca został zmieniony na dowolny inny,
także już po załadowaniu pojemnika.

Rozmieszczenie komponentów w pojemniku może się odbywać bez udziału zarządcy. W takim wypadku
położenie i rozmiar każdego z komponentów należy określić jawnie, za pomocą metod reshape albo resize i
move klasy Component.

public void resize(int width, int height)

Zmiana rozmiarów komponentu na podane.

public synchronized void reshape(int x, int y,
int width, int height)

Zmiana położenia i rozmiarów komponentu na podane.

public void move(int x, int y)

Zmiana położenia lewego-górnego narożnika komponentu na podane.


Następujący aplet, pokazany podczas wykonania na ekranie Bez zarządcy, nie posługuje się zarządcą rozkładu.

Ekran Bez zarządcy

### null.gif

<applet code=Master.class width=300 height=100>
</applet>
===============================================

import java.applet.*;
import java.awt.*;

public
class Master extends Applet {
public void init()
{
Panel panel = nullLayoutPanel();
panel.resize(200, 200);
setLayout(null);
add(panel);
}
Panel nullLayoutPanel()
{
Panel panel = new Panel();
panel.setLayout(null);
Button red = new Button("Red"),

background image

144

green = new Button("Green"),
blue = new Button("Blue");
TextField cell = new TextField("Hello");

panel.add(red);
panel.add(green);
panel.add(blue);
panel.add(cell);

red.reshape(0, 0, 60, 30);
green.reshape(70, 0, 60, 30);
blue.reshape(140, 0, 60, 30);
cell.reshape(0, 40, 100, 30);

return panel;
}
}


FlowLayout


Rozkład ciągły (FlowLayout) polega na rozmieszczaniu komponentów jeden-za-drugim. Jeśli zestaw
komponentów nie mieści się w wierszu pojemnika, to kolejny komponent jest przenoszony do następnego
wiersza.

public FlowLayout()
public FlowLayout(int align)
public FlowLayout(int align, int hGap, int vGap)

Konstruktory określają właściwości rozkładu, w tym sposób wyrównania komponentów (align:
FlowLayout.LEFT, FlowLayout.RIGHT, FlowLayout.CENTER) oraz poziomy (hGap) i pionowy (vGap)
odstęp między komponentami.


Na ekranie Rozkład ciągły pokazano aplet, w którym zastosowano rozkład FlowLayout.

Ekran Rozkład ciągły

### flow.gif

<applet code=Master.class width=160 height=80>
</applet>
===============================================

import java.applet.*;
import java.awt.*;

public
class Master extends Applet {
public void init()
{
Panel panel = flowLayoutPanel();
add(panel);
}
Panel flowLayoutPanel()
{
Panel panel = new Panel();
panel.setLayout(new FlowLayout(FlowLayout.RIGHT));
panel.add(new Button("Greet"));
panel.add(new Button("Clear"));
return panel;
}
}


Przyciski Greet i Clear wyrównano prawostronnie w wierszu pulpitu.

background image

145

BorderLayout


Rozkład brzegowy (BorderLayout) polega na rozmieszczeniu komponentów na obrzeżach i w środku
pojemnika. Obrzeża mają nazwy stron świata: West, East, North, South. Środek pojemnika ma nazwę Center.

public Borderlayout()
public BorderLayout(int hGap, int vGap)

Konstruktory określają właściwości rozkładu. Drugi z nich dodatkowo: poziomy (hGap) i pionowy (vGap)
odstęp między komponentami.


Na ekranie Rozkład brzegowy pokazano aplet, w którym zastosowano rozkład BorderLayout.


Ekran Rozkład brzegowy

### border.gif

<applet code=Master.class width=400 height=80>
</applet>
==============================================

import java.applet.*;
import java.awt.*;

public
class Master extends Applet {
public void init()
{
Panel panel = borderLayoutPanel();
add(panel);
}
Panel borderLayoutPanel()
{
Panel panel = new Panel();
panel.setLayout(new BorderLayout());
panel.add("East", new Button("Greet"));
panel.add("West", new Button("Clear"));
panel.add("North", new TextField(8));
panel.add("Center", new Label("The Center"));
panel.add("South", new TextArea());
return panel;
}
}


GridLayout


Rozkład siatkowy (GridLayout) polega na rozmieszczeniu komponentów w układzie prostokątnej siatki o
podanych rozmiarach. Wszystkie jej klatki mają identyczne rozmiary.

public GridLayout(int rows, int cols)
public GridLayout(int rows, int cols, int hGap, int vGap)

Konstruktory określają liczbę wierszy (rows) i kolumn (cols) rozkładu oraz poziomy (hGap) i pionowy (vGap)
odstęp między komponentami.


Na ekranie Rozkład siatkowy pokazano aplet, w którym zastosowano rozkład GridLayout.

Ekran Rozkład siatkowy

### grid.gif

<applet code=Master.class width=240 height=160>

background image

146

</applet>
===============================================

import java.applet.*;
import java.awt.*;

public
class Master extends Applet {
public void init()
{
Panel panel = gridLayoutPanel();
add(panel);
}
Panel gridLayoutPanel()
{
Panel panel = new Panel();
panel.setLayout(new GridLayout(4, 4, 10, 10));
for(int k = 0, row = 0; row < 4 ; row++)
for(int col = 0; col < 4 ; col++)
if(k++ == 0)
panel.add(new Button("Clear"));
else
panel.add(new Button("Greet" + (k-1)));
return panel;
}
}



_________________________________________________________________________________________

Sterowniki


Sterownikami są komponenty rozmieszczane w najgłębszych pojemnikach. Typowymi sterownikami są:
etykieta (Label), przycisk (Button), klatka (TextField), notatnik (TextArea) i płótno (Canvas). Etykieta i
notatnik są sterownikami biernymi (nie mogą obsługiwać zdarzeń).

Label


Etykieta jest sterownikiem umożliwiającym umieszczenie w pojemniku dowolnie sformatowanego napisu.
Napis może być wyrównany: lewostronnie (Label.LEFT), prawostronnie (Label.RIGHT) albo środkująco
(Label.CENTER).

Na ekranie Etykiety pokazano aplet zawierający sterowniki Label.

Ekran Etykiety

### label.gif

<applet code=Master.class width=100 height=100>
</applet>
===============================================

import java.applet.*;
import java.awt.*;

public
class Master extends Applet {
public void init()
{
setLayout(new BorderLayout());
Label left = new Label("Left");
Label right = new Label("Right", Label.RIGHT);
add("North", left);
add("South", right);
add("Center", new Label("Center", Label.CENTER));
}
}

background image

147


Button


Przycisk jest sterownikiem, którego naciśnięcie (kliknięcie przyciskiem myszki) może być obsłużone, co
zazwyczaj powoduje wykonanie akcji. Zaleca się, aby rodzaj akcji odpowiadał napisowi wykreślonemu na
przycisku.

Uwaga: Po wygenerowaniu zdarzenia ACTION_EVENT opis przycisku jest dostępny poprzez
(String)evt.arg.

Na ekranie Przyciski pokazano aplet zawierający sterowniki Button.

Ekran Przyciski

### button.gif

<applet code=Master.class width=300 height=160>
</applet>
===============================================

import java.applet.*;
import java.awt.*;
import java.util.*;

public
class Master extends Applet {
private Panel panel = new Panel();
Label target = new Label("", Label.CENTER);
public void init()
{
panel.setLayout(
new FlowLayout(FlowLayout.RIGHT, 10, 3)
);
String java = "Java is very easy to learn";
StringTokenizer phrase = new StringTokenizer(java);
while(phrase.hasMoreTokens())
panel.add(new MyButton(phrase.nextToken(), target));
setLayout(new BorderLayout());
add("Center", panel);
add("South", target);
Font font = new Font("Arial", Font.BOLD, 60);
target.setFont(font);
}
}

class MyButton extends Button {
Label target;
MyButton(String label, Label target)
{
super(label);
this.target = target;
}
public boolean action(Event evt, Object arg)
{
target.setText((String)arg);
return true;
}
}


Każde ze słów frazy

Java is very easy to learn


jest wyświetlane na osobnym przycisku.

Naciśnięcie przycisku opatrzonego wybranym słowem (np. easy) powoduje wyświetlenie go w dolnej części
apletu.

background image

148


TextField


Klatka jest sterownikiem, do którego można wprowadzić tekst. Podczas wprowadzania tekstu można
posługiwać się klawiszami kierunkowymi oraz klawiszami Del i Backspace.

Uwaga: Po naciśnięciu klawisza Enter jest generowane zdarzenie ACTION_EVENT. Podczas obsługiwania
go zawartość klatki jest dostępna poprzez zmienną (String)evt.arg.

Na ekranie Klatki pokazano aplet zawierający sterowniki TextField.

Ekran Klatki

### field.gif

<applet code=Master.class width=240 height=80>
</applet>
===============================================

import java.applet.*;
import java.awt.*;

public
class Master extends Applet {
public void init()
{
TextField firstName = new TextField(20),
lastName = new TextField(20);
Label firstNameLabel =
new Label("First name: ", Label.RIGHT),
lastNameLabel =
new Label("Last name: ", Label.RIGHT);
setLayout(new FlowLayout());
add(firstNameLabel);
add(firstName);
add(lastNameLabel);
add(lastName);
}
public boolean action(Event evt, Object arg)
{
String entry = arg.toString();
((TextField)evt.target).setText(entry.toUpperCase());
return true;
}
}


Po naciśnięciu klawisza Enter następuje zmiana liter wprowadzonych do klatki z małych na duże.

TextArea


Notatnik jest sterownikiem implementującym prosty edytor. Edytor jest wyposażony w suwaki. Czynne są
klawisze strzałkowe oraz Enter, Backspace i Delete. Notatnik reaguje tylko na naciśnięcia klawiszy oraz na
czynności wykonane za pomocą myszki (zmienna evt.arg nie ma interpretacji).

Metody appendText, insertText i replaceText umożliwiają przetwarzanie tekstu wprowadzonego do
notatnika.

public void appendText(String str)

Metoda dokleja łańcuch str na końcu notatnika.

public void insertText(String str, int pos)

Metoda wstawia łańcuch str przed znakiem na pozycji pos notatnika.

background image

149

public void replaceText(String str, int f, int t)

Metoda zastępuje wycinek znaków notatnika między jego pozycjami f i t, łańcuchem str.


Na ekranie Notatnik pokazano aplet zawierający sterownik TextArea.

Ekran Notatnik

### area.gif

<applet code=Master.class width=300 height=120>
</applet>
===============================================

import java.applet.*;
import java.awt.*;

public
class Master extends Applet {
TextArea textArea;
public void init()
{
String aProgram =
" public \n" +
" class Simplest { \n" +
" public static \n" +
" void main(String args[]) \n" +
" { \n" +
" } \n" +
" } \n";

int width = Integer.parseInt(getParameter("width")),
height = Integer.parseInt(getParameter("height"));
textArea = new TextArea(aProgram, 100, 40);
setLayout(null);
add(textArea);
textArea.reshape(0, 0, width, height);
}
public boolean handleEvent(Event evt)
{
if(evt.id == Event.KEY_ACTION &&
evt.key == Event.F5 &&
(evt.modifiers & Event.CTRL_MASK) != 0) {
textArea.insertText(" final\n", 0);
return true;
}
return false;
}

}

Wykonanie programu powoduje wyświetlenie notatnika zawierającego kod najkrótszej aplikacji. Po naciśnięciu
klawisza Ctrl-F5, przed pierwszy wiersz tego programu jest wstawiany napis

final


Canvas


Płótno jest sterownikiem, pozbawionym predefiniowanej semantyki (jak na przykład przycisk). Na płótnie
można umieszczać dowolne sterowniki. Można również sporządzać na nim wykresy.

Uwaga: Płótno reaguje tylko na naciśnięcia klawiszy oraz na czynności wykonane za pomocą myszki (zmienna
evt.arg nie ma interpretacji).

Na ekranie Płótna pokazano następujący aplet zawierający sterowniki Canvas.

background image

150

Ekran Płótna

### canvas.gif

<applet code=Master.class width=180 height=180>
</applet>
===============================================

import java.applet.*;
import java.awt.*;

public
class Master extends Applet {
public void init()
{
setLayout(new GridLayout(5, 5));
for(int shade = 0, row = 0; row < 5 ; row++)
for(int col = 0; col < 5 ; col++) {
Canvas canvas = new RedCircle(shade += 10);
add(canvas);
}
}
}

class RedCircle extends Canvas {
int shade;
RedCircle(int shade)
{
this.shade = shade;
}
public void paint(Graphics gDC)
{
gDC.setColor(new Color(shade, 0, 0));
gDC.fillOval(0, 0, 35, 35);
gDC.setColor(Color.white);
gDC.fillOval(0, 0, 15, 15);
}
public boolean mouseUp(Event evt, int x, int y)
{
Graphics gDC = getGraphics();
gDC.drawLine(0, 0, x, y);
return true;
}
}


Wykonanie programu powoduje m.in. wyświetlenie zestawu okręgów zabarwionych na czerwono, każdy w
innym odcieniu.

MenuItem


Polecenie jest sterownikiem umożliwiającym wyposażenie ramki w menu.

Menu jest komponentem klasy Menu, a polecenie menu jest sterownikiem klasy MenuItem. Wybieranie
poleceń odbywa się przez kliknięcie.

Uwaga: Po wygenerowaniu zdarzenia kategorii ACTION_EVENT etykieta polecenia menu jest dostępna
poprzez zmienną (String)evt.arg.

Na ekranie Menu pokazano aplet, który posługuje się odrębną ramką zawierającą sterowniki MenuItem.

Ekran Menu

### menuitem.gif

<applet code=Master.class width=180 height=180>
</applet>
===============================================

background image

151

import java.applet.*;
import java.awt.*;

public
class Master extends Applet {
Frame greetFrame;
Label label = new Label("Choose greeting from menu");
public void init()
{
greetFrame = new MyFrame("Greet", label);
greetFrame.resize(200, 200);
greetFrame.show();

setLayout(new FlowLayout());
add(new Button("Show / Hide"));

int width = Integer.parseInt(getParameter("width")),
height = Integer.parseInt(getParameter("height"));
greetFrame.resize(width, height);
MenuBar menuBar = new MenuBar();
greetFrame.setMenuBar(menuBar);

Menu fileMenu = new Menu("File");
fileMenu.add(new MenuItem("-")); // separator
fileMenu.add(new MenuItem("Exit"));
menuBar.add(fileMenu);

Menu greetMenu = new Menu("Greet");
greetMenu.add(new MenuItem("Say Hello"));
greetMenu.add(new MenuItem("Say Hi"));
menuBar.add(greetMenu);

Menu helpMenu = new Menu("Help");
helpMenu.add(new MenuItem("About"));
menuBar.add(helpMenu);
menuBar.setHelpMenu(helpMenu); // wyrównanie w prawo

greetFrame.setLayout(new BorderLayout());
greetFrame.add("South", label);
}
public boolean handleEvent(Event evt)
{
if(evt.arg.equals("Show / Hide")) {
if(greetFrame.isShowing())
greetFrame.hide();
else
greetFrame.show();
return true;
}
return false;
}
}

class MyFrame extends Frame {
Label label;
MyFrame(String caption, Label label)
{
super(caption);
this.label = label;
}
public boolean handleEvent(Event evt)
{
if(evt.id == Event.WINDOW_DESTROY)
dispose(); // obsługa klikni

ę

cia na x

if(evt.arg.equals("Say Hello")) {
label.setText("Hello Jan B.");
return true;
} else if(evt.arg.equals("Say Hi")) {
label.setText("Hi Jan B.");
return true;
} else if(evt.arg.equals("Exit")) {
dispose();
return true;

background image

152

} else if(evt.arg.equals("About")) {
label.setText("Copyright JanB.");
return true;
}
return false;
}
}


Program ilustruje sposób reagowania na polecenia menu, w które wyposażono ramkę utworzoną przez aplet.


_________________________________________________________________________________________

Pojemniki


Pojemnik jest komponentem, który może zawierać w sobie inne komponenty: pojemniki i sterowniki.
Głębokość zagnieżdżania komponentów w pojemnikach nie jest ograniczona. Na każdym poziomie
zagnieżdżenia może być użyty inny zarządca rozkładu. Poza apletem, typowymi pojemnikami są: panel
(Panel), ramka (Frame) i dialog (FileDialog).

Panel


Panel jest pojemnikiem o domyślnym rozkładzie FlowLayout. Panel zawierający sterowniki jest traktowany jak
pojedynczy komponent i jako taki może być umieszczony w innym pojemniku.

Następujący program, pokazany podczas wykonywania na ekranie Panele, tworzy dwa panele i rozmieszcza je
w górnej i dolnej części apletu z rozkładem brzegowym.

Ekran Panele

### panels.gif

<applet code=Master.class width=160 height=80>
</applet>
==============================================

import java.applet.*;
import java.awt.*;

public
class Master extends Applet {
Label label;
Button tom, bob;
public void init()
{
Panel panel = new Panel();
panel.add(tom = new Button("Tom"));
panel.add(bob = new Button("Bob"));
setLayout(new BorderLayout());
add("North", panel);
add("South", panel);
add("Center", label = new Label("Press a Button"));
}
public boolean action(Event evt, Object arg)
{
if(evt.target == tom)
label.setText("Hello Bob");
else if(evt.target == bob)
label.setText("Hello Tom");
else
return false;
return true;
}
}


Kliknięcie przycisku Tom powoduje wykreślenie napisu

background image

153

Hello Bob


a kliknięcie przycisku Bob powoduje wykreślenie napisu

Hello Tom


Frame


Ramka jest pojemnikiem z domniemanym zarządcą rozkładu BorderLayout. Ramka reaguje na zdarzenia
związane z operacjami wykonywanymi za pomocą myszki i klawiatury.

Następujący program, pokazany podczas wykonania na ekranie Ramki, wyświetla 10 ramek stowarzyszonych z
apletem, a w każdej z nich wykreśla jedną cyfrę.

Ekran Ramki

### frames.gif

<applet code=Master.class width=160 height=80>
</applet>
==============================================

import java.applet.*;
import java.awt.*;

public
class Master extends Applet {
public void init()
{
for(int i = 0; i < 10 ; i ++) {
Frame frame = new MyFrame(i);
frame.reshape(10 * (i+1), 10 * (i+1), 100, 100);
frame.show();
}
}
}

class MyFrame extends Frame {
int digit;
Font font = new Font("TimesRoman", Font.BOLD, 60);
MyFrame(int digit)
{
this.digit = digit;
}
public void paint(Graphics gDC)
{
gDC.setFont(font);
gDC.drawString("" + digit, 50, 50);
}
public boolean handleEvent(Event evt)
{
if(evt.id == Event.WINDOW_DESTROY)
dispose();
return false;
}
}


Dialog


Dialog plikowy jest pojemnikiem ułatwiającym załadowanie i zachowanie pliku. Umożliwia on wyświetlenie
okna z pytaniami o stację, ścieżkę i plik.

Obiekty dialogowe są tworzone za pomocą konstruktorów klasy FileDialog, a wyświetlenie dialogu odbywa się
za pomocą metody show.

background image

154


Operacje na zmiennych obiektu dialogowego są wykonywane m.in. za pomocą metod getFile, getDirectory
oraz setFile i setDirectory.

public FileDialog(Frame parent, String caption, int mode)

Konstruktor tworzy dialog załadowania albo zachowania (SAVE). Rodzaj dialogu można określić za pomocą
argumentu mode (FileDialog.LOAD, albo FileDialog.SAVE).

public FileDialog(Frame parent, String caption)

Konstruktor tworzy dialog załadowania (LOAD).

public void show()

Metoda wyświetla dialog opisany przez obiekt dialogowy.

public String getFile()

Metoda zwraca nazwę pliku.

public String getDirectory()

Metoda zwraca nazwę katalogu.

public void setFile(String file)

Metoda ustawia nazwę pliku.

public void setDirectory(String dir)

Metoda ustawia nazwę katalogu.


Następujący aplet, pokazany podczas wykonania na ekranie Dialog plikowy, ilustruje zasadę posługiwania się
dialogiem plikowym.

Ekran Dialog plikowy

### dialog.gif

<applet code=Master.class width=160 height=80>
</applet>
==============================================

import java.applet.*;
import java.awt.*;

public
class Master extends Applet {
Frame frame = new Frame("Parent");
String dir, file;
public void init()
{
frame.show();
add(new Button("Load"));
add(new Button("Save"));
}
public boolean action(Event evt, Object arg)
{
if(evt.target instanceof Button) {
FileDialog fileDialog = null;
if(arg.equals("Save")) {
fileDialog =
new FileDialog(frame, "Save",
FileDialog.SAVE);
}
if(arg.equals("Load")) {
fileDialog =
new FileDialog(frame, "Load",
FileDialog.LOAD);
}
fileDialog.show();

background image

155

dir = fileDialog.getDirectory();
file = fileDialog.getFile();
repaint();
return true;
}
return false;
}
public void paint(Graphics gDC)
{
gDC.drawString("Dir: " + dir, 10, 50);
gDC.drawString("File: " + file, 10, 70);
}
}


Program wyświetla nazwę katalogu i pliku, wybrane w oknie dialogu plikowego.


_________________________________________________________________________________________

Kontrolki


Kontrolką jest sterownik stanowiący kompozycję komponentów. Komponenty zaleca się wyposażyć w metody
minimumSize i preferredSize, a na rzecz kontrolki wywołać metodę validate. W przeciwnym razie może się
zdarzyć, że elementy kontrolki nie będą właściwie wykreślane.

public Dimension minimumSize()

Metoda określa minimalny rozmiar komponentu.

public Dimension preferredSize()

Metoda określa ulubiony rozmiar komponentu.

public void validate()

Metoda powoduje ponowne przeliczenie rozmiarów i położenia komponentu.


Uwaga: Jedynym sposobem dodania metody minimumSize i preferredSize do klasy predefiniowanej (np.
Button), jest utworzenie jej podklasy.

Na przykład

class MyButton extends Button {
MyButton(String caption)
{
super(caption);
}
public Dimension minimumSize()
{
return new Dimension(20, 20);
}
// ...
}


Klasa MyButton może być używana w taki sam sposób jak klasa Button.

Od klasy Buttonżni się własną metodą minimumSize.


Następujący aplet, pokazany podczas wykonywania na ekranie Kontrolka, ilustruje sposób wykorzystania
własnego sterownika (zdefiniowanego za pomocą klas Display i Key), z trójwymiarowymi przyciskami
umożliwiającymi wprowadzanie liczb całkowitych.

Uwaga: Przycisk S kontrolki oprogramowano w taki sposób, aby umożliwiał wyeksportowanie liczby ze
sterownika (klasa Display definiuje sterownik-wyświetlacz, a klasa Key sterownik-przycisk).

background image

156


Ekran Kontrolka

### display.gif

<applet code=Master.class width=130 height=180>
</applet>
===============================================

import java.applet.*;
import java.awt.*;

public
class Master extends Applet {
private Display theDisplay;
private TextField theCell;
public void init()
{
setLayout(new BorderLayout());
theCell = new TextField("Ready");
theDisplay = new Display();
add("North", theCell);
add("East", theDisplay);
}
public boolean mouseUp(Event evt, int x, int y)
{
int value = theDisplay.getValue();
theCell.setText(String.valueOf(value));
return true;
}
}


Aplet tworzy klatkę tekstową i wyświetlacz z przyciskami. Procedura mouseUp rozpoznaje naciśnięcie przycisku
S wy
świetlacza i obsługuje je w taki sposób, że liczbę znajdującą się w wyświetlaczu wstawia do klatki.

Klasa Display


Klasa Display definiuje wyświetlacz z przyciskami. Zwolnienie przycisku S nie powoduje obsłużenia tego
zdarzenia (po naciśnięciu przycisku S metoda mouseUp zwraca false). Stwarza to możliwość obsłużenia go w
pojemniku kontrolki.

class Display extends Panel {
private int value = 0;
private Panel display = new Panel();
private TextField number = new TextField("0");
private Panel buttons = new Panel();
private static String labels = "7894561230 S";
private char lastKey = '0';

Display()
{
display.setLayout(new BorderLayout());
buttons.setLayout(new GridLayout(4, 3));
for(int i = 0; i < 12 ; i++) {
char chr = labels.charAt(i);
Key key = new Key(this, chr);
buttons.add(key);
}
display.add("North", number);
display.add("Center", buttons);
add(display);
}
public void setLastKey(char key)
{
lastKey = key;
}
int getValue()
{
return value;

background image

157

}
void setValue(int value)
{
this.value = value;
number.setText(new String("" + value));
}
public boolean handleEvent(Event evt)
{
if(evt.id == Event.MOUSE_UP)
return mouseUp(evt, evt.x, evt.y);
return true;
}
public boolean mouseUp(Event evt, int x, int y)
{
switch(lastKey) {
case ' ':
value = 0;
number.setText("0");
return true;
case 'S':
return false; // nie obsłu

ż

ono, posłano dalej

default:
int digit = lastKey - '0';
value = value * 10 + digit;
number.setText(String.valueOf(value));
return true;
}
}
}

Klasa Key


Klasa Key definiuje trójwymiarowy przycisk wyświetlacza. Efekt trójwymiarowości uzyskano przez
wykreślenie łuków w kolorze czarnym i białym. Wybrany rozmiar przycisków określa metoda preferredSize.

Naciśnięcie przycisku wyświetlacza powoduje wykreślenie go w postaci wciśniętej. Zwolnienie przycisku
powoduje zapamiętanie wciśniętego przycisku, ale bez obsłużenia tego zdarzenia (metoda mouseUp zwraca
false). Umożliwia to obsłużenie tego zdarzenia w pojemniku-wyświetlaczu Display.

class Key extends Panel {
Display display;
private char theKey;
private Color color;
private boolean mouseIsDown = false;

Key(Display display, char theKey)
{
this.display = display;
this.theKey = theKey;
}
public boolean mouseDown(Event evt, int x, int y)
{
mouseIsDown = true;
repaint();
return true;
}
public boolean mouseUp(Event evt, int x, int y)
{
mouseIsDown = false;
repaint();
display.setLastKey(theKey);
return false;
}
public void paint(Graphics gDC)
{
Rectangle bounds = bounds();
int w = bounds.width,
h = bounds.height;
Color oldColor = gDC.getColor();

background image

158

drawOval(gDC, Color.red, 0, 0, w, h);
int x = (w /= 2) / 2,
y = (h /= 2) / 2;
drawOval(gDC, Color.green, x, y, w, h);
gDC.setColor(oldColor);
drawLabel(gDC, theKey, x, y, w, h);
}
void drawOval(Graphics gDC, Color color,
int x, int y, int w, int h)
{
gDC.setColor(color);
gDC.fillOval(x, y, w, h);
color = mouseIsDown ? Color.white : Color.black;
gDC.setColor(color);
gDC.drawArc(x, y, w, h, 45, -180);
color = mouseIsDown ? Color.black : Color.white;
gDC.setColor(color);
gDC.drawArc(x, y, w, h, 45, +180);
}
public void drawLabel(Graphics gDC, char label,
int x, int y, int w, int h)
{
Font font = new Font("Arial", Font.BOLD, 12);
gDC.setFont(font);
FontMetrics metrics = gDC.getFontMetrics();
int as = metrics.getAscent(),
ww = metrics.charWidth(label);
x += (w - ww) / 2;
y += (h + as) / 2;
gDC.drawString("" + label, x, y);
}
public Dimension preferredSize()
{
return new Dimension(30, 30);
}
public Dimension minimumSize()
{
return new Dimension(30, 30);
}
}

Pośrednik-Obserwator-Kontroler


Wykorzystując bez zmian zdefiniowane powyżej klasy Display i Key, można podać uogólnione rozwiązanie
postawionego problemu. Stanowi ono ilustrację metody komunikowania się sterowników znanej pod nazwą
Pośrednik-Obserwator-Kontroler (Model-View-Controller). Istotą metody jest skonstruowanie klasy obiektów
pośredniczących, które mogą być jednocześnie obserwowane i kontrolowane.

Jeśli obiekt-kontroler dokona modyfikacji obiektu-pośrednika, to za pomocą metody update zostanie o tym
powiadomiony obiekt-obserwator. Kontroler i obserwator nic o sobie nie wiedzą i komunikują się tylko
poprzez pośrednika. Dzięki temu modyfikacja jednego z tych sterowników nie ma żadnego wpływu na drugi.
Czyni to program prawdziwie modularnym i zmniejsza niebezpieczeństwo niekontrolowanego propagowania
zmian kodu.

Wykonanie następującego programu, w którym rolę pośrednika, obserwatora i kontrolera pełnią odpowiednio
obiekty klas ObservableInt, ObserverTextField i ControllerDisplay ma taki sam skutek jak uprzednio.
Ponadto, wprowadzenie do klatki tekstowej pewnej liczby, powoduje przeniesienie jej do klatki wyświetlacza.

W szczególności, gdyby metodę

public void update(Observable obs, Object arg)
{
setValue(theInt.getInt());
}


zastąpiono metodą

background image

159

public void update(Observable obs, Object arg)
{
return;
}


to naciśnięcie klawisza Enter w obrębie klatki tekstowej anulowałoby przeniesienie liczby do wyświetlacza.

Następujący aplet, pokazany podczas wykonywania na ekranie Kontrolka dla koneserów, ilustruje
zastosowanie metody Pośrednik-Obserwator-Kontroler.

Ekran Kontrolka dla koneserów

### control.gif

Przykład Kontrolka dla koneserów

<applet code=Master.class width=130 height=180>
</applet>
===============================================

import java.applet.*;
import java.awt.*;
import java.util.*;

public
class Master extends Applet {
private Display theDisplay;
private TextField theCell;
ObservableInt theInt = new ObservableInt(0);
public void init()
{
setLayout(new BorderLayout());
theCell = new ObserverTextField(theInt, "Ready");
theDisplay = new ControllerDisplay(theInt);
add("North", theCell);
add("East", theDisplay);
}
}

class ObservableInt extends Observable {
int theInt;
ObservableInt(int theInt)
{
this.theInt = theInt;
}
public synchronized int getInt()
{
return theInt;
}
public synchronized void setInt(int newInt)
{
if(newInt != theInt) {
theInt = newInt;
setChanged();
notifyObservers();
}
}
}

class ObserverTextField
extends TextField
implements Observer {
ObservableInt theInt;
ObserverTextField(ObservableInt theInt, String string)
{
this.theInt = theInt;
theInt.addObserver(this);
setText(string);
}

background image

160

public boolean action(Event evt, Object arg)
{
String string = (String)arg;
int value;
try {
value = Integer.parseInt(string);
}
catch(NumberFormatException e) {
setText("Not a Number");
return true;
}
theInt.setInt(value);
return true;
}
public void update(Observable obs, Object arg)
{
setText("" + theInt.getInt());
}
}

class ControllerDisplay extends Display
implements Observer {
ObservableInt theInt;
ControllerDisplay(ObservableInt theInt)
{
this.theInt = theInt;
theInt.addObserver(this);
}
public boolean handleEvent(Event evt)
{
if(!super.handleEvent(evt))
theInt.setInt(getValue());
return true;
}
public void update(Observable obs, Object arg)
{
setValue(theInt.getInt());
}
}



_________________________________________________________________________________________

Dźwięki



W chwili obecnej są rozpoznawane tylko pliki dźwiękowe w formacie AU. W celu odtworzenia dźwięków
zarejestrowanych w innych formatach należy dokonać ich konwersji. Jednym z lepszych produktów do
wykonania tego zadania jest program Wham dostępny w Internecie.

Do załadowania pliku dźwiękowego i utworzenia identyfikującego go obiektu klasy AudioClip służy metoda
getAudioClip, a do zarządzania plikami dźwiękowymi metody loop, play i stop wywoływane na rzecz takiego
obiektu.

public AudioClip getAudioClip(URL url, String fileName)

Metoda ładuje plik dźwiękowy fileName, znajdujący się pod adresem url.

public abstract void loop()

Wywołanie metody loop powoduje cykliczne odtwarzanie podanego dźwięku.

public abstract void play()

Wywołanie metody play powoduje odegranie podanego dźwięku.

public abstract void stop()

Wywołanie metody stop powoduje zaniechanie dalszego odgrywania dźwięku.

background image

161


Następujący aplet, pokazany podczas wykonywania na ekranie Melodie, odgrywa dwie melodie: jedną
cyklicznie w tle (z pliku Gong.au) i drugą co 1 s (z pliku Ding.au).

Ekran Melodie

### melody.gif

<applet code=Master.class width=100 height=100>
</applet>
===============================================

import java.applet.*;
import java.awt.*;
import java.net.*;
import java.util.*;

public
class Master extends Applet implements Runnable {
private AudioClip backGround;
private AudioClip foreGround;
Thread player = null;
Random rand = new Random();
public void init()
{
URL from = getCodeBase();
backGround = getAudioClip(from, "Gong.au");
foreGround = getAudioClip(from, "Ding.au");
}
public void start()
{
if(backGround != null)
backGround.loop();
if(player == null) {
player = new Thread(this);
player.start();
}
}
public void stop()
{
if(player != null) {
player.stop();
player = null;
}
if(backGround != null)
backGround.stop();
}
public void run()
{
while(player != null) {
if(foreGround != null) {
drawCircle();
foreGround.play();
}
try {
Thread.sleep(1000);
}
catch(InterruptedException e) {
}
}
}
int abs(int par)
{
return par < 0 ? -par : par;
}
void drawCircle()
{
int d = 10;
int x = rand.nextInt() % (100-d),
y = rand.nextInt() % (100-d);
Graphics gDC = getGraphics();
gDC.drawOval(abs(x), abs(y), d, d);

background image

162

}
}


Przed każdym odegraniem dźwięku z pliku Ding.au jest wykreślane małe kółko. Jego położenie na pulpicie
apletu jest przypadkowe.


_________________________________________________________________________________________

Animacje


Animacja polega na sukcesywnym generowaniu albo odtwarzaniu sekwencji kadrów. Generowanie i
wyświetlanie kadrów odbywa się zazwyczaj w odrębnym wątku.

Program apletu realizującego animację (aż do kliknięcia lewym przyciskiem myszki), przybiera zazwyczaj
postać

import java.applet.*;
import java.awt.*;


public
class Master extends Applet
implements Runnable {
// ...

Thread engine;

boolean stopped = false;

// ...

public void init()
{
// ... przygotowanie pierwszego kadru
}

public void start()
{
if(engine == null) {
// utworzenie w

ą

tku

// implementowanego przez
// metod

ę

run

engine = new Thread(this);
// aktywowanie w

ą

tku

engine.start();
}
}

public void stop()
{
if(engine != null && engine.isAlive())
// zniszczenie w

ą

tku

engine.stop();
engine = null;
}

public void paint(Graphics gDC)
{
// ... wykre

ś

lenie kadru

}

public boolean mouseUp(Event evt,
int x, int y)
{
return stopped = true;
}

public void run()
{

background image

163

while(!stopped) {
// ... przygotowanie kolejnego kadru
repaint();
try {
engine.sleep(100);
}
catch(InterruptedException e) {
}
// ...
}
}
}


Generowanie


Następujący aplet, pokazany podczas wykonywania na ekranie Generowanie kadrów, powoduje wyświetlenie
małego okręgu poruszającego się po elipsie. Obraz okręgu, w jego kolejnych położeniach, jest generowany w
odstępach co 100 ms.

Ekran Generowanie kadrów

### circle.gif

<applet code=Master.class width=160 height=160>
</applet>
===============================================

import java.applet.*;
import java.awt.*;

public
class Master extends Applet
implements Runnable {
static final int r = 5;
static final double Pi = Math.PI;
static final int steps = 24;
static final double step = 2 * Pi/steps;
Thread rotor;
boolean stopped = false;
static double startAngle = Pi/4;
int width, height;
double xSubAxis, ySubAxis, angle;

public void init()
{
width = Integer.parseInt(
getParameter("width")
);
height = Integer.parseInt(
getParameter("height")
);
xSubAxis = width /2 - 2*r;
ySubAxis = height/2 - 2*r;
}

public void start()
{
if(rotor == null) {
rotor = new Thread(this);
rotor.start();
}
}

public void stop()
{
if(rotor != null && rotor.isAlive())
rotor.stop();
rotor = null;
}

background image

164

public void paint(Graphics gDC)
{
int width = (int)(2 * xSubAxis),
height = (int)(2 * ySubAxis);
gDC.drawOval(r, r, width, height);
int x = (int)(r + xSubAxis *
(1 + Math.cos(angle))),
y = (int)(r + ySubAxis *
(1 - Math.sin(angle)));
gDC.drawOval(x - r, y - r, 2*r, 2*r);
}

public boolean mouseUp(Event evt,
int x, int y)
{
return stopped = true;
}


public void run()
{
while(!stopped) {
repaint();
try {
rotor.sleep(100);
}
catch(InterruptedException e) {
}
angle += step;
}
}
}


Buforowanie


Ponieważ każde wywołanie metody repaint powoduje wyczyszczenie pulpitu, więc prosta animacja odbywa się
z niezbyt przyjemnym dla oka migotaniem obrazu.

Można temu zaradzić, stosując buforowanie. Polega ono na przygotowaniu obrazu w buforze pamięci
operacyjnej, a następnie rzuceniu zawartości bufora na ekran.

Istotę buforowania wyjaśnia następujący wycinek programu

// okre

ś

lenie rozmiarów apletu

Dimension d = size();
int w = d.width, h = d.height;

// utworzenie bufora w pami

ę

ci operacyjnej

backBuffer = createImage(w, h);

// utworzenie wykre

ś

lacza zwi

ą

zanego z buforem

Graphics gDC = backBuffer.getGraphics();

// przygotowanie obrazu za pomoc

ą

metody paint

paint(gDC);

// utworzenie wykre

ś

lacza zwi

ą

zanego z pulpitem

gDC = getGraphics();

// skopiowanie bufora na pulpit
gDC.drawImage(backBuffer, 0, 0, this);



Następujący aplet, pokazany podczas wykonania na ekranie Buforowanie, wykorzystuje technikę buforowania
do generowania kadrów.

Ekran Buforowanie

background image

165

### buffer.gif

<applet code=Master.class width=160 height=100>
</applet>
===============================================

import java.applet.*;
import java.awt.*;

public
class Master extends Applet
implements Runnable {
static final int r = 5;
static final double Pi = Math.PI;
static final int steps = 24;
static final double step = 2 * Pi/steps;
Thread rotor;
boolean stopped = false;
static double startAngle = Pi/4;
int width, height;
double xSubAxis, ySubAxis, angle;
Image backBuffer;

public void init()
{
width = Integer.parseInt(
getParameter("width")
);
height = Integer.parseInt(
getParameter("height")
);
xSubAxis = width /2 - 2*r;
ySubAxis = height/2 - 2*r;
}

public void start()
{
if(rotor == null) {
rotor = new Thread(this);
rotor.start();
}
}

public void stop()
{
if(rotor != null && rotor.isAlive())
rotor.stop();
rotor = null;
}

public void paint(Graphics gDC)
{
int width = (int)(2 * xSubAxis),
height = (int)(2 * ySubAxis);
gDC.drawOval(r, r, width, height);
int x = (int)(r + xSubAxis *
(1 + Math.cos(angle))),
y = (int)(r + ySubAxis *
(1 - Math.sin(angle)));
gDC.drawOval(x - r, y - r, 2*r, 2*r);
}

public void run()
{
while(!stopped) {
Dimension d = size();
int w = d.width, h = d.height;
backBuffer = createImage(w, h);
Graphics gDC = backBuffer.getGraphics();
paint(gDC); // wykre

ś

lenie do bufora

gDC = getGraphics();
gDC.drawImage(backBuffer, 0, 0, this);

background image

166

try {
rotor.sleep(100);
}
catch(InterruptedException e) {
}
angle += step;
}
}
}


W odróżnieniu od poprzedniego przykładu, tło wykresu jest białe.

Odtwarzanie


Następujący aplet, pokazany podczas wykonania na ekranie Odtwarzanie kadrów, wyświetla obracające się
ś

migło. Kolejne kadry animacji pochodzą z pliku Turbo.gif.


Do upewnienia się, że animacja rozpocznie się dopiero po przygotowaniu kadrów, wykorzystano zarządcę
mediów. Do wyświetlania kolejnych kadrów zastosowano technikę buforowania.

Ekran Odtwarzanie kadrów

### propel.gif


Ekran Plik Turbo.gif

### turbo.gif

<applet code=Master.class width=160 height=160>
<param name=Image value=Turbo.gif>
</applet>
===============================================

import java.applet.*;
import java.awt.*;
import java.net.*;

public
class Master extends Applet
implements Runnable {
Thread engine;
boolean stopped = false;
MediaTracker tracker;
String imgFrames;
Image imageFrames;
Image backBuffer;
static final int frameSize = 160;
static final int frameCount = 8;
int pos = 0, xPos = 0;

public void init()
{
imgFrames = getParameter("Image");
// utworzenie zarz

ą

dcy mediów

tracker = new MediaTracker(this);
URL codeBase = getCodeBase();
// zainicjowanie pobrania obrazu
imageFrames = getImage(codeBase, imgFrames);
// powierzenie zarz

ą

dcy mediów

// nadzoru sprowadzenia obrazu
tracker.addImage(imageFrames, 1);
try {
// wstrzymanie wykonania apletu
// do chwili sprowadzenia obrazu
tracker.waitForID(1);
}
catch(InterruptedException e) {
}
}

background image

167


public void start()
{
if(engine == null) {
engine = new Thread(this);
engine.start();
}
}

public void stop()
{
if(engine != null && engine.isAlive())
engine.stop();
engine = null;
}

public void paint(Graphics gDC)
{
gDC.drawImage(imageFrames, xPos,
0, null);
}

public boolean mouseUp(Event evt,
int x, int y)
{
return stopped = true;
}

public void run()
{
while(!stopped) {
xPos = -pos * frameSize;
Dimension d = size();
int w = d.width, h = d.height;
backBuffer = createImage(w, h);
Graphics gDC = backBuffer.getGraphics();
paint(gDC);
gDC = getGraphics();
gDC.drawImage(backBuffer, 0, 0, this);
try {
engine.sleep(100);
}
catch(InterruptedException e) {
}
pos = ++pos % frameCount;
}
}
}


Pozornie jest wykreślany kompletny zestaw kadrów, ale dzięki ujemnej współrzędnej xPos i ograniczeniu
wykre
ślania do pulpitu apletu, jest każdorazowo wykreślany tylko jeden kadr.


_________________________________________________________________________________________

Przesyłanie


Aplet pochodzący z sieci rozległej może komunikować się tylko z serwerem z którego został załadowany.

W szczególności, jeśli aplet załadowano na podstawie opisu znajdującego się pod adresem

http://www.janb.com/java/cafe/text.html


to może on wykonywać operacje wejścia-wyjścia tylko spod adresu

http://www.janb.com/java/cafe/File


w którym File jest nazwą pliku (np. subdir/source.doc)

background image

168


Uwaga: Między adresem komputera (tu: www.janb.com) a ścieżką do File może wystąpić niejawna ścieżka
pośrednicząca wstawiona tam przez serwer.

Przykład Przesyłanie danych

<applet code=Master.class width=100 height=100>
</applet>
===============================================

import java.applet.*;
import java.awt.*;
import java.net.*;
import java.io.*;

public
class Master extends Applet {
URL url;
int EOF = StreamTokenizer.TT_EOF;
String text = "";
public void init()
{
try {
url = new URL(
"http://" +
"www.janb.com/java/cafe/" +
"greet.txt"
);
}
catch(MalformedURLException e) {
}
StreamTokenizer tokens = null;
try {
tokens = new StreamTokenizer(url.openStream());
while(tokens.nextToken() != EOF)
text += tokens.sval;
}
catch(IOException e) {
}
}
public void paint(Graphics gDC)
{
gDC.drawString(text, 10, 20);
}
}


Wykonanie apletu powoduje wyświetlenie zawartości pliku greet.txt znajdującego się w katalogu, z którego
pochodzi aplet.

Podczas uruchamiania apletu można plik greet.txt umieścić na dysku lokalnym i zamiast adresu

http://www.janb.com/java/cafe/greet.txt


podać na przykład adres

file:///c:/java/cafe/greet.txt



_________________________________________________________________________________________

Komunikacja



Komunikowanie się apletów, których opisy znajdują się w tym samym pliku HTML, wymaga nadania im nazw.
Nazwy apletów podaje się w opisach apletów we frazie name.

background image

169

Następujący program, pokazany podczas wykonania na ekranie Komunikacja, ilustruje zasadę komunikowania
się apletów, nazwanych One i Two. Kliknięcie pulpitu jednego z nich powoduje wykreślenie okręgu na
pulpicie drugiego. Opisy apletów w dokumencie HTMLżne, ale B-kod obu apletów jest wspólny.

Ekran Komunikacja

### onetwo.gif

<applet code=Master.class width=160 height=100 name=One>
</applet>

<applet code=Master.class width=160 height=100 name=Two>
</applet>
========================================================

import java.applet.*;
import java.awt.*;

public
class Master extends Applet {
String name;
Thread myThread = null;
public void init()
{
name = getParameter("name");
}
Applet otherApplet()
{
String other = null;
if(name.equals("One"))
other = "Two";
if(name.equals("Two"))
other = "One";
AppletContext context = getAppletContext();
return context.getApplet(other);
}
public boolean mouseDown(Event evt, int x, int y)
{
Applet other = otherApplet();
Graphics gDC = other.getGraphics();
gDC.drawOval(x - 20, y - 20, 40, 40);
return true;
}
public boolean mouseUp(Event evt, int x, int y)
{
otherApplet().repaint();
return true;
}
}



_________________________________________________________________________________________

Przełączenia



Aplet można zaprojektować w taki sposób, aby wykonanie operacji na jego sterowniku powodowało
przełączenie na inny dokument HTML, niż ten z którego pochodzi właśnie wyświetlany aplet.

Następujący aplet, pokazany podczas wykonywania na ekranie Wyszukiwarki, ilustruje zasadę przełączania
stron dokumentów HTML.

Kliknięcie jednego z przycisków przywołuje na ekran stronę domową jednej z ze znanych wyszukiwarek:
AltaVista, HotBot i Yahoo.

Ekran Wyszukiwarki

### yahoo.gif

background image

170

<applet code=Master.class width=160 height=100>
</applet>
===============================================

import java.applet.*;
import java.awt.*;
import java.net.*;

public
class Master extends Applet {
public void init()
{
add(new Button("AltaVista")); // wyszukiwarka
add(new Button("HotBot")); // wyszukiwarka
add(new Button("Yahoo")); // wyszukiwarka
}
public boolean action(Event evt, Object arg)
{
URL newURL = null;
try {
if(!(evt.target instanceof Button))
return false;
if(arg.equals("Alta Vista"))
newURL = new URL("http://altavista.digital.com/");
else if(arg.equals("Hot Bot"))
newURL = new URL("http://hotbot.com/");
else
newURL = new URL("http://www.yahoo.com/");
}
catch(MalformedURLException e) {
}
AppletContext context = getAppletContext();
context.showDocument(newURL);
return true;
}
}

background image

171

Literatura


Bielecki, J.:

Java po C++, Intersoftland, 1996.

(zwięzła i treściwa, dla programujących w C++)


Chan, P.:

The Java Class Libraries, Addison Wesley, 1996.

(doskonała, szczegółowy opis bibliotek, w tym AWT)

Gosling, J.:

The Java Language Specification, Addison Wesley, 1996.

(doskonała, obszerny opis języka, bez opisu AWT)

Flanagan, D.:

Java in a Nutshell, O'Reilly & Associates, 1996.

(doskonała, ciekawe przykłady, zwięzłe opisy bibliotek)

Winston, P.:

On to Java, Addison Wesley, 1996.

(doskonała, wyszła spod ręki zawodowego dydaktyka)


LeMay, L.:

Teach Yourself Java in 21 Days, Sams Net, 1996.

(dobra dla początkujących, uproszczona i nieścisła)

Walrath, K.:

The Java Tutorial, JavaSoft, 1996.

(dobra dla początkujących, ciekawa i szczegółowa)


Arnold, K.:

The Java Programming Language, JavaSoft, 1996.

(przeciętna, wiele błędów, mimo współautorstwa J.G.)

McClellan, A.:

Java By Example, The SunSoft Press, 1996.

(niekompletna, łatwa, dobra dla początkujących)

Linden, P.:

Just Java, The SunSoft Press, 1996.

(taka sobie, dla niezbyt wymagających)

Morrison, M.:

Java Unleashed, Sams Net, 1996.

(przegadana, ale łatwa i szczegółowa)

Naughton, P.:

The Java Handbook, Osborne/McGraw-Hill, 1996.

(warta uwagi ze względu na autora, dobra dla początkujących)

Newman, A.:

Using Java, QUE, 1996.

(obszerna, taka sobie, kilka ciekawych pomysłów)

Pew, J.:

Instant Java, The SunSoft Press, 1996.

(ciekawy zbiór gotowych apletów, nie do nauki języka)

Rodley, J:

Writing Java Applets, Coriolis Group Books, 1996.

(przeciętna, głównie o programowaniu sieciowym)

background image

172














Słownik terminów

background image

173

A

abort

zaniechać

abrubt

gwałtowny

accessible

dostępny

active

aktywny

alpha component

składnik alfa

ambiguous

dwuznaczny

applet

aplet

ascent

uniesienie

B

background

tło

baseline

linia bazowa

blinking

migotanie

boolean value

orzeczenie

boolean variable

orzecznik

brightness

jaskrawość

browser

przeglądarka

buffering

buforowanie

button

przycisk

bytecode

B-kod

C

callback

obsługa

canvas

płótno

card

karta

cast

konwersja

check mark

znacznik

checkbox

nastawa

choice

wybór

client

odbiorca

client area

pulpit

clipping

obcinanie

clone

klon

compatible

zgodny

concurrent

współbieżny

connection

połączenie

constraint

wymuszenie

consumer

konsument

container

pojemnik

control

sterownik

control flow

przepływ sterowania

control point

punkt charakterystyczny

copy constructor

konstruktor kopiujący

critical section

sekcja krytyczna

cropping

wycinanie

current

bieżący

custom

zamówiony

D

daemon

demon

datagram

datagram

deadlock

impas

debugger

uruchamiacz

default

domyślny

definitive

definitywny

delay

oczekiwanie

deprecate

zniechęcać

descent

obniżenie

dialog

dialog

directory

katalog

background image

174

domain

domena

drag

przeciągać

drop down

opadać

dump

zrzut

E

elaboration

opracowanie

ellipsis

wielokropek

embedded

wbudowany

envelope

koperta, otoczka

environment

otoczenie

event driven

sterowany zdarzeniami

event

zdarzenie

F

factory expression

wyrażenie fabrykujące

factory method

metoda fabrykująca

file

plik

filtered

filtrowany

final

ustalony

firewall

zapora

flow of control

przepływ sterowania

flush

wymieść

folder

katalog

foreground

lico

frame

ramka

friendship

zaprzyjaźnienie

G

garbage

nieużytek

graphics context

kontekst graficzny

H

handler

obsługa

heap

sterta

height

wysokość

home page

strona domowa

host

gospodarz

hue

odcień

I

identifier

identyfikator

incremental

przyrostowy

initializer

inicjator

instance

egzemplarz

integrity

integralność

interface

oblicze

L

l-value

l-wyrażenie

label

etykieta

layout

rozkład

leading

ś

wiatło

linking

łączenie

list

lista

loading

ładowanie

locator

lokalizator

M

manager

zarządca

member

składnik

metrics

metryka

modal

dominujący

background image

175

multiple inheritance

wielodziedziczenie

multithreaded

wielowątkowy

N

native

rodzimy

narrowing

zawężenie

null

pusty

O

observer

obserwator

opaque

nieprzeźroczysty

overloaded

przeciążony

override

przedefiniować

P

package

pakiet

passive

bierny

peer

równorzędny

pipe

potok

pointer

pozycja

port

port

portable

przenośny

preempt

wywłaszczyć

preferred

ulubiony

prepared

przygotowany

priority

priorytet

process

proces

producer

producent

property

właściwość

protocol

protokół

R

radio button

przełącznik

rank

ranga

reanimate

wskrzesić

recursive

rekurencyjny

reentrant

wielobieżny

reference

odnośnik

region

obszar

relative

względny

rendering

wizualizacja

repetetive

cykliczny

resolve

związać

rest

spoczywać

reusable

wieloużytkowy

rounding

zaokrąglenie

run

wykonywać

S

saturation

nasycenie

scope

zakres

security

bezpieczeństwo

self contained

samodzielny

server

dostawca

session

sesja

shadow

przesłonić

shallow

płytki

shared

dzielony

side effect

skutek uboczny

signature

sygnatura

size

rozmiar

sleep

spać

background image

176

socket

gniazdo

source

ź

ródło

starved

zagłodzony

stream

strumień

stub

pień

style

styl

subclass

podklasa

superclass

nadklasa

supported

utrzymywany

suspended

zawieszony

T

target

cel

task

zadanie

teller

kasjer

text area

notatnik

text field

klatka

thread

wątek

tracker

nadzorca

transient

nietrwały

transparent

przeźroczysty

typeface

krój

U

unbalanced

niezrównoważony

V

verify

weryfikować

virtual

pozorny

volatile

ulotny

W

wait

czekać

widening

poszerzenie

widget

sterownik

background image

177















Dodatki

background image

178

Dodatek A

Priorytety operatorów


Następujące zestawienie wyszczególnia pełen zestaw operatorów, wraz z ich priorytetami i wiązaniami.

Priorytet

Wiązanie

Operator

1

lewe

++ -- (następnikowe)

2

prawe

++ -- (poprzednikowe)

3

prawe

+ - ~ ! (Type)

4

lewe

* / %

5

lewe

+ -

6

lewe

<< >> >>>

7

lewe

< <= > >= instanceof

8

lewe

== !=

9

lewe

&

10

lewe

^

11

lewe

|

12

lewe

&&

13

lewe

||

14

prawe

?:

15

prawe

= *= /= %= += -= <<= >>= >>>= &= ^= |=

background image

179

Dodatek B

Parametry zdarzeń


Następujące zestawienie określa w jaki sposób komponenty reagują na zdarzenia. W nawiasach podano jakie
spośród pól arg, when, x, y, modifiers mają interpretację dla wymienionych komponentów. Pola target nie
wymieniono, bo ma interpretację zawsze.

ACTION_EVENT

wykonanie akcji na sterowniku

Button

przycisk

(arg: etykieta

String)

Checkbox

nastawa

(arg: nowy stan

boolean)

Choice

wybór

(arg: etykieta

String)

List

lista

(arg: etykieta

String)

MenuItem

polecenie

(arg: etykieta

String)

TextField

klatka

(arg: zawarto

ść

String)



MOUSE_DOWN

naci

ś

ni

ę

cie przycisku myszki

MOUSE_UP

zwolnienie przycisku myszki

MOUSE_MOVE

przemieszczenie kursora

MOUSE_DRAG

przeci

ą

gni

ę

cie kursora

Component

(when, x, y, modifiers)



MOUSE_ENTER

wej

ś

cie kursora w obszar komponentu

MOUSE_EXIT

wyj

ś

cie kursora z obszaru komponentu

Component

(when, x, y)



LIST_SELECT

wyró

ż

nienie elementu listy

LIST_DESELECT

usuni

ę

cie wyró

ż

nienia

List

(arg: indeks

int)



GOT_FOCUS

uzyskanie gotowo

ś

ci do odbierania znaków

LOST_FOCUS

utrata gotowo

ś

ci

Component


KEY_PRESS

naci

ś

ni

ę

cie zwykłego klawisza

KEY_RELAEASE

zwolnienie zwykłego klawisza

KEY_ACTION

naci

ś

ni

ę

cie klawisza funkcjyjnego

KEY_ACTION_RELEASE zwolnienie klawisza funkcyjnego

Component

(when, x, y, key, modifiers)



SCROLL_LINE_UP

małe przewini

ę

cie do przodu (wiersz, znak)

SCROLL_LINE_DOWN

małe przewini

ę

cie wstecz

SCROLL_PAGE_UP

du

ż

e przewini

ę

cie do przodu (strona)

SCROLL_PAGE_DOWN

du

ż

e przewini

ę

cie wstecz

SCROLL_ABSOLUTE

przewini

ę

cie na pozycj

ę

Scrollbar

(arg: pozycja

int)



WINDOW_DESTROY

zniszczenie okna

WINDOW_EXPOSE

odsłoni

ę

cie okna

WINDOW_ICONIFY

zmniejszenie okna do ikony

WINDOW_DEICONIFY

powi

ę

kszenie ikony do okna

WINDOW_MOVED

przemieszczenie okna

Window

(x, y)

background image

180

Dodatek C

Klasa uruchomieniowa


Zdefiniowana tu klasa uruchomieniowa Debug ułatwia wyszukiwanie błędów występujących podczas
wykonywania aplikacji i apletów.

Wykonanie instrukcji

new Debug();


powoduje utworzenie ramki, w której są wyświetlane argumenty przeciążonej funkcji toFrame oraz funkcji
assert.

Uwaga: Przed użyciem klasy Debug w wersji źródłowej należy upewnić się, że moduł zawiera polecenie

import java.awt.*;


Na przykład

Debug.toFrame("Hello");
Debug.toFrame(var+1);
Debug.toFrame("var = " + var);
Debug.toFrame(Thread.currentThread());

Debug.assert("In paint: boxWidth > 5", boxWidth > 5);

Bezpośrednio po wywołaniu funkcji off, a przed najbliższym wywołaniem funkcji on, wstrzymuje się
wy
świetlanie argumentów metod toFrame i assert.

Uwaga: Jeśli nie wywołano konstruktora klasy Debug, to wywoływanie jej funkcji jest ignorowane.

Na przykład

new Debug(100, 100); // wywołanie konstruktora
// ...
Debug.off(); // zaniechanie wy

ś

wietlania

// ...
Debug.on(); // przywrócenie wy

ś

wietlania

// ...


Uwaga: Wykonanie operacji new Debug() może być tylko jednokrotne. W przypadku naruszenia tego
wymagania jest wysyłany wyjątek klasy InstantiationError.



import java.awt.*; // wymagane zawsze

class Debug {

/**
Klasa uruchomieniowa
Copyright © Jan Bielecki
*/

static Frame frame;
String header = "Debug\n=====\n";
static TextArea textArea;
static boolean opened = false;
static boolean active = false;
Debug()
{
this(200, 400);
}
Debug(int width, int height)
{
if(opened)
throw new InstantiationError();

background image

181

opened = true;
active = true;
frame = new DebugFrame("Debug");
frame.resize(width, height);
textArea = new TextArea(header);
frame.add("Center", textArea);
frame.show();
}
static synchronized
void toFrame(String string)
{
if(active)
textArea.appendText(string + '\n');
}
static synchronized
void toFrame(double num)
{
toFrame("" + num);
}
static synchronized
void toFrame(Object object)
{
if(active)
toFrame(object.toString());
}
static synchronized
void assert(String what, boolean isTrue)
{
if(active && !isTrue)
textArea.appendText("Assertion \"" + what +
"\" failed\n");
}
static synchronized void on()
{
if(opened)
active = true;
}
static synchronized void off()
{
active = false;
}
}

class DebugFrame extends Frame {
DebugFrame(String caption)
{
super(caption);
}
public boolean handleEvent(Event evt)
{
if(evt.id == Event.WINDOW_DESTROY) {
hide();
dispose();
return true;
}
return super.handleEvent(evt);
}
}

background image

182

Dodatek D

Styl programowania


Istnieje tyle stylów programowana ilu jest programistów. Jednak bez trudu można stwierdzić, że jedne style są
lepsze od innych. Dlatego, w celu ujednolicenia zapisu i tym samym ułatwienia wymiany programów, podano
tu opis stylu zastosowanego w niniejszej książce.

Wygląd

Program zapisuje się czcionką równomierną, na przykład Courier. Żaden wiersz programu nie liczy więcej niż
60 znaków.

W celu spełnienia tych wymagań oraz zwiększenia czytelności kodu stosuje się łamanie wierszy i dodawanie
odstępów (spacji i pustych wierszy). Łamania wyrażeń dokonuje się w taki sposób, aby ostatnim znakiem
złamanego wiersza był operator.

public static
boolean draw(Graphics gDC,
Color color,
int x, int y,
int width, int height)
{
StreamTokenizer tokens = new StreamTokenizer(
"c:\\cafe" +
"myfile.txt"
);
// ...
}


Konsekwencja

Identyczne konstrukcje języka zawsze wyglądają tak samo.

int arr[];
int[] vec; // niekonsekwentnie

System.out.println();
System.out.print('\n'); // niekonsekwentnie


Nadmiarowość

Nadmiarowość jest dozwolona tylko w celu zwiększenia czytelności programu. Dotyczy to w szczególności:
użycia zbędnego odnośnika this, użycia zbędnych nawiasów oraz użycia zbędnych specyfikatorów.

class Square {
int side;
Square(int base)
{
this.side = base; // zb

ę

dne this

// ...
if((a > 0) && (a < 10)) // zb

ę

dne nawiasy

System.exit(0);
}
// ...
}

abstract // zb

ę

dne abstract

interface Discardable
{
static final int ZERO = 0; // zb

ę

dne static i final

// ...
}


Komentarze

background image

183

Komentarz wierszowy jest wyrównany w pionie względem pozostałych takich komentarzy z jego otoczenia.

int x; // xCen
int y; // yCen


Komentarz blokowy zapisuje się od pierwszej kolumny. W wierszach następnych zapisuje się dodatkowy znak
* (gwiazdka). Zakończenie komentarza zapisuje się w osobnym wierszu. Ogół gwiazdek tworzy linię pionową.

/* Hello
* from
* me
*/


Uwaga: W dalszym opisie istnienie komentarzy nie będzie brane pod uwagę. W tym sensie za ostatni znak
wiersza

int x; // xCen


będzie odtąd uznawany średnik.

Wcięcia

Jeśli pewna konstrukcja składniowa zawiera inną (na przykład definicja klasy zawiera deklarację metody), to
konstrukcję wewnętrzną wcina się względem zewnętrznej.

Wcięcie uzyskuje się przez wstawienie ciągu spacji. Minimalny rozmiar wcięcia: 4 spacje. Wszystkie wcięcia
muszą być równe. Znaku tabulacji nie używa się.

class Greet {
int fun(int a)
{
if(a < 0)
return 0;
return a * a;
}
}


Identyfikatory

Identyfikatory pakietów zaczynają się od małej litery. Identyfikatory typów, stałych i etykiet od dużej.
Identyfikatory pól, zmiennych i procedur od małej.

Zaleca się dobieranie nazw mnemonicznych, na przykład

getV i setV

dla pobrania/zmiany (np. getColor, setColor)

isV

dla badania (np. isEmpty)

toV

dla konwersji (np. toString)


Nazwy jednoliterowe stosuje się tylko w specjalnych przypadkach, np.

b

dla byte

c

dla char

d

dla double

e

dla Exception

f

dla float

i, j, k

dla liczników

l

dla long

o

dla Object

s

dla String

v

dla wartości

background image

184

W identyfikatorach wielosłowowych stosuje się duże litery, a nie znaki podkreślenia. Znaku podkreślenia
używa się tylko w identyfikatorach stałych.

int index;
void getArea();
public anyVeryLongMeaningfulName;
public static final double Pi = 3.14;
final Color COLOR_RED = Color.red;


Separatory

Po każdym przecinku, dwukropku i średniku występuje odstęp (wyjątek: instrukcja for). Przed przecinkiem
oraz średnikiem oraz po nawiasie otwierającym i przed nawiasem zamykającym nie stawia się odstępu (wyjątek:
ś

rednik w instrukcji for).

fun(a, fun(a, b), b);
fun ( a , b ); // zb

ę

dne odstepy

fun(a, b); // dobrze


Specyfikatory

Specyfikatory klas i interfejsów wymienia się w następującej kolejności: dostępność (public, protected,
private), abstrakcyjność (abstract), ustaloność (final), rodzaj (class, interface).

Specyfikatory procedur wymienia się w następującej kolejności: dostępność (public, protected, private),
statyczność (static), abstrakcyjność (abstract), rodzimość (native), ustaloność (final), synchroniczność
(synchronized), typ (void, int, itp.).

Specyfikatory pól i zmiennych wymienia się w następującej kolejności: dostępność (public, protected,
private), statyczność (static), nietrwałość (transient), ulotność (volatile), ustaloność (final), typ (void, int,
itp.).

public abstract
class Input {
protected static void fun();
public abstract synchronized int met(int x);
private static volatile final int var = 12;
// ...
}


Bloki

Klamrę otwierającą ciało klasy albo interfejsu oraz blok zawarty w innej instrukcji (np. w inicjatorze klasy)
umieszcza się po spacji, w tym samym wierszu co nagłówek klasy albo interfesju, albo początkowa fraza
instrukcji. W pozostałych przypadkach klamrę otwierającą umieszcza się w osobnym wierszu (dotyczy to
przede wszystkim klamry otwierającej ciało funkcji).

Klamrę zamykającą blok zawsze umieszcza się w osobnym wierszu.

class Any {
void fun()
{
while(true) {
int i = 0;
{
// ..
}
}
}
}


Definicje klas i interfejsów

background image

185

Słowo kluczowe class i interface umieszcza się od pierwszej kolumny wiersza. Jeśli definicja jest poprzedzona
specyfikatorami, to umieszcza się je od pierwszej kolumny poprzedniego wiersza.

public abstract
class Greet {
// ...
}


Definicje procedur

Nagłówek procedury umieszcza się na początku wiersza, z jednym wcięciem. Klamrę otwierającą ciało
procedury umieszcza się w osobnym wierszu. W przypadku licznych parametrów dopuszcza się przeniesienia
po przecinku, wcięte do pierwszego parametru.

public
class Point {
Point(int x)
{
// ...
}
Point(int a, int b, int c,
int d, int e)
{
// ...
}
}


Etykiety

Etykieta zaczyna się w osobnym wierszu, począwszy od tej samej kolumny, od której zaczyna się następująca
po niej instrukcja.

Again:
while(a > 5) {
// ...
break Again;
}


Wyrażenia

Między pustymi nawiasami kwadratowymi i okrągłymi, po obu stronach kropki oraz po operatorze
jednoargumentowym nie umieszcza się spacji. Po obu stronach operatora dwuargumentowego umieszcza się
spację.

int arr[];
int len = +arr.length() + 1;


Uwaga: Jeśli argumentem operacji dwuargumentowej jest liczba, to zezwala się, aby operator nie był otoczony
spacjami jeśli zwiększy to czytelność programu.

arr[2*i+1] = 2*(i-1);


Wyrażenie które nie mieści się w wierszu jest dzielone między kolejne wiersze w taki sposób, że ostatnim
znakiem wiersza jest operator. Przeniesiona do następnego wiersza część wyrażenia jest wówczas wyrównana
albo wcięta i wyrównana.

System.out.println("Hello" +
" " +
"World");
System.out.println(
"Hello" +
" " +
"World"
);

background image

186


Instrukcje

Każda instrukcja i podinstrukcja zaczyna się w osobnym wierszu.

if(a > 5) break; //

ź

le

if(a > 5)
break;

instrukcje if

Jeśli po frazie if występuje klamra otwierająca, to odpowiadającą jej klamrę zamykającą umieszcza się pod if.

Jeśli po frazie else występuje fraza if, to frazy else i if zapisuje się w tym samym wierszu.

Jeśli instrukcja if zawiera frazę frazą else, to każdy z następujących napisów

else

} else

} else {


umieszcza się w osobnym wierszu, w pionowym wyrównaniu pod if.

if(a > 5)
fun(a, b)

if(a > 5)
fun(a, b)
else {
fun(c, d);
fun(e, f);
}

if(a > 5) {
fun(a, b);
fun(c, d);
} else {
fun(e, f);
return;
}

if(a > 5)
fun(i);
else if(b > 5) {
fun(i+1);
return;
}

instrukcje for

Przed pierwszym średnikiem frazy zawartej w nagłówku instrukcji for nie stawia się spacji. Po każdej stronie
drugiego umieszcza się jedną spację.

for(int i = 0; i < 5 ; i++) {
fun(i);
fun(i+1);
}

instrukcje while

Jeśli ciałem instrukcji while jest blok, to zamykająca go klamra występuje w pionowym wyrównaniu pod while.

while(a > 5) {
fun(i);
fun(i+1);
}

instrukcje do

background image

187

Jeśli ciałem instrukcji do jest blok, to otwierająca go klamra występuje w tym samym wierszu co nawias
zamykający nagłówek, a napis

} while(


występuje w pionowym wyrównaniu pod do.

do {
fun(i);
fun(i+1);
} while(i < 4);


instrukcje switch

Klamra otwierająca blok instrukcji switch występuje w tym samym wierszu co nagłówek instrukcji.

Każda fraza case zaczyna się i kończy w osobnym wierszu. Instrukcje frazy case są względem niej wcięte.

switch(a) {
case 0:
a = 3;
break;
case 1:
a = 4;
}

instrukcje try

Klamra otwierająca bloku try występuje w tym samym wierszu co słowo kluczowe try. Każda fraza catch
zaczyna się od nowego wiersza.

try {
fun(i);
}
catch(IOException e) {
}
catch(Exception e) {
System.out(e.getMessage());
}
finally {
i = 0;
}

background image

188

Dodatek E

Typy predefiniowane


Predefiniowanymi typami danych są typy orzecznikowe, arytmetyczne i łańcuchowe. Typy arytmetyczne dzielą
się na całkowite (byte, short, int, long), rzeczywiste (float, double) i znakowe (char). Typami łańcuchowymi
są predefiniowane typy obiektowe: String i StringBuffer.

Uwaga: Ponieważ typ znakowy jest typem arytmetycznym, więc na jego danych można wykonywać takie
same operacje jak na danych całkowitych i rzeczywistych.

Typ orzecznikowy


Typem orzecznikowym jest typ "boolean". Zmienne typu orzecznikowego mogą przybierać wartości true
(prawda) i false (fałsz).

Każde wyrażenie orzecznikowe exp może być poddane konwersji na wyrażenie całkowite za pomocą operacji

exp ? 1 : 0


(por. Dodatek Operacje i operatory), a każde wyrażenie całkowite exp może być poddane konwersji na
wyrażenie orzecznikowe za pomocą operacji

exp != 0


Typy całkowite


Typami całkowitymi są: "byte", "short", "int" i "long". Zmienne typów całkowitych są

byte

8-bitowe

(od -128 do 127)

short

16-bitowe

(od -32768 do 32767)

int

32-bitowe

(od -2,147,483,648 do 2,147,483,647)

long

64-bitowe

(od -9,223,372,036,854,775,808 do

+9,223,372,036,854,775,807)

Dane przypisane zmiennym całkowitym są reprezentowane w uzupełnień do 2. Wynika stąd m.in., że jeśli
zaneguje się wszystkie bity danej, a następnie doda do niej 1, to skutek będzie taki, jakby znak danej zmieniono
na przeciwny.

Typ liczby wynika jednoznacznie z jej wartości (jako kryterium wyboru przyjmuje się oszczędność
reprezentacji).

Niezależnie od jej wartości, liczba zakończona literą L albo l jest typu "long".

Na przykład 32767 jest typu "short", a 32768 jest typu "int", ale 0L jest typu "long".

Typy rzeczywiste


Typami rzeczywistymi są: "float" i "double". Zmienne typów całkowitych są:

float

32-bitowe (w przybliżeniu od -3.4e38 do 3.4e38)

double

64-bitowe (w przybliżeniu od -1.8e308 do 1.8e308)


Dane przypisane zmiennym rzeczywistym są reprezentowane w zapisie modułu-i-znaku, a wyniki nietypowych
operacji, jak na przykład

background image

189


1.0 / 0

(Double.POSITIVE_INFINITY)

1.0 /-0

(Double.NEGATIVE_INFINITY)

1.0 / 0 * 0

(Double.NaN tj. NotANumber)


mają wartości, które można reprezentować przez symbole podane w nawiasach.

Każda liczba z wąskiego przedziału wokół 0 jest reprezentowana przez zero-ze-znakiem (tj. ujemne-zero albo
dodatnie-zero). Z punktu widzenia relacji równe i nie-równe oba te zera uznaje się za równe.

Typ liczby rzeczywistej wynika jednoznacznie z jej wartości (jako kryterium wyboru przyjmuje się
oszczędność reprezentacji).

Każda prosta liczba rzeczywista (np. -1.2, 1., -.2, 1.2e-1) oraz każda prosta liczba rzeczywista zakończona literą
D albo d jest typu "double". Każda prosta liczba całkowita albo rzeczywista zakończona literą F albo f jest typu
"float".

Na przykład -2.4 oraz 3e2 jest typu "double", a -2e-3f jest typu "float".

Typ znakowy


Typem znakowym jest "char". Zmiennym typu znakowego są zazwyczaj przypisywane 16-bitowe znaki
Unikodu. Unikod umożliwia reprezentowanie znaków wszystkich języków europejskich oraz większości
znaków pozostałych języków, w tym ideograficznych znaków Han używanych w krajach azjatyckich.

Pierwszych 256 Unikodu jest identycznych ze znakami kodu ASCII Latin-1 (kod ten zawiera m.in. większość
znaków języków zachodnio-europejskich).

Literały typu "char" mają postać 'c' gdzie c jest: znakiem widocznym (np. 'a'), symbolem znaku (np. '\n'),
ósemkowym kodem znaku (np. '\141') albo czterocyfrowym szesnastkowym kodem znaku (np. '\u0061').

Typy łańcuchowe



Predefiniowanymi typami łańcuchowymi są String i StringBuffer. W istocie nie są to odrębne typy, gdyż
zostały implementowane jako klasy obiektowe. Jednak ze względu na szczególną rolę jaką pełnią w języku,
zasługują na specjalne potraktowanie.

Zarówno String, jak i StringBuffer, są klasami ustalonymi (final), to jest takimi, które nie mogą być
dziedziczone. Właściwość ta zapewnia im bardzo efektywną implementację.

Obiekty klasy Stringniemodyfikowalne, a więc, jeśli rezultat pewnej operacji jest klasy String, to nie musi
być odrębnym obiektem. Obiekty klasy StringBuffermodyfikowalne. Klasa StringBuffer jest używana
przede wszystkim do tworzenia nowych obiektów klasy String.

W szczególności, wyrażenie

4 + "sale"


jest niejawnie przekształcane w wyrażenie

new StringBuffer().append(4).append("sale").toString()


Klasa String

background image

190

Metody klasy String są używane do porównywania, porządkowania, rozpoznawania, wycinania i
przekształcania znaków.

Uwaga: Znaki łańcuchów są indeksowane od 0. Jeśli metoda ma zwrócić indeks znaku, ale takiego znaku nie
ma, to zwraca wartość -1.

boolean equals(Object obj)

Metoda służy do porównania łańcucha z obiektem przekształconym w łańcuch (najczęściej z innym
łańcuchem).

String hello = "Hello";
String world = "World";
boolean result = hello.equals(world); // false


int compareTo(String string)

Metoda służy do porównania dwóch łańcuchów. Jeśli są równe, to rezultatem jest 0. Jeśli pierwszy jest
mniejszy, to rezultatem jest liczba ujemna, a jeśli drugi, to dodatnia.

String hello = "Hello";
String world = "World";
int value = hello.compareTo(world);
if(value < 0)
text = "less";
else if(value == 0)
text = "equal";
else
text = "greater";
boolean result = text.equals("less"); // true


char charAt(int pos)

Metoda zwraca znak występujący na podanej pozycji łańcucha.

String string = "Hello";
char chr = string.charAt(4); // 'o'


int indexOf(char chr)

Metoda zwraca indeks pierwszego wystąpienia podanego znaku w łańcuchu.

String string = "buffer";
int result = string.indexOf('u'); //1


int indexOf(String string)

Metoda zwraca indeks pierwszego znaku stanowiącego podany podciąg łańcucha.

String string = "remaining";
int pos = string.indexOf("main"); // 2


String substring(int from, int to)

Metoda zwraca podciąg łańcucha składający się ze znaków występujących "między" znakami o podanych
indeksach.

String string = "0123456789";
String result = string.substring(2, 6); // 2345 (sic!)


String toUpperCase(String string)

Metoda zwraca łańcuch powstały z oryginału przez zamianę każdej małej litery na dużą.

background image

191

String string = "Hello";
String result = string.toUpperCase(); // HELLO


String toLowerCase(String string)

Metoda zwraca łańcuch powstały z oryginału przez zamianę każdej dużej litery na małą.

String string = "Hello World";
String result = string.toLowerCase(); // hello world


Klasa StringBuffer


Metody klasy StringBuffer są używane do przekształcania ciągów znaków. W odróżnieniu od metod klasy
String, metody te na ogół nie tworzą nowych obiektów, ale tylko modyfikują je.

void setCharAt(int index, char chr)

Metoda zmienia znak na podanej pozycji łańcucha.

StringBuffer string = new StringBuffer("Hello World");
string.setCharAt(5, '*');
String result = new String(string); // Hello*World


StringBuffer append(char chr)

Metoda wydłuża łańcuch o podany znak.

StringBuffer string = new StringBuffer("Hello");
string.append('*').append("World");
String result = new String(string); // Hello*World


StringBuffer append(String string)

Metoda wydłuża łańcuch o podany łańcuch.

StringBuffer string = new StringBuffer("Hello");
string.append("World");
String result = new String(string); // HelloWorld


StringBuffer append(char arr[], int offset, int len)

Metoda wydłuża łańcuch o znaki podanego wycinka tablicy.

char arr[] = { ' ', 'W', 'o', 'r', 'l', 'd' };
StringBuffer string = new StringBuffer("Hello");
string.append(arr, 1, 5);
String result = new String(string); // HelloWorld


StringBuffer append(Object obj)

Metoda wydłuża łańcuch o łańcuch utworzony z podanego obiektu, po wywołaniu na jego rzecz metody
toString jego klasy.

Double value = new Double(2 - 3.4);
StringBuffer string = new StringBuffer("x");
string.append(value);
String result = new String(string); // x-1.4


StringBuffer insert(int offset, char chr)

Metoda wstawia podany znak w podanym miejscu łańcucha.

StringBuffer string = new StringBuffer("HelloWorld");

background image

192

string.insert(5, '*');
String result = new String(string); // Hello*World"


StringBuffer insert(int offset, String string)

Metoda wstawia podany łańcuch w podanym miejscu łańcucha.

StringBuffer string = new StringBuffer("HelloWorld");
string.insert(5, "***");
String result = new String(string); // Hello***World"


StringBuffer insert(int offset, char arr[])

Metoda wstawia znaki tablicy w podanym miejscu łańcucha.

char arr[] = { '*', ' ', '*' };
StringBuffer string = new StringBuffer("HelloWorld");
string.insert(5, arr);
String result = new String(string); // Hello* *World"


StringBuffer insert(int offset, Object obj)

Metoda wstawia znaki łańcucha utworzonego z podanego obiektu, po wywołaniu na jego rzecz metody
toString jego klasy.

Integer value = new Integer(2);
StringBuffer string = new StringBuffer("x");
string.insert(0, value);
String result = new String(string); // 2x


Typy kopertowe


Typami kopertowymi są predefiniowane typy obiektowe "Boolean", "Integer", "Long", "Float", "Double",
"Character. Obiekt typu kopertowego stanowi otoczkę dla zmiennej odpowiadającego mu typu podstawowego:
"boolean", "int", "long", "float", "double", "char".

Na zmiennych typu kopertowego można wykonywać wiele operacji, których nie można wykonywać na
zmiennych typu podstawowego.

W szczególności, zmienne typu kopertowego można tworzyć na stercie, a za pomocą funkcji ich typu można
dokonywać wielu użytecznych konwersji, podanych tu dla typu "Integer".

public static Integer valueOf(String str)

Rezultatem funkcji jest zmienna typu kopertowego zainicjowana wartością liczby zawartej w podanym
łańcuchu.

Integer var(0);
var = Integer.valueOf("20");
System.out.print(var); // 20


public static String toString(int val)

Rezultatem funkcji jest odnośnik do obiektu zainicjowanego łańcuchem utworzonym z argumentu.

int var = 20;
String str = var.toString();
System.out.print(str); // 20

public static int parseInt(String str)

Rezultatem funkcji jest zmienna typu podstawowego zainicjowana wartością liczby zawartej w podanym
łańcuchu.

background image

193

int val = Integer.parseInt(str);

background image

194

Dodatek F

Operacje i operatory


Opisem operacji jakie maj¹ byæ wykonane na danych jest wyra¿enie. O kolejnoœci wykonania operacji
wchodz¹cych w sk³ad wyra¿enia decyduje sposób u¿ycia nawiasów oraz uwzglêdnienie priorytetów i wi¹zañ
operatorów (por. Dodatek Priorytety operatorów). We wszystkich pozosta³ych przypadkach wyra¿enia s¹
opracowywane œciœle od-lewej-do-prawej (dotyczy to w szczególnoœci kolejnoœci opracowywania
argumentów procedur).

Wyrażenia stałe


W niektórych miejscach programu (na przyk³ad w wyra¿eniach fraz case), mo¿e wyst¹piæ tylko wyra¿enie sta³e
ca³kowite
.

W sk³ad takiego wyra¿enia mog¹ wchodziæ jedynie litera³y, identyfikatory statycznych zmiennych ustalonych
zainicjowanych wyra¿eniami sta³ymi oraz konwersje do typu ca³kowitego.

Na przyk³ad

class Any {
static final int one = 1;
static final double two = 2.9;
void sub(int par)
{
switch(par) {
case one:
// ...
case (int)(two + 2):
break;
}
}
}


Wyrażenie

(int)(two + 2)


jest wyrażeniem stałym o wartości 4 (sic!).

Respektowanie nawiasów


Jeœli wyra¿enie w nawiasach okr¹g³ych jest poprzedzone operatorem, to wykonanie operacji okreœlonej przez
ten operator nast¹pi dopiero po opracowaniu wyra¿enia w nawiasach.

Na przyk³ad

a - (b + c)


Operacja odejmowania zostanie wykonana dopiero po wykonaniu operacji dodawania.

Uwzględnianie priorytetów


Jeœli wyra¿enie jest argumentem dwóch operatorów o ró¿nym priorytecie, to najpierw wykonuje siê operacjê
okreœlon¹ przez operator o wy¿szym priorytecie.

Na przyk³ad

a + b * c; // a + (b * c);

background image

195


Najpierw zostanie wykonana operacja mno¿enia, a dopiero po niej operacja dodawania.

Uwzględnianie wiązań


Jeœli wyra¿enie mo¿e byæ uznane za argument dwóch operatorów o równym priorytecie, to kolejnoœæ
wykonania operacji jest okreœlona przez wi¹zanie.

Jeœli wi¹zanie operatora jest lewe, to najpierw jest wykonywana operacja okreœlona przez operator znajduj¹cy
siê z lewej strony wyra¿enia, a jeœli jest prawe, to najpierw z prawej strony.

Na przyk³ad

double num;
int fix;
num = fix = 4;


Poniewa¿ operator przypisania ma wi¹zanie prawe, wiêc ostatnia instrukcja jest traktowana tak jak instrukcja

num = (fix = 4);


a ta jak para instrukcji

fix = 4;
num = fix;


Realizowanie skutków ubocznych


Skutkiem ubocznym opracowania wyra¿enia jest wykonanie przypisañ i przes³añ danych. Skutek uboczny
operacji musi byæ zrealizowany nie póŸniej ni¿ w najbli¿szym punkcie charakterystycznym.

Punkt charakterystyczny programu wystêpuje tu¿ za ka¿dym kompletnym wyra¿eniem i deklaratorem, tu¿ po
operatorach

&& (koniunkcja), || (dysjunkcja)


oraz tu¿ po znaku ? (pytajnik) w trójargumentowym operatorze ?:.

Na przyk³ad

int fix = 0;
boolean bool = fix++ != 0 || fix != 0;


W chwili opracowywania prawego argumentu operatora dysjunkcji zmienna fix ma wartoϾ 1.

Gdyby przed tym operatorem nie wystêpowa³ punkt charakterystyczny, to mog³aby mieæ wartoœæ 0.


_________________________________________________________________________________________

Operacje


Operacje na danych mogą być wykonywane za pomocą wyrażeń fabrykujących, albo za pomocą operatorów.
Ponieważ napisy [] (nawias, nawias), . (kropka), () (nawias, nawias) i new nie są operatorami, więc
indeksowania tablicy, wybierania składników klasy, wywoływania procedur oraz zarządzania pamięcią
operacyjn
ą nie realizuje się za pomocą operatorów.

Indeksowanie tablicy

background image

196


Indeksowanie tablicy wyra¿a siê za pomoc¹ znaków [] (nawias, nawias). Rezultatem indeksowania jest element
tablicy. Wi¹zanie indeksowania tablicy jest lewe.

Np. arr[10][20] == (arr[10])[20]


Operacja indeksowania ma postaæ

ref[ind]


w której ref jest odnoœnikiem do tablicy, a ind jest wyra¿eniem ca³kowitym.

Wyra¿enie ref[ind] jest l-nazw¹ tego elementu tablicy identyfikowanej przez ref, który ma indeks ind.

Na przyk³ad

int vec[] = { 10, 20, 30 };


Zmienna vec jest odnoœnikiem do 3-elementowego wektora. Wyra¿enie vec[0] jest nazw¹ pierwszego, a
wyra¿enie vec[2] jest nazw¹ ostatniego elementu tego wektora.


Wybór składnika


Wybór sk³adnika klasy wyra¿a siê za pomoc¹ znaku . (kropka). Rezultat wyboru pola jest zmienn¹ opisan¹ przez
to pole. Wi¹zanie wyboru sk³adnika jest lewe.

Np.

var.fun().var == (var.fun()).var


Operacja wyboru ma postaæ

ref.name


w której ref jest odnoœnikiem do obiektu, a name jest identyfikatorem jego sk³adnika.

Wyra¿enie ref.name jest l-nazw¹ sk³adnika obiektu identyfikowanego przez ref.

Na przyk³ad

class Child {
String name;
int age = 0;
Child(String name, int age)
{
this.name = name;
this.age = age;
}
void setAge(int name)
{
this.age = age;
}
void sub()
{
Child tom = new Child("Thomas", 12),
bob = new Child("Robert", 0) ;
// ...
System.out.println(tom.name);
bob.setAge(12);
// ...
}
}


background image

197

Wywołanie procedury


Wywo³anie procedury wyra¿a siê za pomoc¹ znaków () (nawias, nawias). Jeœli typ procedury jest ró¿ny od
"void", to istnieje jej rezultat.

Np. sub(x, y).z = 2;


Operacja wywo³ania procedury ma postaæ

ref.proc(arg, arg, ... , arg)


w której ref jest odnoœnikiem identyfikuj¹cym obiekt klasy, zawieraj¹cej procedurê proc, a ka¿de arg jest
argumentem wywo³ania.

Rezultatem wywo³ania procedury typu ró¿nego od "void" jest zmienna zainicjowana wartoœci¹ okreœlon¹ w
instrukcji powrotu.

Na przyk³ad

class Master {
int fix;
Master met()
{
return new Master();
}
void sub()
{
met().fix = 12;
met() = null; // bł

ą

d (met() nie jest l-nazw

ą

)

}
}


Typ rezultatu metody met jest odnoœnikowy. Wywo³anie met() jest nazw¹ zmiennej, ale nie jest l-nazw¹.

Zarządzanie pamięcią operacyjną


Zarz¹dzanie pamiêci¹ operacyjn¹ odbywa siê za pomoc¹ operacji new. Wykonanie operacji new powoduje
przydzielenie obszaru pamiêci na stercie. Zwolnienie obszaru dokona siê automatycznie, ale nie wczeœniej ni¿
po tym, gdy ani jeden odnoœnik nie bêdzie ju¿ identyfikowa³ tego obszaru.

Przydzielenie obiektu


Operacja przydzielenia obiektu ma postaæ

new Class(arg, arg, ... , arg)


w której Class jest nazw¹ klasy, a ka¿de arg jest argumentem jej konstruktora.

Rezultatem operacji przydzielenia jest odnoœnik do w³aœnie przydzielonego obiektu.

Na przyk³ad

String ref = new String("Hello");
ref = new String();


Przydzielenie tablicy


Operacja przydzielenia tablicy ma postaæ

background image

198

new Type [exp][exp] ... [exp]


w której Type jest identyfikatorem typu (np. "int" albo "String"), a ka¿de exp jest wyra¿eniem ca³kowitym
(jeœli pewne z nich jest puste, to wszystkie nastêpuj¹ce po nim tak¿e musz¹ byæ puste).

Rezultatem operacji przydzielenia tablicy jest odnoœnik do w³aœnie przydzielonej tablicy.

Na przyk³ad

int vec[] = new int [2];
vec = new int [4];
String arr[][] = new String [3][];
arr = new String [3][4];


Po wykonaniu instrukcji

int vec[] = new int [2];


odnoœnik vec identyfikuje 4-elementow¹ tablicê zmiennych typu "int".

Po wykonaniu instrukcji

String arr[][] = new String [3][];


odnoœnik arr identyfikuje 3-elementow¹ tablicê odnoœników do tablic odnoœników do obiektów klasy String
(ka¿dy z 3 odnoœników tablicy ma wartoœæ null).

Po wykonaniu instrukcji

arr = new String [3][4];


odnoœnik arr identyfikuje 3-elementow¹ tablicê odnoœników do 4-elementowych tablic odnoœników do
obiektów klasy String (ka¿dy z 3 odnoœników ma wartoœæ ró¿n¹ od null, ale ka¿dy z pozosta³ych ma wartoœæ
null).


_______________________________________________________________________________________

Operatory


Wykaz operatorów języka, z podaniem ich priorytetów i wiązań znajduje się w Dodatku Priorytety operatorów.

Rezultatem każdej z wymienionych tu operacji jest zmienna. Każde wyrażenie określone przez operację jest
chwilową nazwą tej zmiennej. To, czy jest l-nazwą wynika z opisu operacji.

Operatory konwersji


Operatorem konwersji jest (Type). Wi¹zanie operatora konwersji jest prawe.

Np. (double)(int)12.8 == (double)((int)12.8)


Operacja konwersji ma postaæ

(Type)exp


w której Type jest nazw¹ typu docelowego, a exp jest wyra¿eniem poddawanym konwersji.

Rezultatem konwersji jest zmienna typu "Type", zainicjowana wartoœci¹ wyra¿enia exp po przekszta³ceniu jej
do typu "Type".

background image

199


Uwaga: Jeœli typem docelowym jest tablica odnoœników, to w nazwie typu nie mog¹ wyst¹piæ rozmiary
tablicy.

Na przyk³ad

String str[] = { "Hello", "World" };
Object obj[] = (Object [])str;
System.out.print((String)obj[0]); // Hello
system.out.print(obj[1]); // World


Konwersje nieodnośnikowe


Rezultatem konwersji nieodnoœnikowej (np. z typu "double" do "int") jest zmienna typu "Type", zainicjowana
wartoœci¹ wyra¿enia exp po przekszta³ceniu jej do typu "Type".

Na przyk³ad

System.out.print(12.8); // 12.8
System.out.print((double)(int)12.8); // 12


Konwersje odnośnikowe


Konwersja odnoœnikowa jest poprawna tylko wówczas, gdy jest dopuszczalna i wykonalna. Rezultatem
poprawnej konwersji odnoœnikowej (np. z typu "Vector" do "Object") jest odnoœnik zainicjowany wartoœci¹
argumentu.

Konwersja odnoœnikowa jest dopuszczalna tylko wówczas, gdy jest to¿samoœciowa (np. z typu "Vector" do
"Vector") albo gdy polega na przekszta³ceniu z klasy do nadklasy (np. z "Vector" do "Object"), z klasy do
podklasy (np. z "Object" do "Vector") albo z klasy do implementowanego przez ni¹ interfejsu (np. z "Vector"
do "Cloneable").

Konwersja odnoœnikowa jest wykonalna tylko wówczas, gdy jest dopuszczalna, a wyra¿enie

exp instanceof Type


(por. opis operatora instanceof) ma wartoϾ true.

Na przyk³ad

String city = "Warsaw";
Object obj = city; // konwersja niejawna
city = (String)obj; // konwersja jawna
Vector vec = (Vector)obj; // bł

ą

d (konwersja niewykonalna)


Operatory zwiększenia i zmniejszenia


Operatorem zwiêkszenia jest ++ (plus, plus), a operatorem zmniejszenia jest --(minus, minus). Ka¿dy z nich
mo¿e wyst¹piæ w postaci przyrostkowej albo przedrostkowej. Wi¹zanie operatora przyrostkowego jest lewe, a
przedrostkowego prawe.

Argument operacji zwiêkszenia i zmniejszenia musi byæ l-nazw¹ zmiennej.

Np.

fix--

++++fix == ++(++fix) // bł

ą

d (++fix nie jest l-nazw

ą

)


background image

200

Operacje przyrostkowe


Przyrostkowa operacja zwiêkszenia ma postaæ

var++


a przyrostkowa operacja zmniejszenia ma postaæ

var--


w których var jest wyra¿eniem arytmetycznym (m.in. typu "char").

Wyra¿enie var++ jest nazw¹ zmiennej tymczasowej zainicjowanej wyra¿eniem var. Skutkiem ubocznym
wykonania operacji jest zwiêkszenie wartoœci zmiennej var o 1.

Wyra¿enie var-- jest nazw¹ zmiennej zainicjowanej wyra¿eniem var. Skutkiem ubocznym wykonania operacji
jest zmniejszenie wartoœci zmiennej var o 1.

Na przyk³ad

int fix = 12;
int fix1 = fix--;
System.out.print(fix); // 11
System.out.print(fix1); // 12


Operacje przedrostkowe


Przedrostkowa operacja zwiêkszenia ma postaæ

++var


a przedrostkowa operacja zmniejszenia ma postaæ

--var


w których var jest wyra¿eniem arytmetycznym (m.in. typu "char").

Wyra¿enie ++var jest l-nazw¹ zmiennej var. Skutkiem ubocznym wykonania operacji jest zwiêkszenie
wartoœci zmiennej var o 1.

Wyra¿enie --var jest l-nazw¹ zmiennej var. Skutkiem ubocznym wykonania operacji jest zmniejszenie
wartoœci zmiennej var o 1.

Na przyk³ad

int fix = 10;
int fix1 = ++fix;
System.out.print(fix); // 11
System.out.print(fix1); // 11


Operatory zachowania i zmiany znaku


Operatorami zachowania i zmiany znaku s¹ + (plus) i - (minus). Wi¹zanie tych operatorów jest prawe.

Np. -+var == -(+var)


Operacja zachowania znaku ma postaæ

background image

201

+exp


a operacja zmiany znaku ma postaæ

-exp


w których exp jest wyra¿eniem arytmetycznym.

Wyra¿enie +exp oraz wyra¿enie -exp jest nazw¹ zmiennej takiego samego typu jakiego jest wyra¿enie exp, ale
po poddaniu go promocjom (np. z typu "char" do "int").

Na przyk³ad

char fix = '2';
System.out.print(fix); // 2
System.out.print(+fix); // 50 (kod cyfry 2)


Operatory czynnikowe


Operatorami czynnikowymi s¹ * (gwiazdka), / (skoœnik) i % (procent). Operatory czynnikowe s¹ u¿ywane w
operacjach mno¿enia, dzielenia i wyznaczania reszty z dzielenia. Wi¹zanie operatorów czynnikowych jest lewe.

Np. a / b * c == (a / b) * c


Operator *


Operacja mno¿enia ma postaæ

expL * expR


w której expL i expR s¹ wyra¿eniami arytmetycznymi.

Rezultat mno¿enia jest nazw¹ zmiennej, zainicjowanej iloczynem argumentów.

Na przyk³ad

System.out.print('#' * 2); // 70


Operator /


Operacja dzielenia ma postaæ

expL / expR


w której expL i expR s¹ wyra¿eniami arytmetycznymi.

Rezultat dzielenia jest nazw¹ tymczasowej zmiennej, zainicjowanej ilorazem lewego i prawego argumentu.
Jeœli oba argumenty s¹ ca³kowite, to rezultat tak¿e jest ca³kowity.

Na przyk³ad

System.out.print(5.0 / 2); // 2.5
System.out.print(5 / 2); // 2
System.out.print(1 / 2 * 4); // 0
System.out.print(1. / 2 * 4); // 2


background image

202

Operator %


Operacja wyznaczenia reszty z dzielenia ma postaæ

expL % expR


w której expL i expR s¹ wyra¿eniami arytmetycznymi ca³kowitymi.

Rezultat wyznaczenia reszty jest nazw¹ zmiennej zainicjowanej reszt¹ z dzielenia lewego argumentu przez
prawy. Znak reszty jest taki sam jak znak lewego argumentu.

Przyjmuje siê z definicji, ¿e

a % b == a - (a / b) * b


Na przyk³ad

System.out.print(14 % 3); // 2
System.out.print(14 % -3); // 2
System.out.print(-14 % 3); // -2


Operatory składnikowe


Operatorami sk³adnikowymi s¹ + (plus) i - (minus). Operatory sk³adnikowe s¹ u¿ywane w operacjach
dodawania i odejmowania. Wi¹zanie operatorów sk³adnikowych jest lewe.

Np. a - b - c == (a - b) - c


Operator +


Operacja dodawania ma postaæ

expL + expR


w której expL i expR s¹ wyra¿eniami arytmetycznymi, znakowymi albo ³añcuchowymi.

Rezultat dodawania jest nazw¹ zmiennej, zainicjowanej sum¹ argumentów.

Na przyk³ad

System.out.print(2 + 3); // 5
System.out.print('#' + 1); // 36
System.out.print('a' + 0); // 97
System.out.print('a' + "0"); // a0

Operator -


Operacja odejmowania ma postaæ

expL - expR


w której expL i expR s¹ wyra¿eniami arytmetycznymi.

Rezultat odejmowania jest nazw¹ zmiennej, zainicjowanej ró¿nic¹ lewego i prawego argumentu.

Na przyk³ad

System.out.print(2 - 3); // -1

background image

203

System.out.print('z' - 'a'); // 25


Operator pochodzenia


Operatorem pochodzenia jest instanceof. Wiązanie operatora przynależności jest lewe.

Np.

((Object)new String()) instanceof String


Operacja pochodzenia ma postać

exp instanceof Class


w której exp jest wyrażeniem odnośnikowym, a Class jest identyfikatorem klasy.

Rezultatem operacji pochodzenia jest orzecznik, który ma wartośc true tylko wówczas, gdy odnośnik exp
identyfikuje obiekt klasy Class albo jej podklasy.


Na przykład

class Person {
// ...
}

class Woman extends Person {
// ...
}

class Master {
static void sub(Person person)
{
if(person instanceof Woman) {
Woman woman = (Woman)person;
// ...
}
}
}


Instrukcja

Woman woman = (Woman)person;


zostanie wykonana tylko wówczas, gdy odnośnik person identyfikuje obiekt klasy Woman.

Tak się stanie, gdy funkcja sub zostanie wywołana za pomocą instrukcji

Master.sub(new Woman());


ale nie stanie się, jeśli zostanie wywołana za pomocą instrukcji

Master.sub(new Person());


Operatory porównania


Operatorami porównania s¹: < (mniejsze), > (wiêksze), <= (mniejsze, równe), >= (wiêksze, równe), == (równe),
!= (nie równe). Dwa ostatnie operatory maj¹ priorytet ni¿szy ni¿ pozosta³e. Wi¹zanie operatorów porównania
jest lewe.

Np. a < b < c == (a < b) < c


Operacja porównania ma postaæ

background image

204

expL @ expR


w której @ jest dowolnym operatorem porównania, a expL i expR s¹ wyra¿eniami: oba arytmetycznymi, oba
orzecznikowymi
, albo oba odnoœnikowymi.

Rezultatem operacji porównania jest zmienna orzecznikowa o wartoœci true jeœli relacja okreœlona przez
porównanie jest prawdziwa, albo zmienna o wartoœci false jeœli relacja jest fa³szywa.

Porównania arytmetyczne


Porównanie zmiennych arytmetycznych (w tym zmiennych typu "char") polega na porównaniu ich wartoœci
liczbowych.

Na przyk³ad

System.out.print(1.2 >= 1); // true


Porównania orzecznikowe


Porównanie zmiennych orzecznikowych polega na porównaniu ich wartoœci logicznych. Zezwala siê na u¿ycie
tylko operatorów równe (==) i nie-równe (!=).

Na przyk³ad

boolean flag = true;
System.out.print(flag != false); // true


Porównania odnośnikowe


Porównania odnoœników mog¹ byæ tylko na równoœæ (==) i nie-równoœæ (!=). Jeœli oba odnoœniki
identyfikuj¹ ten sam obiekt (w szczególnoœci tablicê), to rezultat porównania na równoœæ ma wartoœæ true, a
w przeciwnym razie ma wartoœæ false. Analogicznie definiuje siê porównanie na nie-równoœæ.

Na przyk³ad

String one = "Hello" + "World",
two = "HelloWorld";
System.out.print(one == two); // true
System.out.print(one + "" != one); // true


Operatory logiczne


Operatorami logicznymi s¹: ! (zaprzeczenie), && (koniunkcja) i || (dysjunkcja). Najwy¿szy priorytet ma
zaprzeczenie, a najni¿szy dysjunkcja. Wi¹zanie operatora zaprzeczenia jest prawe, a pozosta³ych operatorów
lewe.

Np. a || !b && c == a || ((!b) && c)


Operator !


Operacja zaprzeczenia ma postaæ

!exp

background image

205

w której exp jest wyra¿eniem orzecznikowym.

Rezultatem operacji zaprzeczenia jest zmienna orzecznikowa. Zmienna ta ma wartoœæ true tylko wówczas,
gdy wyra¿enie ma wartoœæ false.

Na przyk³ad

boolean flag = true;
System.out.print(!flag); // false
System.out.print(!(12 > 3); // false


Operator &&


Operacja koniunkcji ma postaæ

expL && expR


w której expL i expR s¹ dowolnymi wyra¿eniami orzecznikowymi.

Rezultatem koniunkcji jest zmienna orzecznikowa. Zmienna ta ma wartoœæ true tylko wówczas, gdy oba
wyra¿enia maj¹ wartoœæ true.

Uwaga: Tu¿ przed operatorem && wystêpuje punkt charakterystyczny. Jeœli wyra¿enie expL ma wartoœæ
false, to rezultat koniunkcji jest znany, a wiêc nie opracowuje siê wyra¿enia expR.

Na przyk³ad

int vec[] = { 10, 20, 30 };
int chr = System.in.read();
boolean flag = chr >= '0' && chr <= '2';
if(flag && chr == vec[chr - 'a'])
System.out.println();


Operacja == jest wykonywana tylko wówczas, gdy zmienna flag ma wartoœæ true.

Operator ||


Operacja dysjunkcji ma postaæ

expL || expR


w której expL i expR s¹ dowolnymi wyra¿eniami orzecznikowymi.

Rezultatem dysjunkcji jest zmienna orzecznikowa. Zmienna ta ma wartoœæ false tylko wówczas, gdy oba
wyra¿enia maj¹ wartoœæ false.

Uwaga: Tu¿ przed operatorem || wystêpuje punkt charakterystyczny. Jeœli wyra¿enie expL ma wartoœæ
true, to rezultat dysjunkcji jest znany, a wiêc nie opracowuje siê wyra¿enia expR.

Na przyk³ad

int vec[] = { 10, 20, 30 };
int chr = System.in.read();
boolean flag = chr < '0' && chr > '2';
if(flag || chr == vec[chr - 'a'])
System.out.println();


Operacja == jest wykonywana tylko wówczas, gdy zmienna flag ma wartoœæ false.

background image

206

Operatory bitowe


Operatorami bitowymi s¹: ~ (zanegowanie bitów), & (iloczyn bitów), | (suma bitów), ^ (suma modulo 2 bitów),
<< (przesuniêcie bitów w lewo), >> (przesuniêcie bitów w prawo), >>> (przesuniêcie bitów w prawo-bez-
znaku
). Najwy¿szy priorytet ma zanegowanie, ni¿szy przesuniêcia, a potem kolejno: iloczyn, suma modulo 2 i
suma
. Wi¹zanie operatora zanegowania bitów jest prawe, a pozosta³ych lewe.

Np. a || !b && c << d == a || ((!b) && (c << d))


Operator ~


Operacja zanegowania bitów ma postaæ

~exp


w której exp jest wyra¿eniem ca³kowitym.

Rezultatem operacji zanegowania bitów jest zmienna takiego samego typu jak exp, po poddaniu jej
promocjom, a nastêpnie zanegowaniu ka¿dego jej bitu.

Uwaga: Negacj¹ bitu 1 jest bit 0, a negacj¹ bitu 0 jest bit 1.

Na przyk³ad

int red = 1, green = 2, blue = 4;
int hue = red | green; // 00 ... 011 (kolor

ż

ółty)

hue = ~hue; // 11 ... 100 (kolor niebieski)


Trzy najmniej znacz¹ce bity zmiennej hue reprezentuj¹ jeden z 8 kolorów. Wykonanie operacji zanegowania
bitów powoduje zmianê koloru na dope³niaj¹cy.


Operator &


Operacja iloczynu bitów ma postaæ

expL & expR


w której expL i expR s¹ wyra¿eniami ca³kowitymi.

W celu utworzenia wyniku operacji, zmienne expL i expR poddaje siê konwersjom do typu wspólnego, a
nastêpnie ka¿dy bit wyniku tworzy siê z odpowiadaj¹cych sobie bitów argumentów wyznaczaj¹c ich iloczyn
logiczny
.

Uwaga: Iloczyn logiczny pary bitów ma wartoœæ 1 tylko wówczas gdy oba bity s¹ jedynkowe.

Na przyk³ad

int fix = 6; // 00 ... 110
int mask = '\u0003'; // 00 ... 011
fix = fix & ~mask;
System.out.print(fix); // 4 (00 ... 100)


Wykonanie operacji na zmiennej fix powoduje wyzerowanie tych wszystkich jej bitów, które w masce mask
jedynkowe.


Operator ^

background image

207


Operacja sumy modulo 2 bitów ma postaæ

expL ^ expR


w której expL i expR s¹ wyra¿eniami ca³kowitymi.

W celu utworzenia wyniku operacji, zmienne expL i expR poddaje siê konwersjom do typu wspólnego, a
nastêpnie ka¿dy bit wyniku tworzy siê z odpowiadaj¹cych sobie bitów argumentów wyznaczaj¹c ich sumê
logiczn¹ modulo 2
.

Uwaga: Suma logiczna modulo 2 pary bitów ma wartoœæ 1 tylko wówczas gdy bity s¹ ró¿ne.

Na przyk³ad

int fix = 6; // 00 ... 110
int mask = '\u0003'; // 00 ... 011
fix = fix ^ mask;
System.out.print(fix); // 5 (00 ... 101)


Wykonanie operacji na zmiennej fix powoduje zanegowanie tych wszystkich jej bitów, które w masce mask
jedynkowe.


Operator |


Operacja sumy bitów ma postaæ

expL | expR


w której expL i expR s¹ wyra¿eniami ca³kowitymi.

W celu utworzenia wyniku operacji, zmienne expL i expR poddaje siê konwersjom do typu wspólnego, a
nastêpnie ka¿dy bit wyniku tworzy siê z odpowiadaj¹cych sobie bitów argumentów wyznaczaj¹c ich sumê
logiczn¹
.

Uwaga: Suma logiczna pary bitów ma wartoœæ 0 tylko wówczas gdy oba bity s¹ zerowe.


Na przyk³ad

int fix = 5; // 00 ... 101
int mask = '\u0003'; // 00 ... 011
fix = fix | mask;
System.out.print(fix); // 7 (00 ... 111)


Wykonanie operacji na zmiennej fix powoduje ustawienie tych wszystkich jej bitów, które w masce mask
jedynkowe
.


Operator <<


Operacja przesuniêcia bitów w lewo ma postaæ

expL << N


w której expL i N s¹ wyra¿eniami ca³kowitymi.

W celu utworzenia wyniku operacji, zmienn¹ expL poddaje siê promocji, a nastêpnie ka¿dy bit wyniku tworzy
siê z bitów tej nowej zmiennej po przesuniêciu ich o N pozycji w lewo.

background image

208


Uwaga: Podczas przesuwania w lewo bity najbardziej znacz¹ce s¹ odrzucane, a na pozycje najmniej znacz¹ce
wchodz¹ bity 0.

Na przyk³ad

int fix = 7; // 00 ... 0111
fix = fix << 2;
System.out.print(fix); // 28 (00 ... 011100)


Bity zmiennej fix przesuniêto o 2 pozycje w lewo.

Operator >>


Operacja przesuniêcia bitów w prawo ma postaæ

expL >> N


w której expL i N s¹ wyra¿eniami ca³kowitymi.

W celu utworzenia wyniku operacji, zmienn¹ expL poddaje siê promocji, a nastêpnie ka¿dy bit wyniku tworzy
siê z bitów tej nowej zmiennej po przesuniêciu ich o N pozycji w prawo.

Uwaga: Podczas przesuwania w prawo bity najmniej znacz¹ce s¹ odrzucane, a bit najbardziej znacz¹cy nie
ulega zmianie.

Na przyk³ad

int fix = 15; // 00 ... 01111
fix = fix >> 2;
System.out.print(fix); // 3 (00 ... 00011)


Bity zmiennej fix przesuniêto o 2 pozycje w prawo.

Operator >>>


Operacja przesuniêcia bitów w-prawo-bez-znaku ma postaæ

expL >>> N


w której expL i N s¹ wyra¿eniami ca³kowitymi.

W celu utworzenia wyniku operacji, zmienn¹ expL poddaje siê promocji, a nastêpnie ka¿dy bit wyniku tworzy
siê z bitów tej nowej zmiennej po przesuniêciu ich o N pozycji w prawo.

Uwaga: Podczas przesuwania w prawo bity najmniej znacz¹ce s¹ odrzucane, a bit najbardziej znacz¹cy jest
zerowany.

Na przyk³ad

int fix = -1; // 111 ... 111
fix = fix >>> 30;
System.out.print(fix); //3 (000 ... 011)


Bity zmiennej fix przesuniêto o 2 pozycje w prawo.

Operatory warunku

background image

209


Operatorem warunku jest ?: (pytajnik, dwukropek). Wi¹zanie operatora warunku jest prawe.

Np. a ? b : c ? d : e == a ? b : (c ? d : e)


Operacja warunku ma postaæ

exp ? expT : expF


w której exp jest wyra¿eniem orzecznikowym, a expT i expF s¹ wyra¿eniami dowolnego typu.

Uwaga: Bezpoœrednio za pytajnikiem wystêpuje punkt charakterystyczny.

Wyra¿enie warunkowe jest nazw¹ zmiennej typu wspólnego "Type" wyra¿eñ expT i expF o wartoœci

(Type)expT dla exp o wartoœci true

(Type)expF dla exp o wartoœci false


Uwaga: W ka¿dym przypadku jest opracowywane wyra¿enie exp oraz co dok³adnie jedno z wyra¿eñ expT i
expF.

Na przyk³ad

int fix, fix1 = 10, fix2 = 20;
fix = System.in.read();
fix = (fix > 0 ? fix1 : fix2);
fix = fix > 0 ? fix1 : fix2; // identyczne z poprzednim
System.out.print(fix); // 10 (sic!)
(fix > 0 ? fix1 : fix2) = 4; // bł

ą

d (wymagana l-nazwa)


Operatory przypisania


Operatorem przypisania jest = (równa siê) oraz ka¿dy operator o postaci @= w której @ jest jednym z
nastêpuj¹cych operatorów

* / % + - << >> ^ & |


Wi¹zanie operacji przypisania jest prawe.

Np. a = b += c == a = (b += c)


Operator =


Prosta operacja przypisania ma postaæ

var = exp


w której var jest l-nazw¹ zmiennej, a exp jest wyra¿eniem.

Na przyk³ad

int fix = 10; // zainicjowanie
fix = 20; // przypisanie
++(fix = 20); // bł

ą

d (fix = 20 nie jest l-nazw

ą

)


Jeœli var jest typu arytmetycznego, to exp musi byæ typu arytmetycznego. Jeœli jest typu odnoœnikowego
Class, to wyra¿enie exp musi identyfikowaæ obiekt klasy Class albo jej podklasy.

background image

210

Wykonanie prostej operacji przypisania sk³ada siê z wyznaczenia wartoœci wyra¿enia exp, poddania jej
ewentualnej konwersji do typu zmiennej var, a nastêpnie przypisania zmiennej var.

Uwaga: Jeœli typ wyra¿enia exp jest ró¿ny od typu zmiennej var, to wymaga siê, aby konwersja mog³a byæ
wykonana niejawnie.

Na przyk³ad

int fix;
fix = 12;
fix = (int)4.8;
fix = 4.8; // bł

ą

d (brak konwersji)

String name = "Isabel";
Object obj;
obj = name; // obj = (Object)name;
name = (String)obj;
name = obj; // bł

ą

d (brak konwersji)


Operatory @=


Rozszerzona operacja przypisania ma postaæ

var @= exp


Jest ona równowa¿na operacji

var = var @ exp


ale tylko wówczas gdy opracowanie wyra¿enia var jest jednokrotne.

Na przyk³ad

int arr[] = { 10, 20 }, fix = 0;
arr[fix += 1] += 3; // fix = fix + 1; arr[1] = arr[1] + 3;
System.out.print(fix); // 1


Instrukcja

arr[fix += 1] += 3; // zwi

ę

kszenie fix o 1


jest wykonywana tak jak

fix += 1;
arr[fix] = arr[fix] + 3;


a wiêc nie jest równowa¿na instrukcji

arr[fix += 1] = arr[fix += 1] + 3; // zwi

ę

kszenie fix o 2



Wyszukiwarka

Podobne podstrony:
Bielecki Visual C 6.0 (Podstawy programowania), Jan Bielecki
RACHUNKOWOŚC FINASOWA - ĆWICZENIA 26.09.2010, Jan i Piotr Matuszewicz - Rachunkowość od podstaw
Visual C 6 0 Podstawy programowania Jan Bielecki(1)
Jan Bielecki Visual C 6 podstawy programowania
Jan Bielecki Visual C 6 0 Podstawy programowania
Bazy danych i mysql od podstaw Nieznany (3)
PHP4 od podstaw php4po
pozycja bokserska 1 up by Esi, BOKS, SZKOŁA BOKSU nauka boksu od podstaw
Scheda SCIENZE, NAUKA JĘZYKÓW OBCYCH, WŁOSKI, POMYSŁ NA LEKCJE WŁOSKIEGO OD PODSTAW
znaczenie taktyki w walce up by Esi, BOKS, SZKOŁA BOKSU nauka boksu od podstaw
dystans 3 up by Esi, BOKS, SZKOŁA BOKSU nauka boksu od podstaw
Jemielniak D, Latusek D Zarządzanie Teoria i praktyka od podstaw Ćwiczenia
Algorytmy Od podstaw(1)
j hiszpański lekcje od podstaw
Internet od podstaw
LINUX, SZKOLNE PLIKI-mega zbiory (od podstawówki do magisterki), Systemy operacyjne
obrona przez odchylenie 2 up by Esi, BOKS, SZKOŁA BOKSU nauka boksu od podstaw
ciosy wstęp up by Esi, BOKS, SZKOŁA BOKSU nauka boksu od podstaw
poruszanie się 2 up by Esi, BOKS, SZKOŁA BOKSU nauka boksu od podstaw

więcej podobnych podstron