| | Poniżej znajduje się kurs javy
1. Język Java
Początki języka Java sięgają roku 1990, gdy Bill Joy napisał dokument pod
tytułem "Further", w którym sugerował inżynierom Sun Microsystems stworzenie
obiektowego środowiska w oparciu o C++. Dokument ten miał pewien wpływ
na twórców projektu Green (James Gosling, Patrick Naughton i Mike Sheridan).
W roku 1991 w ramach projektu Green opracowano w języku C kompilator oraz
interpretator wynalezionego przez Goslinga języka OAK (Object Application
Kernel), który miał być narzędziem do oprogramowania "inteligentnych" konsumenckich
urządzeń elektronicznych. Ponieważ nazwa "OAK" okazała się zastrzeżona,
zmieniono ją na "Java".
Obecnie należy raczej mówić o środowisku Java, na które składa się:
1. Obiektowy język Java, którego składnia wykazuje znaczne podobieństwo
do składni języka C++. Nazwa pliku z programem źródłowym w języku Java,
ma postać "nazwa.java", gdzie "nazwa" musi być nazwą zdefiniowanej w tym
pliku klasy publicznej.
2. Kompilator, który przetwarza program "nazwa.java" na tak zwany B-kod
(bytecode, J-code), zapisywany automatycznie w plikach z rozszerzeniem
nazwy ".class". B-kod jest przenośną postacią programu, która może
być zinterpretowana przez odpowiednią maszynę wirtualną, to jest "urządzenie
logiczne", na którym będzie wykonywany program binarny.
3. Specyfikacje maszyny wirtualnej Java (JVM Java Virtual Machine)
i plików klas. JVM można uważać za abstrakcyjny komputer, który wykonuje
programy, zapisane w plikach z rozszerzeniem nazwy ".class". Maszyna wirtualna
może być implementowana na rzeczywistych komputerach na wiele sposobów,
na przykład jako interpretator wbudowany w przeglądarkę WWW (np. Netscape),
lub jako oddzielny program, który interpretuje pliki "nazwa.class". Może
to być także implementacja polegająca na przekształceniu tuż przed rozpoczęciem
fazy wykonania pliku z B-kodem na program wykonalny, specyficzny dla
danej maszyny. Mechanizm ten można określić jako tworzenie kodu wykonalnego
w locie (ang. Just-In-Time, np. kompilator JIT firmy Symantec). Interpretatory
B-kodu, tj. różne maszyny wirtualne, są także często napisane w języku
Java.
4. Biblioteka Javy. Środowisko języka Java zawiera bogatą bibliotekę,
a w niej zbiór składników dla prostego, niezależnego od platformy graficznego
interfejsu użytkownika.
1.1. Elementarny program: tekst źródłowy, kompilacja,
interpretacja
Java wprowadza swoistą terminologię dla swoich konstrukcji syntaktycznych
i jednostek (modułów) kompilacji. Programem w języku Java jest aplikacja
(application) lub aplet (applet). Aplikacja jest programem samodzielnym,
zaś aplet jest programem wbudowanym (np. w przeglądarkę WWW). Każda aplikacja
musi zawierać dokładnie jeden moduł źródłowy nazywany modułem głównym aplikacji,
którego klasa publiczna zawiera publiczną funkcję klasy (funkcje takie
są poprzedzane słowem kluczowym static) main. Tekst źródłowy najprostszego
programu może mieć postać:
public
class Hello {
public static void main(String args[])
{
System.out.print("Hello, World!\n");
}
}
Dla skompilowania powyższego programu jego tekst źródłowy należy umieścić
w pliku o nazwie Hello.java. Zakładając, że dysponujemy systemem JDK z
kompilatorem javac, program skompilujemy poleceniem:
javac Hello.java
Udana kompilacja wygeneruje plik z B-kodem o nazwie Hello.class, zawierający
sekwencję instrukcji dla interpretatora JVM. Kod ten wykonujemy przez wywołanie
interpretatora o nazwie java poleceniem:
java Hello
Interpretator wyszuka plik o nazwie Hello.class, ustali, czy klasa
Hello zawiera publiczną metodę statyczną main i wykona instrukcje zawarte
w bloku main. Zauważmy przy okazji, że w języku Java wszystkie stałe, zmienne
i funkcje są elementami składowymi klas; nie ma wielkości globalnych, definiowanych
poza klasą. Ponadto nie deklaruje się metod (funkcji) składowych jako rozwijalnych
(inline) bądź nie decyzja należy do kompilatora.
W przykładowym programie do metody main jako parametr jest przekazywana
(z wiersza rozkazowego) tablica obiektów (łańcuchów) klasy String; metoda
main nie zwraca wyniku, zaś wartością parametru arg[0] jest pierwszy po
nazwie programu spójny ciąg znaków. Ciało main zawiera jedną instrukcję
System.out.print("Hello, World!\n");
(W języku Java każda instrukcja kończy się średnikiem, który pełni
rolę symbolu terminalnego).
Słowo System jest nazwą klasy w standardowym środowisku języka. Klasa
System zawiera statyczny obiekt składowy typu PrintStream o nazwie out;
wywołanie System.out oznacza pisanie do standardowego strumienia wyjściowego.
Klasa PrintStream zawiera szereg przeciążeń metody o nazwie print; jedno
z nich przyjmuje parametr String. Kompilator automatycznie tłumaczy literał
stały "Hello, World\n" na odpowiedni obiekt klasy String; odniesienie (referencja)
do tego obiektu jest przekazywane do metody System.out.print(). Metoda
print generuje jeden wiersz wyjściowy i powraca do metody main, która kończy
wykonanie.
1.2. Klasy: definicja, dziedziczenie, tworzenie obiektów
Klasę Javy można traktować jako wzorzec i jednocześnie generator obiektów.
Jako wzorzec klasa zapewnia hermetyzację (zamknięcie w jednej jednostce
syntaktycznej) danych i metod oraz ukrywanie informacji, które nie powinny
być widoczne dla użytkownika. Jako generator zapewnia tworzenie obiektów
za pomocą operatora new, którego argumentem jest konstruktor klasy.
Definicja klasy ma postać:
Deklaracja klasy
{
Ciało klasy
}
Deklaracja klasy składa się w najprostszym przypadku ze słowa kluczowego
class i nazwy klasy. Przed słowem kluczowym class może wystąpić jeden ze
specyfikatorów: abstract, public, final, lub dwa z nich, np. public abstract,
public final. Specyfikator abstract odnosi się do klas abstrakcyjnych,
które nie mogą mieć wystąpień, zaś final deklaruje, że dana klasa nie może
mieć podklas. Brak specyfikatora oznacza, że dana klasa jest dostępna tylko
dla klas zdefiniowanych w tym samym pakiecie. Specyfikator public mówi,
że klasa jest dostępna publicznie. Klasa abstrakcyjna może zawierać metody
abstrakcyjne (bez implementacji, poprzedzone słowem kluczowym abstract;
w miejscu ciała metody abstrakcyjnej występuje średnik); wówczas każda
jej podklasa musi podawać implementacje tych metod. Każda klasa, która
odziedziczy metodę abstrakcyjną, ale nie dostarczy jej implementacji, sama
staje się klasą abstrakcyjną.
Po nazwie klasy mogą wystąpić frazy: extends nazwa_superklasy oraz
implements nazwy_interfejsów. Fraza extends nazwa_superklasy mówi,
że klasa dziedziczy (zawsze publicznie) od klasy nazwa_superklasy, zaś
implements nazwy_interfejsów deklaruje, że w danej klasie zostaną zdefiniowane
metody, zadeklarowane w implementowanych interfejsach. Jeżeli dana klasa
implementuje więcej niż jeden interfejs, wtedy nazwy kolejnych interfejsów
oddziela się przecinkami.
F Uwaga. W języku Java każda klasa dziedziczy od predefiniowanej klasy
Object. Zatem, jeżeli w definicji klasy nie występuje fraza extends, to
jest to równoważne niejawnemu wystąpieniu w tej definicji frazy extends
Object.
Zauważmy, że oprócz słowa kluczowego class i nazwy klasy wszystkie
pozostałe elementy w deklaracji klasy są opcjonalne. Jeżeli nie umieścimy
ich w deklaracji, to kompilator przyjmie domyśnie, że klasa jest niepubliczną,
nieabstrakcyjną i niefinalną podklasą predefiniowanej klasy Object.
Ciało klasy jest zamknięte w nawiasy klamrowe i może zawierać zmienne
składowe (to jest pola lub zmienne wystąpienia), zmienne klasy (statyczne,
tj. poprzedzone słowem kluczowym static), konstruktory i metody oraz funkcje
klasy (statyczne). Nazwa każdej zmiennej składowej, zmiennej klasy, metody
lub funkcji klasy musi być poprzedzona nazwą typu (np. boolean, double,
char, float, int, long, void). Przed nazwą typu może wystąpić jeden ze
specyfikatorów dostępu: private (dostęp tylko dla elementów klasy, np.
private double d;), protected (dostęp tylko w podklasie, nawet jeśli podklasa
należy do innego pakietu; nie dotyczy zmiennych klasy) lub public (dostęp
publiczny). Brak specyfikatora oznacza, że dany element jest dostępny tylko
dla klas w tym samym pakiecie. Po specyfikatorze dostępu może wystąpić
słowo kluczowe final. Słowo final przed nazwą typu zmiennej wystąpienia
lub zmiennej klasy deklaruje jej niemodyfikowalność (np. public static
final int i = 10;), zaś w odniesieniu do metody oznacza, że nie może ona
być redefiniowana w podklasie (np. public final void f(int i) {/* ... */
}).
Dostęp do elementów klasy uzyskuje się za pomocą operatora kropkowego.
Jeżeli element danej klasy (zmienna lub metoda) przesłania (overrides)
jakiś element swojej superklasy, to można się do niego odwołać za pomocą
słowa kluczowego super, jak w poniższym przykładzie:
class ASillyClass /* Deklaracja klasy */
{
static final int MAX = 100; /** Definicja stałej */
boolean aVariable;/* Deklaracja zmiennej wystąpienia */
static public int x = 10; //Definicja zmiennej klasy
void aMethod() { //Definicja metody
aVariable = true;// Instrukcja przypisania
}
class AsillerClass extends ASillyClass {
boolean aVariable;
void aMethod() {
aVariable = false;
super.aMethod(); /* Wywołanie metody superklasy */
System.out.println(aVariable);
System.out.println(super.aVariable);
}
Dostęp do zmiennych składowych klasy (statycznych) jest możliwy bez
tworzenia obiektów tej klasy. Np. dla klasy ASillyClass możemy napisać
instrukcję:
System.out.println(ASillyClass.x);.
Klasy i omawiane niżej interfejsy są typami referencyjnymi (odnośnikowymi).
Wartościami zmiennych tych typów są odnośniki do wartości lub zbiorów wartości
reprezentowanych przez te zmienne. Np. instrukcja
ASillyClass oob;
jedynie powiadamia kompilator, że będziemy używać zmiennej oob, której
typem jest ASillyClass. Do zmiennej oob możemy przypisać dowolny obiekt
typu ASillyClass utworzony za pomocą operatora new:
oob = new ASillyClass();
W powyższej instrukcji argumentem operatora new jest generowany przez
kompilator konstruktor ASillyClass() klasy ASillyClass, który inicjuje
obiekt utworzony przez operator new. Operator new zwraca odnośnik do tego
obiektu, po czym przypisuje go do zmiennej oob.
1.3. Interfejsy
Konstrukcja o postaci
interface nazwa {
/* Deklaracje metod i definicje stałych */
}
jest w języku Java typem definiowanym przez użytkownika. Deklaracja
metody składa się z sygnatury (sygnatury metod zawierają typ zwracany,
nazwy metod i typy argumentów) i terminalnego średnika. Ponieważ interfejs
może zawierać jedynie deklaracje metod i definicje stałych, odpowiada on
klasie abstrakcyjnej z zadeklarowanymi publicznymi polami danych i metodami
abstrakcyjnymi. W związku z tym w definicji interfejsu zabrania się użycia
specyfikatorów private i protected, zaś użycie specyfikatorów abstract
i public jest zbyteczne.
Weźmy dla przykładu dwa interfejsy PlaneLike i BoatLike
interface PlaneLike {
void takeOff();
float kmph();
}
interface BoatLike {
void swim();
float knots();
}
i zdefiniujmy ich implementacje w klasach Plane i Boat, które dziedziczą
od wspólnej superklasy Vehicle:
class Vehicle {}
class Plane extends Vehicle implements PlaneLike {
/* Plane must implement kmph(), takeOff() */
public void takeOff() { System.out.println("Plane is taking off");
}
public float kmph() { return 600; }
}
class Boat extends Vehicle implements BoatLike {
/* Boat must implement knots(),swim() */
public void swim() { System.out.println("Boat is swimming"); }
public float knots() { return 20; }
}
Poprawne będą wówczas deklaracje
Plane biplane = new Plane();
biplane.takeOff();
Boat vessel = new Boat();
vessel.swim();
a także deklaracje
PlaneLike aircraft = new Plane();
aircraft.takeOff();
BoatLike motorboat = new Boat();
motorboat.swim();
Załóżmy teraz, że chcielibyśmy skonstruować klasę SeaPlane, której
obiekty powinny się zachowywać w pewnych okolicznościach jak pojazdy wodne,
zaś w innych jak pojazdy powietrzne. W języku, który wyposażono w mechanizm
dziedziczenia mnogiego (np. C++) klasa SeaPlane miałaby dwie superklasy:
Plane i Boat, jak pokazano w części a) rysunku 1.1.
Rys. 1.1. a) Graf dziedziczenia mnogiego dla SeaPlane.
b) Graf dziedziczenia pojedynczego dla SeaPlane
W języku Java podobny efekt można osiągnąć poprzez implementację w klasie
SeaPlane obu interfejsów, t.j. PlaneLike i BoatLike (część b rysunku 1.1):
class SeaPlane extends Vehicle implements
PlaneLike, Boatlike {
/** SeaPlane must implement kmph(), takeOff(),
knots(), swim() */
}
Dla osiągnięcia pożądanego zachowania się obiektów klasy SeaPlane moglibyśmy
umieścić w głównym module źródłowym Multi1.java następujący kod:
public
class Multi1 {
public static void main(String args[])
{
Boat vessel = new Boat();
Plane biplane = new Plane();
System.out.println("Let's starting!");
PlaneLike ref1 = new SeaPlane(biplane);
ref1.takeOff();
System.out.println(ref1.kmph());
BoatLike ref2 = new SeaPlane(vessel);
ref2.swim();
System.out.println(ref2.knots());
}
}
Jednak ze względu na wielokrotną używalność kodu lepszym rozwiązaniem
będzie taka definicja klasy SeaPlane, która wykorzystuje mechanizm delegacji,
tj. bezpośredniej współpracy z klasami Plane i Boat:
class SeaPlane extends Vehicle implements
PlaneLike, BoatLike {
// define a Plane and Boat instance variables
// i.e. collaborate with Plane and Boat classes
private Plane itAsPlane;
private Boat itAsBoat;
//Konstruktor
SeaPlane(Plane itAsPlane)
{
this.itAsPlane=itAsPlane;
}
//Konstruktor
SeaPlane(Boat itAsBoat)
{
this.itAsBoat=itAsBoat;
}
// forward the messages to the appropriate collaborator
public float kmph() { return itAsPlane.kmph(); }
public void takeOff() { itAsPlane.takeOff(); }
public float knots() { return itAsBoat.knots(); }
public void swim() { itAsBoat.swim(); }
}
Interfejs nie może dziedziczyć klas, ale może dziedziczyć dowolnie wiele
interfejsów. Np. korzystając z podanych wyżej definicji moglibyśmy utworzyć
interfejs
interface SeaPlaneLike extends PlaneLike, BoatLike{
public long SPEED_LIMIT = 1000;
}
i wykorzystać go w klasie SeaPlane, implementując metody zadeklarowane
w interfejsach PlaneLike i BoatLike.
1.4. Pliki źródłowe i pakiety
Program języka Java może się składać z wielu niezależnie kompilowalnych
modułów źródłowych, w których umieszcza się definicje klas oraz interfejsów.
Moduły źródłowe są przechowywane w plikach o nazwie Nazwa.java, gdzie Nazwa
jest nazwą klasy publicznej; pliki te stanowią jednostki kompilacji. Jeżeli
w pliku Nazwa.java zdefiniowano tylko jedną klasę, to w wyniku kompilacji
tego pliku powstaje plik wynikowy Nazwa.class. Jeżeli program jest aplikacją,
to w zestawie modułów źródłowych musi się znaleźć dokładnie jeden moduł
źródłowy (moduł główny aplikacji) z klasą publiczną, która zawiera publiczną
i statyczną funkcję main (każdy inny moduł źródłowy może zawierać klasę
z funkcją main, jeżeli nie jest to klasa publiczna).
Moduł źródłowy, w którym definicje klas oraz interfejsów poprzedzono
deklaracją pakietu o postaci package nazwa_pakietu; staje się pakietem.
Deklaracja pakietu rozszerza przestrzeń nazw programu i pozwala na lepsze
zarządzanie programem wielomodułowym. Jeżeli moduł źródłowy nie zawiera
deklaracji pakietu, to należy on do tzw. pakietu domyślnego (pakietu bez
nazwy). Np. zadeklarowana wcześniej klasa Hello, umieszczona w pliku Hello.java
należy do pakietu domyślnego.
Pakiety są ściśle powiązane z katalogami, w których umieszcza się moduły
źródłowe i pliki wynikowe. Załóżmy np., że w katalogu mike\myprog\pakiet1
(Win95 DOS) umieszczono główny plik źródłowy aplikacji Student.java o
postaci:
package mike.myprog.pakiet1;
import mike.myprog.pakiet1.Grade;
public
class Student {
int i = 10;
public static void main(String args[])
{
System.out.println("Hello, I am here!");
Grade mygrade = new Grade();
mygrade.printgrade();
}
}
Plik zawiera definicję klasy Student, poprzedzoną deklaracją pakietu
oraz deklaracją importu klasy Grade. Plik ten może zostać skompilowany
(wywołaniem kompilatora javac z katalogu nadrzędnego w stosunku do katalogu
mike: javac mike\myprog\pakiet1\Student.java). Jeżeli np. plik Grade.java
ma postać:
package mike.myprog.pakiet1;
class Grade {
int i = 10;
public void printgrade()
{
System.out.println("My grades are higher
than " + i);
}
}
class Empty {}
to zostaną utworzone dwa pliki wynikowe: Student.class i Grade.class,
a wywołanie interpretatora java mike\myprog\pakiet1\Student spowoduje wyprowadzenie
na ekran napisu:
Hello, I am here!
My grades are higher than 10
Uwaga. Deklaracja importu nie oznacza włączania do pliku Student.java
tekstu zawartego w pliku Grade.java.Natomiast pozwala ona użytkownikowi
klasy Student używać skrótowych nazw: np. zamiast pisać mike\myprog\pakiet1\Grade
mygrade = new mike\myprog\pakiet1\Grade(); mogliśmy napisać krótko Grade
mygrade = new Grade();. Gdybyśmy chcieli używać również klasy Empty, to
deklaracja importu miałaby postać: import mike.myprog.pakiet1.*. W języku
Java ważna jest także kolejność deklaracji:najpierw deklaracja pakietu,
po niej deklaracje importu, po czym definicje klas.
1.5. Polimorfizm
Polimorfizm możemy określić jako wirtualizację operacji; jest to możliwość
dynamicznego (późnego, realizowanego w fazie wykonania) wiązania nazwy
operacji do wielu implementacji (metod) tej operacji w różnych klasach
pozostających w relacji dziedziczenia. Wiązaniu towarzyszy mechanizm wyboru
konkretnej implementacji. Wybór implementacji zależy od nazwy metody oraz
od typu dynamicznego tego obiektu, dla którego została wywołana operacja,
a nie od typu zmiennej, wskazującej ten obiekt.
Zauważmy, że przeciążanie operacji nie prowadzi do polimorfizmu; w
tym przypadku wiązanie, dopasowanie parametrów wywołania operacji do określonej
sygnatury i wybór implementacji są statyczne, ponieważ są wykonywane w
fazie kompilacji. Muszą wówczas istnieć różnice w sygnaturach operacji
(w typie wyniku i/lub w typach parametrów wejściowych), a kryterium wyboru
implementacji zależy od tych różnic.
W języku Java wszystkie deklaracje metod, które nie są metodami klasy,
ani metodami finalnymi, można traktować jako operacje wirtualne. Tak więc
definicja metody o danej sygnaturze w superklasie może być przesłonięta
przez definicję metody o tej samej sygnaturze w podklasie. W rezultacie
kompilator nie może związać nazwy metody z jej implementacją (istnieją
co najmniej dwie implementacje o takich samych sygnaturach). Wiązanie może
się odbyć dopiero w fazie wykonania; wówczas interpretator określa typ
dynamiczny obiektu na którym zostaje wywołana operacja i wiąże tę operację
z właściwą dla danego obiektu implementacją. Ilustrację wywołania polimorficznego
pokazuje poniższy prosty przykład.
Plik Polimorf.java
class Base {
public void msg()
{
System.out.println("Base class method");
}
}
class Derived extends Base {
public void msg()
{
System.out.println("Derived class method");
}
}
public class Polimorf {
int i = 10;
public static void main(String args[])
{
Base b1 = new Base();
b1.msg();
b1 = new Derived();
if(b1 instanceof Derived)
System.out.println("Derived");
b1.msg();
}
}
Metoda msg() ma dwie implementacje: w klasie Base i w klasie Derived.
Odniesienie (referencja) b1 do klasy Base jest najpierw inicjowane obiektem
klasy Base, a więc instrukcja b1.msg(); wywoła metodę tej klasy. Następnie
odniesieniu b1 zostaje przypisany obiekt klasy Derived, co spowoduje, że
następna instrukcja b1.msg(); wywoła implementację metody msg() zdefiniowaną
w klasie Derived. Instrukcja if wykorzystująca operator instanceof służy
do sprawdzenia, jaki jest typ dynamiczny obiektu b1. Wynikiem wykonania
programu będzie następujący wydruk:
Base class method
Derived
Derived class method
1.6. Obsługa wyjątków
Wyjątki pozwalają zachować kontrolę nad przebiegiem wykonania funkcji (metod),
a także pojedynczych instrukcji zawartych w funkcjach. Wyjątek jest zdarzeniem,
które pojawia się podczas wykonania i rozrywa normalną kolejność wykonania
instrukcji.
W języku Java istnieje bardzo rozbudowana hierarchia (drzewo) predefiniowanych
klas wyjątków, których superklasą jest klasa Throwable, a głównymi gałęziami
drzewa są klasy Error i Exception. Część wyjątków należy do grupy tzw.
wyjątków weryfikowalnych (checked exceptions): kompilator sprawdza, czy
program zawiera procedury obsługi dla każdego wyjątku z tej grupy. Natomiast
wyjątki klasy Error i jej podklas należą do grupy wyjątków nieweryfikowalnych
(unchecked exceptions), ponieważ mogą one wystąpić w wielu punktach programu
i powrót z nich jest trudny lub wręcz niemożliwy. Do grupy wyjątków nieweryfikowalnych
należą też wyjątki klasy RuntimeException (podklasa Exception) i jej podklas,
ponieważ zadeklarowanie w programie takich wyjątków nie mogłoby znacznie
pomóc w ustaleniu (przez kompilator) poprawności programów.
Dla obsługi wyjątków weryfikowalnych wprowadzono cztery słowa kluczowe:
throw, throws, try, catch i finally. Słowo kluczowe throw służy do jawnego
zgłaszania wyjątków nieweryfikowalnych i występuje w instrukcji throw o
składni throw wyrażenie;gdzie wyrażenie musi oznaczać zmienną lub wartość
typu referencyjnego do klasy Throwable lub jej podklas. Zgłoszenie wyjątku
w instrukcji throw spowoduje natychmiastowe opuszczenie bloku lub funkcji
zawierającego instrukcję throw i znalezienie instrukcji try, która przechwyci
zgłoszony wyjątek. Jeżeli nie ma takiej instrukcji try, zostanie wywołana
metoda UncaughtException i wykonanie programu (lub wątku) zostanie zakończone.
Fraza:throws klasa_wyjątków
może wystąpić w nagłówku funkcji (metodzie wystąpienia, konstruktorze,
funkcji klasy), np.
public staic void main(String args[]) throws Exception {/*...*/}
void printNumber(int number) throws WrongNumberException {/*...*/}
Fraza throws klasa_wyjątków oznacza, że dana funkcja może zgłaszać
jedynie wyjątki podanej klasy.
Jeżeli wykonanie pewnej instrukcji programu może spowodować powstanie
wyjątkowego zdarzenia, to musi ona być ujęta w blok instrukcji try, po
którym muszą wystąpić procedury obsługi wyjątku mające postać frazy catch
i bezpośrednio po catch (opcjonalnie) frazy finally. Składnia tej konstrukcji
ma postać:
try {I}catch(arg1 e1) {I} catch(arg2 e2) {I} ... catch(argn en)
{I} ... finally {I}
gdzie I oznacza instrukcje, arg1 .. argn klasy wyjątków, e1 ... en
zmienne odniesienia do tych klas.
Blok catch należy traktować jako ciało procedury obsługi wyjątku należącego
do klasy argi.
Jeżeli funkcja/metoda zawiera instrukcję try, to wyjątki mogą być zgłaszane
wyłącznie w bloku try. Po zgłoszeniu wyjątku sterowanie opuszcza kod, który
zgłosił wyjątek i przechodzi do pierwszej w kolejności procedury catch;
jeżeli ta nie obsługuje wyjątku zgłoszonej klasy, jest on przekazywany
do następnej. Blok frazy finally (jeśli występuje) jest wykonywany zawsze
gdy kończy się wykonanie instrukcji try i to nawet wtedy, gdy wykonanie
try zostaje gwałtownie przerwane.
Prosty program, który jedynie ilustruje składnię zgłaszania i obsługi
wyjątków może wyglądać następująco:
import java.io.*;
public class TestEx {
public TestEx() {}
final int CONSTANT = 10;
void ff() throws IOException
{
try
{
System.out.println("I was here, within try statement.");
if(CONSTANT < 0)
throw new IOException("CONSTANT should be negative");
}
catch(IOException exc)
{
System.err.println("Caught IOException: "+ exc.getMessage());
}
finally
{
System.out.println("And now I am
in finally blok");
}
}//end ff
public static void main(String args[]) throws IOException
{
TestEx te = new TestEx();
te.ff();
}//end main
}//end class
Podany niżej przykład definiuje klasę ListOfNumbers, wywołującą z pakietów
Javy dwie metody klas, które mogą zgłosić wyjątki.
import java.io.*;
import java.util.Vector;
public class ListOfNumbers {
private Vector victor;
private static final int size = 10;
public ListOfNumbers () {
victor = new Vector(size);
for (int i = 0; i < size; i++)
victor.addElement(new Integer(i));
}
public void writeList() {
PrintWriter p = null;
try {
System.out.println("Entering try statement");
p = new PrintWriter(new FileOutputStream("OutFile.txt"));
for (int i = 0; i < size; i++)
p.println("Value at: " + i + " = " + victor.elementAt(i));
} catch (ArrayIndexOutOfBoundsException e) {
System.err.println("Caught ArrayIndexOutOfBoundsException:
" +
e.getMessage());
} catch (IOException e) {
System.err.println("Caught
IOException: " + e.getMessage());
} finally {
if (p !=
null) {
System.out.println("Closing
PrintWriter");
p.close();
} else {
System.out.println("PrintWriter not open");
}
}
}
public static void main(String args[]) {
ListOfNumbers lst = new ListOfNumbers();
lst.writeList();
}//end main
}//end ListOfNumbers
Konstruktor ListOfNumbers() tworzy obiekt klasy Vector o dziesięciu
elementach będących wartościami od 0 do 9. W klasie ListOfNumbers zdefiniowano
także metodę writeList(), która zapisuje ten ciąg liczb do pliku tekstowego
OutFile.txt. Metoda writeList() wywołuje dwie metody, które mogą zgłosić
wyjątki:
konstruktor klasy FileOutputStream, który zgłasza IOException jeżeli
z jakiegoś powodu nie może otworzyć pliku:
p = new PrintWriter(new FileOutputStream("OutFile.txt"));
metodę elementAt() klasy Vector, która zgłasza wyjątek ArrayIndexOutOfBoundsException
jeżeli przekazana do niej wartość indeksu jest zbyt mała (liczba ujemna)
lub zbyt duża (większa niż liczba elementów zawartych aktualnie w obiekcie
klasy Vector).
Instrukcje System.err.println wykorzystują komunikaty generowane
przez metodę getMessage klasy Throwable (lub jej podklasę), która podaje
dodatkowe informacje o zaistniałym błędzie.
Jeżeli na dysku jest wystarczająco dużo miejsca na zapisanie
pliku OutFile.txt, to program założy taki plik z zawartością:
Value at: 0 = 0
...
Value at 9 = 9
oraz wyprowadzi na ekran dwa wiersze tekstu:
Entering try statement
Closing PrintWriter
1.7. Zarządzanie pamięcią
Język Java jest wyposażony w mechanizm zbierania nieużytków (garbage
collection). System wykonawczy Javy automatycznie zwraca przydzielony obiektowi
obszar pamięci gdy stwierdzi, że do danego obszaru nie odnosi się już żadna
referencja.
1.8. Współbieżność
Program Javy jest z reguły wykonywany w obrębie jednego procesu,
którego stos może być wykorzystywany przez wiele współbieżnych wątków programu
(wątkiem nazywa się sekwencyjny przepływ sterowania w procesie, który wykonuje
dany program). Nawet najprostsza aplikacja, która wyświetlała napis "Hello,
World!", miała dwa wątki wykonania: wątek główny (main thread) wykonujący
kod tej aplikacji oraz kolektor nieużytków.
Jeżeli maszyna wirtualna jest wieloprocesorowa, wątki mogą być
wykonywane współbieżnie; na komputerze jednoprocesorowym współbieżność
może być emulowana (np. w systemie Windows95) przez przydzielanie poszczególnym
wątkom pewnej liczby kwantów czasu procesora. W tym drugim przypadku jest
realizowana tzw. wielowątkowość wywłaszczeniowa (preemptive), która nie
dopuszcza do "zagłodzenia" (starving) wątków o niskich priorytetach. Priorytet
jest liczbą z przedziału 1..10; jeżeli priorytet nie zostanie ustawiony
jawnie (funkcją setPriority klasy Thread), to nowy wątek przejmie prirytet
wątku, który go utworzył.
Uwaga. Zagłodzenie może się zdarzyć wtedy, gdy jeden lub więcej
wątków w programie jest blokowanych przed dostępem do pewnego zasobu i
wskutek tego nie mogą biec dalej. Krańcową postacią zagłodzenia jest zakleszczenie
lub impas (deadlock).Impas pojawia się wtedy, gdy dwa lub więcej wątków
czeka na warunek, który nie może być spełniony; typowym przykładem jest
sytuacja, gdy istnieją dwa wątki i każdy z nich czeka na wykonanie
czegoś przez partnera.
Wykonanie metody start (public synchronized void start()) na
rzecz utworzonego wcześniej obiektu klasy Thread powoduje utworzenie wątku.
Przebieg wykonania wątku zależy od implementacji metody run (public void
run()), wywoływanej niejawnie przez system tuż po utworzeniu wątku.
Zakończenie wykonania metody run powoduje niejawne wywołanie metody stop
(public static final void stop()), która niszczy wątek. Stanami wątków
można sterować przez wywołania finalnych metod wystąpienia suspend i resume
(zawieszenie i wznowienie wątku zawieszonego), wait i notify (wstrzymanie
i uwolnienie wątku wstrzymanego), wywołania finalnych metod synchronizowanych
join() i join(long millisec), które powodują wstrzymanie wykonywania wątku
aż do zniszczenia go przez inny wątek oraz przez wywołania metod klasy:
public static void sleep(long millisec), która powoduje uśpienie wątku
na podany okres czasu i public static void yield(), która oddaje dostęp
do procesora innemu wątkowi (o ile taki istnieje). Ponadto można wywołać
metodę public final boolean isAlive() dla stwierdzenia, czy wątek istnieje.
Wątek Javy może być tworzony na dwa sposoby: albo jako obiekt
podklasy, która dziedziczy od klasy Thread (klasa java.lang.Thread zawiera
metody, które kontrolują i synchronizują poszczególne wątki), albo jako
obiekt klasy, która implementuje interfejs Runnable (java.lang.Runnable).
W obu przypadkach należy podać implementację metody run() zadeklarowanej
w interfejsie Runnable. Pierwszy sposób ilustruje poniższy przykład.
public class Thread1 extends Object {
public static void main(String args[]) {
Thread x = new MyThread("Fastthread");
Thread y = new MyThread("Slow thread");
x.setPriority(Thread.MAX_PRIORITY);
x.start();
y.start();
}//end main()
}//end Thread1
class MyThread extends Thread {
protected String name = "not initialized";
public MyThread(String nameString)
{ name = nameString; }
public void run() {
for(int i =1;i<=10;i++) {
try {
Thread.currentThread().sleep(300);
}//end try
catch(InterruptedException e) {
} //end catch
System.out.println(name+"continues... "+i);
}//end for
System.out.println(name+" is DONE!! ");
}//end run()
}//end MyThread
W przykładzie utworzono dwa wątki, x i y, przy czym jeden z nich
(x )ustawiono na najwyższy osiągalny priorytet. Każdy wątek będzie wykonywany
oddzielnie, ale wątek x zakończy działanie wcześniej.
Drugi sposób implementuje interfejs Runnable:
public class TwoThreads extends Object {
public static void main(String args[]) {
MyClass xx = new MyClass();
MyClass yy = new MyClass();
Thread x = new Thread(xx);
Thread y = new Thread(yy);
x.setPriority(Thread.MAX_PRIORITY);
x.start();
y.start();
}//end main()
}//end TwoThreads
class MyClass implements Runnable {
public void run()
{
for(int I=0;I<=10;I++)
{ System.out.println("Thread progress="+I); }
System.out.println("Thread completed");
}
}
1.8.1 Synchronizacja wątków
W podanych wyżej przykładach wątki były niezależne i asynchroniczne.
Inaczej mówiąc, każdy wątek zawierał wszystkie dane i metody potrzebne
dla jego wykonania i nie wymagał żadnych zewnętrznych zasobów lub metod.
Ponadto przebieg każdego wątku miał własny rytm, niezależny od stanu, czy
aktywności drugiego, biegnącego współbieżnie.
Istnieje jednak wiele sytuacji, gdy oddzielne, współbieżnie wykonywane
wątki współdzielą pewne dane i muszą uwzględniać stany i aktywności innych
wątków. Powszechnie znanym modelem programistycznym takich sytuacji jest
model producent/konsument, w którym producent generuje strumień danych
pobieranych następnie przez konsumenta. Przykładem praktycznym może być
program, w którym jeden wątek (producent) zapisuje dane do pliku, podczas
gdy drugi wątek czyta dane z tego samego pliku. Innym przykładem może być
pisanie znaków na klawiaturze: wątek producenta umieszcza naciśnięcia klawisza
w kolejce zdarzeń, a wątek konsumenta odczytuje zdarzenia z tej samej kolejki.
Jeszcze innym przykładem może być wysyłanie znaków do tego samego strumienia
(np. do System.out) z kilku współbieżnie wykonywanych wątków. Widzimy,
że podane przykłady wykorzystują współbieżne wątki, które dzielą wspólny
zasób: w pierwszym współdzielą plik, a w drugim kolejkę zdarzeń, w trzecim
ten sam strumień. Ponieważ wątki współdzielą wspólny zasób, muszą być w
jakiś sposób synchronizowane (np. w trzecim przykładzie przy braku synchronizacji
łańcuchy znaków pochodzące z różnych źródeł mogą być bezsensownie przemieszane).
Segmenty kodu programu, które żądają dostępu do tego samego obiektu
z dwóch oddzielnych, współbieżnych wątków, nazywa się sekcjami krytycznymi.
W języku Java sekcją krytyczną może być blok lub metoda; identyfikuje się
je słowem kluczowym synchronized.
W przypadku bloku mamy instrukcję synchronized o składni
synchronized ( Wyrażenie ) Blok
w której Wyrażenie musi być typu referencyjnego, a Blok jest
instrukcją grupującą, objetą nawiasami klamrowymi.
Instrukcja synchronized przejmuje wzajemnie wykluczającą blokadę
na rzecz wykonywanego wątku, wykonuje Blok, po czym zwalnia blokadę. Inaczej
mówiąc, wykonanie instrukcji synchronized powoduje przydzielenie wątkowi
podanego Bloku jako sekcji krytycznej, a po wykonaniu go na rzecz obiektu
identyfikowanego przez Wyrażenie, zwolnienie sekcji.
Ilustracją wykorzystania instrukcji synchronized może być poniższy
prosty program:
class TestSynchro {
public static void main(String[] args) {
TestSynchro t = new TestSynchro();
synchronized(t) {
synchronized(t) {
System.out.println("made it!");
}
}//end synchronized
}//end main
}//end TestSynchro
który wydrukuje napis
made it!
Metoda jest synchronizowana jeżeli w jej nagłówku umieszczono
słowo kluczowe synchronized. Synchronizowana metoda operująca na obiekcie
pewnej klasy automatycznie nakłada blokadę na ten obiekt przed wykonaniem
jego ciała (funkcji, metod) i automatycznie zwalnia blokadę przy powrocie,
podobnie jak instrukcja synchronized. Tak więc segment kodu
class Test {
int count;
synchronized void bump() { count++; }
static int classCount;
static synchronized void classBump() {
classCount++;
}
}
jest równoważny segmentowi
class BumpTest {
int count;
void bump(){
synchronized (this){
count++;
}//end synchronized statement
}
static int classCount;
static void classBump() {
try {
synchronized (Class.forName("BumpTest")) {
classCount++;
} end synchronized statement
}//end try
catch (ClassNotFoundException e) {
...
}end catch
}//end classBump
}//end BumpTest
Wymieniona "blokada" (lock) jest w programie wielowątkowym związana
z obiektem, na którym wątek ma wykonywać pewne operacje; jest ona często
nazywana monitorem obiektu. Do czasu zwolnienia monitora, zostanie zablokowane
wykonanie każdego innego wątku, który podejmie próbę wywołania (na rzecz
tego samego obiektu), dowolnej metody sychronizowanej danej klasy.
Zauważmy, że w klasie Test metoda classBump() jest statyczna
(jest metodą klasy). Zatem, mimo iż metoda ta jest synchronizowana, może
być jednocześnie wywoływana na rzecz wielu obiektów, a więc blokada nie
będzie efektywna. Dla uniknięcia możliwości jednoczesnego wykonywania pewnej
operacji na tym samym obiekcie (w szczególności zawierającym metody statyczne)
przez dwa różne wątki dobrą praktyką jest definiowanie klas tak, aby były
przygotowane na użycie współbieżne, jak ilustruje to poniższy kod:
public class Box {
private Object boxContents;
public synchronized Object get() {
Object contents = boxContents;
boxContents = null;
return contents;
}//end get
public synchronized boolean put(Object contents) {
if (boxContents != null)
return false;
boxContents = contents;
return true;
}//end put
}
Każde wystąpienie klasy Box ma pewną zawartość zmiennej wystąpienia,
która utrzymuje referencję do dowolnego obiektu. W rezultacie można włożyć
obiekt do pudełka (Box) wywołaniem metody put(), która zwróci false jeżeli
pudełko jest już pełne. Podobnie można wyjąć obiekt z pudełka wywołaniem
metody get(), która zwróci null jeżeli pudełko jest puste. Gdyby put()
i get() nie były synchronizowane i dwa wątki wykonywałyby te metody na
tym samym obiekcie klasy Box w tym samym czasie, wtedy wykonanie programu
dwuwątkowego mogłoby przebiegać w sposób przez nas nieoczekiwany.
1.9. Obiekty sieciowe
Komputery w sieci Internet komunikują się ze sobą albo poprzez TCP (Transport
Control Protocol) albo poprzez User Datagram Protocol (UDP).
Uwaga. Nazwą TCP określa się zwykle protokół (zbiór zasad według
których odbywa się komunikacja w sieci komputerowej); TCP/IP (gdzie IP
jest akronimem od Internet Protocol) jest warstwowym zestawem protokołów
i odpowiada siedmiowarstwowemu modelowi ISO/OSI (Open Systems Interconnection)
z warstwami: fizyczną(1), łącza danych(2), sieciową(3), transportową(4),
sesji(5), prezentacji(6) i aplikacji(7). Np. Protokół IP jest implementacją
warstwy (3), zaś TCP i UDP implementują warstwę (4) modelu ISO/OSI, jak
pokazano w tabeli 1.1.
Tabela 1.1 Zależność pomiędzy modelem ISO/OSI a TCP/IP
|
Warstwa
|
ISO/OSI
|
TCP/IP
|
|
1
|
Aplikacji |
Aplikacji: SMTP, HTTP, FTP, RPC, TELNET,RLOGIN, inne usługi |
|
2
|
Prezentacji |
|
|
3
|
Sesji |
czeka na warunek, kt\u243\'f3ry nie mo\u380\'bfe by\u263\'e6 spe\u322\'b3niony; typowym przyk\u322\'b3adem jest\par
sytuacja, gdy istniej\u177\'b1 dwa w\u177\'b1tki i ka\u380\'bfdy z nich czeka na wykonanie\par
czego\u182\'b6 przez partnera.\par
Wykonanie metody start (public synchronized void start()) na\par
rzecz utworzonego wcze\u182\'b6niej obiektu klasy Thread powoduje utworzenie w\u177\'b1tku.\par
Przebieg wykonania w\u177\'b1tku zale\u380\'bfy od implementacji metody run (public void\par
run()), wywo\u322\'b3ywanej niejawnie przez system tu\u380\'bf po utworzeniu w\u177\'b1tku.\par
Zako\u324\'f1czenie wykonania metody run powoduje niejawne wywo\u322\'b3anie metody stop\par
(public static final void stop()), kt\u243\'f3ra niszczy w\u177\'b1tek. Stanami w\u177\'b1tk\u243\'f3w\par
mo\u380\'bfna sterowa\u263\'e6 przez wywo\u322\'b3ania finalnych metod wyst\u177\'b1pienia suspend i resume\par
(zawieszenie i wznowienie w\u177\'b1tku zawieszonego), wait i notify (wstrzymanie\par
i uwolnienie w\u177\'b1tku wstrzymanego), wywo\u322\'b3ania finalnych metod synchronizowanych\par
join() i join(long millisec), kt\u243\'f3re powoduj\u177\'b1 wstrzymanie wykonywania w\u177\'b1tku\par
a\u380\'bf do zniszczenia go przez inny w\u177\'b1tek oraz przez wywo\u322\'b3ania metod klasy:\par
public static void sleep(long millisec), kt\u243\'f3ra powoduje u\u182\'b6pienie w\u177\'b1tku\par
na podany okres czasu i public static void yield(), kt\u243\'f3ra oddaje dost\u281\'eap\par
do procesora innemu w\u177\'b1tkowi (o ile taki istnieje). Ponadto mo\u380\'bfna wywo\u322\'b3a\u263\'e6\par
metod\u281\'ea public final boolean isAlive() dla stwierdzenia, czy w\u177\'b1tek istnieje.\par
W\u177\'b1tek Javy mo\u380\'bfe by\u263\'e6 tworzony na dwa sposoby: albo jako obiekt\par
podklasy, kt\u243\'f3ra dziedziczy od klasy Thread (klasa java.lang.Thread zawiera\par
metody, kt\u243\'f3re kontroluj\u177\'b1 i synchronizuj\u177\'b1 poszczeg\u243\'f3lne w\u177\'b1tki), albo jako\par
obiekt klasy, kt\u243\'f3ra implementuje interfejs Runnable (java.lang.Runnable).\par
W obu przypadkach nale\u380\'bfy poda\u263\'e6 implementacj\u281\'ea metody run() zadeklarowanej\par
w interfejsie Runnable. Pierwszy spos\u243\'f3b ilustruje poni\u380\'bfszy przyk\u322\'b3ad.\par
public class Thread1 extends Object \{\par
public static void main(String args[]) \{\par
Thread x = new MyThread("Fastthread");\par
Thread y = new MyThread("Slow thread");\par
x.setPriority(Thread.MAX_PRIORITY);\par
x.start();\par
y.start();\par
\}//end main()\par
\}//end Thread1\par
class MyThread extends Thread \{\par
protected String name = "not initialized";\par
public
DNS, LDAP |
|
4
|
Transportowa |
Transportowa: TCP/UDP, IGMP |
|
5
|
Sieciowa |
Międzysieciowa: IP, ICMP, protokoły rutingu |
|
6
|
Łącza danych |
Interfejs sieciowy: ARP, RARP, LLC 802.2, Ethernet 802.3 |
|
7
|
Fizyczna |
Fizyczna dla różnych mediów |
Pisząc program "sieciowy" w języku Java nie musimy deklarować, czy żądamy
komunikacji via TCP czy UDP, ponieważ zdefiniowane w pakiecie java.net
klasy dostarczają środki dla niezależnej od systemu komunikacji w sieci;
klasy URL, URLConnection, Socket i ServerSocket korzystają z TCP, a klasy
DatagramPacket, DatagramSocket i MulticastSocket korzystają z UDP.
Tym niemniej warto pamiętać, że powszechnie używane protokoły: Telnet
i wirtualnego terminala odpowiadają warstwie 5 i częściowo 6, zaś FTP (File
Transfer Protocol) odpowiada warstwom 6 i 7 modelu ISO/OSI. Zauważmy też,
że HTTP (Hypertext Transfer Protocol), jest aplikacją (warstwa (7) modelu
ISO/OSI. Gdy używamy HTTP do czytania z pewnego lokalizatora URL (Uniform
Resource Locator) danych z pliku w formacie HTML (HyperText Markup
Language), otrzymywane dane muszą wystąpić w tej samej kolejności, w której
były przesyłane, co wymaga niezawodnego kanału komunikacji punkt-punkt,
jaki zapewnia protokół połączeniowy TCP. Z kolei UDP, bezpołączeniowy protokół
przesyłania niezależnych pakietów danych (datagramów) pomiędzy dwoma aplikacjami
w sieci, jest wystarczający dla takich aplikacji, jak np. program
ping, czy też programu podającego aktualny czas.
Komputery w sieci są identyfikowane przez 32-bitowe adresy IP,
zaś biegnące na danym komputerze sieciowym procesy poprzez 16-bitowe
numery portów (port można uważać za utrzymywaną przez system kolejkę danych,
które mają być dostarczane do danego procesu wykonującego pewien program).
Na przykład programowi wykorzystującemu protokół HTTP przydziela się zwyczajowo
port o numerze 80.
Programy odbierają lub wysyłają dane poprzez wiązane z numerami
portów gniazda (sockets). Gniazdo, definiowane przez warstwę transportową
modelu ISO/OSI, reprezentuje punkt końcowy połączenia pomiędzy programami
wykonywanymi na komputerach sieciowych w systemie dostawca-odbiorca (klient-server);
jest to interfejs programowy umożliwiający aplikacjom dostęp do protokołów
TCP i UDP i wymianę danych poprzez sieć pracującą pod kontrolą protokołów
TCP/IP. Program serwera wykonywany na konkretnej maszynie ma przypisane
do pewnego numeru portu gniazdo, poprzez które "nasłuchuje" ewentualnego
żądania nawiązania łączności przez klienta. Jeżeli klient zna adres komputera
sieciowego, na którym jest wykonywany serwer oraz numer portu, do którego
serwer jest dołączony, to może przesłać takie żądanie. Serwer akceptuje
połączenie, a następnie tworzy dla klienta nowe gniazdo, związane z innym
numerem portu, ponieważ na pierwotnym gnieździe musi nadal prowadzić "nasłuch"
żądań połączenia. Po stronie klienta, gdy uzyska potwierdzenie połączenia,
tworzone jest odpowiednie gniazdo (związane z lokalnym numerem portu na
maszynie klienta), poprzez które może prowadzić komunikację z serwerem.
W pakiecie java.net klasy Socket i ServerSocket służą do komunikacji
w oparciu o protokół połączeniowy TCP, zaś klasy DatagramSocket, DatagramPacket
oraz MulticastSocket są wykorzystywane do komunikacji UDP. Klasy: URL oraz
URLConnection i URLEncoder służą do nawiązania łączności z World Wide Web
(WWW). Komunikacja, która wykorzystuje te klasy, reprezentuje wyższy poziom
abstrakcji, ponieważ korzystają one między innymi z implementacji gniazd.
Poniżej pokazano przykład aplikacji, która wykorzystuje klasy
URL i URLConnection dla dostępu do zasobu sieciowego (pliku tekstowego
"abc") WWW zlokalizowanego pod adresem www.task.gda.pl/~dark/.
import java.net.*;
import java.io.*;
//import java.util.*;
public class URLTest {
public static void main(String[] args) {
URL url;
try {
url = new URL("http://www.task.gda.pl/~dark/abc");
URLConnection uc = url.openConnection();
BufferedReader d = new BufferedReader(new
InputStreamReader(uc.getInputStream()));
//DataInputStream dis = new DataInputStream(uc.getInputStream());
String line = d.readLine();
System.out.println(line);
}//end try
catch (Exception e)
{ e.printStackTrace(); }
}// end main
}// end URLTest
W programie wykorzystano konstruktor URL(String). Klasa URL jest
wyposażona w cztery publiczne konstruktory:
public URL(String spec) throws MalformedURLException tworzy
obiekt URL z podanej reprezentacji obiektu klasy String, lub zgłasza wyjątek,
jeżeli łańcuch "spec" podaje nieznany protokół. Dla danego protokołu (http,
file, ftp) jest przyjmowany domyślny numer portu. Numer portu można też
podać jawnie, np. http://www.task.gda.pl:80/~dark/abc.
public URL(URL context, String spec) throws MalformedURLException
tworzy obiekt URL przez parsing (wydzielenie) specyfikacji "spec" w obrębie
zadanego kontekstu.
public URL(String protocol, String host, int port, String file)
throws MalformedURLException tworzy obiekt URL z podanego protokołu,
nazwy komputera, numeru portu i nazwy pliku.
public URL(String protocol, String host, String file) throws
MalformedURLException tworzy absolutny obiekt URL z podanego protokołu,
nazwy komputera i nazwy pliku; przyjmuje port domyślny dla danego protokołu.
2. Aplety - programy Javy na stronach WWW
Aplet jest niewielkim programem Javy przeznaczonym do uruchamiania w obrębie
innej aplikacji - przeglądarki WWW lub Java Applet Viewer. Najprostszy
aplet drukujący jedno zdanie na ekranie może mieć następującą postać:
HelloWorld.java
import java.applet.*;
import java.awt.*;
public class HelloWorld extends Applet {
public void init() {
add(new Label("Hello
World!"));
}
}
Podobnie jak aplikację, kompilujemy aplet poleceniem:
javac HelloWorld.java
w wyniku czego otrzymamy plik ze skompilowaną klasą o nazwie
HelloWorld.class.
Następnie tworzymy prostą stronę HTML zawierającą TAG <applet>
wywołujący aplet Java i otwieramy tę stronę przeglądarką WWW.
HelloWorld.html
<html>
<head>
<title>HelloWorld</title>
</head>
<body>
<h1>Aplet HelloWorld</h1>
<applet code=HelloWorld.class width=200 height=50>
</applet>
</body>
</html>
Wynik działania apletu możemy również obejrzeć używając przeglądarki
apletów dostarczanej przez Suna razem z całym środowiskiem JDK:
appletviewer HelloWorld.html
Każdy aplet tworzony jest jako podklasa klasy java.applet.Applet, dostarczającej
interfejs do środowiska, w obrębie którego jest on uruchamiany i znajdującej
się na dole następującej hierarchii standardowych klas Javy:
java.lang.Object
|
+----java.awt.Component
|
+----java.awt.Container
|
+----java.awt.Panel
|
+----java.applet.Applet
W cyklu życia apletu występują cztery istotne zdarzenia. Gdy one
następują, wywoływane są odpowiednie metody klasy Applet, które mogą być
przesłonięte przez metody zdefiniowane w jej podklasie, jak w poniższym
przykładzie:
public class MyApplet extends Applet {
. . .
public void init() { . . . }
public void start() { . . . }
public void stop() { . . . }
public void destroy() { . . . }
. . .
}
Metody te są wywoływane przez środowisko, w którym uruchamiany
jest aplet, w momentach wystąpienia następujących zdarzeń:
init - po załadowaniu apletu (lub jego przeładowaniu) w celu
jego zainicjowania,
start - w momencie uruchamiania apletu, po jego załadowaniu lub
po ponownym odwiedzeniu strony zawierającej aplet,
stop - w momencie zatrzymywania pracy apletu, gdy opuszczana
jest strona lub następuje wyjście z przeglądarki,
destroy - przed wyjściem z przeglądarki.
Aplet nie musi przesłaniać wszystkich z tych metod, może tylko
niektóre albo żadnej. W metodzie init powinno się zamieszczać kod, który
normalnie powinien być zawarty w konstruktorze klasy. Jednak w momencie
wykonywania konstruktora klasy apletu nie ma gwarancji, że jest już zdefiniowane
całe jego środowisko. Dlatego takie zadania jak na przykład jednokrotne
załadowanie obrazów wykorzystywanych przez aplet powinny być zawarte właśnie
w metodzie init. W metodach start i stop można na przykład odpowiednio
uruchamiać i zatrzymywać animację tak, żeby w czasie gdy użytkownik nie
ogląda strony z apletem niepotrzebnie nie zużywać zasobów komputera.
Aplet dziedziczy metody interfejsu użytkownika z nadrzędnych
klas AWT (Abstract Window Toolkit) Component, Container i Panel.
Z klasy Component dziedziczy metody paint i update, które odpowiadają
za graficzną reprezentację apletu na stronie przeglądarki. Metody te aplet
może przesłonić:
class MyApplet extends Applet {
. . .
public void paint(Graphics g) { . . . }
. . .
}
Z klasy Component aplet dziedziczy również metody obsługi zdarzeń,
zarówno dotyczące konkretnych wydarzeń, takie jak action czy mouseDown,
jak i ogólną metodę handleEvent przechwytującą wszystkie zdarzenia.
Aplet jest podklasą klasy Container, może więc zawierać inne
obiekty klasy Component, takie jak klawisze, etykiety, listy wyboru itd.
Jak w innych obiektach klasy Container wzajemne rozłożenie komponentów
kontrolowane jest poprzez klasę LayoutManager.
Aplety są uruchamiane w środowisku przeglądarki, która narzuca
im pewne ograniczenia związane z zachowaniem bezpieczeństwa. Różnią się
one nieco pomiędzy różnymi przeglądarkami. Aplet ściągnięty do przeglądarki
poprzez sieć:
-
nie może czytać i pisać do plików znajdujących się na komputerze, który
go wykonuje,
-
nie może tworzyć połączeń sieciowych do komputerów innych niż ten, z którego
został ściągnięty,
-
nie może uruchamiać żadnych programów na komputerze, który go wykonuje,
-
może czytać tylko wybrane parametry systemowe,
-
nie może ładować bibliotek ani definiować metod w kodzie maszynowym.
Ograniczeniem (choć nie związanym z bezpieczeństwem) jest również
graficzny interfejs użytkownika apletu. Aplet może przedstawiać swoją reprezentację
graficzną tylko w obrębie prostokąta na stronie WWW o z góry zadanych wymiarach,
bądź w postaci generowanych okienek, które różnią się od tych generowanych
przez aplikacje.
Pliki składające się na aplet: klasy, pliki z grafikami, dźwiękami
i inne pliki pomocnicze mogą być połączone w jeden plik o formacie Java
Archive (JAR), co pozwala uzyskać wiele korzyści:
-
bezpieczeństwo - zawartość pliku JAR można podpisać cyfrowo; dzięki temu
mogą być złagodzone niektóre z wcześniej wymienionych ograniczeń związane
z dostępem do lokalnych zasobów dyskowych oraz z pełnym dostępem do sieci,
-
skrócenie czasu ładowania - cały aplet może ściągnięty przez przeglądarkę
w jednej transakcji HTTP bez konieczności otwierania nowego połączenia
dla każdego pliku,
-
kompresja - pliki są kompresowane zgodnie z formatem ZIP.
Do obsługi plików JAR służy standardowe narzędzie jar zawarte w pakiecie
JDK. Sposób jego użycia jest analogiczny do programu tar z systemu Unix.
| Operacja |
Polecenie |
| Tworzenie pliku JAR |
jar cf jar-file input-file(s) |
| Oglądanie zawartości pliku JAR |
jar tf jar-file |
| Rozpakowywanie pliku JAR |
jar xf jar-file |
| Uruchamianie apletu spakowanego w postaci pliku JAR |
<applet code=AppletClassName.class archive="JarFileName.jar"
width=width height=height></applet> |
Uwaga. Internet Explorer używa własnego standardu kompresji - CAB.
Netscape Communicator obsługuje format JAR..
Tag <applet> na stronie HTML może więcej parametrów niż podane wcześniej
w prostym przykładzie. Bardziej złożone wywołanie może mieć następującą
postać:
<applet code=AppletSubclass.class codebase="directory/" width=anInt
height=anInt align=left>
<param name=parameter1Name value=aValue>
<param name=parameter2Name value=anotherValue>
Wymagana jest przeglądarka obsługująca aplety Javy!
</applet>
Domyślnie przeglądarka szuka klas apletu oraz plików skompresowanych
w tym samym katalogu, z którego został ściągnięty plik HTML zawierający
tag <applet>. Można jednak wskazać inne miejsce podając parametr codebase.
Wartością parametru może być ścieżka względna wobec strony HTML, ścieżka
bezwzględna lub w ogólności dowolny URL. Klasy apletu mogą więc być ściągane
z innego serwera niż strona HTML. Opcje width i height określają rozmiary
prostokąta na stronie przeglądarki, w obrębie którego wizualizowany jest
aplet. Opcja align określa położenie apletu, podobnie jak dla tagu <img>:
left, right, top, texttop, middle, absmiddle, baseline, bottom, absbottom.
Do apletu można przekazywać parametry definiując tagi <param> w obrębie
tagu
<applet>. Wartości tych parametrów można następnie odczytywać w kodzie
apletu wywołując metodę klasy Applet
public String getParameter(String name)
Opcjonalny tekst w obrębie tagu <applet> jest pisany w okienku przeglądarki,
gdy ta nie potrafi obsługiwać Javy lub obsługa została wyłączona.
3. Standardowe klasy Javy
Nazwy standardowych pakietów Javy rozpoczynają się od prefiksu java.
W skład JDK 1.1 wchodzą następujące pakiety: java.applet, java.awt, java.beans,
java.io, java.lang, java.math, java.net, java.rmi, java.security, java.sql,
java.text, java.util. Zostaną one omówione pokrótce, najpierw te najczęściej
wykorzystywane.
W pakiecie java.lang zdefiniowana jest klasa Object, która jest klasą
nadrzędną wobec wszystkich innych klas Javy.
Pakiet zawiera także klasy opakowujące (kopertowe) zmienne podstawowych
typów języka Java takie jak Boolean, Byte, Integer, Long, Double, Character
itd., definiując użyteczne metody operujące na zmiennych tych typów, metody
konwersji pomiędzy typami, zamianę na łańcuchy znaków i inne.
Klasa Math zawiera metody wykonujące podstawowe operacje numeryczne
takie jak funkcje eksponencjalne, logarytmiczne, trygonometryczne.
W pakiecie java.lang zawarte są dwie klasy obsługujące łańcuchy znaków:
String i StringBuffer. Klasa String używana jest do przechowywania i wykonywania
operacji na stałych łańcuchach; po utworzeniu obiektu tej klasy nie można
zmienić jego wartości. Klasa ta zawiera metody do sprawdzania poszczególnych
znaków, porównywania łańcuchów, wyodrębniania podłańcuchów, tworzenia kopii
z zamianą na małe albo duże litery. Klasa StringBuffer implementuje łańcuchy
znaków, które mogą być zmieniane. Podstawowe metody tej klasy to append
dodająca znaki na końcu bufora i insert wstawiająca znaki w określonym
miejscu. Każdy obiekt przydziela bufor na przechowywany łańcuch znaków.
Jeżeli całkowita długość łańcucha wzrośnie powyżej rozmiaru bufora, automatycznie
przydzielany jest większy.
Dostęp do zasobów systemowych można uzyskać poprzez niezależne od systemu
API zdefiniowane w finalnej klasie System oraz zależne od systemu API zawarte
w Runtime. Można uzyskać dostęp do parametrów systemowych, standardowych
strumieni: wyjściowego, wejściowego i błędów, metod ładowania bibliotek,
odczytu czasu komputera, szybkiego kopiowania tablic itd.
Klasa Thread definiuje wątki programu Javy pozwalające na jego współbieżną
pracę.
Pakiet java.io definiuje klasy implementujące operacje wejścia-wyjścia.
W pakiecie tym (jak również i w innych) występują klasy i metody pochodzące
z JDK 1.0 równolegle z nowymi klasami wprowadzonymi wraz z wejściem JDK
1.1. Pozostawiono stare klasy w celu zapewnienia zgodności dla dawniej
pisanych programów. Często przestarzałe (deprecated) konstrukcje
są odradzane i proponowane są ich udoskonalone odpowiedniki.
Podstawowymi klasami pozwalającymi czytać i pisać z plików, łańcuchów
znaków i innych strumieni, są: Reader wraz z podklasami BufferedReader,
CharArrayReader, InputStreamReader, FileReader, StringReader oraz Writer
wraz z podklasami BufferedWriter, CharArrayWriter, OutputStreamWriter,
FileWriter, PrintWriter, StringWriter. Ich starsze odpowiedniki to InputStream
i OutputStream wraz z ich podklasami.
Strumień wejściowy znaków może być dzielony na jednostki logiczne -
tokeny, poprzez skonstruowanie na nim obiektu klasy StreamTokenizer. Można
zdefiniować składnię, według której wyróżniane będą tokeny typu słowo,
liczba, znaczniki końca linii i pliku oraz pomijane komentarze.
Zdefiniowane są również klasy wspierające obsługę struktury drzewa
plików w systemie - File oraz czytanie i pisanie do plików o dostępie swobodnym
- RandomAccessFile.
W pakiecie java.util można znaleźć m.in. szereg klas definiujących różne
struktury danych przechowujące inne obiekty. Klasa Vector implementuje
tablicę obiektów, która może rosnąć lub zmniejszać się w miarę jak obiekty
są dodawane lub usuwane. Tak jak w zwykłej tablicy dostęp do jej elementów
może odbywać się poprzez całkowity indeks, można również szukać wystąpienia
obiektu w wektorze, którego wartość jest równa podanemu. Wszystkie elementy
wektora najwygodniej jest przeglądać wykorzystując interfejs Enumeration:
Vector v;
for(Enumeration e = v.elements(); e.hasMoreElements()
;)
System.out.println(e.nextElement());
Każdy wektor stara się optymalizować zarządzanie rozmiarem zajmowanej
pamięci. Gdy wektor zajmuje całą przydzieloną pamięć, przed dodaniem kolejnego
elementu jego rozmiar jest automatycznie zwiększany o wartość capacityIncrement.
Program może jednak sam zwiększyć rozmiar wektora przed wstawieniem dużej
porcji danych, aby uniknąć wielu realokacji. Podklasą klasy Vector jest
Stack realizujący kolejkę LIFO obiektów z metodami push i pop.
Klasa Dictionary jest abstrakcyjnym rodzicem dowolnej klasy implementującej
odwzorowanie kluczy na wartości. Jej podklasą jest Hashtable, której
konstrukor posiada dwa parametry initialCapacity i loadFactor. Gdy liczba
elementów w tablicy mieszającej (hash table) osiąga wartość iloczynu tych
parametrów, rozmiar tablicy jest automatycznie zwiększany i przeliczane
są pozycje elementów. Większy współczynnik wypełnienia pozwala oszczędniej
gospodarować pamięcią kosztem dłuższego czasu potrzebnego do wyszukiwania
elementów. Efektywniej jest również zawczasu przydzielić odpowiedniej wielkości
tablicę, niż potem zdać się na automatyczne przeładowywania. Poniżej przedstawiono
przykład tablicy mieszającej obiektów typu Integer posiadających nazwy
jako klucze:
Hashtable numbers = new Hashtable();
numbers.put("one", new Integer(1));
numbers.put("two", new Integer(2));
numbers.put("three", new Integer(3));
Aby wydobyć element z tablicy można posłużyć się następującym kodem:
Integer n = (Integer)numbers.get("two");
if(n != null)
System.out.println("two = " + n);
Obiekty przechowywane w tablicy mieszającej muszą implementować metody
hashCode i equals dziedziczone z klasy java.lang.Object.
Przy użyciu klasy BitSet można tworzyć zbiory bitów i wykonywać na
nich operacje logiczne.
Klasa StringTokenizer pozwalająca dzielić łańcuchy znaków na tokeny
jest znacznie prostsza w użyciu niż klasa java.io.StreamTokenizer. Określa
się w niej tylko jakie znaki rozdzielają kolejne tokeny, domyślnie są to
znaki z łańcucha " \t\n\r", na przykład:
StringTokenizer st = new StringTokenizer("To jest tylko
test");
while (st.hasMoreTokens())
System.out.println(st.nextToken());
Można również znaleźć klasy obsługujące daty - Date i Calendar, ustawiające
parametry specyficzne dla kraju, regionu lub języka - Locale, TimeZone.
W pakiecie java.util.zip znajdują się klasy pozwalające tworzyć i czytać
pliki skompresowane w formatach ZIP i GZIP.
Pakiet java.net zawiera klasy realizujące połączenia sieciowe zarówno
na poziomie gniazd, jak i adresów URL wskazujących zasoby w WWW. Podstawowe
klasy to Socket, URL, URLConection. Poniższy przykład pokazuje jak z apletu
można w prosty sposób przejść do wybranej strony WWW:
try {
URL task = new URL("http://www.task.gda.pl/");
getAppletContext().showDocument(task);
} catch (MalformedURLException e) {} //
new URL() failed
Aplety i aplikacje Javy komunikują się z użytkownikiem wykorzystując
klasy z pakietu java.awt składające się na graficzny interfejs użytkownika
AWT (Abstract Window Toolkit). AWT dostarcza typowe komponenty graficzne
takie, jak klawisze, pola do wprowadzania tekstu, listy wyboru itd. poprzez
klasy Button, Checkbox, Choice, Label, List, Menu, Scrollbar, TextArea,
TextField będące pochodnymi klasy Component. Wykorzystując klasę Canvas,
można rysować dowolne obrazy graficzne na ekranie, a po dodaniu odpowiedniej
obsługi zdarzeń można zdefiniować dowolny własny komponent.
Wraz z JDK 1.1 został wprowadzony nowy model obsługi zdarzeń, który
jest znacznie bardziej elastyczny i wydajny w porównaniu z modelem z JDK
1.0. Stary model dla zachowania zgodności jest również obsługiwany, choć
jego użycie jest odradzane. W modelu 1.1 AWT zdarzenia są generowane przez
źródła, którymi mogą być komponenty interfejsu użytkownika, myszka, klawiatura
itd. Może być wydelegowany jeden lub więcej odbiorców zdarzenia pochodzącego
od określonego źródła, który jest obiektem dowolnej klasy implementującej
przynajmniej jeden z interfejsów obsługi zdarzeń takich, jak ActionListener,
KeyListener czy MouseListener. Zasadę działania tego modelu można prześledzić
w poniższym przykładzie:
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
public class URLButton extends Applet implements ActionListener
{
public void init() {
Button button = new Button("TASK
Home");
add(button);
button.addActionListener(this);
}
public void actionPerformed(ActionEvent event) {
. . .
}
}
Zastosowanie klas Applet i AppletContext z pakietu java.applet było
już demonstrowane we wcześniejszych przykładach. Definiują one środowisko,
w którym uruchamiane są aplety. Pozwalają również na komunikację pomiędzy
apletami znajdującymi się na tej samej stronie.
Środowisko budowania niezależnych od platformy, modyfikowalnych wizualnych
komponentów JavaBeans API zawarte jest w pakiecie java.beans. Wykorzystując
przeznaczone do tego narzędzia programistyczne, można wizualnie z komponentów
konstruować aplikacje, aplety, servlety lub komponenty złożone. Narzędzie
tego typu pozwala wybrać z zestawu potrzebny nam komponent, wstawić go
do okienka programu, zmodyfikować jego wygląd i zachowanie, zdefiniować
interakcję z innymi komponentami; wszystko to nie pisząc ani jednej linii
kodu.
JDBC API (Java DataBase Conectivity) zdefiniowane w pakiecie java.sql
wprowadza jednolity standard dostępu do dowolnych relacyjnych baz danych.
Klasy z tego pakietu implementują połączenia z bazą danych, zapytania SQL,
wyniki tych zapytań itp. Dostęp do bazy odbywa się za pośrednictwem drivera,
który może być napisany całkowicie w Javie i wtedy może być np. ściągnięty
jako część apletu, może być też napisany z wykorzystaniem kodu maszynowego
określonej platformy, aby uzyskać dostęp do istniejących już bibliotek
dostępu do baz danych. Drivery JDBC można podzielić na cztery kategorie:
1. Pomost JDBC-ODBC, który zapewnia dostęp do bazy poprzez binarne
drivery ODBC.
2. Driver w Javie zamieniający odwołania JDBC na odwołania do binarnego
drivera dostępowego konkretnej bazy danych znajdującego się na lokalnym
komputerze.
3. Całkowicie w Javie napisany driver komunikujący się poprzez sieć
z oprogramowaniem pośredniczącym w dostępie do bazy (middleware). Protokół
dostępowy zależy od producenta tego oprogramowania, producent ten jest
też najczęściej dostawcą odpowiedniego drivera JDBC.
4. Całkowicie w Javie napisany driver komunikujący się poprzez sieć
bezpośrednio z serwerem bazy danych. Dostawcą drivera często jest producent
bazy danych.
RMI (Remote Method Invocation) zawarty w java.rmi umożliwia tworzenie
rozproszonych aplikacji w Javie. Aplikacja taka złożona jest z serwera,
który tworzy obiekty posiadające metody mogące być wywoływane zdalnie,
udostępnia odnośniki do tak zdefiniowanych obiektów i czeka na wywołania
tych metod przez klientów; klient ściąga odnośniki do zdalnych obiektów
i wywołuje ich metody. RMI zapewnia mechanizm, poprzez który odbywa się
komunikacja pomiędzy serwerem i klientem oraz przesyłane są dane w obie
strony.
W pakiecie java.math znajdują się klasy BigInteger i BigDecimal pozwalające
na tworzenie i operacje na liczbach całkowitych dowolnej precyzji. BigInteger
implementuje operacje arytmetyki modularnej, obliczanie największego wspólnego
podzielnika, generator liczb pierwszych, operacje na bitach itd. Z liczb
zdefiniowanych w tej klasie korzysta pakiet java.security wprowadzający
jednolity model kryptografii i ochrony danych. Znajdują się tam abstrakcyjne
klasy definiujące kody zapewniające integralność danych (message digests)
oparte na jednokierunkowych funkcjach mieszających (one-way hash functions),
podpisy cyfrowe oparte na parze kluczy jawnego i tajnego, zarządzanie kluczami
i certyfikaty. Jest to tylko ramowy szkielet koncepcji kryptograficznych,
pod który producenci oprogramowania mogą podłączać konkretne algorytmy
i metody. Razem z JDK Sun dostarcza jedynie klasy do cyfrowego podpisywania
dokumentów opartego na algorytmie DSA oraz obliczania kodów dokumentów
metodami MD5 i SHA-1.
W pakiecie java.text znajdziemy klasy obsługujące różne formaty danych
tekstowych, np. DateFormat obsługujący daty w różnych standardach czy NumberFormat
tworzący i analizujący różne formaty liczb.
4. Servlety - programy Javy na serwerze WWW
Servlety są modułami, które są uruchamiane wewnątrz serwerów przetwarzających
zapytania i generujących odpowiedzi, takich jak np. rozszerzone o obsługę
Javy serwery WWW. Rozszerzają one funkcjonalność tych serwerów. Można w
uproszczeniu powiedzieć, że servlety dla serwerów są tym, czym aplety dla
przeglądarek.
Servlety stanowią alternatywę dla skryptów CGI, umożliwiając łatwą
metodę dynamicznego tworzenia dokumentów HTML. Są one łatwiejsze do pisania
dzięki wykorzystaniu środowiska Javy. Są również szybciej wykonywane, gdyż
wywołanie servletu odbywa się nie poprzez uruchomienie nowego procesu,
co jest kosztowne ze względu na czas procesora i zasoby pamięciowe, lecz
jako wątek. Co więcej, kod wykonywalny dla servletu jest ładowany do serwera
tylko raz, gdy po raz pierwszy żądana jest usługa oferowana przez dany
servlet lub automatycznie, gdy zostanie zmieniony kod servleta. Potem servlet
pozostaje w pamięci serwera i może równolegle obsługiwać wiele zapytań
z możliwością komunikacji pomiędzy nimi.
Klasy implementujące funkcje servletów znajdują się w pakiecie javax.servlet
dostępnego z firmy Sun jako Java Servlet Development Kit. Zawierają one
abstrakcyjną klasę GenericServlet oraz interfejs Servlet, które definiują
podstawowe własności ogólnych servletów, czyli modułów przetwarzających
zapytania i generujących odpowiedzi. Jednak najczęściej korzysta się z
podklas znajdujących się w pakiecie javax.servlet.http definiujących servlety
HTTP. Klasa HttpServlet posiada m.in. takie metody jak doGet i doPost obsługujące
typowe zapytania CGI, czy uniwersalną metodę service obsługującą wszystkie
rodzaje zapytań. Najprostszy servlet może wyglądać następująco:
HelloWorldServlet.java
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class HelloWorldServlet extends HttpServlet {
public void doGet (HttpServletRequest req,
HttpServletResponse res)
throws ServletException, IOException
{
res.setContentType("text/html");
ServletOutputStream out = res.getOutputStream();
out.println("<html>");
out.println("<head><title>Hello
World</title></head>");
out.println("<body>");
out.println("<h1>Hello World</h1>");
out.println("</body></html>");
}
}
Większość użytecznych servletów czyta parametry wejściowe przekazane
im z przeglądarki poprzez metody GET lub POST i na ich podstawie tworzy
odpowiedni dokument HTML. Prosty przykład takiego servletu przedstawiono
poniżej.
Hello.java
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class Hello extends HttpServlet {
public void service(HttpServletRequest req,
HttpServletResponse res)
throws ServletException, IOException
{
res.setContentType("text/html");
PrintWriter toClient = res.getWriter();
toClient.println("<html><head>");
toClient.println("<title>Hello</title>");
toClient.println("</head>");
toClient.println("<h1>Hello "
+
req.getParameterValues("name")[0]
+ "!</h1>");
toClient.println("</body></html>");
toClient.close();
}
}
Powyższy servlet można wywołać na kilka sposobów. Można podać w przeglądarce
URL
http://www.task.gda.pl/servlet/Hello?name=Darek
i wywołać servlet przekazując parametry metodą GET. Można na stronie
HTML umieścić formularz:
<form action=http://www.task.gda.pl/servlet/Hello method=POST>
Podaj swoje imię: <input type=text name=name>
<input type=submit value=Wyślij>
</form>
i wywołać servlet przekazując parametry metodą POST. Można również wykorzystać
tag <servlet> na stronie HTML zapisanej w pliku z rozszerzeniem shtml.
Hello.shtml
...
<servlet code=Hello>
<param name=name value=Darek>
</servlet>
...
Wtedy serwer WWW rozszerzony o funkcjonalność servletów zastąpi na stronie
HTML powyższy tag wynikiem generowanym przez servlet Hello i tak przetworzoną
stronę prześle do przeglądarki.
Dostępnych jest już wiele serwerów WWW obsługujących servlety Javy.
Przykładami mogą być Java Web Server firmy Sun lub darmowy dla celów niekomercyjnych
JRun firmy Live Software. Ten ostatni może pracować jako samodzielny serwer
WWW, bądź jako moduł rozszerzający funkcjonalność innych znanych serwerów
WWW np. Apache, Microsoft IIS, Netscape.
5. Podsumowanie
Zalety języka Java:
-
"ożywienie" WWW - swego rodzaju "lingua franca" Internetu
-
język pozwala łatwo pisać aplikacje sieciowe
-
przenośność - niezależność od platformy dzięki JVM; Java poprzez swój B-kod
staje się najbliższą aproksymacją (zbudowaną raczaej z software niż z hardware,
chociaż anonsowane są już "Java chips") jednego z najstarszych marzeń przemysłu
komputerowego: prawdziwie uniwersalna maszyna wirtualna
-
prawie czysta implementacja paradygmatu obiektowego
-
usunięcie arbitralnego pojęcia wskaźnika jak w C++
-
wsparcie dla zbierania nieużytków
-
brak samodzielnych (tj. definiowanych poza klasami) funkcji zewnętrznych
-
weryfikacja kodu w fazie kompilacji i w fazie wykonania na zgodność ze
standardem języka
-
dynamiczne ładowanie klas (w locie)
-
biblioteki klas dla GUI, sieciowe i WWW
Wady:
-
Brak definiowanych przez użytkownika przeciążonych operatorów
-
złamanie zasad obiektowości w standardowych bibliotekach klas: w klasach
opakowań (kopertowych), w klasach kolekcji i w klasach łańcuchów
-
Komunikaty są rzadko polimorficzne i rzadko trafiają na oczekiwaną strukturę
dziedziczenia
-
brak klas parametryzowanych (genericity, templates)
-
dziedziczenie tylko pojedyncze
-
usunięcie instrukcji assert znanej w C i C++
-
pogmatwana (zawikłana) struktura modularna z trzema wpływającymi wzajemnie
na siebie (interagującymi) koncepcjami (klasy, pakiety zagnieżdżone, pliki
źródłowe)
|