Java - Podstawy, Programowanie, Zastosowania.DOC

(398 KB) Pobierz
JAVA

3

Java

 

 

 

 

 

 

 

Język Java: Podstawy, Programowanie, Zastosowania

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


1. Język Java

Początki języka Java sięgają roku 1990, gdy Bill Joy napisał dokument pod tytułem “Further”, w którym sugerował inżynierom Sun Microsystems stworzenie obiektowego środowiska w oparciu o C++. Dokument ten miał pewien wpływ na twórców projektu Green (James Gosling, Patrick Naughton i Mike Sheridan). W roku 1991 w ramach projektu Green opracowano w języku C kompilator oraz interpretator wynalezionego przez Goslinga języka OAK (Object Application Kernel), który miał być narzędziem do oprogramowania “inteligentnych” konsumenckich urządzeń elektronicznych. Ponieważ nazwa “OAK” okazała się zastrzeżona, zmieniono ją na “Java”.

Obecnie należy raczej mówić o środowisku Java, na które składa się:

  1. Obiektowy język Java, którego składnia wykazuje znaczne podobieństwo do składni języka C++. Nazwa pliku z programem źródłowym w języku Java, ma postać “nazwa.java”, gdzie “nazwa” musi być nazwą zdefiniowanej w tym pliku klasy. Jeżeli plik “nazwa.java” zawiera definicje wielu klas, a wśród nich jedną klasę publiczną, to “nazwa” musi być nazwą tej klasy publicznej.
  2. Kompilator, który przetwarza program “nazwa.java” na tak zwany B-kod (bytecode, J-code), zapisywany automatycznie w plikach z rozszerzeniem nazwy  “.class”. B-kod jest przenośną postacią programu, która może być zinterpretowana przez odpowiednią maszynę wirtualną, to jest “urządzenie logiczne”, na którym będzie wykonywany program binarny.
  3. Specyfikacje maszyny wirtualnej Java (JVM Java Virtual Machine) i plików klas. JVM można uważać za abstrakcyjny komputer, który wykonuje programy, zapisane w plikach z rozszerzeniem nazwy “.class”. Maszyna wirtualna może być implementowana na rzeczywistych komputerach na wiele sposobów, na przykład jako interpretator wbudowany w przeglądarkę WWW (np. Netscape), lub jako oddzielny program, który interpretuje pliki “nazwa.class”. Może to być także implementacja polegająca na przekształceniu tuż przed rozpoczęciem fazy wykonania pliku z B-kodem na program wykonalny, specyficzny dla danej maszyny. Mechanizm ten można określić jako tworzenie kodu wykonalnego w locie (ang. Just-In-Time, np. kompilator JIT firmy Symantec). Interpretatory B-kodu, tj. różne maszyny wirtualne, są także często napisane w języku Java.
  4. Biblioteka Javy. Środowisko języka Java zawiera bogatą bibliotekę, a w niej zbiór składników dla prostego, niezależnego od platformy graficznego interfejsu użytkownika.

Rysunek 4-1 ilustruje usytuowanie środowiska programowego języka Java, posadowionego na dowolnej platformie sprzętowo- programowej komputera (platforma sprzętowo-programowa oznacza sprzęt komputera i jego system operacyjny).

 

 

Rys. 1-1. Usytuowanie systemu Java

 

Pokazany w części a) rysunku blok Java API (Application Programming Interface) reprezentuje klasy, interfejsy i obiekty wchodzące w skład aktualnej maszyny wirtualnej, którą zwykle określa się jako platformę języka Java (Java Platform). Umożliwiają one programom języka Java na dostęp do zasobów komputera. Może to być dostęp (względnie) systemowo niezależny (implementowany przez klasę System w pakiecie JDK) i na dostęp systemowo zależny (implementowany przez klasę Runtime, reprezentującą środowisko wykonawcze w pakiecie JDK), jak pokazano w części b) rysunku 4-1.

