Jezyk Java.doc

(231 KB) Pobierz

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.

http://www.staff.amu.edu.pl/~psi/informatyka/java/kurs/img00001.gif

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

Zgłoś jeśli naruszono regulamin