Prawo Cheopa - nic nigdy nie zostało zbudowane w zaplanowanym czasie, lub zgodnie z kosztorysem.
Java - Tworzenie klas w Javie
Klasy  |   Metoda  |   Pole  |   Modyfikatory  |   Obiekty  |   Konstruktory  |   Dziedziczenie  |   Usuwanie obiektów  |   Tablice

Klasa jako typ danych

Paradygmat programowania zorientowanego obiektowo w Javie opiera się na pojęciu klasy. Klasa jest modułem posiadającym: nazwę i atrybuty w postaci pól danych i metod.

Zdefiniowanie klasy jest jedynym sposobem zdefiniowania nowego typu danych w Javie.

Posługując się pojęciami klasy można definiować różnorodne typy danych wykorzystując strukturę hierarchiczną klas oraz dziedziczenie, umożliwiające tworzenie hierarchii typu: klasa ogólna - klasa szczegółowa.

Klasa jest narzędziem do tworzenia nowych typów danych, których "materializacja" (utworzenie zmiennej typu danej klasy) nazwa się obiektem:.

Definicja klasy wygląda następująco:

   [modyfikatory] class NazwaKlasy 
         [extends NazwaKlasyBazowej] [implements NazwyKlasInterfejsów]
   {
          // Ciało klasy:
          // definicje pól danych , metod i klas wewnętrznych definiowanej klasy
   }

Elementy deklaracji pomiędzy nawiasami [ i ] są opcjonalne. Deklaracja klasy może zawierać następujące elementy:

  • modyfikatory deklarują rodzaj klasy (np.: public, abstract lub final static);
     
  • NazwaKlasy określa nazwę deklarowanej klasy;
     
  • NazwaKlasyBazowej jest nazwą klasy rodzica deklarowanej klasy;
     
  • NazwyKlasInterfejsów to lista rozdzielona przecinkami nazw klas interfejsów implementowanych przez deklarowana klasę;
     
  • ciało klasy to dowolna liczba definicji pól danych, metod i klas wewnętrznych. Definicje pól i metod klasy mogą występować w dowolnej kolejności. Klasa może zawierać definicje pól, metod oraz klasy wewnętrzne, lokalne i anonimowe.
     
Podsumowując:
  • należy rozróżniać definicję klasy od jej konkretyzacji (obiektu);
     
  • definicja klasy określa jej budowę i zachowanie;
     
  • obiekt danej klasy powstaje wówczas, gdy na podstawie definicji klasy deklarowana jest zmienna; dopiero wtedy staje się on egzemplarzem swojej klasy i następuje dynamiczny przydział pamięci dla tworzonego obiektu;
     
  • można tworzyć wiele obiektów tej samej klasy.
     

Metody

Metody w Javie są odpowiednikami funkcji w strukturalnych językach programowania. Każda funkcja w Javie jest związana z klasą (spełnia rolę jej metody).

Definicja metody ma następującą składnię:

[modyfikatory] TypZwracanejWartości NazwaMetody ( [ListaParametrówFormalnych] )
{ 
	//(kod źródłowy metody
}

Elementy deklaracji pomiędzy nawiasami [ i ] są opcjonalne. Deklaracja metody może zawierać następujące elementy:

  • modyfikatory określają tryb dostępu i właściwości metody;
     
  • TypRezultatu określa typ wyniku metody; jeżeli funkcja nie zwraca wyniku przez instrukcję return, to w deklaracji typu metody podajemy void;
     
  • NazwaMetody - musi być poprawnym identyfikatorem Javy;
     
  • ListaParametrówFormalnych - lista parametrów formalnych przekazywanych do metody; Jeżeli metoda nie ma żadnych argumentów, to lista jest pusta;
     
  • w nawiasach { } poniżej listy argumentów znajduje się ciało metody.
     
Przykład klasy z definicją pól i metod:
   class MojaKlasa
   {   // deklaracja pól danych klasy
       int iLiczba = 0;

       // deklaracje metod klasy:
       // metoda sprawdzająca, czy przekazany parametr jest parzysty 
       boolean parzysta(int i)
       {   if (i % 2 == 0) return true; 
           else            return false; 
       }

       // metoda wyświetla wartość iLiczba
       void wartosc()
       {   System.out.println("Wartość pola iLiczba wynosi: " + iLiczba);
       }
   }

