User Tools

Site Tools


java:inheritance

This is an old revision of the document!


Κληρονομικότητα

Βασικό χαρακτηριστικό του αντικειμενοστραφούς προγραμματισμού είναι η δυνατότητα να παράγουμε νέες κλάσεις με βάση υφιστάμενες κλάσεις, εξειδικεύοντας και επεκτείνοντας τα χαρακτηριστικά των υφιστάμενων. Η διαδικασία επέκτασης των υφιστάμενων κλάσεων σε νέες ειδικότερες κλάσεις ονομάζεται κληρονομικότητα.

Κάθε κλάση που κληρονομεί από μία άλλη κλάση ονομάζεται υποκλάση (subclass) της γονικής κλάσης από την οποία κληρονομεί. Αντίστοιχα, η γονική κλάση ονομάζεται υπερκλάση (superclass) της κληρονομούμενης κλάσης.

Όπως φαίνεται και στο παραπάνω σχήμα μία κλάση (subclass) μπορεί να κληρονομεί ΜΟΝΟ ΜΙΑ άλλη κλάση. Αντίστροφα μία κλάση (superclass) μπορεί να κληρονομείται από πολλές διαφορετικές κλάσεις. Παρακάτω δίνουμε ένα παράδειγμα κληρονομικότητας από το site της Oracle, όπου η κλάση MountainBike αποτελεί εξειδίκευση της κλάσης Bicycle.

Bicycle.java
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;
    }
 
}
MountainBike.java
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 και protected μέλη της γονικής κλάσης
  • Έχει πρόσβαση στα package private μέλη (δηλ. τα μέλη χωρίς προσδιοριστή πρόσβασης) μόνο αν βρίσκεται στο ίδιο πακέτο με την γονική κλάση.
  • Δεν έχει πρόσβαση στα private μέλη της κλάσης.

Αν υπάρχουν public μέθοδοι οι οποίες επιτρέπουν την πρόσβαση σε private πεδία, τότε αυτές μπορούν να χρησιμοποιηθούν για τον ορισμό ή για την λήψη της τιμής τους.

Τι μπορούμε να κάνουμε σε μία υποκλάση...

  • Να χρησιμοποιήσουμε τα πεδία της γονικής κλάσης στα οποία έχουμε πρόσβαση (public, protected, package-private στο ίδιο package).
  • Να ορίσουμε νέα πεδία.
  • Να ορίσουμε νέα πεδία που να έχουν ίδιο όνομα με πεδία της γονικής κλάσης. Σε αυτή την περίπτωση “κρύβουμε” τα πεδία της γονικής κλάσης. Η συγκεκριμένη πρακτική δεν συνίσταται και παραπέμπει σε λανθασμένη σχεδίαση κώδικα.
  • Να χρησιμοποιήσουμε τις μεθόδους της γονικής κλάσης στις οποίες έχουμε πρόσβαση (public, protected, package-private στο ίδιο package).
  • Μπορούμε να γράψουμε νέες στατικές ή μη στατικές μεθόδους για τη υποκλάση.
  • Μπορούμε να γράψουμε νέες μεθόδους που έχουν το ίδιο signature (ίδιο όνομα, ίδιο αριθμό και ίδιο τύπο ορισμάτων), ώστε να επαναορίσουμε (override) τις μεθόδους αυτές στην υποκλάση. Η πρακτική αυτή είναι συνήθης.
  • Μπορούμε να γράψουμε νέες στατικές (static) μεθόδους που έχουν το ίδιο signature, ώστε να επαναορίσουμε (override) τις μεθόδους αυτές στην υποκλάση. Επαναορίζοντας στατικές μεθόδους, κρύβουμε τις αντίστοιχες μεθόδους της γονικής κλάσης.
  • Μπορούμε να γράψουμε κατασκευαστές της υποκλάσης που χρησιμοποιούν κατασκευαστές της γονικής κλάσης.

Implicit και Explicit Casting of Objects

Σε συνέχεια του προηγούμενου παραδείγματος κληρονομικότητας μπορούμε να γράψουμε

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 η μέθοδος αυτή δεν μπορεί να επαναοριστεί σε μία υποκλάση της κλάσης αυτής. Ο συχνότερος λόγος για να δηλώσετε μία μέθοδο ως final είναι αν η υλοποίηση της μεθόδου δεν πρέπει να αλλάξει. Ένα παράδειγμα είναι το παρακάτω:

class ChessAlgorithm {
    enum ChessPlayer { WHITE, BLACK }
    ...
    final ChessPlayer getFirstPlayer() {
        return ChessPlayer.WHITE;
    }
    ...
}

Γενικότερα, μέθοδοι που καλούνται από τους κατασκευαστές της κλάσης θα πρέπει να ορίζονται ως final καθώς αν αλλάζουν την υλοποίηση τους σε υποκλάσεις μπορούν να δημιουργηθούν προβλήματα ως προς την ορθή αρχικοποίηση των μεταβλητών της κλάσης.

Τέλος, μπορείτε να προσδιορίσετε μία κλάση ως final, όταν θέλετε να δηλώσετε ότι η συγκεκριμένη κλάση δεν πρέπει να έχει υποκλάσεις. Ένα παράδειγμα τέτοια κλάσης είναι η κλάση String της standard βιβλιοθήκης της Java.

java/inheritance.1455292032.txt.gz · Last modified: 2016/02/12 15:47 by gthanos