Skip to the content.

2.5 Πολυμορφισμός

© Γιάννης Κωστάρας


<- Δ ->

Η λέξη πολυμορφισμός (polymorphism) σημαίνει “πολλές μορφές”. Στον αντικειμενοστραφή προγραμματισμό σημαίνει ότι μια μέθοδος μπορεί να παίρνει πολλές μορφές. Σχετίζεται με την αποσύνδεση των μεθόδων από τους τύπους.

Υπάρχουν τα εξής είδη πολυμορφισμού:

Υπερφόρτωση μεθόδων (method overloading)

Αναφέρεται σε μεθόδους που έχουν το ίδιο όνομα αλλά δέχονται διαφορετικές παραμέτρους (είτε διαφορετικό αριθμό παραμέτρων είτε ίδιο αριθμό αλλά με διαφορετικούς τύπους δεδομένων).

Π.χ.

public class Shape {   
  protected abstract void draw();
  protected abstract void draw(Graphics g);
}

Η μέθοδος draw() έχει υπερφορτωθεί. Το ίδιο μπορεί να γίνει και με τις μεθόδους κατασκευής (constructors). Σημειώστε ότι είναι λάθος μεταγλώττισης να ορίσετε δυο μεθόδους με το ίδιο όνομα και αριθμό και τύπο παραμέτρων που να επιστρέφουν όμως διαφορετικό τύπο δεδομένων. Η υπερφόρτωση αφορά μόνο τον τύπο και τον αριθμό των παραμέτρων.

Υπερκάλυψη μεθόδων (method overriding)

Την είδαμε στο προηγούμενο μάθημα. Π.χ. η κλάση Circle υπερκάλυψε (δηλ. επανακαθόρισε) τις δυο abstract μεθόδους area() και perimeter() της υπερκλάσης Shape. Αν θέλουμε να καλέσουμε πρώτα τον κώδικα της υπερσκελισμένης μεθόδου της υπερκλάσης μέσα στην μέθοδο της υποκλάσης, χρησιμοποιούμε τη δεσμευμένη λέξη super, π.χ.

public double area() {
	super.area();
	//...
}

Σύγκριση Υπερκάλυψης (Override) και Υπερφόρτωσης (Overload)

Η Υπερφόρτωση (overloading) μπορεί να πραγματοποιηθεί

Επίσης, οι μέθοδοι κατασκευής μπορούν να υπερφορτωθούν.

Η Υπερκάλυψη (Overriding) μπορεί να συμβεί μόνο μέσω κληρονομικότητας συνήθως όταν η υπερκλάση έχει ορίσει το γνώρισμα ή τη μέθοδο με τον τροποποιητή protected. Παραδείγματα υπερκάλυψης είδαμε στο προηγούμενο μάθημα όπου π.χ. η κλάση Circle υπερκάλυψε τις μεθόδους area() και perimeter() της κλάσης Shape.

Οι ακόλουθες δηλώσεις μεθόδων δεν μπορούν να υπερκαλυφθούν:

Μία υποκλάση δεν μπορεί να κάνει override μια static μέθοδο (στην περίπτωση αυτή γίνεται επικάλυψη (hiding) όπως είδαμε στο προηγούμενο μάθημα). Μία υποκλάση δεν μπορεί να κάνει override μία final μέθοδο. Οι μέθοδοι κατασκευής δεν υπερκαλύπτονται.

Όταν μια υποκλάση υπερκαλύπτει μια μέθοδο της υπερκλάσης, θα πρέπει να δώσει τα ίδια ή περισσότερα δικαιώματα πρόσβασης, αλλά όχι λιγότερα. Π.χ. αν η γονική μέθοδος παρέχει protected πρόσβαση, τότε η υποκλάση δεν μπορεί να δώσει π.χ. πρόσβαση private (λάθος μεταγλώττισης).

Πρόσδεση (Binding) και τύποι πρόσδεσης

Με τον όρο Πρόσδεση (Binding) εννοούμε την σύνδεση δυο πραγμάτων, π.χ. τη σύνδεση μιας μεταβλητής με μια τιμή είτε το κάλεσμα μιας μεθόδου με τις παραμέτρους που της περνάμε. Υπάρχουν δυο είδη πρόσδεσης:

Π.χ.

public class PaintApp {

	public void draw(Shape s) {
		//...
		s.draw();
	}
}

PaintApp app = new PaintApp();
Shape circle = new Circle()
Shape rectangle = new Rectangle();
app.draw(circle);
app.draw(rectangle);

Όπως βλέπουμε, η μέθοδος draw() δέχεται ως όρισμα ένα αντικείμενο τύπου Shape. Δεν μπορεί να γνωρίζει εκ των προτέρων αν αυτό είναι Circle ή Rectangle παρά μόνο κατά την εκτέλεση του προγράμματος.

Η Java διαθέτει τον τελεστή instanceof ο οποίος μας επιτρέπει να ελέγξουμε τον τύπο ενός αντικειμένου, π.χ.

if (s instanceof Circle) {
	((Circle)c).getRadius();
}

Ένα από τα πιο συνηθισμένα bugs είναι η κλήση μιας υπερσκελισμένης μεθόδου στον constructor της υπερκλάσης. Ας δούμε ένα παράδειγμα:

public interface IShape {
    double area();
    double perimeter();
    void draw();
}

public abstract class Shape implements IShape {

    protected final Point[] points;

    Shape(int edges) {
        this.points = new Point[edges];
        draw();	// (2)
    }

    Shape(Point[] points) {
        this.points = points;
    }

    public int getEdges() {
        return this.points.length;
    }
    
    @Override
    public void draw() {
        System.out.println("Draws a shape...");
    }
}

public class Rectangle extends Shape {

    private final int width, height;

    Rectangle(int width, int height) {
        super(4);
        this.width = width;
        this.height = height;
    }

    Rectangle(Point[] points, int width, int height) {
        super(points);
        this.width = width;
        this.height = height;
    }

    public int getWidth() {
        return width;
    }

    public int getHeight() {
        return height;
    }

    @Override
    public double area() {
        return width * height;
    }

    @Override
    public double perimeter() {
        return 2 * width + 2 * height;
    }
    
    @Override
    public void draw() {
        System.out.println("Draws a rectangle...");
    }    
}

public class Circle extends Shape {

    private final int radius;

    Circle() {
        super(1);	// (1)
        this.radius = 1;
    }

    Circle(Point[] points, int radius) {
        super(points);
        this.radius = radius;
    }

    public int getRadius() {
        return radius;
    }

    @Override
    public double area() {
        return Math.PI * (radius * radius);
    }

    @Override
    public double perimeter() {
        return Math.PI * 2 * radius;
    }

    @Override
    public void draw() {
        System.out.println("Draws a circle...");	// (3)
    }
}

public class Main {
    public static void main(String[] args) {
        Shape s = new Circle();
        s.draw();	// (4)
    }
}

Τι θα τυπώσει ο παραπάνω κώδικας; Το NetBeans ήδη προειδοποιεί: Overridable method call in constructor. Η υπερφορτωμένη μέθοδος draw() καλείται από τον constructor της Shape. Ενώ θα περιμέναμε η κλήση της draw() στον constructor της Shape να καλέσει την Shape.draw(), καλεί την Circle.draw() γι’ αυτό και η έξοδος του προγράμματος εκτυπώνει:

Draws a circle...
Draws a circle...

Πιο αναλυτικά η ροή του προγράμματος φαίνεται στα νούμερα μέσα στις παρενθέσεις.


<- Δ ->