====== Κληρονομικότητα ======
Βασικό χαρακτηριστικό του αντικειμενοστραφούς προγραμματισμού είναι η δυνατότητα να παράγουμε νέες κλάσεις με βάση υφιστάμενες, εξειδικεύοντας και επεκτείνοντας τα χαρακτηριστικά τους. Η διαδικασία επέκτασης των υφιστάμενων κλάσεων σε νέες ειδικότερες κλάσεις ονομάζεται κληρονομικότητα.
Κάθε κλάση που κληρονομεί από μία άλλη κλάση ονομάζεται υποκλάση (//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) μετατροπές τύπων ]]|