1
Jan Bielecki
Java
od
podstaw
2
Moim przyjaciołom
Hanne i Rainerowi
Langmaack
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
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
5
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
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
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ą
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);
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.
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);
}
}
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
}
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;
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;
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.
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
(błąd operacji arytmetycznej)
NumberFormatException
(zły format liczby)
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
(błą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 błę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.*;
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);
}
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.
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).
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();
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
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>
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 &&
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
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
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.
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
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'
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
++ - / +=
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
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
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.
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
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.
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 są nieujemne.
Na przykład
char chr;
// ...
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
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
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.
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) {
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ć
;
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
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) {
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.
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
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.
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 są
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ć
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;
// ...
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ę
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]
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" };
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
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++" } };
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
{
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
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);
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;
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;
}
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
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);
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);
}
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
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)
{
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[])
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
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");
}
// ...
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
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.
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ę
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 + " ")
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];
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.
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.
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();
}
}
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)
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;
}
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.*.;
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.
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;
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
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);
}
}
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.
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".
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
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.
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".
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();
}
88
}
Ponieważ klasa Primary implementuje interfejs Runnable, więc albo musi być abstrakcyjna, albo musi
zawierać definicję metody run (wybrano to drugie).
Błąd w operacji przypisania
prm = rnb
wynika z tego, że nie istnieje konwersja standardowa z typu "Runnable" do typu "Primary".
Błą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;
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()
{
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,
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[])
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()
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;
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");
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 {
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!
###
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());
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.
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.
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)
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);
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();
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.
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
są 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
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
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;
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;
}
}
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;
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 {
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);
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 {
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;
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]:
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).
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
###
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.
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.
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");
}
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.
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);
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
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)
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.
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
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();
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
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
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++);
}
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();
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
###
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)
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.
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.
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!
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 = "";
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.
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)
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)
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.
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)
{
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.
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);
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"),
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.
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>
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));
}
}
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.
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.
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.
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>
===============================================
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;
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
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.
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();
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 róż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).
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;
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();
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ą
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);
}
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.
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);
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()
{
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;
}
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
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);
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) {
}
}
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)
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.
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 są róż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
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;
}
}
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)
172
Słownik terminów
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
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
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ć
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
177
Dodatki
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
= *= /= %= += -= <<= >>= >>>= &= ^= |=
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)
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();
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);
}
}
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
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
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
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"
);
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
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;
}
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
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 String są niemodyfikowalne, a więc, jeśli rezultat pewnej operacji jest klasy String, to nie musi
być odrębnym obiektem. Obiekty klasy StringBuffer są modyfikowalne. 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
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żą.
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");
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.
193
int val = Integer.parseInt(str);
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);
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
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);
// ...
}
}
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æ
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".
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
ą
)
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æ
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
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
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æ
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
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.
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 s¹
jedynkowe.
Operator ^
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 s¹
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 s¹
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.
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
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.
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