Tworzymy klasę, która nic nie rozszerza (poza Object) gdy nie przechodzi ona testu IS-A dla jakiegokolwiek innego typu(?).
Tworzymy podklasę (rozszerzamy klasę nadrzędną) wtedy, gdy musimy utworzyć bardziej wyspecjalizowaną wersję klasy oraz potrzebujemy zastąpić lub dodać nowe zachowania (metody).
Korzystamy z klasy abstrakcyjnej, gdy chcemy zdefiniować wzorzec dla grupy podklas i mamy przynajmniej trochę kodu implementacji, który wszystkie podklasy mogłyby wykorzystać. Robimy daną klasę abstrakcyjną, gdy chcemy zagwarantować, że nikt nie utworzy obiektów tego typu.
Korzystamy z interfejsu, gdy chcemy zdefiniować rolę, jaką inne klasy mogą spełniać, niezależnie od ich pozycji w drzewie dziedziczenia.
Dog d = (Dog) x.getObject(aDog);
implements
:Dog implements Pet
super.runReport();
W Javie obiekty są umieszczane w stercie (heap), inwokacje metod i lokalne zmienne na stosie (stack). Gdy JVM się uruchamia, dostaje trochę pamięci od systemu operacyjnego. Wiemy, że wszystkie obiekty są na stercie, w której działa Garbage Collector.
Gdy przywołujemy jakąś metodę, jest ona umieszczana na szczycie stosu w ramce, w której znajdują się: obecny stan metody, aktualnie wykonywana linia kodu i wartości wszystkich zmiennych lokalnych.
Na szczycie danego stosu zawsze znajduje się aktualnie uruchomiona metoda dla tego stosu. Pozostaje na nim dopóki nie dojdzie do końcowego nawiasu klamrowego.
Jeśli lokalną zmienną jest referencja do obiektu, tylko sama zmienna (tj. referencja) jest umieszczana na stosie, obiekt jest na stercie. Zmienne obiektu (tj. zdefiniowane w klasie, lecz poza jej metodami) są na stercie, w obiekcie, do którego należą. Jeśli te zmienne są referancjami do obiektów, a nie prymitywami, Java alokuje miejsce w obiekcie (w którym, a nie do którego, definowane są referencje) tylko na samą zmienna referencyjną a nie na obiekt. Jeśli zmienna jest deklarowana, ale nie jest do niej przypisany żaden obiekt, JVM alokuje pamięć tylko na tą zmienną. (private Babe kasia;) Nie jest tworzony obiekt na stercie, dopóki nie przypiszemy jej nowego obiektu (private Babe kasia = new Babe();)
Trzy etapy deklaracji, tworzenia i przypisywania obiektu:
Duck myDuck = new Duck();
1 - Duck myDuck - tworzymy zmienną referencyjną typu klasy bądź interfejsu 2 - new Duck() - tworzymy nowy obiekt 3 - = - przypisujemy zmienną referencyjną obiektowi.
Jeśli nie napisaliśmy konstruktora, kompilator wygeneruje go za nas. Wtedy wygląda on tak:
public Duck() {}
Większość ludzi używa konstruktorów aby utworzyć i ustawić wartości zmiennym danego obiektu. Dzięki temu, można uniknąć chwil, gdy dany obiekt jest inicjowany, ale pewne jego zmienne nie są ustawione:
public class Duck { int size; public Duck() { System.out.println("Quack"); } public void setSize(int newSize) { size = newSize; } } ---- public class UseADuck { public static void main(String[] args) { Duck d = new Duck() // tutaj kaczka "żyje", ale nie ma ustawionego rozmiaru d.setSize(42); } }
Fajnie by było, gdyby korzystający z naszej klasy użytkownicy mogli ją wykorzystać dwojako. Pierwszy sposób, to dostarczanie rozmiaru kaczuszki (jako argumentu konstruktora) i drugiej, gdzie nie definiują jej rozmiaru, przez co nadany jej zostanie rozmiar standardowy.
Nie da się tego zrobić ładnie z tylko jednym konstruktorem. Jeśli przyjmuje on jakiś parametr, musimy go dostarczyć. (Można zrobić coś w stylu, że gdy argument == 0, to ustawiamy standardowy rozmiar, ale to utrudnia korzystanie z naszej klasy, bo jej użytkownik będzie musiał wiedzieć o tym fakcie). Poniżej poprawny sposób:
public class Duck2 { int size; public Duck2() { // standardowy rozmiar size = 27; } public Duck2(int duckSize) { // rozmiar pobierany z argumentu size = duckSize; } }
Jeśli mielibyśmy dwa, przyjmujące int
, konstruktory, program by się nie skompilował. Możemy mieć dwa konstruktory przyjmujące takie same typy, jeśli będą w różnych kolejnościach:
public class Chick { // znamy hawtness dziewczyny public Chick(int hawtness) { } // nie znamy ani poziomu gorącości ani czy lubi World of Warcraft public Chick() { } // wiemy, że nie lubi WoWa (niespodzianka!) public Chick(boolean likesWow) { } // wiemy, że lubi WoWa i że nie best zbyt gorąca public Chick(boolean likesWow, int hawtness) { } // zamieniona kolejność, skompiluje się! public Chick(int hawtness, boolean likesWow) { } }
0/0.0/false
dla prymitywów i null
dla referencji.Przykładowo, klasa Hawtbabe dziedziczy z klasy Object. Tworząc nowy obiekt klasy Hawtbabe, tworzymy tylko jeden obiekt na stercie, Hawtbabe. Wszystkie zmienne obiektu z obydwu klas muszą tam być.