Aplikacje w Javie wykład 5
Klasy c.d. (przeciążanie metod, polimorfizm)
Metody i klasy abstrakcyjne
Interfejsy
Treści prezentowane w wykładzie zostały oparte o:
Barteczko, JAVA Programowanie praktyczne od podstaw, PWN, 2014
http://docs.oracle.com/javase/8/docs/
C. S. Horstmann, G. Cornell, Java. Podstawy, Helion, Gliwice 2008
1
Bloki inicjacyjne
W Javie zasadniczo nie można używać instrukcji sterujących poza ciałami
metod i konstruktorów. Od tej zasady istnieją dwa wyjątki, a mianowicie użycie:
niestatycznego bloku inicjacyjnego (inicjującego)
statycznego bloku inicjacyjnego (inicjującego)
Niestatyczny blok inicjacyjny wprowadzamy ujmując kod wykonywalny
w nawiasy klamrowe i umieszczając taką konstrukcję w definicji klasy poza
ciałem jakiejkolwiek metody (czy konstruktora). Kod bloku zostanie wykonany
na etapie inicjacji obiektu, czyli przy tworzeniu obiektu, przed wywołaniem
konstruktora.
Taka możliwość może okazać się przydatna, gdy mamy kilka konstruktorów
i chcemy wyróżnić pewien kod, który będzie inicjował obiekt niezależnie od
użytego konstruktora i przed użyciem jakiegokolwiek z nich.
Jeśli blok inicjujący zgłasza wyjątek, to musi on być zadeklarowany w klauzuli
throws każdego konstruktora.
Bloki inicjujące mogą zawierać dowolne instrukcje (np. pętle). Można
zdefiniować wiele bloków inicjujących (ale nie należy tego robić, ze względu na
czytelność).
2
Bloki inicjacyjne (inicjujące) -przykład
class A {
final static int n = 10;
int tab[] = new int[n];
{
//Blok inicjujący tablicę, nie musimy tej inicjacji
//wpisywać do poszczególnych konstruktorów.
//Można to osiągnąć przy pomocy odpowiedniej metody.
for(int i=0; i
tab[i] = i;
System.out.println("Inicjalizacja");
//Wywołanie metod też jest dozwolone
}
A() {
//...
}
A(int i) {
//...
}
}
3
Bloki inicjacyjne
Bloki inicjujące są przydatne:
w klasach anonimowych, bo tam nie da się zdefiniować konstruktora,
instrukcje w bloku inicjującym mogą inicjować zmienne końcowe (final),
czego nie można zrobić w treści metody.
Statyczne (klasowe) bloki inicjacyjne.
Można też definiować bloki inicjujące wykonujące się przy tworzeniu klasy (a nie
obiektu). Czyli dokonujące inicjacji na rzecz całej klasy. Ich deklaracja wygląda
tak samo jak w przypadku zwykłych (egzemplarzowych) bloków inicjujących,
tyle że cały blok poprzedza słowo static. Oczywiście nie ma w nim dostępu
do zmiennych egzemplarzowych. W tych blokach nie można także zgłaszać
sprawdzalnych wyjątków (bo nie ma gdzie ich przechwytywać).
Korzystamy z nich, gdy pojawia się potrzeba wykonania jakiegoś kodu jeden
raz, przy pierwszym odwołaniu do klasy (np. użyciu metody statycznej lub
stworzeniu pierwszego obiektu). Przy inicjacji pól statycznych możemy
skorzystać z dowolnych wyrażeń, składających się ze zmiennych i stałych
statycznych oraz z wywołań statycznych metod, ale - oczywiście - nie sposób
użyć instrukcji wykonywalnych (np. sterujących).
4
REGUAY INICJACJI
Inicjacja klasy powoduje jednokrotne zainicjowanie elementów statycznych
tzn. najpierw wszystkie pola statyczne uzyskują wartości domyślne, a
następnie wykonywane są inicjatory statyczne (tzn. inicjatory pól
statycznych oraz statyczne bloki inicjacyjne) w kolejności ich występowania
w klasie. Inicjacja klasy następuje w wyniku jej załadowania przez JVM, co
może się zdarzyć przy uruchomieniu głównej klasy programu lub pierwszym
odwołaniu z programu do innej klasy na skutek odwołania do składowej
statycznej
Tworzenie każdego obiektu (new) powoduje nadanie niestatycznym polom
klasy wartości domyślnych (0, false dla typu boolean, null dla
referencji), następnie wykonanie inicjatorów niestatycznych w kolejności ich
występowania w klasie, po czym wykonywany jest konstruktor. W momencie
tworzenia jakiegokolwiek obiektu wszystkie pola statyczne są już
zainicjowane i zostały już wykonane wszystkie inicjatory statyczne.
W inicjatorach statycznych można odwoływać się do wszystkich statycznych
metod klasy, ale tylko do tych statycznych pól, których deklaracje
poprzedzają inicjator.
W inicjatorach niestatycznych można odwoływać się do wszystkich metod
klasy, do wszystkich pól statycznych (niezależnie od miejsca ich
występowania), ale tylko do tych pól niestatycznych, których deklaracje
poprzedzają inicjator.
W konstruktorze można odwoływać się do wszystkich metod i pól klasy (są
już zainicjowane).
5
REGUAY INICJACJI
public class InitOrder {
private static int s = 100;
private static final int C;
private int a = 1;
InitOrder() {
report("Konstruktor: s, C, a, b mają wartości :", s, C, a, b);
}
private int b = 2;
{
report("Blok inicjacyjny: s, C, a =", s, C, a);
}
static {
report("Statyczny blok inicjacyjny, zmienna s = ", s);
C = 101; // opózniona inicjacja stałej!
}
private static void report(String msg, int ... args ) {
System.out.print(msg + " ");
for (int i : args) {
System.out.print(i + " ");
}
System.out.println();
}
public static void main(String[] args) {
report("Wywołanie metody main");
new InitOrder();
}
}
6
REGUAY INICJACJI
W wyniku zostanie wyprowadzony kod:
Statyczny blok inicjacyjny, zmienna s = 100
Wywołanie metody main
Blok inicjacyjny: s, C, a = 100 101 1
Konstruktor: s, C, a, b mają wartości : 100 101 1 2
Podsumowując: najpierw - i tylko raz - inicjowane są kolejno pola statyczne,
a przy każdym tworzeniu obiektu - kolejno - pola niestatyczne.
7
Dziedziczenie - przedefiniowanie metod
Przedefiniowanie (nadpisywanie) metody (ang. overriding) nadklasy w klasie
pochodnej oznacza dostarczenie w klasie pochodnej definicji nieprywatnej i
niestatycznej metody z taką samą sygnaturą (czyli nazwą i listą parametrów) jak
sygnatura metody nadklasy, ale z ewentualnie inną definicją ciała metody, przy
czym:
typy wyników tych metod muszą być takie same lub kowariantne (co
oznacza m.in., że typ wyniku metody z podklasy może być podtypem wyniku
metody nadklasy),
przedefiniowanie nie może ograniczać dostępu: specyfikator dostępu
metody przedefiniowanej w podklasie musi być taki sam lub szerszy (np.
public zamiast protected) niż metody przedefiniowywanej,
metoda przedefiniowana (z podklasy) nie może zgłaszać więcej lub bardziej
ogólnych wyjątków kontrolowanych niż metoda przedefiniowywana (z
nadklasy).
8
Dziedziczenie - przedefiniowanie metod
Istotą przedefiniowania jest modyfikacja, uszczegółowienie funkcjonalności.
Jest to wielka zaleta, bez tego programowanie obiektowe nie byłoby
możliwe.
Aby uniknąć możliwości popełniania podobnych błędów w przypadkach, gdy
zmiany funkcjonalności fragmentów kodu nie są potrzebne, czy też są nawet
niebezpieczne, w deklaracji metod stosuje się słowo kluczowe final.
Słowo to użyte w deklaracji metody zabrania jej przedefiniowania.
Uwaga: Przy przedefiniowaniu metod używajmy adnotacji @Override
class Thought {
public void message() {
System.out.println("I like holidays");
}
}
public class Advice extends Thought {
@Override // adnotacja @Override jest opcjonalna
public void message() {
System.out.println("Don't worry be happy");
super.message();// metoda z nadklasy
}
}
9
Przedefiniowanie i klauzula throws
Przedefiniowanie metody nie może poszerzać zakresu wyjatków
kontrolowanych wymienionych w klauzuli throws ( wyjątki kontrolowane, to te
których klasy są pochodne od klasy Exception, ale nie RuntimeException).
Oznacza to, że:
jeżeli metoda z klasy bazowej nie ma klauzuli throws, to metoda
przedefiniowująca ją w klasie pochodnej nie może wymienić w swojej
klauzuli throws żadnych wyjątków kontrolowanych,
jeżeli metoda z klasy bazowej wymienia w swojej klauzuli throws jakieś
wyjatki kontrolowane, to metoda przedefiniowująca ją w klasie pochodnej
nie może wymienić żadnej nadklasy tych wyjątków ani żadnych
dodatkowych innych klas wyjątków kontrolowanych, może natomiast
wymienić dowolne wyjątki pochodzące z podklas wyjątków, zgłaszanych
przez metodę z klasy bazowej,
niezależnie od metody z klasy bazowej metoda przedefiniowana w klasie
pochodnej może nie zgłaszać żadnych wyjatków i nie mieć klauzuli throws,
metoda przedefiniowana w klasie pochodnej zawsze może zgłaszać wyjatki
niekontrolowane i ewentualnie wymieniać je w swojej klauzuli throws
10
Przedefiniowanie i klauzula throws
Przykład:
class A {
void met1() throws Exception {}
void met2() throws IOException {}
void met3() throws Exception {}
void met4() {}
}
class B extends A {
void met1() throws IOException {}
//wyjątek IOException jest pochodny od Exception
void met2() throws FileNotFoundException, IOException {}
//FileNotFoundException jest pochodny od IOException,
void met3() {}
void met4() throws NumberFormatException { }
// NumberFormatException jest pochodny od RuntimeException
}
11
Przedefiniowywanie i przeciążanie metod
Przedefiniowanie (nadpisywanie) metod (overriding) należy odróżniać od
przeciążania metod (overloading)
Metody przeciążone mają po prostu te same nazwy, ale inną liczbę i/lub typy
parametrów. Zwróćmy uwagę, że:
po pierwsze, przeciążone metody mogą należeć do tej samej lub różnych
klas (z których jedna pośrednio lub bezpośrednio dziedziczy inną),
po drugie przeciążanie nie wyklucza przedefiniowania: jeśli np. w klasie A
zdefiniowano dwie publiczne metody z tą samą nazwą (co oznacza, że są
one przeciążone, bo sygnatury metod deklarowanych w jednej klasie muszą
się różnić), to w klasie B dziedziczącej klasę A możemy je dodatkowo
przeciążyć (czyli podać w deklaracji inny zestaw parametów) oraz
przedefiniować (pozostawiając sygnaturę bez zmian i dostarczając innej
definicji kodu metody).
Metoda prywatna nigdy nie może być przedefiniowana w podklasie. Deklaracja
w podklasie metody o tej samej sygnaturze co metoda prywatna nadklasy
oznacza praktycznie wprowadzenie "niezależnego" bytu do naszego programu
(zatem możemy tu już mieć np. całkiem inny typ wyniku niż w metodzie
prywatnej o tej samej sygnaturze z nadklasy).
12
Pokrywanie metod
Pokryciem metody nazywa się dostarczenie w podklasie definicji metody
statycznej o tej samej sygnaturze i tym samym lub kowariantnym typem
wyniku jak metoda statyczna z nadklasy.
Pokrywanie (hiding) nie dotyczy metod niestatycznych, co więcej jeśli w
podklasie dostarczymy definicji metody statycznej o tej samej sygnaturze jak
metoda niestatyczna nadklasy, to wystąpi błąd w kompilacji.
Pokrywanie może dotyczyć również pól: oznacza ono wtedy deklarację w
podklasie pola o takim samym identyfikatorze jak pole z nadklasy. Pokrycie
identyfikatorów pól różni się zarówno od pokrywania identyfikatorów metod
jak i przedefiniowania metod. Pole statyczne może pokryć pole niestatyczne
i odwrotnie. Pole pokrywające pole nadklasy może mieć całkiem inny typ niż
pokryte pole nadklasy.
Odwołania do przedefiniowanych metod oraz pokrytych metod i pól nadklasy
z poziomu metod podklasy realizowane są za pomocą konstrukcji:
super.odwołanie_do_składowej
13
Metody wirtualne. Polimorfizm
Przypomnijmy, że obiekt klasy pochodnej posiada wszystkie atrybuty i
metody klasy bazowej, a więc zawiera w sobie obiekt klasy bazowej
(nadklasy). Dlatego odniesienie do takiego obiektu można zapamiętać w
zmiennej referencyjnej klasy bazowej.
class A {& }
class B extends A {& }
public class TestAB{
A ob1 = new A();
A ob2 = new B();
&
}
Jeśli w podklasie (klasie pochodnej) zostanie przedefiniowana jakaś
metoda, zdefiniowana pierwotnie w nadklasie (klasie bazowej), to przy
wywołaniu tej metody zostanie uruchomiona metoda tej klasy, do której
faktycznie należy obiekt, a nie tej klasy która jest typem zmiennej
referencyjnej zawierającej odniesienie do obiektu. Oznacza to, że wiązanie
odwołań do metod z kodem programu następuje nie w czasie kompilacji
programu, lecz fazie wykonania programu tuż przed każdorazowym
wykonaniem instrukcji wywołującej przedefiniowaną metodę.
14
Metody wirtualne. Polimorfizm
class A {
// ...
void fun() {
System.out.println("Jestem z nadklasy");
}
// ...
}
class B extends A {
// ...
void fun() {
System.out.println("Jestem z podklasy");
}
// &
}
public class TestAB{
public static void main(String[] args) {
A ob1 = new A();
A ob2 = new B();
ob1.fun();//Jestem z nadklasy
ob2.fun();//Jestem z podklasy
}
}
15
Metody wirtualne. Polimorfizm
Metody wirtualne to takie metody, dla których wiązanie odwołań z kodem
programu następuje w fazie wykonania programu. Nazywa się to "dynamic
binding" lub "late binding".
W Javie wszystkie metody są wirtualne za wyjątkiem:
metod statycznych (bo nie dotyczą obiektów, a klasy)
metod deklarowanych ze specyfikatorem final, który oznacza, że
metoda jest ostateczna i nie może być przedefiniowana,
metod prywatnych (bo metody prywatne nie mogą zostać
przedefiniowane).
Mówi się, że odwołania do metod wirtualnych są polimorficzne, a słowo
"polimorficzne" używane jest w tym sensie, iż konkretny efekt odwołania
może przybierać różne kształty, w zależności od tego jaki jest faktyczny typ
obiektu, na rzecz którego wywołano metodę wirtualną.
Thought mysl1 = new Thought();
mysl1.message(); // "I like holidays"
mysl1 = new Advice();
mysl1.message(); //"Don't worry be happy" - polimorfizm
16
Polimorfizm - przykład
class Pojazd {
public void ruszaj() {
System.out.println("Pojazdy służą do "
+ "przemieszczania się ");
}
}
class Auto extends Pojazd {
@Override
public void ruszaj() {
super.ruszaj(); // wywołanie metody nadklasy
System.out.println("Auto to wygodny środek transportu");
}
}
public class TestAuto {
public static void main (String args []){
Pojazd b = new Auto (); // ref. do Pojazdu, ale obiekt Auto
b.ruszaj(); //wywoła metodę z klasy Auto
}
}
Output:
Pojazdy służą do przemieszczania się
Auto to wygodny środek transportu
17
Polimorfizm - przykład
Rozważmy klasę Zwierz opisującą takie właściwości różnych zwierząt jak: nazwa
rodzaju, sposób komunikowania się ze światem oraz imię.
class Zwierz {
private String imie = "bez imienia";
Zwierz() { }
Zwierz(String imie) {
this.imie = imie;
}
String zwrocGatunek(){
return "Jakis zwierz";
}
final String zwrocImie(){
return imie;
}
String dajGlos(){
return "?";
}
//Metoda "mowa" symuluje wydanie głosu
//wypisując odpowiedni komunikat
void mowa() {
System.out.println(zwrocGatunek()+" "
+zwrocImie()+" mówi "+dajGlos());
}
}
18
Polimorfizm - przykład
Dla bazowej klasy Zwierz zdefiniujmy klasy pochodne Pies i Kot:
class Pies extends Zwierz {
Pies() { }
Pies(String imie) {
super(imie);
}
String zwrocGatunek() {
return "Pies";
}
String dajGlos(){
return "HAU, HAU!";
}
}
class Kot extends Zwierz {
Kot(){ }
Kot(String imie) {
super(imie);
}
String zwrocGatunek() {
return "Kot";
}
String dajGlos(){
return "Miauuuu...";
}
}
19
Polimorfizm - przykład
W klasie TestPolimorfizm wypróbujemy naszą hierarchię klas zwierząt przy
symulowaniu rozmów pomiędzy poszczególnymi osobnikami. Rozmowę symuluje
statyczna funkcja rozmowaZwierzat, która ma dwa argumenty obiekty typu
Zwierz, oznaczające aktualnych rozmówców.
public class TestPolimorfizm{
public static void main(String[] arg) {
Zwierz z1 = new Zwierz(),z2 = new Zwierz();
Pies pies = new Pies(),
kuba = new Pies("Kuba"),
reksio = new Pies("Reksio");
Kot kot = new Kot();
rozmowaZwierzat(z1, z2);
rozmowaZwierzat(kuba, reksio);
rozmowaZwierzat(kuba, kot);
rozmowaZwierzat(reksio, pies);
}
static void rozmowaZwierzat(Zwierz z1, Zwierz z2) {
z1.mowa();
z2.mowa();
System.out.println("--------------------------------");
}
}
20
Polimorfizm - przykład
W wyniku wykonania programu otrzymamy:
Jakis zwierz bez imienia mówi ?
Jakis zwierz bez imienia mówi ?
----------------------------------------
Pies Kuba mówi HAU, HAU!
Pies Reksio mówi HAU, HAU!
----------------------------------------
Pies Kuba mówi HAU, HAU!
Kot bez imienia mówi Miauuuu...
----------------------------------------
Pies Reksio mówi HAU, HAU!
Pies bez imienia mówi HAU, HAU!
----------------------------------------
Podsumowując polimorfizm polega na tym, że metoda mowa(), określona w
klasie Zwierz, dzięki wirtualności metod zwrocGatunek() i dajGlos()
działa prawidłowo dla różnych zwierząt (obiektów podklas klasy Zwierz).
21
Polimorfizm przykład
Z polimorfizmem spotkaliśmy się już wielokrotnie korzystając z metody
toString(), która jest po raz pierwszy zdefiniowana w klasie Object:
public class Object{
//toString()- zwraca id obiektu jako napis
//nazwa_klasy@unikalny_identyfikator_obiektu.
}
Klasę Object dziedziczą wszystkie klasy (pośrednio lub bezpośrednio).
W klasach tych można więc zawsze przedefiniować metodę toString().
A przedefiniowane metody wołane są polimorficznie - zawsze więc uzyskamy
właściwy opis obiektu (określony w danej klasie), lub - jeśli nie zdefiniowano w niej
metody toString - opis z pierwszej nadklasy, w której jest ona zdefiniowana, np.
public class Para{
int a,b;
@Override
public String toString(){
return "(" + a + "," + b + ")";
}
}
Pamiętajmy, że przedefiniowanie metody wymaga, aby miała ona identyczną
sygnaturę i identyczny (lub kowariantny) typ wyniku jak metoda z nadklasy, a
także by nie ograniczała jej widzialności (dostępu).
22
Metody i klasy abstrakcyjne
Metoda abstrakcyjna to metoda, która nie ma implementacji (ciała) i jest
zadeklarowana ze specyfikatorem abstract.
abstract int getSomething(); //nie ma ciała
Klasa, w której zadeklarowano jakąkolwiek metodę abstrakcyjną jest klasą
abstrakcyjną i musi być opatrzona specyfikatorem abstract.
Klasa abstrakcyjna może (ale nie musi) zawierać metody abstrakcyjne.
Wystarczy zadeklarować ją ze specyfikatorem abstract.
abstract class SomeClass {
int n;
abstract int getSomething();
void say() { System.out.println("Coś tam, coś tam");
}
Nie można tworzyć obiektów klasy abstrakcyjnej.
Klasa abstrakcyjna może być dziedziczona przez nowe klasy. Klasa
pochodna powinna przedefiniować (a właściwie zdefiniować) wszystkie
metody abstrakcyjne, które odziedziczyła z abstrakcyjnej klasy bazowej. W
przeciwnym wypadku klasa pochodna nadal pozostanie klasą abstrakcyjną i
nie będzie można tworzyć jej obiektów.
23
Metody i klasy abstrakcyjne
Metody abstrakcyjne to takie, co do których nie wiemy jeszcze jaka może być
ich konkretna implementacja, ale wiemy, że powinny wystąpić w zestawie
metod każdej konkretnej klasy dziedziczącej klasę abstrakcyjną. Konkretna
implementacja (definicja w klasie kodu metody) może być bardzo różna, w
zależności od konkretnego rodzaju obiektów, które opisuje dana klasa.
Przykład. Rozważmy klasę Figura, reprezentującą dowolną figurę
geometryczną, którą można narysować i zmazać. Przyjmiemy, ze chociaż figury
rysuje się różnie, to ściera się je tak samo. Dla zapewnienia wspólnego interfejsu
dla wszystkich figur metody rysuj() i zmaz() powinny być zadeklarowane w
klasie Figura. Ponieważ nie można narysować ogólnej figury, to metoda rysuj
będzie abstrakcyjna. Poza tym, ogólne figury nie istnieją, zatem klasa Figura
nie powinna w ogóle mieć instancji (każdy obiekt klasy Figura musi być jakąś
konkretną figurą, obiektem jakiejś klasy pochodnej)
public abstract class Figura {
public abstract void rysuj();
public void zmaz() {
System.out.println("Figura zmazana");
}
}
24
Metody i klasy abstrakcyjne
W definicji klasy pochodnej po klasie Figura musimy przedefiniować metodę
rysuj(), zaś metodę zmaz() możemy, ale nie musimy, gdyż nie jest ona
abstrakcyjna.
class Trojkat extends Figura {
private int wysokosc;
public Trójkat(int wysokosc) {
this.wysokosc = wysokosc;
}
public void rysuj() {
for (int i = 0; i < wysokosc; i++) {
for (int j = 0; j < wysokosc - i; j++)
System.out.print( );
for (int j = 0; j < i * 2 - 1; j++)
System.out.print( ^ );
System.out.println();
}
}
}
25
Metody i klasy abstrakcyjne
Wówczas możemy używać tej klasy jak poniżej:
Trojkat t = new Trojkat(7);
Figura f = new Trojkat(3);
//! Figura g = new Figura(); // nie można tworzyć instancji
t.rysuj();
t.zmaz();
f.rysuj();
Oczywiście należy pamiętać, że referencje do obiektu typu Trojkat
możemy przechowywać w zmiennej typu Figura, co wcale nie oznacza, że
obiekt f z powyższego listingu jest klasy Figura jest on klasy Trojkat.
26
INTERFEJSY
Interfejs (deklarowany za pomocą słowa kluczowego interface) to:
zestaw publicznych abstrakcyjnych metod (ich nagłówków)
i/lub publicznych stałych (statycznych)
Poczynając od wersji 8 Javy, w definicji interfejsu mogą się znalezć definicje
publicznych metod statycznych oraz publicznych domyślnych metod
niestatycznych, poprzedzone słowem kluczowym default.
Implementacja interfejsu w klasie - to zdefiniowanie w tej klasie
wszystkich abstrakcyjnych metod interfejsu. To że klasa ma implementować
interfejs X oznaczamy słowem kluczowym implements X.
Każda klasa implementująca interfejs musi zdefiniować WSZYSTKIE jego
metody abstrakcyjne albo musi być zadeklarowana jako klasa abstrakcyjna.
W Javie klasa (oprócz dziedziczenia innej klasy) może implementować
dowolną liczbę interfejsów.
Zatem w Javie nie ma wielodziedziczenia klas, ale za to jest możliwe
wielodziedziczenie interfejsów.
27
Interfejsy
Ogólna postać definicji interfejsu :
[public] interface NazwaInterfejsu [extends other interfaces]{
typ nazwaZmiennej = wartosc;
...
typ nazwaMetody(lista_parametrów);
...
}
Uwagi:
modyfikator dostępu public przed słowem interface może nie występować -
wówczas interfejs jest dostępny tylko w bieżącym pakiecie,
pola to zawsze publiczne stałe statyczne nawet jeśli nie użyjemy żadnych
modyfikatorów kompilator traktuje je jako public static final
metody to: metody abstrakcyjne (bez implementacji) (ponieważ do tej pory
interfejsy zawierały tylko metody abstrakcyjne, to słowo abstract pomijamy),
metody domyślne default oraz metody statyczne
Interfejsy - podobnie jak klasy - wyznaczają typy danych (referencyjne) . Możemy
korzystać z operatora instanceof oraz rzutowania.
28
Interfejsy
Ogólna postać definicji klasy implementującej interfejs:
[modyfikator] class NazwaKlasy [extends KlasaBazowa]
implements
NazwaInterfejsu_1, ..., NazwaInterfejsu_n{
...
}
Uwagi:
klasa może implementować wiele interfejsów,
klasa musi definiować wszystkie metody implementowanych interfejsów
albo musi być zadeklarowana jako klasa abstrakcyjna
klasa może zawierać własne (nie będące częścią interfejsu) pola i metody.
29
Interfejsy - przykład
Przykład: Interfejs określający abstrakcyjną funkcjonalność "wydającego głos" :
public interface Speakable {
int QUIET = 0; // <- publiczne stałe statyczne
int LOUD = 1; // domyślnie public static final
String getVoice(int voice); // <- metoda abstrakcyjna;
// ponieważ w interfejsie mogą być
// tylko publiczne metody abstrakcyjne,
// specyfikatory public i abstract niepotrzebne
}
a jego implementacja w przykładowej klasie Wodospad:
public class Wodospad implements Speakable {
@Override
public String getVoice(int voice) {
// metody interfejsu muszą być zdefiniowane jako publiczne!
if (voice == LOUD) return "SZSZSZSZSZSZ....";
else if (voice == QUIET) return "szszszszszsz....";
else return "?";
}
}
30
Interfejsy - przykład
Rozważmy teraz interfejs, opisujący obiekty zdolne się poruszać:
public interface Moveable {
void start();
void stop();
}
Zauważmy, że ta funkcjonalność dotyczy zarówno Psa, jak i pojazdów np.
Samochodu. Zdefiniujmy ponownie klasę Zwierz jako abstrakcyjną:
public abstract class Zwierz {
private String name = "bez imienia";
public Zwierz() {}
public Zwierz(String name) {
this.name = name;
}
public abstract String getTyp();
public String getName() {
return name;
}
}
31
Interfejsy - przykład
public class Pies extends Zwierz implements Speakable, Moveable {
public Pies() {}
public Pies(String name) {
super(name);
}
public String getTyp() {
return "Pies";
}
public String getVoice(int voice) {
if (voice == LOUD)
return "HAU... HAU... HAU... ";
else
return "hau... hau...";
}
public void start() {
System.out.println("Pies " + getName() + " biegnie");
}
public void stop() {
System.out.println("Pies " + getName() + " stanął");
}
}
32
Interfejsy - przykład
public class TestInterfaces {
public static void main(String[] args) {
Pies kuba = new Pies("Kuba");
kuba.start();
System.out.println(kuba.getVoice(Speakable.LOUD));
kuba.stop();
}
}
Wyjście:
Pies Kuba biegnie
HAU... HAU... HAU...
Pies Kuba stanął
Zmienna kuba jest typu Pies, a to znaczy, że jest również typu Zwierz oraz
typu Speakable i Moveable.
33
Interfejsy przykład polimorfizmu
public class TestInterfaces {
public static void main(String[] args) {
System.out.println("Rozmowa:");
rozmowa(new Pies("Burek"), new Wodospad(),
new Pies("Azor"));
}
public static void rozmowa(Speakable... rozmowcy) {
for (Speakable rozmowca : rozmowcy) {
if (rozmowca instanceof Zwierz)
System.out.print(((Zwierz)rozmowca).getTyp() + ": ");
System.out.println(rozmowca.getVoice(Speakable.LOUD));
}
}
}
Wynik:
Rozmowa:
Pies: HAU... HAU... HAU...
SZSZSZSZSZSZ....
Pies: HAU... HAU... HAU...
34
Interfejsy metody domyślne
W Javie 8 wprowadzono mozliwość definiowania metod domyślnych poprzedzonych
słowem kluczowym default. W takim przypadku w interfejsie zawarta jest
implementacja tych metod. Klasy implementujące ten interfejs mogą, ale nie muszą
przedefiniowywać takie metody, chyba że klasa implementuje dwa lub więcej
interfejsów zawierających metodę o identycznej sygnaturze. Wówczas musi ona zostać
przedefiniowana w klasie.
Przykład: W interfejsie Speakable zdefiniujmy domyślną metodę getVoice()
public interface Speakable {
int QUIET = 0; // <- publiczne stałe statyczne
int LOUD = 1; // domyślnie public static final
String getVoice(int voice); // <- metoda abstrakcyjna;
default String getVoice(){
return getVoice(QUIET);
}
}
Nie zmieniając nic w klasie Pies, dla obiektu tej klasy można np.
Pies kuba = new Pies("Kuba");
System.out.println(kuba.getVoice()); //hau... hau...
35
Wyszukiwarka
Podobne podstrony:
W5 Tranzystor
w5 PSYCH
Zaopatrzenie w wod kan W5
PK W5
KC K W5
4OS 11 w5
W5 Rodzina jako system
OBWODY ELEKTRYCZNE i MAGNETYCZNE w5
W5 14 03
PiS W5
W5 Kart
W5 Manipul i roboty pneum i hydraul
W5 wycena obligacji slajdy
EZNiOS Log 13 w5 system slajdy
FO W5 Fale
więcej podobnych podstron