Tak więc programy użytkownika można kompilować na dowolnej platformie sprzętowo-programowej, na której posadowiono kompilator języka Java. Otrzymany w wyniku kompilacji B-kod można traktować jako zbiór instrukcji kodu dla dowolnej implementacji maszyny wirtualnej, jak pokazano na rysunku 4-2.

 

 

Rys. 1-2. Przetwarzanie programów użytkownika

 

1.1. Elementarny program: tekst źródłowy, kompilacja, interpretacja

Java wprowadza swoistą terminologię dla swoich konstrukcji syntaktycznych i jednostek (modułów) kompilacji. Programem w języku Java jest aplikacja (application) lub aplet (applet). Aplikacja jest programem samodzielnym, zaś aplet jest programem wbudowanym (np. w przeglądarkę WWW). Każda aplikacja musi zawierać dokładnie jeden moduł źródłowy nazywany modułem głównym aplikacji, którego klasa zawiera publiczną funkcję klasy (funkcje takie są poprzedzane słowem kluczowym static) main. Tekst źródłowy najprostszego programu może mieć postać:

//plik Hello.java

public class Hello {

    public static void main(String args[])

    {

     System.out.print("Hello, World!\n");

    } //end main

} // end Hello

Dla skompilowania powyższego programu jego tekst źródłowy należy umieścić w pliku o nazwie Hello.java. Zakładając, że dysponujemy systemem JDK z kompilatorem javac, program skompilujemy poleceniem:

javac Hello.java

Udana kompilacja wygeneruje plik z B-kodem o nazwie Hello.class, zawierający sekwencję instrukcji dla interpretatora JVM. Kod ten wykonujemy przez wywołanie interpretatora o nazwie java poleceniem:

java Hello

Interpretator wyszuka plik o nazwie Hello.class, ustali, czy klasa Hello zawiera publiczną metodę statyczną main i wykona instrukcje zawarte w bloku main. Zauważmy przy okazji, że w języku Java wszystkie stałe, zmienne i funkcje są elementami składowymi klas; nie ma wielkości globalnych, definiowanych poza klasą. Ponadto nie deklaruje się metod (funkcji) składowych jako rozwijalnych (inline) bądź nie – decyzja należy do kompilatora.

W przykładowym programie do metody main jako parametr jest przekazywana (z wiersza rozkazowego) tablica obiektów (łańcuchów) klasy String; metoda main nie zwraca wyniku (typem zwracanym jest void), zaś wartością parametru arg[0] jest pierwszy po nazwie programu spójny ciąg znaków. Ciało main zawiera jedną instrukcję

System.out.print("Hello, World!\n");

(W języku Java każda instrukcja kończy się średnikiem, który pełni rolę symbolu terminalnego).

Słowo System jest nazwą klasy w standardowym środowisku języka. Klasa System zawiera statyczny obiekt składowy typu PrintStream o nazwie out; wywołanie System.out oznacza pisanie do standardowego strumienia wyjściowego. Klasa PrintStream zawiera szereg przeciążeń metody o nazwie print; jedno z nich przyjmuje parametr typu String. Kompilator automatycznie tłumaczy literał stały "Hello, World\n" na odpowiedni obiekt klasy String; odnośnik (referencja) do tego obiektu jest przekazywana do metody System.out.print(). Metoda print() generuje jeden wiersz wyjściowy i powraca do metody main, która kończy wykonanie.

1.2. Klasy: definicja, dziedziczenie, tworzenie obiektów

Klasę Javy można traktować jako wzorzec i jednocześnie generator obiektów. Jako wzorzec klasa zapewnia hermetyzację (zamknięcie w jednej jednostce syntaktycznej) danych i metod oraz ukrywanie informacji, które nie powinny być widoczne dla użytkownika. Jako generator zapewnia tworzenie obiektów za pomocą operatora new, którego argumentem jest konstruktor klasy.

Definicja klasy ma postać:

Deklaracja klasy

{

  Ciało klasy

}

Deklaracja klasy składa się w najprostszym przypadku ze słowa kluczowego class i nazwy klasy. Przed słowem kluczowym class może wystąpić jeden ze specyfikatorów: 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).

