Klasy i metody abstrakcyjne, interfejsy. Typy uogólnione.
Klasa abstrakcyjna - klasa która jest zadeklarowana jako abstract .....
Nie wolno tworzyć biektów takiej klasy, ale wolno po niej dziedziczyć.
Klasa abstrakcyjna może ( ale nie musi) zawierać metodyabstrakcyjne.
Metoda abstrakcyjna - metoda bez implementacji ( sam nagłówek). Stosowana gdy chcemy wymusić aby wszystkie podklasy zawierały definicję metody, a nie możemy jej zrealizować w nadklasie. Np.
public abstract class Publikacja // klasa abstrakcyjna zawiera metodę dajPunkty, a nie da się
// policzyć punktów za publikację jako taką
{ String tytul;
String autor;
int rok; // rok wydania
....
abstract int dajPunkty(); //metoda abstrakcyjna
......
}
public class Ksiazka extends Publikacja
{ String wydawnictwo;
static private int punkty=10;
......
int dajPunkty( )
{return punkty;} //implementacja metody abstrakcyjnej
......
}
public class Artykul extends Publikacja
{ String czasopismo;
static private int punkty =5;
......
int dajPunkty( )
{return punkty;} //implementacja metody abstrakcyjnej
......
}
Klasę czysto abstrakcyjną (zawiera tylko metody abstrakcyjne i ewentualnie pola stałe) nazywamy interfejsem. W przeciwieństwie do klas możliwe jest wielodziedziczenie.
public interface Interface extends Interface1,interface2,...,InterfaceN
{ // definicje stałych ( modyfikatory domyślne : public, static, final - można opuścić
// sygnatury metod ( domyślny modyfikator public można opuścić)
}
Można tworzyć zmienne typu interfejsowego, wartością takiej zmiennej może być referencja do obiektu dowolnej klasy implementującej dany interfejs.
public interface Interface
{...}
public class Klasa implements Interface
{ ....}
Klasa obiekt = new Klasa();
Inteface x=obiekt;
Jest to swego rodzaju umowa określająca minimalny zestaw metod który musi realizować każda klasa implementująca interfejs. Każda klasa może implementować dowolną liczbę interfejsów.
class A implements Interfejs1,Interfejs1,.....,IntefejsN
Jeśli klasa nie implementuje wszystkich metod interfejsu to musi być klasą abstrakcyjną.
Typy uogólnione ( typy abstrakcyjne, klasy sparametryzowane, klasy generyczne, szablony klas, wzorce klas itp.).
Chcemy zdefiniować klasę Para umożliwiającą przechowywanie dwóch obiektów dowolnych typów. Można wykorzystać fakt, że wszystkie klasy dziedziczą po klasie Obiekt.
class Para
{ Obiekt pierwszy;
Obiekt Drugi;
Para(Obiekt Pierwszy, Obiekt drugi)
{ this.pierwszy= pierwszy;
this drugi= drugi; }
Obiekt dajPierwszy( )
{ return pierwszy;}
.....
}
Możemy utworzyć parę zawierającą np. dane osoby i napis zawierający komentarz.
Osoba os1=new Osoba("Nowak");
Para para=new Para(os1, "Opis"); // tu jest OK - rzutowanie w górę
Osoba os2= para.dajPierwszy( ); // błąd kompilacji, trzeba użyć rzutowania w dół
Osoba os2 = (Osoba) para.dajPierwszy( ); // kompilator traci możliwość kontroli typów
Możemy jednak zastosować rozwiązanie bezpieczne tworząc klasę sparametryzowaną :
class Para< T1, T2>
{ T1 pierwszy;
T2 Drugi;
Para(T1 Pierwszy, T2 drugi)
{ this.pierwszy= pierwszy;
this drugi= drugi; }
T1 dajPierwszy( )
{ return pierwszy;}
.....
}
Użycie takiej pary :
Osoba os1=new Osoba("Nowak");
Para<Osoba,String> para=new Para<Osoba,String>(os1, "Opis");
Osoba os2= para.dajPierwszy( );
Typy będące parametrami typów uogólnionych nie mogą być typami prostymi. Nie jest to problem ponieważ każdy typ prosty ma zdefiniowany odpowiadający mu typ obiektowy.
int - Integer; long - Long; float - Float; double - Double; char - Char; boolean - Boolean.
Kompilator przyjmuje, że typy będące parametrami wzorca mogą być dowolną podklasą klasy Obiekt. Dla poprawy funkcjonalności należałoby przeciążyć podstawowe ( clone, toString, equals) metody klasy Obiekt.
Dzięki mechanizmowi automatycznego opakowania i rozpakowania można ich używać zamiennie .
int a=5;
Integer b =a;
Integer b=10; // zamiast new Integer(10);
a=b;
Możliwości dziedziczenia :
class ParaInt extends Para<Integer,Integer>
{ public ParaInt( Integer pierwszy, Integer drugi)
{super(pierwszy,drugi); }
}
class ParaJednorodna<T> extends Para<T,T>
{ public ParaJednorodna(T pierwszy, T drugi)
{super(pierwszy, drugi);}
}
Parametry typu uogólnionego mogą zostać zastąpione dowolną klasą, możemy jednak ograniczyć możliwości zastępowania.
class Para1< T implements Cloneable> extends ParaJednorodna<T>
{...} // parametr t musi być klasą implementującą interface Cloneable
class Para2< T implements Cloneable&Serializable> extends ParaJednorodna<T>
{...} // parametr T musi być klasą implementującą interfejsy Cloneable i Serializable
class Para3<T1, T2 extends T1> {..} // T2 musi być podklasą T1
Użycie dżokerów :
Para<?,?> a = .....
Para<?,A> b=
Para<A, ? extends A> c =
Para <A, ? super A> d =
Para< A, ? extends Cloneable> e =
Można również parametryzować metody ( również w zwykłych klasach ), należy przy tym stosować zasady obowiązujące przy przeciążaniu metod.
<T> T metoda(T parametr) {..}