Polimorfizm dr Jarosław Skaruz http://jareks.ii.uph.edu.pl jaroslaw@skaruz.com
O czym będzie? finalne składowe klasy abstrakcyjne interfejsy polimorfizm
Finalne składowe Domyślnie wszystkie pola i metody mogą być przesłonięte w podklasach Można ograniczyć przesłanianie pól i metod za pomocą słowa final np. public final int wysokosc = 100; public final double oblicz(double parameter) Mechanizm zapewnia, że w podklasach nie można zdefiniować pola wysokość i metody oblicz
Finalne klasy Domyślnie wszystkie klasy umożliwiają dziedziczenie od nich Można ograniczyć możliwość dziedziczenia za pomocą słowa final np. public final class Pieniadz { // składowe klasy public final double oblicz(double parameter) Próba dziedziczenia z klasy finalnej zakończy się błędem kompilatora
Klasy abstrakcyjne W pewnych sytuacjach chcemy zawsze wykorzystać pola i metody klasy za pomocą mechanizmu dziedziczenia. Takie nadklasy nazywamy klasami abstrakcyjnymi Klasa abstrakcyjna jest jedynie klasą konceptualną, tzn. nie ma możliwości utworzenia obiektu klasy abstrakcyjnej Klasy abstrakcyjne dostarczają jedynie pól i metod wykorzystywanych przez wiele innych podklas. Stanowią one korzeń w hierarchii dziedziczenia
Składnia klas abstrakcyjnych public abstract class nazwaklasy { abstract typ nazwametody(); public typ nazwametody2() { // ciało metody 2 Każda podklasa musi definiować metody, które są abstrakcyjne w nadklasie Nie można definiować abstrakcyjnych konstruktorów ani abstrakcyjnych statycznych metod.
Przykład klas abstrakcyjnych Figura jest klasą abstrakcyjną Wszystkie klasy dziedziczące od figury posiadają wspólne cechy i zachowania
Kod klasy abstrakcyjnej Figure public abstract class Figure { public abstract double area(); public void move() { // nieabstrakcyjna metoda // ciało metody Czy możemy już utworzyć obiekt klasy Figure? Figure figure = new Figure()
Klasy dziedziczące po klasie abstrakcyjnej public Circle extends Figure { protected double r; protected static final double PI =3.1415926535; public Circle() { r = 1.0; public double area() { return PI * r * r; public Rectangle extends Figure { protected double w, h; public Rectangle() { w = 0.0; h=0.0; public double area() { return w * h;
Podsumowanie klas abstrakcyjnych Klasa z minimum jedną metodą abstrakcyjną musi być automatycznie abstrakcyjna Klasa abstrakcyjna, pomimo braku metod abstrakcyjnych, nie może posiadać obiektów Każda podklasa klasy abstrakcyjnej musi implementować wszystkie metody abstrakcyjne nadklasy Podklasa klasy abstrakcyjnej, która nie implementuje wszystkich metod klasy abstrakcyjnej, musi być sama klasą abstrakcyjną Słowo final uniemożliwia dziedziczenie oraz przesłanianie pól i metod
Interfejsy Interfejs jest bytem podobnym pod względem koncepcji do klas abstrakcyjnych Może zawierać jedynie deklaracje metod i stałe finalne Stosuje się, gdy wiele klas współdzieli wspólne zachowanie Każda klasa implementująca dany interfejs musi posiadać implementacje wszystkich metod zadeklarowanych w interfejsie Klasa może implementować dowolnie wiele interfejsów Jest sposobem obejścia ograniczenia związanego z jednokrotnym dziedziczeniem
Interfejsy W ogólności w definicji interfejsu wszystkie metody są domyślnie public i abstract, Stale są domyślnie public, static i final. zabrania się używania specyfikatorów private i protected Dostęp do zadeklarowanej w interfejsie metody lub stałej uzyskuje się za pomocą operatora kropkowego, Np. Sleeper.ONE_SECOND.
Przykład interfejsów
Definicja interfejsów public interface InterfaceName { // deklaracje pól (jedynie stałe finalne) // Deklaracje metod public interface Speaker { public void speak( );
Implementacje interfejsów Interfejsy używane są podobnie jak nadklasy, których metody są dziedziczone class Politician implements Speaker { public void speak(){ System.out.println( Talk politics ); class Priest implements Speaker { public void speak(){ System.out.println( Religious Talks ); class Lecturer implements Speaker { public void speak(){ System.out.println( Talks Object Oriented Design and Programming! );
Dziedziczenie interfejsów Podobnie jak klasy, interfejsy również mogą dziedziczyć po innych interfejsach interface InterfaceName2 extends InterfaceName1 { // ciało interfejsu 2 Klasa może dziedziczyć po innej klasie oraz jednocześnie implementować wiele interfejsów class ClassName extends SuperClass implements InterfaceName [, InterfaceName2, ] { // Body of Class
Zastosowanie interfejsów Załóżmy, że w programie chcemy odwzorować piosenkarzy, autorów piosenek oraz osoby jednocześnie piszące teksty piosenek jak i wykonujące utwory public interface Singer { public void sing(); public interface SongWriter { public void writesong(); public class SingerSongWriter implements Singer, SongWriter { public void sing() { // ciało metody public void songwriter() { // ciało metody
Podsumowanie interfejsów Interfejsy podobnie jak klasy abstrakcyjne dostarczają wzorzec zachowania dla klas implementujących interfejs Interfejsy stanowią separację projektu obiektowego i implementacji Dostarczają opisu metod a nie ich implementacji Pozwalają klasom implementującym na powielanie zachowania z wielu interfejsów jednocześnie Pozwalają na użycie obiektu, bez konieczności znajomości jego typu Zalecenie! W przypadku rozszerzenia danego interfejsu o jakąś metodę należy stworzyć nowy interfejs udostępniający zakładaną funkcjonalność. Dodanie do uprzedniego interfejsu spowoduje wygenerowanie błędu w klasach, które go implementują.
Interfejsy vs klasy abstrakcyjne W klasie abstrakcyjnej w przeciwieństwie do interfejsów mogą być pola nonstatic oraz mogą być implementacje metod. Klasy abstrakcyjne mogą być wykorzystywane do koncepcji współdzielenia kodu. Metoda w klasie abstrakcyjnej może wywoływać metodę innej klasy dziedziczącej po klasie abstrakcyjnej
Polimorfizm Polimorfizm pozwala wirtualnej maszynie na określenie w trakcie wykonywania programu, która wersja metody powinna zostać wywołana. W trakcie kompilacji nie można stwierdzić jaką referencję będzie przechowywała zmienna typu Figure.
Polimorfizm - przykład public class Shape { public double area() { return 0.0; public double perim() { return 0.0; public class Square extends Shape { double b; Square(double x) { b = x; public double area() { return b*b; public double perim() { return 4 * b; public class Rectangle extends Shape { double a,b; Rectangle(double x, double y) { a = x; b =y; public double area() { return a*b; public double perim() { return 2*a+2*b;
Polimorfizm - przykład public class Program { public static void main (String [] arg) { Shape [] tab = {new Square(3), new Rectangle(1,2); System.out.print(" Square : "); System.out.println(" area = " + tab[0].area() + " perim = " + tab[0].perim()); System.out.print(" Rectangle : "); System.out.println(" area = " + tab[1].area() + " perim = " + tab[1].perim());
Polimorfizm przykład 2 public class Animals { boolean hungry = true; Animals() { public String song() { if (hungry) return "???????? "; else return " OK " ; public void change() { hungry =!hungry; public class Dog extends Animals { public String song() {//przesłonięcie if (hungry) return " wow wow wow "; else return " woooooow " ; public class Cat extends Animals { public String song() { //przesłonięcie if (hungry) return " miau miau miau "; else return " miauuuuu " ;
Polimorfizm przykład 2 public class Program { public static void main (String [] arg) { Animals [] collection = new Animals [6]; collection [0] = new Dog(); collection [1] = new Cat(); collection [2] = new Animals(); for(int i =0; i< 3; i++) System.out.println( collection[i].song() ); // " wow wow wow // " miau miau miau // "?????????????? "
Dziedziczenie interfejsów - przykład interface OriginPoint { double originx = 0.0d; // automatycznie static i final double originy = 0.0d; public double distancetoorigin(); interface Shape { public double area(); public double perim(); public String whoami(); //dziedziczenie interfejsu interface ShapeCP extends Shape { public void setcp(double x, double y); public void viewcp();
Dziedziczenie interfejsów - przykład // implementacja wielu interfejsów public class Square implements ShapeCP, OriginPoint { private double a,xc,yc; Square(double n ) { a = n; public double area() {return a*a; public double perim() {return 4*a; public String whoami() {return "Square"; public void setcp(double x, double y){xc = x; yc = y; ; public void viewcp(){ System.out.println("Center at " + xc + " : " + yc); public double distancetoorigin() { return Math.sqrt(Math.pow(xc-originX,2.0)+ Math.pow(yc-originY,2.0));;
Dziedziczenie interfejsów przykład c.d. public class Rectangle implements ShapeCP, OriginPoint { private double a,b,xc,yc; Rectangle(double x, double y){a = x; b = y; public double area() {return a*b; public double perim() {return 2*a+2*b; public String whoami() {return "Rectangle"; public void setcp(double x, double y){xc = x; yc = y; public void viewcp(){ System.out.println("Center at " + xc + " : " + yc); public double distancetoorigin() { return Math.sqrt(Math.pow(xc-originX,2.0)+ Math.pow(yc-originY,2.0));
Dziedziczenie interfejsów przykład c.d. public class Program { public static void main(string [] arg) { Square s = new Square(2); Rectangle r = new Rectangle(3,1); s.setcp(1,1); r.setcp(5,5); ShapeCP t1 = s; // na interfejs można rzutować t1.viewcp(); // późne wiązanie ShapeCP t2 = r; t2.viewcp(); // późne wiązanie // t1 i t2 nie można użyć bo ShapeCP nie zna metody distancetoorigin() if (s.distancetoorigin() > r.distancetoorigin()) System.out.println(r.whoI() + " jest bliżej niż " + s.whoi()); else System.out.println(s.whoI() + " jest bliżej niż " + r.whoi());