Dla metod, których wynik jest różny od void działanie metody musi zakończyć się przy pomocy instrukcji return. Wyrażenie występujące po słowie return musi być zgodne z zadeklarowanym typem wyniku metody. Dla metod o typie wyniku void sterowanie opuszcza metodę po napotkaniu instrukcji return bez parametrów lub po wykonaniu wszystkich instrukcji w ciele metody.

Nie wolno używać zmiennych lokalnych o nazwach identycznych, jak nazwa któregokolwiek z argumentów metody: spowoduje to błąd podczas kompilacji.

Można natomiast przeciążać (ang. function overloading) nazwę metody: można stosować tę samą nazwę dla różnych metod, jeżeli tylko różnią się one między sobą liczbą lub rodzajem argumentów lub są metodami różnych klas.

Przykład:
   void MyMethod()
   { ... } 

   void MyMethod(int i, String s)
   { ... }

   void MyMethod(String s)
   { ... }

Pola

Pola danych są atrybutami klasy, pełniącymi rolę podobną do zmiennych lub stałych w strukturalnych językach programowania. Deklarowane są tak samo jak zmienne lokalne. Zaleca się, aby dla przejrzystości programu stosować konwencję nazewnictwa, w której nazwy deklarowanych pól danych klasy poprzedza się literą m i znakiem podkreślenia (od ang. data member). Przestrzeganie tej konwencji nie jest oczywiście obowiązkowe.

Definicja pola danych klasy przyjmuje postać:

modyfikatoryPola  TypPola  NazwaPola;

Gdzie:

  • modyfikatoryPola określają tryb dostępu (np. private) i właściwości pola (np. statyczne);
     
  • TypPola określa typ pola danych (może to być któryś z typów podstawowych lub klasa);
     
  • NazwaPola definiuje nazwę deklarowanego pola.
     

Modyfikatory klas, metod i pól

W Javie występują dwa rodzaje modyfikatorów:

  • modyfikatory dostępu;
     
  • modyfikatory właściwości modyfikowanego elementu.
     
Modyfikatorów pierwszej grupy wpływają na zakres widoczności oraz umożliwiają kontrolę dostępu do pól danych i metod klasy z innych klas. Do tej grupy modyfikatorów należą:
  • dla klas:
    • private - klasa dostępna jest tylko wewnątrz pakietu (pliku); brak w definicji klasy jakiegokolwiek modyfikatora powoduje automatycznie uznanie klasy za private;
       
    • public - udostępniana na zewnątrz pakietu (ang. package), w którym się znajduje; w jednym pliku może wystąpić tylko 1 klasa typu public; aby użyć jej poza pakietem, w którym się znajduje, należy odwołać się: NazwaKlasy.java;
       
  • dla metod i pól danych:
    • public - pole/metoda dostępna jest dla wszystkich klas;
       
    • private - dostęp do pól/metod posiadają jedynie inne metody tej samej klasy;
       
    • protected - powoduje, że pole/metoda będzie widoczne (dostępne) w klasie, wszystkich podklasach (klasach dziedziczących z klasy zawierających pole/metodę) i w całym pakiecie;
       
    • package - modyfikator domyślny, wszystkie pola/metody bez modyfikatora dostępu traktowane są jako typu package; pola/metody mogą być używane przez inne klasy danego pakietu;
       
Zakres widoczności pól/metod klasy zależny od użytego modyfikatora:
Modyfikatorklasapodklasapakietwszędzie
privatetak   
protectedtaktaktak 
publictaktaktaktak
packagetak tak 

