Java - Tworzenie klas w Javie
Klasy |
Metoda |
Pole |
Modyfikatory |
Obiekty |
Konstruktory |
Dziedziczenie |
Usuwanie obiektów |
Tablice
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:
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:
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 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:
Modyfikatory klas, metod i pól W Javie występują dwa rodzaje modyfikatorów:
Oprócz modyfikatorów dostępu istnieją jeszcze modyfikatory właściwości:
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/metodKlasa 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.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:
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(); } .............. }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. 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 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.
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.
|