Kurs Java Podstawy - rozszerzony

Klasy abstrakcyjne

W Javie do tworzenia pewnej abstrakcji służą jak już się dowiedzieliśmy interfejsy. Istnieje jednak również drugi sposób, są to klasy abstrakcyjne. Ich użycie, a także budowa jest bardzo podobna do interfejsów, ale zawiera też pewne różnice, poniżej znajduje się lista najważniejszych cech klas abstrakcyjnych:

  • mogą zawierać metody abstrakcyjne, czyli takie, które nie posiadają implementacji (ani nawet nawiasów klamrowych)
  • może zawierać stałe (zmienne oznaczone jako public static final)
  • mogą zawierać zwykłe metody, które niosą jakąś funkcjonalność, a klasy rozszerzające mogą ją bez problemu dziedziczyć
  • klasy rozszerzające klasę abstrakcyjną muszą stworzyć implementację dla metod oznaczonych jako abstrakcyjne w klasie abstrakcyjnej
  • metod abstrakcyjnych nie można oznaczać jako statyczne (nie posiadają implementacji)
  • podobnie jak w przypadku interfejsów nie da się tworzyć instancji klas abstrakcyjnych

Jaka jest więc przewaga klas abstrakcyjnych nad interfejsami, skoro na pierwszy rzut oka nie widać praktycznie żadnych różnic? Przede wszystkim tutaj możemy już umieścić jakąś implementację, natomiast klasy rozszerzające będą miały jedynie obowiązek rozbudować funkcjonalność poprzez zaimplementowanie metod abstrakcyjnych.

Dzięki zastosowaniu obu z tych mechanizmów jesteśmy w stanie tworzyć niesamowicie elastyczne i bezpieczne struktury, ponieważ niezależnie które z nich wykorzystamy (lub połączymy oba) mamy pewność, że spełniony jest pewien kontrakt. W przypadku, gdy przykładowo chcemy w 4 klasach uzyskać 4 metody o identycznej funkcjonalności, różniące się implementacją tylko jednej nie ma zbytnio sensu tworzyć najpierw interfejsu, później klasy implementującej 4 metody, a dopiero na końcu klas ją specjalizujących, lepiej utworzyć klasę abstrakcyjną i zaoszczędzić sobie tym samym jeden poziom abstrakcji. Niewątpliwą wadą będzie natomiast to, że w Javie możemy implementować wiele interfejsów, ale rozszerzać (extend) tylko jedną klasę.

Na koniec zobaczmy prawidłową i błędną implementację przykładowej klasy abstrakcyjnej:

Dobrze:

public abstract class Emeryt {
	public static final int ILOSC_OCZU = 2; //stałe są ok

	//metoda abstrakcyjna
	public abstract String krzyczNaDzieci();

	//zwykła metoda z implementacją
	public static void biegnijDoSklepu(int odleglosc, int predkosc) {
		double czas = (double)odleglosc/predkosc;
		System.out.println("Biegne po kiełbase bede za "+czas);
	}
}

Źle:

public abstract class Emeryt {
	public static final int ILOSC_OCZU = 2; //stałe są ok

	//metoda abstrakcyjna nie może być statyczna
	public abstract static String krzyczNaDzieci();

	//metoda abstrakcyjna nie może posiadać implementacji
	//ani nawet nawiasów klamrowych
	public abstract void biegnijDoSklepu() {}
}

Pierwszą z klas bez problemu możemy rozszerzyć na przykład klasami Dziadek i Babcia, oczywiście przydała by się też opcja walki o miejsce w autobusie.

<- Poprzednia LekcjaNastępna Lekcja ->

Komentarze

Staszek

"oczywiście przydała by się też opcja walki o miejsce w autobusie" - tak, myslę że ta metoda powinna być nawet abstrakcyjna w klasie abstrakcyjnej :)

Pawlas

Nie to żebym się czepiał, ale wydaje mi się, że powinno w kodzie być tak:
double czas = (double)odleglosc/predkosc;;
zamiast
double czas = (double)predkosc/odleglosc;
może być mylące :)

ANS3pl

v = s/t - prędkość = droga / czas
tv = s - prędkość * czas = droga
t = s/v - czas = droga /prędkość
jednostkami:
s = m / (m/s)

Witek

Witam
Do klasy abstrakcyjnej Emeryt dopisałem klasę testującą „ Test_Klasa_Abstrakcyjna”. Może ktoś mi powiedzieć czy to jest poprawnie napisana klasa testująca klasę Emeryt, jeżeli nie to prosiłbym o podanie co jest nie tak. Z góry dziękuję.
Ps.
Wszystko działa poprawnie.