Oprócz modyfikatorów dostępu istnieją jeszcze modyfikatory właściwości:

  • dla klas:
    • abstract - definiuje klasę abstrakcyjną zawierającą chociaż 1 metodę abstrakcyjną (niezaimplementowaną); nie można tworzyć żadnych obiektów tej klasy; klasa taka jest użyteczna podczas budowania hierarchii klas (dziedziczenie): stanowi wzór do tworzenia klas pochodnych;
       
    • final - uniemożliwia tworzenie klas pochodnych; stosuje się w celu uniemożliwienia klasa potomna "podszycia się" pod klasę bazową oraz czasami do oznaczenia klas, nad którymi prace zostały ukończone;
       
    • synchronizable - klasa z mechanizmem obsługi wątków (ang. threads);
       
  • dla metod:
    • abstract - metody niezaimplementowane (bez kodu i zmiennych); użyteczna podczas budowania hierarchii klas (dziedziczenie): stanowi wzór dla metod klas pochodnych, które wymagają jej różnych implementacji;
       
    • static - metoda wspólna dla wszystkich obiektów danej klasy; należy ona do klasy, a nie do obiektu i może być wywoływana bez tworzenia obiektu danej klasy;
       
    • final - uniemożliwia klasom pochodnym "przesłanianie" metody;
       
    • synchronized - metoda blokująca dostęp do obiektu, do którego należy i odblokowująca go, gdy zakończy działanie; jeżeli dostęp do obiektu został wcześniej zablokowany, to metoda oczekuje na jego odblokowanie zanim zacznie się wykonywać; mechanizm bardzo istotny w przypadku programów wielowątkowych (ang. multitheads);
       
    • native - oznacza funkcję z wykorzystaniem nieprzenośnych cech danej platformy: programy napisane w Javie są niezależne od platformy sprzętowej i systemowej; jeżeli konieczne jest skorzystanie z mechanizmów specyficznych dla danej platformy, to modyfikator native powoduje, że program będzie mógł być uruchamiany tylko na tej 1 dedykowanej platformie;
       
  • dla pól danych:
    • final - wartość pola jest ustalana podczas tworzenia obiektu i nie może być później modyfikowana: pole danych jest stałą;
       
    • static - pole danych wspólne dla wszystkich obiektów danej klasy; należy do klasy, a nie do obiektu i zmiana wartości w jednym obiekcie, powoduje, że zmienia się jego wartość dla wszystkich obiektów;
       
    • transient - pole danych nie jest trwałą częścią obiektu i nie będzie zachowane przy archiwizacji obiektu;
       
    • volatile - oznacza pole, które może być modyfikowane asynchronicznie, przez konkurencyjne wątki w programach wielowątkowych.
       
Podczas deklarowania klas, metod i pól danych można łączyć różne modyfikatory.
Przykłady deklaracji klas, metod i pól danych:
class MojaKlasa                   // klasa widziana tylko wewnątrz pakietu (pliku)
public class MojaKlasa            // klasa dostępna dla wszystkich
public final class MojaKlasa      // klasa dostępna dla wszystkich,
                                  // ale bez możliwości tworzenia klas potomnych

void MojeMetoda()                 // metoda widoczna tylko wewnątrz swojej klasy
public void MojeMetoda()          // metoda dostępna dla wszystkich
static void MojeMetoda()          // metoda dostępna w klasie,
                                  // wspólna dla wszystkich obiektów tej klasy
public static void MojeMetoda()   // metoda dostępna dla wszystkich,
                                  // wspólna dla wszystkich obiektów tej klasy
public final static void MojeMetoda()  // metoda dostępna dla wszystkich,
                                  // wspólna dla wszystkich obiektów tej klasy,
                                  // bez możliwości jej przesłaniania

int x;                            // pole widziane tylko wewnątrz klasy/metody
public int x;                     // pole dostępne dla wszystkich obiektów
static int x;                     // pole widziane tylko wewnątrz klasy/metody,
                                  // identyczna wartość dla każdego obiektu
public final static int x = 0;    // pole o stałej wartości (stała) 
                                  // dostępne dla wszystkich klas/metod

Obiekty

Klasa to abstrakcja: tylko definiuje typ danych. Konkretyzację, "materializacje" klasy stanowi obiekt o typie danej klasy. Definicja klasy określa "budowę i zachowanie" obiektu. Obiekt danej klasy jest generowany dynamicznie na podstawie wzorca (definicji klasy). Raz zdefiniowana klasa może mieć wiele obiektów.

