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. === Bullet Points === * Gdy nie chcemy by można było utworzyć obiekt danej klasy, oznaczamy ją słowem kluczowym "abstract". * Klasa abstrakcyjna może posiadać abstrakcyjne i nieabstrakcyjne metody. * Jeśli klasa ma przynajmniej jedną metodę abstrakcyjną, musi być oznaczona jako abstrakcyjna. * Metoda abstrakcyjna nie ma ciała, jej deklaracja kończy się średnikiem (brak nawiasów klamrowych). * Wszystkie metody abstrakcyjne muszą zostać zaimplementowane w pierwszej konkretnej podklasie w drzewie dziedziczenia. * Każda klasa w Javie jest podklasą klasy Object (java.lang.Object), czy to bezpośrednio czy pośrednio. * Metody mogą być deklarowane z argumentami i/lub zwracanymi typami Object. * Zmienna referencyjna typu Object może być tylko wykorzystana do przywołania metod zdefiniowanych w klasie Object, niezależnie od typu obiektu, którego jest referencją. * Zmienna referencyjna typu Object nie może być przypisana do jakiegokolwiek innego typu bez rzutowania. Rzutowanie może być wykorzystane do przypisania zmiennej referencyjnej jednego typu do zmiennej referencyjnej podtypu, ale podczas działania programu, rzutowanie zakończy się błędem, jeśli obiekt na stercie (heap) nie jest kompatybilny z rzutowanym. Przykład: Dog d = (Dog) x.getObject(aDog); * Obiekty wychodzą z ArrayList jako obiekty Object, to znaczy że mogą być referencjonowane(:P) bez rzutowania tylko za pomocą zmiennej referencyjnej typu Object. * Wielokrotne dziedziczenie jest w Javie zabronione. Możemy rozszerzać tylko jedną klasę (klasa może mieć tylko jedną bezpośrednią superklasę) * Interfejs jest w 100% klasą abstrakcyjną. Definiuje tylko abstrakcyjne metody. * Interfejs tworzymy za pomocą słowa kluczowego interface. * Implementujemy interfejs za pomocą słowa kluczowego ''implements'': Dog implements Pet * Klasa może implementować wiele interfejsów. * Klasa, która implementuje interfejs _musi_ zaimplementować wszystkie jego metody, ponieważ wszystkie metody interfejsu są z definicji public i abstract. * Aby przywołać wersję metody z superklasy z podklasy, w której ją nadpisujemy korzystamy ze słowa kluczowego super: super.runReport(); ====== 9 rozdział ====== 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. ===== Metody są na stosie ===== 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();) ===== Cud powstawania obiektów ===== 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; } } == Aby się skompilować, każdy konstruktor musi mieć różną listę argumentów == 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) { } } == Bullet Points == * Zmienne obiektu żyją w obiekcie, do którego należą, na stercie. * Jeśli zmienną obiektu jest referencja do obiektu, referencja oraz obiekt, na który wskazuje są na stercie. * Zawsze, jeżeli to możliwe, zapewnij bezargumentowego konstruktora, by ułatwić programistom utworzenie działającego obiektu. Zapewnij standardowe dane. * Zmiennym obiektu przypisywane są standardowe wartości, nawet jeśli nie zrobimy tego sami. Wartości te to ''0/0.0/false'' dla prymitywów i ''null'' dla referencji. ===== Superklasy, dziedziczenie i konstruktory ===== 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ć.