Definicje klas
i obiektów
Podstawy
Do tej pory używaliśmy klas jedynie po to, by zdefiniować metodę
main(). Klasy mają znacznie szersze zastosowanie w Java.
W OOP (także w Java) klasy służą do definiowania nowych typów
danych o znacznie większej sile wyrazu niż typy tradycyjne.
Mając definicję klasy możemy tworzyć obiekty typu danej klasy. Klasa
pełni rolę wzorca (schematu budowy) obiektu. Obiekty danej klasy
nazywamy instancjami klasy.
Klasy w Java pozwalają na zastosowanie idei hermetyzacji. Osiąga się to przez wyspecyfikowanie danych i kodu klasy, operującego na danych. Proste klasy mogą zawierać tylko dane lub tylko kod lecz
większość klas zawiera zarówno kod jak i dane.
Zwykle użytkownik nie ma bezpośredniego dostępu do danych obiektu, natomiast ma do dyspozycji metody dostępu do danych, przewidziane w klasie. Klasa definiuje więc interfejs do danych swoich instancji (właściwie: zmiennych instancji).
Ogólny schemat klasy
W Java definicja klasy rozpoczyna się słowem kluczowym class. Ogólna postać definicji klasy jest następująca:
class nazwaKlasy { typ zmiennaInst_1; // ... typ zmiennaInst_n; typ metoda_1(parametry) { // ciało metody_1 } // ... typ metoda_n(parametry) { // ciało metody_n }
} // koniec definicji klasy
Zobacz:
BoxDemo.java
Deklaracja obiektów
Wytworzenie obiektu z danej klasy to zadanie dwuetapowe:
Po pierwsze: trzeba zadeklarować zmienną typu danej klasy.
Zmienna taka nie jest obiektem. Jest tylko zmienną mogącą wskazywać na obiekt.
Kod
Box myBox;
Efekt null
myBox
Po drugie: trzeba utworzyć obiekt danej klasy i przypisać go do
zmiennej. Operator new dynamicznie rezerwuje (w czasie
wykonania) obszar pamięci na obiekt i zwraca wskazanie do tego obszaru.
Kod
myBox = new Box();
Efekt
myBox
width height depth
Konstruowanie obiektu
Postać ogólna:
zmiennaKlasy = new nazwaKlasy();
nazwaKlasy jest nazwą klasy, której obiekt właśnie powołujemy.
Nazwa ta, waraz z ewentualnymi parametrami wyznacza konstruktor klasy. Konstruktor to specjalna metoda, o nazwie takiej jak klasa.
Definiuje ona co trzeba zrobić by utworzyć obiekt danej klasy.
Jeżeli klasa nie posiada konstruktora, to jest wywoływany konstruktor domyślny. Po utworzeniu konstruktora dla klasy konstruktor domyślny nie jest dostępny.
Konstruktory nie posiadają typu wynikowego, nawet void. Dzieje się tak dlatego, że niejawnym typem wynikowym każdego konstruktora jest klasa, której obiekt konstruktor tworzy.
Prócz powołania obiektu, zadaniem konstruktora jest jego
inicjalizacja, tak by po utworzeniu był w pełni sprawnym obiektem. Zobacz: BoxDemo3.java
Słowo kluczowe this
Czasem metoda obiektu potrzebuje odwołać się do obiektu wywołującego tę metodę.
Umożliwia to słowo kluczowe this. Wewnątrz metody this oznacza obiekt, na rzecz którego wywołano metodę.
this jest często używany do tworzenia metod polimorficznych,
ale o tym opowiemy później.
W Java nie można deklarować dwóch takich samych zmiennych lokalnych w obrębie jednego zakresu, jednak istnieje możliwość
przesłonięcia zmiennych instancyjnych klasy przez parametry metody. Wewnątrz metody zmienne instancyjne byłyby niedostępne, gdyby nie możliwość użycia zmiennej this:
Box (double width, double height, double depth){ this.width = width;
this.height = height; this.depth = depth; }
Zbieracz śmieci
Operator new dynamicznie rezerwuje pamięć na tworzony obiekt. Można się zastanowić jak obiekty są niszczone a ich pamięć
zwalniana.
W niektórych językach (np. C++) obiekty zarezerwowane dynamicznie muszą być zwalniane ręcznie poleceniem delete (lub podobnym). W Java przyjęto inne rozwiązanie. JVM posiada mechanizm zwany
zbieraczem śmieci (ang.: garbage collection), który
automatycznie zwalnia pamięć przydzieloną obiektom, do których
brak jakichkolwiek odwołań.
Zbieracz śmieci włącza się sam, gdy jest co posprzątać. Nie ma jednej reguły jego włączania i wyłączania się.
Różne JVM posiadają różne algorytmy zbierania śmieci, lecz mają
jedną wspólną cechę: programista nie musi pamiętać o zwalnianiu
Metoda finalize
Ze zbieraczem śmieci związana jest metoda finalize. Zbieracz
śmieci zaraz przed zwolnieniem obiektu uruchamia metodę finalize zwalnianego obiektu.
Metoda ta jest stosowana, gdy obiekt rezerwuje pewne nie-Javowe zasoby, które muszą być zwolnione w specjalny sposób, np:
deskryptory plików, czy fonty.
Na metodzie tej nie możemy opierać poprawności implementowanego algorytmu, ponieważ nie wiemy, kiedy zostanie wywołana przez
zbieracz śmieci.
Postać metody:
protected void finalize() { // tu kod metody
Przeciążanie metod
W Java możemy definiować wiele metod o tej samej nazwie w obrębie jednej klasy. Metody takie nazywamy przeciążonymi. Muszą one
różnić się typami i/lub liczbą argumentów. Mogą także różnić się typami wynikowymi ale nie wystarcza to by metody były różne.
Zobacz: OverloadDemo.java
Metody przeciążone wspierają polimorfizm.
W językach nie pozwalających na przeciążanie metod/funkcji często dla wyrażenia podobnych operacji dla różnych typów wejściowych
trzeba używać różnych nazw. Np. w C funkcja abs() zwraca wartość bezwzględną liczby całkowitej, labs() długiej liczby całkowitej,
fabs() liczby rzeczywistej, ... .
W Java dzięki przeciążaniu metod można zdefiniować jedną nazwę metody działającą na różnych typach.
Zobacz: AbsDemo.java
W podobny sposób jak zwykłe metody, możemy przeciążać także
Przekazywanie parametrów
Obiekty jako parametry metod. Ponieważ klasy w Java są
traktowane jak typy w tradycyjnych językach programowania, to
można przekazywać obiekty (parametry typu klasowego) do metod i zwracać obiekty jako wartości. Zobacz: ParameterDemo.java
Przekazywanie parametrów metod przez wartość i zmienną.
W Java sposób przekazywania parametrów do metody zależy od ich typu. Parametry typów prostych zawsze są przekazywane przez
wartość, natomiast przekazywanie obiektów do metod zawsze odbywa się przez zmienną.
Przyponienie pojęć:
Przekazywanie przez wartość: przekazywane wartości nie ulegają
trwałym zmianom - po wywołaniu metody wartość jak przed
Przekazywanie przez zmienną: przekazywane wartości ulegają
trwałym zmianom - po wywołaniu metody wartość się zmienia
Kontrola dostępu
Klasy definiowane dotychczas nie posiadały ważnej cechy
programowania obiektowego: hermetyzacji, tj. oddzielenia świata
zewnętrznego od danych klasy (patrz Stack.java).
Java pozwala na kontrolę dostępu na poziomie pakietu, klasy i
komponentu klasy. Realizowana jest ona za pomocą słów kluczowych
(specyfikacji kontroli dostępu):
public elementy są dostępne dla dowolnego innego kodu programu
także spoza klasy
private dla elementu klasy sprawia, że jest on dostępny tylko dla
innych elementów tej samej klasy
protected ma zastosowanie w procesie dziedziczenia klas
(omówimy później)
Domyślną specyfikacją kontroli dostępu, tj. gdy nic nie wyspecyfikowano, jest public.
Elementy static
Zwykle by móc używać elementów zdefiniowanych w klasie, musimy najpierw utworzyć obiekt będący instancją danej klasy. Istnieje jednak możliwość zdefiniowania elementów, do których nie musimy się
odwoływać za pośrednictwem obiektów. Do wyróżnienia takich
elementów służy słowo kluczowe static. Typowym przykładem jest metoda main(). static może być stosowane do:
Zmiennych instancji: są one wówczas zmiennymi globalnymi
wszystkich obiektów danej klasy. Utworzenie obiektu danej klasy nie
powoduje powstania statycznej zmiennej instacji.
Metod: podlegają one następującym restrykcjom: Mogą wywoływać tylko inne metody statyczne
Mogą korzystać wyłącznie ze zmiennych statycznych Nie mogą odwoływać się do zmiennych this i super
Jeżeli trzeba wykonać obliczenia by zainicjalizować zmienne
statyczne, można zdefiniować blok static. Będzie on wykonany tylko raz, gdy klasa jest po raz pierwszy załadowana.
Zobacz: Static1.java i
Elementy final
Słowo kuczowe final służy do oznaczenia zmiennych, których
wartość ma być niezmienna we wszystkich instancjach. Pełnią one rolę stałych i muszą być inicjalizowane przy deklaracji. Przykłady:
final int FILE_NEW = 1; final int FILE_OPEN = 2; final int FILE_SAVE = 3; final int FILE_QUIT = 4;
Dalsza część programu może używać powyższych zmiennych jak stałych. Dodatkowo nie zajmują one miejsca w instancjach klasy. Przyjętą konwencją jest pisanie zmiennych final dużymi literami.
Słowo kuczowe final może również być zastosowane do metod, lecz znaczenie takiego zastosowania jest zupełnie inne niż w przypadku
zmiennych. Zastosowanie final do metod wyjaśnimy podczas omawiania dziedziczenia klas.
Tablice
Omawiając tablice nie wspomnieliśmy, że są one implementowane jako obiekty. Jako obiekty posiadają specjalny atrybut length
reprezentujący liczbę elementów jakie tablica może przechowywać (a nie ich aktualną liczbę).
class Length {
public static void main(String args[]) { int a1[] = new int[10];
int a2[] = {1, 2, 3, 4, 5, 6, 7, 8}; int a3[] = {4, 3, 2, 1};
int c = a1.length + a2.length + a3.length; System.out.println(c);
} }
Klasy wewnętrzne
W Java istnieje możliwość definiowania klasy wewnątrz innej klasy. Klasy takie nazywa się klasami zagnieżdżonymi. Zakres klasy wewnętrznej jest ograniczony klasą zewnętrzną, tj.:
Jeżeli klasa B jest zdefiniowana wewnątrz klasy A, wówczas B jest znana wewnątrz A, natomiast nie jest poza A.
Klasa wewnętrzna posiada dostęp do wszystkich elementów klasy zewnętrznej (nawet prywatnych), natomiast klasa zewnętrzna nie posiada dostępu do elementów klasy wewnętrznej.
Istnieją dwa rodzaje klas wewnętrznych:
Statyczne: zdefiniowane ze słowem kluczowym static. Nie mogą
odwoływać się one do elementów klasy zewnętrznej (niestatycznej) wprost. Muszą zdefiniować obiekt klasy zewnętrznej i odwoływać się za jego pośrednictwem; rzadko używane.
Niestatyczne: najczęściej używane. Mają dostęp do wszystkich
elementów klasy zewnętrznej tak jak jej metody niestatyczne
Zobacz: Inner1.java Inner2.java
Klasa String
Klasa String jest prawdopodobnie najczęściej używaną klasą języka Java, ponieważ każdy napis używany w programie jest obiektem klasy
String. Dotyczy to także napisów stałych, np.:
System.out.println(“To jest napis”);
Napis “To jest napis” jest obiektem klasy String.
Obiekty klasy String są niezmienne, tj. raz utworzone nie mogą
zmieniać swojej zawartości. Jeżeli chcemy zmienić zawartość napisu, trzeba utworzyć nowy zmodyfikowany.
W Java istnieje także klasa StringBuffer pozwalająca na dokonywanie zmian w przechowywanych napisach.
Obiekty klasy String posiadają metody (m.in.)
boolean equals(String o) równość dwóch napisów int length() długość napisu
char charAt(int i) znak na pozycji i
Parametry linii komend
W Java, pododnie jak w innych językach programowania, istnieje
możliwość przekazania parametrów wywołania programu do metody
main(). Parametry te są przekazane do programu jako tablica
elementów typu String. Następujący program wypisuje wszystkie argumenty, z którymi został wywołany:
class CommandLine {
public static void main(String args[]) { for(int i = 0; i < agrgs.length; i++) System.out.println(“arg[“ + “] =” + args[i]);
} }
Przykładowe wywołanie:
$ java CommandLine to tylko test