Przykładowo, po zdefiniowaniu klasy OSOBA możemy użyć wielu obiektów tej klasy ("egzemplarzy" klasy OSOBA).

Deklaracja zmiennej typu obiektowego ma postać podobną do deklaracji zmiennej typu wewnętrznego:

NazwaTypu  NazwaZmiennej [ = WartośćPoczątkowa ];
W wyrażeniu nadającym wartość początkową zmiennej może zostać utworzony nowy obiekt, który zostanie przypisany do zadeklarowanej zmiennej obiektowej. Przykładowa deklaracja:
   Date mojaData = new Date();

       tworzy nową zmienną obiektową mojaData typu Date, i przypisuje jej
       za pomocą instrukcji new nowy obiekt: new Date(). Date() jest 
       wywołaniem metody zwanej konstruktorem, służącej do inicjalizacji obiektu.
       Dla klas, w której zdefiniowano kilka konstruktorów, możemy użyć dowolnego
       z nich przy inicjalizacji zmiennej obiektowej.

Deklaracja:
	
	Date mojaData;

       oznacza jedynie zmienną, która może przechowywać referencję do obiektów 
       typu Date, ale nie tworzony nowego obiektu. Tak zadeklarowanej 
       zmiennej możemy przypisać nowy obiekt (referencję do obiektu) używając
       instrukcji przypisania:
	
   mojaData = new Date(2003, 1, 23);

Każdy typ wewnętrzny Javy ma swój odpowiednik obiektowy (np.: typ int ma odpowiadający mu typ obiektowy Integer) i jako obiekty posiadają wiele konstruktorów i metod, np.: metodę toString(), której wynikiem jest reprezentacja znakowa zmiennej typu Integer.

Dostęp do metod i pól danych obiektu (zmiennej obiektowej) realizujemy za pomocą wyrażeń kropkowych postaci:

   ZmiennaObiektowa.NazwaMetody ( [ parametry... ] );
   ZmiennaObiektowa.NazwaPola;

gdzie: 
ZmiennaObiektowa - nazwa obiektu (również domyślna nazwa bieżącego obiektu: this); 
NazwaMetody      - nazwa wywoływanej metody; 
parametry        - opcjonalne parametry przekazywane do wywoływanej metody;
NazwaPola        - nazwa pola danych klasy. 

Konstruktory

Tworząc klasę możemy - ale nie musimy - zadeklarować konstruktor: metodę o nazwie identycznej, jak nazwa klasy, wywoływany automatycznie podczas tworzenia nowego obiektu klasy. Klasa może posiadać wiele konstruktorów, różniących się listą argumentów. Ponieważ każda klasa w Javie dziedziczy z klasy Object, posiada więc domyślny konstruktor bezparametrowy odziedziczony z tej klasy. Dlatego też nie musimy - o ile nie jest to konieczne - tworzyć własnych konstruktorów.

Gdy chcemy zainicjować pola danych naszej klasy powinniśmy zadeklarować jej konstruktor. Np:

   public class OSOBA
   {   public int  m_wiek;
	   public char m_plec;

       public OSOBA()
       {   m_wiek = 0;
           m_plec = 'K';
       }

       public OSOBA(int W, char P)
       {   m_wiek = W;
           m_plec = P;
       }
   }
W przykładzie mamy zadeklarowane 2 konstruktory:
  1. konstruktor bezparametrowy OSOBA(), który inicjalizuje pola danych klasy zawsze w ten sam sposób;
     
  2. konstruktor z parametrami OSOBA(int W, char P) inicjujący pola wartościami argumentów W i P.
     

Słowo kluczowe this oznacza referencję obiektu do samego siebie. Używamy go, aby jednoznacznie zidentyfikować zmienne, których interpretacja może nie być jednoznaczna.