Po nazwie klasy mogą wystąpić frazy: ‘extends nazwa_superklasy’ oraz ‘implements nazwy_interfejsów’. Fraza ‘extends nazwa_superklasy’ mówi, że klasa dziedziczy (zawsze publicznie) od klasy nazwa_superklasy, zaś ‘implements nazwy_interfejsów’ deklaruje, że w danej klasie zostaną zdefiniowane metody, zadeklarowane w implementowanych interfejsach. Jeżeli dana klasa implementuje więcej niż jeden interfejs, wtedy nazwy kolejnych interfejsów oddziela się przecinkami.

Podklasa klasy abstrakcyjnej zawierającej metody abstrakcyjne może podawać definicje metod abstrakcyjnych. Podklasa podająca te definicje staje się klasą konkretną, tj. może mieć wystąpienia. Każda klasa, która odziedziczy metodę abstrakcyjną, ale nie dostarczy jej implementacji, sama staje się klasą abstrakcyjną, a jej definicja także musi być poprzedzona słowem kluczowym abstract.

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

  } // end aMethod

} // end aSillyClass

 

class ASillerClass extends ASillyClass {

boolean aVariable;

void aMethod() {

aVariable = false;

super.aMethod(); /* Wywołanie metody superklasy */

System.out.println(aVariable);

System.out.println(super.aVariable);

} // end aMethod

} // end ASillerClass

Klasy i omawiane niżej interfejsy są typami referencyjnymi (odnośnikowymi). Wartościami zmiennych tych typów są odnośniki do wartości lub zbiorów wartości reprezentowanych przez te zmienne. Np. instrukcja

ASillyClass oob;

jedynie powiadamia kompilator, że będziemy używać zmiennej oob, której typem jest ASillyClass. Do zmiennej oob możemy przypisać dowolny obiekt typu ASillyClass utworzony za pomocą operatora new:

oob = new ASillyClass();

W powyższej instrukcji argumentem operatora new jest generowany przez kompilator konstruktor ASillyClass() klasy ASillyClass, który inicjuje obiekt utworzony przez operator new. Operator new zwraca odnośnik do tego obiektu, po czym przypisuje go do zmiennej oob.

 

Jeżeli dana klasa nie zawiera deklaracji konstruktorów, to kompilator dostarcza konstruktor domyślny z pustym wykazem argumentów, który w swoim bloku wywołuje konstruktor super() jej bezpośredniej nadklasy. Weźmy dla ilustracji definicję klasy Point:

public class Point { int x, ,y; }

Jest ona równoważna definicji

public class Point { int x, ,y; public Point() { super(); } }

z niejawnym wywołaniem dostarczanego przez kompilator konstruktora superklasy, od której bezpośrednio dziedziczy klasa Point.

Podobne, niejawne wywołania konstruktora super() są wykonywane w drzewach dziedziczenia. Rozpatrzmy następujący program:

//plik Super1.java

class Point { int x,y; Point() { x=1;y=2; } }

class CPoint extends Point { public int color = 0xFF00FF; }

public class Super1 {

public static void main(String args[]) {

CPoint cp = new CPoint();

System.out.println("cp.color= " + cp.color);

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

  }//end main

}//end Super1

Instrukcja CPoint cp = new CPoint(); tworzy nowe wystąpienie klasy CPoint. Najpierw jest przydzielany obszar w pamięci dla obiektu cp, aby mógł przechowywać wartości x oraz y, po czym pola te są inicjowane do wartości domyślnych (tutaj zero dla każdego pola). Następnie jest wołany konstruktor Cpoint(). Ponieważ klasa CPoint nie deklaruje konstruktorów, kompilator automatycznie dostarczy konstruktor o postaci CPoint(){super();}, który wywoła konstruktor klasy Point bez argumentów, tak jakby zamiast super() napisano:

Point(){super();x=1;y=2; }.

W hierarchii dziedziczenia konstruktor super() może być wywoływany jawnie, jak w przykładzie poniżej:

//plik Super2.java

class Point { int x,y; Point(int x, int y)

{ this.x = x; this.y = y; } }

