====== Κληρονομικότητα ====== Βασικό χαρακτηριστικό του αντικειμενοστραφούς προγραμματισμού είναι η δυνατότητα να παράγουμε νέες κλάσεις με βάση υφιστάμενες, εξειδικεύοντας και επεκτείνοντας τα χαρακτηριστικά τους. Η διαδικασία επέκτασης των υφιστάμενων κλάσεων σε νέες ειδικότερες κλάσεις ονομάζεται κληρονομικότητα. Κάθε κλάση που κληρονομεί από μία άλλη κλάση ονομάζεται υποκλάση (//subclass//) της γονικής κλάσης από την οποία κληρονομεί. Αντίστοιχα, η γονική κλάση ονομάζεται υπερκλάση (//superclass//) της κληρονομούμενης κλάσης. {{ :java:inheritance-1.png | }} Όπως φαίνεται και στο παραπάνω σχήμα μία κλάση (//subclass//) μπορεί να κληρονομεί __**ΜΟΝΟ ΜΙΑ**__ άλλη κλάση. Αντίστροφα μία κλάση (//superclass//) μπορεί να κληρονομείται από πολλές διαφορετικές κλάσεις. Παρακάτω δίνουμε ένα παράδειγμα κληρονομικότητας ως συνέχεια των προηγούμενων ενοτήτων. Ορίζουμε την κλάση ''BasicRectangle'' η οποία αποτελεί το απλό ορθογώνιο παραλληλόγραμμο που γνωρίσαμε στην αρχή και την κλάση ''Rectangle'' που αποτελεί εξειδίκευση της κλάσης ''BasicRectangle'' ορίζοντας επιπλέον το πεδίο ''origin''. public class BasicRectangle { int width; int height; public BasicRectangle(int initWidth, int initHeight) { width = initWidth; height = initHeight; } public void setWidth(int newWidth ) { width = newWidth; } public void setHeight(int newHeight ) { height = newHeight; } public int getWidth() { return width; } public int getHeight() { return height; } public String toString() { return "width: "+width+", height: "+height; } } class Point { int x; // x coordinate int y; // y coordinate public Point(int xPos, int yPos) { x = xPos; y = yPos; } int getX() { return x; } void setX(int xPos) { x = xPos; } int getY() { return y; } void setY(int yPos) { y = yPos; } public String toString() { return "("+x+","+y+")"; } } public class Rectangle extends BasicRectangle { Point origin; public Rectangle(int initWidth, int initHeight, Point initOrigin) { super(initWidth, initHeight); origin = initOrigin; } public Rectangle(int initWidth, int initHeight, int originX, int originY) { super(initWidth, initHeight); origin = new Point(originX,originY); } public void setOrigin(Point newOrigin) { origin = newOrigin; } public Point getOrigin() { return origin; } int area() { return width * height; } // Move rectangle origin by dx,dy public void moveOrigin(int dx, int dy) { origin.setX( origin.getX() + dx ); origin.setY( origin.getY() + dy ); } public String toString() { String str = origin.toString() + " "; str = str + super.toString(); return str; } } === Παρατηρήσεις === * Η κλάση //BasicRectangle// έχει τα πεδία //width, height//, ενώ η κλάση //Rectangle// διαθέτει τα πεδία αυτά και επιπλέον το πεδίο //origin//. * Βασικό χαρακτηριστικό της κληρονομικότητας είναι ότι η κληρονομούμενη κλάση έχει όλα τα χαρακτηριστικά της γονικής κλάσης. Υπό αυτή την έννοια, τα αντικείμενα της κλάσης //Rectangle// είναι και του τύπου //BasicRectangle//. * Παρατηρήστε τη δεσμευμένη λέξη //super// ως πρώτη εντολή του κάθε κατασκευαστή. Μέσω της κλήσης //super// καλείται ο κατασκευαστής της γονικής κλάσης. ===== Προσβασιμότητα των κληρονομούμενων πεδίων ===== Μία κλάση η οποία κληρονομεί μία άλλη κλάση έχει πρόσβαση στα μέλη (πεδία και μεθόδους) της κλάσης αυτής ως εξής: * Έχει πρόσβαση στα //**public**// και //**protected**// μέλη της γονικής κλάσης * Έχει πρόσβαση στα //**package private**// μέλη (δηλ. τα μέλη χωρίς προσδιοριστή πρόσβασης) μόνο αν βρίσκεται στο ίδιο πακέτο με την γονική κλάση. * Δεν έχει πρόσβαση στα //**private**// μέλη της κλάσης. Αν υπάρχουν //**public**// μέθοδοι οι οποίες επιτρέπουν την πρόσβαση σε //**private**// πεδία, τότε αυτές μπορούν να χρησιμοποιηθούν για τον ορισμό ή για την λήψη της τιμής τους. ===== Τι μπορούμε να κάνουμε σε μία υποκλάση... ===== * Να χρησιμοποιήσουμε τα πεδία της γονικής κλάσης στα οποία έχουμε πρόσβαση (public, protected, package-private στο ίδιο package). * Να ορίσουμε νέα πεδία. * Να χρησιμοποιήσουμε τις μεθόδους της γονικής κλάσης στις οποίες έχουμε πρόσβαση (public, protected, package-private στο ίδιο package). * Μπορούμε να γράψουμε νέες στατικές ή μη στατικές μεθόδους για τη υποκλάση. * Μπορούμε να γράψουμε νέες μεθόδους που έχουν το ίδιο //signature// (ίδιο όνομα, ίδιο αριθμό και ίδιο τύπο ορισμάτων), ώστε να επαναορίσουμε (//override//) τις μεθόδους αυτές στην υποκλάση. Η πρακτική αυτή είναι συνήθης. * Μπορούμε να γράψουμε νέες στατικές (//static//) μεθόδους που έχουν το ίδιο //signature//, ώστε να επαναορίσουμε (//override//) τις μεθόδους αυτές στην υποκλάση. Επαναορίζοντας στατικές μεθόδους, κρύβουμε τις αντίστοιχες μεθόδους της γονικής κλάσης. * Μπορούμε να γράψουμε κατασκευαστές της υποκλάσης που χρησιμοποιούν κατασκευαστές της γονικής κλάσης. ===== ΔΕΝ συνιστάται να κάνουμε σε μία υποκλάση... ===== * Να ορίσουμε νέα πεδία που να έχουν ίδιο όνομα με πεδία της γονικής κλάσης. Σε αυτή την περίπτωση "κρύβουμε" τα πεδία της γονικής κλάσης. Η συγκεκριμένη πρακτική δεν συνίσταται και παραπέμπει σε λανθασμένη σχεδίαση κώδικα. |Προηγούμενο: [[ :java:access_modifiers | Περιοριστές Πρόσβασης ]] | Επόμενο: [[ :java:type_casting | Ρητές (explicit) και άρρητες (implicit) μετατροπές τύπων ]]|