Przykład użycia this w definiowaniu konstruktorów:
   public class OSOBA
   {   public int  W;
       public char P;
       public String Nazwisko;

       public OSOBA(int W, char P, String nazwisko)
       {   // gdyby parametry i pola miały różne nazwy 
           // użycie this nie byłoby konieczne
           this.W = W; 
           this.P = P; 
           Nazwisko = nazwisko;
       }

       public OSOBA(int W, char P)
       {   this(W, P, "Brak nazwiska");   }

       public OSOBA()
       {   this(0, 'K', "Brak nazwiska");   }

       public OSOBA DaneOsoby()
       {   return this;   }
   }

Uwaga: jeżeli jeden konstruktor wywołuje inny konstruktor danej klasy, to musi to być pierwsza instrukcja tego konstruktora.

Konstruktorów używamy do inicjalizacji pól danych obiektu w momencie jego tworzenia. Jednakże dane statyczne klasy istnieją - jako jej atrybuty - również wtedy, gdy nie ma żadnego obiektu danej klasy. Aby można zainicjować zmienne statyczne w Javie zdefiniowano inicjator statycznych pól danych. Inicjalizacja statyczna następuje podczas pierwszego ładowania klasy do pamięci. Klasa może mieć dowolną liczbę inicjatorów statycznych. Inicjatory statyczne wykonywane są w kolejności ich wystąpienia w definicji klasy.

Przykład użycia inicjatora statycznych pól danych:

Klasa Zawodnik posiada zmienną statyczną miejsca, która jest tablicą 
numerów zawodników. Gdy tworzony jest nowy obiekt typu Zawodnik, konstruktor 
przydziela wolny dotychczas numer. Oznacza to, że statyczna tablica numerów musi
być zainicjowana zanim pierwszy z obiektów typu Zawodnik zostanie utworzony.

   class Zawodnik
   {   static final byte m_Ilu = 100;
       static Miejsce miejsca[] = new Miejsce[m_Ilu];

       // inicjalizacja pól statycznych
       static
       {   for (int i = 0; i < m_Ilu; i++)
               miejsca[i] = new Miejsce();
       }
       ..............
   }

Dziedziczenie

Java umożliwia dziedziczenie pól i metod z jednej klasy (ang. superclass) przez inną klasę (ang. subclass): posiada ona te same pola i metody, co nadklasa i dodatkowo pola i metody, które są w niej zdefiniowane. Można powiedzieć, że podklasa jest uszczegółowionym przypadkiem nadklasy.

Relację dziedziczenia z nadklasy deklarujemy słowem kluczowym extends.

Prywatne (z modyfikatorem private) pola danych i metody nadklasy nie są dziedziczone.

Klasa może dziedziczyć tylko z jednej nadklasy, ale może implementować kilka interfejsów.

Pola i metody zdefiniowanych w klasie potomnej przesłaniają pola i metody nadklasy o identycznych nazwach.

Jeżeli chcemy odwołać się do oryginalnych pól i metod nadklasy w podklasie, to używamy słowa kluczowego super, które reprezentuje nazwę nadklasy. Pozwala to odwoływać się do składników nadklasy przesłoniętych (ang. shadow) - pól i metod ponownie zdefiniowanych w podklasie - przy dziedziczeniu.

Usuwanie obiektów

Java nie wymaga używania destruktorów klasy, ponieważ istnieje mechanizm automatycznego zarządzania pamięcią (ang. garbage collection). Obiekt istnieje w pamięci tak długo, jak długo istnieje do niego jakakolwiek referencja w programie. Gdy żadna referencja do obiektu nie jest już używana przez żadną zmienną, obiekt jest automatycznie niszczony, a jego pamięć zwalniana.

Istnieje możliwość deklaracji specjalnej metody finalize, wykonywanej przed usunięciem obiektu z pamięci. Deklaracja takiej metody jest potrzebna, gdy np.: obiekt ma referencje do urządzeń wejścia-wyjścia i przed usunięciem obiektu należy je zamknąć.

Proces zwalniania nieużytków zasobów jest włączany okresowo, uwalniając pamięć zajmowaną przez nieużywane obiekty: przeglądany jest obszar przydzielonej programowi pamięci dynamicznie i zaznaczane obiekty, do których istnieją referencje. Po analizie wszystkich ścieżek referencji do obiektów usuwane są te, do których nie ma żadnej referencji.

