This is an old revision of the document!
Βασικό χαρακτηριστικό του αντικειμενοστραφούς προγραμματισμού είναι η δυνατότητα να παράγουμε νέες κλάσεις με βάση υφιστάμενες κλάσεις, εξειδικεύοντας και επεκτείνοντας τα χαρακτηριστικά των υφιστάμενων. Η διαδικασία επέκτασης των υφιστάμενων κλάσεων σε νέες ειδικότερες κλάσεις ονομάζεται κληρονομικότητα.
Κάθε κλάση που κληρονομεί από μία άλλη κλάση ονομάζεται υποκλάση (subclass) της γονικής κλάσης από την οποία κληρονομεί. Αντίστοιχα, η γονική κλάση ονομάζεται υπερκλάση (superclass) της κληρονομούμενης κλάσης.
Όπως φαίνεται και στο παραπάνω σχήμα μία κλάση (subclass) μπορεί να κληρονομεί ΜΟΝΟ ΜΙΑ άλλη κλάση. Αντίστροφα μία κλάση (superclass) μπορεί να κληρονομείται από πολλές διαφορετικές κλάσεις. Παρακάτω δίνουμε ένα παράδειγμα κληρονομικότητας από το site της Oracle, όπου η κλάση MountainBike αποτελεί εξειδίκευση της κλάσης Bicycle.
public class Bicycle { private int cadence; private int gear; private int speed; public Bicycle(int startCadence, int startSpeed, int startGear) { gear = startGear; cadence = startCadence; speed = startSpeed; } public int getCadence() { return cadence; } public int getGear() { return gear; } public int getSpeed() { return speed; } public void setCadence(int newValue) { cadence = newValue; } public void setGear(int newValue) { gear = newValue; } public void applyBrake(int decrement) { speed -= decrement; } public void speedUp(int increment) { speed += increment; } }
public class MountainBike extends Bicycle { private int seatHeight; // the MountainBike subclass has // one constructor public MountainBike(int startHeight, int startCadence, int startSpeed, int startGear) { super(startCadence, startSpeed, startGear); seatHeight = startHeight; } // the MountainBike subclass has // one method public void setHeight(int newValue) { seatHeight = newValue; } public int getSeatHeight() { return seatHeight; } }
Μία κλάση η οποία κληρονομεί μία άλλη κλάση έχει πρόσβαση στα μέλη (πεδία και μεθόδους) της κλάσης αυτής ως εξής:
Αν υπάρχουν public μέθοδοι οι οποίες επιτρέπουν την πρόσβαση σε private πεδία, τότε αυτές μπορούν να χρησιμοποιηθούν για τον ορισμό ή για την λήψη της τιμής τους.
Σε συνέχεια του προηγούμενου παραδείγματος κληρονομικότητας μπορούμε να γράψουμε
MountainBike myBike = new MountainBike();
Από την παραπάνω δήλωση η μεταβλητή myBike είναι τύπου MountainBike. Επειδή όμως ο τύπος MountainBike κληρονομεί από την μεταβλητή Bicycle η συγκεκριμένη μεταβλητή είναι και τύπου Bicycle. Επομένως θα μπορούσαμε να γράψουμε
Bicycle myBicycle = myBike; //ή Βicycle yourBicycle = new MountainBike();
Την παραπάνω ανάθεση την ονομάζουμε Implicit Casting διότι αναθέτουμε μία μεταβλητή ενός τύπου δεδομένων (myBike) σε μία μεταβλητή γονικού τύπου δεδομένων (myBicycle), χωρίς type casting.
Ας δοκιμάσουμε το ανάποδο παράδειγμα τώρα
Bycycle myBicycle = new Bicycle(); MountainBike myBike = myBicycle;
Σε αυτή την περίπτωση ο compiler διαμαρτύρεται, διότι η μεταβλητή myBicycle είναι τύπου Bicycle και δεν είναι απαραίτητο ότι είναι και τύπου MountainBike. Αν θέλουμε να ξεπεράσουμε το παραπάνω πρόβλημα θα πρέπει να γράψουμε το εξής:
Bycycle myBicycle = new Bicycle(); MountainBike myBike = (MountainBike) myBicycle;
Εδώ ενημερώνουμε τον compiler ότι η μεταβλητή myBicycle είναι και τύπου MountainBike, λαμβάνοντας ο προγραμματιστής την ευθύνη ότι κάτι τέτοιο ισχύει. Εάν δεν ισχύει κάτι τέτοιο κατά την εκτέλεση του προγράμματος θα παραχθεί μία εξαίρεση (exception)*. Θα δούμε πιο κάτω τι είναι και πως διαχειριζόμαστε τις εξαιρέσεις.
Ένας πιο ασφαλής τρόπος για να επαναλάβουμε τον παραπάνω κώδικα, χωρίς να παραχθεί εξαίρεση είναι ο εξής:
Bycycle myBicycle = new Bicycle(); MountainBike myBike; if (myBicycle instanceof MountainBike) { myBike = (MountainBike)myBicycle; }
Μπορείτε να δηλώσετε μία ή περισσότερες μεθόδους μία κλάσης ως final. Δηλώνοντας μία μέθοδο ως final η μέθοδος αυτή δεν μπορεί να επαναοριστεί σε μία υποκλάση της κλάσης αυτής. Ο συχνότερος λόγος για να δηλώσετε μία μέθοδο ως final είναι αν η υλοποίηση της μεθόδου δεν πρέπει να αλλάξει. Ένα παράδειγμα είναι το παρακάτω:
class ChessAlgorithm { enum ChessPlayer { WHITE, BLACK } ... final ChessPlayer getFirstPlayer() { return ChessPlayer.WHITE; } ... }
Γενικότερα, μέθοδοι που καλούνται από τους κατασκευαστές της κλάσης θα πρέπει να ορίζονται ως final καθώς αν αλλάζουν την υλοποίηση τους σε υποκλάσεις μπορούν να δημιουργηθούν προβλήματα ως προς την ορθή αρχικοποίηση των μεταβλητών της κλάσης.
Τέλος, μπορείτε να προσδιορίσετε μία κλάση ως final, όταν θέλετε να δηλώσετε ότι η συγκεκριμένη κλάση δεν πρέπει να έχει υποκλάσεις. Ένα παράδειγμα τέτοια κλάσης είναι η κλάση String της standard βιβλιοθήκης της Java.
| Προηγούμενο: Διεπαφές | Επόμενο: Κληρονομικότητα πολλαπλών γονικών κλάσεων |