class CPoint extends Point {

static final int WHITE = 0, BLACK = 1;

int color;

CPoint(int x, int y) { this(x,y,WHITE); }

CPoint(int x, int y, int color) { super(x,y); this.color=color; }

}

 

public class Super2 {

public static void main(String args[]) {

int a = 10, b = 20;

CPoint cp = new CPoint(a,b);

System.out.println("cp.color= " + cp.color);

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

  }//end main

}//end Super2

Zmienna this w definicji konstruktora klasy Point jest odnośnikiem (referencją), identyfikującym obiekt, na rzecz którego wywołuje się konstruktor. Tak więc lewa strona instrukcji this.x = x identyfikuje pole x obiektu klasy Point, zaś prawa strona jest wartością argumentu x, którą inicjuje się to pole. Natomiast słowo kluczowe this w definicji dwuargumentowego konstruktora klasy CPoint służy do wywołania konstruktora trójargumentowego tej klasy w instrukcji this(x,y,WHITE).

Zatem w instrukcji CPoint cp = new CPoint(a,b); konstruktor CPoint(int,int) wywołuje ze swojego bloku (instrukcja this(x,y,WHITE);) drugi konstruktor, CPoint(int,int,int), dostarczając mu argument WHITE. Drugi konstruktor woła konstruktor superklasy, przekazuje mu współrzędne x oraz y, a następnie inicjuje pole color wartością WHITE.

F Uwaga. instrukcja { this(argumenty);musi być pierwszą instrukcją w ciele konstruktora lub metody; to samo dotyczy instrukcji super(argumenty);.

Dostęp do zmiennych składowych klasy (statycznych) jest możliwy bez tworzenia obiektów tej klasy. Np. dla klasy ASillyClass możemy napisać instrukcję:

System.out.println(ASillyClass.x);.

Gdyby w klasie ASillyClass zdefiniować statyczną funkcję klasy, np.

public static void bMethod(){/*instrukcje */}

to w takiej funkcji nie istnieje odnośnik this, a zatem żadna z jej instrukcji nie może się bezpośrednio odwołać do niestatycznej składowej x; odwołanie byłoby możliwe jedynie przez zadeklarowanie zmiennej odnośnikowej do klasy ASillyClass i zainicjowanie jej odnośnikiem do utworzonego za pomocą operatora new nowego obiektu, jak pokazano w poniższej sekwencji instrukcji:

ASillyClass ob = new ASillyClass();

System.out.println(ob.x);.

1.3. Interfejsy

Konstrukcja o postaci

interface nazwa {

/* Deklaracje metod i definicje stałych */

}

jest w języku Java typem definiowanym przez użytkownika. Deklaracja metody składa się z  sygnatury (sygnatura metody zawiera typ zwracany, nazwę metody i typy argumentów) i terminalnego średnika. Ponieważ interfejs może zawierać jedynie deklaracje metod i definicje stałych, odpowiada on klasie abstrakcyjnej z zadeklarowanymi publicznymi polami danych i metodami abstrakcyjnymi. W związku z tym w definicji interfejsu zabrania się używania specyfikatorów private i protected, zaś użycie specyfikatorów abstract i public jest zbyteczne.

Weźmy dla przykładu dwa interfejsy PlaneLike i BoatLike

interface PlaneLike {

void  takeOff();

float kmph();

}

 

interface BoatLike {

void swim();

float knots();

}

i zdefiniujmy ich implementacje w klasach Plane i Boat, które dziedziczą od wspólnej superklasy Vehicle:

class Vehicle {}

class Plane extends Vehicle implements PlaneLike {

/* Plane must implement kmph(), takeOff() */

public void takeOff() { System.out.println("Plane is taking off"); }

public float kmph() { return 600; }

}

class Boat extends Vehicle implements BoatLike {

/* Boat must implement knots(),swim() */

public void swim() { System.out.println("Boat is swimming"); }

public float knots() { return 20; }

}

Poprawne będą wówczas deklaracje

Plane biplane = new Plane();

biplane.takeOff();

Boat vessel = new Boat();

...

Zgłoś jeśli naruszono regulamin