Mechanizm czyszczenia pamięci działa w wątku o niskim priorytecie synchronicznie lub asynchronicznie, zależnie od środowiska systemu operacyjnego na którym wykonywany jest program.

Program może jawnie uruchomić mechanizm czyszczenia pamięci przez wywołanie metody System.gc(). Nie gwarantuje on jednak, że obiekt zostanie usunięty: w systemach, które pozwalają środowisku przetwarzania Javy sprawdzać, kiedy wątek się rozpoczął i przerwał wykonanie innego wątku (np. Windows 95/NT), mechanizm czyszczenia pamięci działa tylko asynchronicznie w czasie bezczynności systemu.

Mechanizm czyszczenia pamięci umożliwia obiektowi przed usunięciem "posprzątanie po sobie" poprzez wywołanie metody finalize. Podczas finalizacji obiekt może zwolnić zasoby systemowe: pliki, gniazdka (ang. sockets), referencje do innych obiektów, i inne. Metoda finalize jest zdefiniowana w klasie java.lang.Object. Klasa musi ją zredefiniować, aby umożliwić finalizację dla zasobów używanych przez obiekty własnego typu.

Tablice

Tablice w Javie są również obiektami. Typ tablicowy jest podklasą klasy Object i implementuje interfejs Cloneable. Dla każdej tworzonej tablicy Java tworzy odpowiadający jej obiekt tablicowy. Obiekt tablicowy posiada pole length, które zawiera informację o długości zaalokowanej tablicy.

Deklaracja tablicy składa się z dwu części: typu tablicy i nazwy tablicy. Typ tablicy określa typ danych, jakie tablica będzie przechowywała.

Przykłady deklaracji tablicy:
   int  tabLi [ ];
   char tabZn [ ];

lub 

   int [ ]   tabLi;
   char [ ]  tabZn;

Deklaracja tablicy nie przydziela jej pamięci: aby pamięć została przydzielona, musimy utworzyć obiekt typu tablicowego, używając operatora new:

   tabLi = new int[10];

Dla typów wewnętrznych (np. int, char, byte) możemy użyć listy inicjalizującej:

   int tablicaInt[] = {1, 2, 3, 4, 5};

Taka deklaracja alokuje tablicę składającą pięcioelementową typu int i przypisuje im wartości od 1 do 5. Operator new jest użyty domyślnie.
Tablicę możemy zainicjować również tak:

   int tabLi[] = new int[100];

   for (int i =0; i < 100; i++)
   {   tabLi[i] = i; 
   }

Java sprawdza każde odwołanie do elementów tablicy. Jeśli indeks nie jest liczbą z zakresu dozwolonych (w przykładzie: od 0 do 100), to odwołanie do tablicy powoduje wystąpienie wyjątku ArrayIndexOutOfBoundException. Jeśli wystąpienie tego wyjątku zostanie obsłużone, program może kontynuować swoje działanie, w przeciwnym razie jego wykonanie zostanie przerwane, a na ekranie wyświetlona zostanie informacja o miejscu wystąpienia błędu.

Tablicę, której elementy są obiektami, deklarujemy następująco:

NazwaKlasy nazwaZmiennej[] = new NazwaKlasy[n];

Wszystkie referencje mają w tablicy obiektów mają wartość początkową null. Aby móc odwołać się do elementów takiej tablicy, musimy zainicjalizować jej elementy (obiekty).

Przykłada inicjacji tablicy obiektów:

   MojaKlasa tablica[] = new MojaKlas[10];    // utworzenie tablicy 10-elementowej
   for (int i = 0; i < 10; i++ )
   {   tablica[i] = new MojaKlasa();      // przypisanie obiektów
   }

Tablice wielowymiarowe realizowane są przez tworzenie tablice, których elementami są tablice, np.:

   int  tabLi [10] [10];
   char tabZn [20] [5];

Należy pamiętać, iż tablice w Javie są indeksowane od 0. Wynika stąd, że dla tablicy 10-elementowej możemy odwoływać się do elementów o indeksach od 0 do 9.

« wstecz   dalej »