public class Test_Klasa_Abstrakcyjna {

/**
* Przykład klasy Abstrakcyjnej
*
* @author Wiciu
*
*/
public static void main(String[] args) {


Emeryt dziadek = new Emeryt() {

String str = "Tak dziadek krzyczy";

@Override

public String krzyczyNaDzieci() {
System.out.println(str);
return str;
}
};

dziadek.krzyczyNaDzieci();

System.out.println("Ilosc oczu dziadka: " + Emeryt.ILOSC_OCZU);

Emeryt.biegnijDoSklepu(10, 4);
}

}

Slawek

Generalnie ok, tyle, że nie powinno się raczej szaleć z klasami anonimowymi - generalnie są one nieco zaprzeczeniem programowania obiektowego. Zdecydowanie lepiej, gdybyś wydzielił sobie klasę np Dziadek (class Dziadek extends Emeryt { ... } ). Klas abstrakcyjnych powinno się używać mniej więcej w taki sposób jak interfejsów, jedyna różnica jest taka, że w nich można część rzeczy już zaimplementować.

Witek

Witam
Dzięki za szybką odpowiedź. Klas anonimowych jeszcze ich nie przerabiałem dlatego poprosiłem o skomentowanie tej klasy ponieważ program Eclips’e automatycznie wygenerował mi kod klasy anonimowej, dlatego miałem pewne wątpliwości pomimo, że program działa poprawnie i nic nie podkreśla, teraz zrobię przykład tak jak napisałeś .
Jeszcze raz dzięki.

Jack

Co właściwie robią te metody abstrakcyjne, skoro tam nie ma nawiasów klamrowych?

ANS3pl

klasa dziedzicząca musi tę metodę zdefiniować(napisać ciało w{}) inaczej staje się klasą abstrakcyjną( przynajmniej tak jest c++) jeżeli mamy klasę np. odtwarzacza muzyki i metodę abstrakcyjną, którą wywołuje program, chociażby silnik gry, to nie trzeba zmieniać kodu w silniku. Wywołuje tą abstrakcyjną metodę (już nie abstrakcyjną) z ciała klasy obudowywującej. Czyli można pisać dużo odtwarzaczy dźwięku, każdy z nich może całkiem inaczej to robić a silnik byłby niezmieniony.

parmen39

Np. Dla klasy figura geometryczna mozna zdefiniowac methody, ktore sa konieczne do zaimplementowania np. rysuj obiekt ( abstract draw() ), poniewaz wspolne cechy np. wspolrzedne obiektu, mozna zdefiniowac, natomiast rysowanie kola czy trojkata zasadniczo sie rozni, wiec trzeba to samemu zrobic.
Brak nawiasow klamrowych informuje, ze nie mia ciala methody, po prostu MUSISZ ja zaimplementowac samemu. Powodzenia.

Igor

Możliwość rozszeżać tylko jedną klasę nie jest wadą, a zaletą

GZII13

zauważyłem ze zmienne z klasy abrakcyjnej mogę nadac wartosc w klasie ktora ja rozszerza, a przy interfejsie to wyskakuje ze final nie może być zmieniony

obi

Skoro masz pole typu final, to nie rozumiem Twojego zdziwienia.

Konrad

Nic mi nie wchodzi do głowy bez zadań :)

Janosch

Popieram. Z zadaniami można było powalczyć z wiedzą z lekcji, a tak człowiekowi wydaje się, że wie, a jak przychodzi co do czego to tabula rasa. Zadania idealnie utrwalały wiedzę z lekcji bo trzeba było trochę pogłówkować. Panowie autorzy, rzućcie nam wyzwanie w postaci zadań :)

Alex

Do interfejsów i klas abstrakcyjnych wydawało mi się, że pojmuję. Ale tych Interfejsów i klas abstrakcyjnych nie mogę za bardzo pojąć. Jeżeli interfejsy i klasy abstrakcyjne to tylko nośnik stałych i deklaracji metod to po co mi to? Jeśli i tak muszę w danej klasie pisać całą zawartość danej metody.

DDO

Wyobraźmy sobie pewną kategorie przedmiotow. mają one cechy wspólne ale niektóre operacje choć wymagają tych samych zasobów( argumentow) i dają te same rezultaty mogą inaczej przebiegać. np pieczenie w mikrofalowce i w piekarniku. hamowanie w samochodzie i rowerze jedzenie w wykonaniu niemowlaka i dorosłego mają cechy wspólne jednak operacje abstrakcyjne różnią się wykonaniem

scalarny

Dlaczego Eclipse gdy kompiluje program, wyświetla mi wynik poprzedniego starszego programu w konsoli, skoro zawsze tworzę osobny Java Project i w nim dodaje new class? Coś włączyłem i się przestawiło że łączy z poprzednimi?

Sławek Ludwiczak

Kliknij prawym przyciskiem myszy na projekt lub konkretną klasę zawierającą metodę main i wybierz opcję Run -> As Java Application.
Powinno pomóc.