Interfaces και κληρονομικοτητα
Interfaces (Διεπαφές)
Τα interface ως έννοια στην Java μοιάζουν στον τρόπο ορισμού με τις κλάσεις αλλά περιλαμβάνουν μόνο σταθερές, ορισμούς μεθόδων (δεν περιλαμβάνουν υλοποίηση μεθόδων) και φωλιασμένους τύπους. Από τα interface δεν μπορούμε να δημιουργήσουμε στιγμιότυπο. Μπορούμε μόνο να τα υλοποιήσουμε μέσα σε μια κλάση (χρήση “implements”) ή να τα προεκτείνουμε (χρήση extends βλέπε κληρονομικότητα). Ουσιαστικά ένα interface αποτελεί ένα αφηρημένο σκελετό – σχέδιο μιας κλάσης και αποτελεί ένα είδος συμβολαίου -υπόσχεσης ότι η κλάση που το υλοποιεί παρέχει την υλοποίηση του σχεδίου του δηλαδή των μεθόδων. Ένα interface αποτελεί τύπο αναφοράς. Όταν μια κλάση το υλοποιεί μπορούμε να αναφερθούμε σε στιγμιότυπο της με τον τύπο του interface που υλοποιεί
Εδώ βλέπουμε τον ορισμό ενός interface
public interface OperateCar {
// δήλωση σταθερών
// ορισμών μεθόδων
// τύπος enum με τιμές RIGHT, LEFT
int turn(Direction direction,
double radius,
double startSpeed,
double endSpeed);
int changeLanes(Direction direction,
double startSpeed,
double endSpeed);
int signalTurn(Direction direction, boolean signalOn);
int getRadarFront(double distanceToCar, double speedOfCar);
int getRadarRear(double distanceToCar, double speedOfCar);
......
// περισσότερες μέθοδοι
}
Και εδώ την χρήση του
public class OperateBMW760i implements OperateCar {
// the OperateCar method signatures, with implementation --
// for example:
int signalTurn(Direction direction, boolean signalOn) {
// κώδικας για στροφή της BMW LEFT φλας on
// κώδικας για στροφή της BMW LEFT φλας off
// κώδικας για στροφή της BMW RIGHT φλας on
// κώδικας για στροφή της BMW RIGHT φλας off
}
// υπόλοιπη υλοποίηση
}
Σημείωση:
Όλες οι μέθοδοι ενός interface είναι εξ ορισμού public και δεν έχουν σώμα μέσα σε αγκύλες
Όλες οι σταθερές ενός interface είναι εξ ορισμού public static final.
Επειδή τα παραπάνω εννοούνται μπορούν να παραλειφθούν.
Παράδειγμα Χρήσης Interface (Διεπαφής) - Τεχνική Casting
Όπως είδαμε επιγραμματικά σε προηγούμενο κεφάλαιο, για να υλοποιήσουμε ένα interface πρέπει να χρησιμοποιήσουμε την έκφραση implements στο όρισμα μίας κλάσης. Μία κλάση μπορεί να υλοποιεί περισσότερα από ένα interface τα ονόματα των οποίων ορίζονται μετά το implements χωρισμένα με κόμμα. Το implements ακολουθεί πάντα το extends αν αυτό ορίζεται.
Παράδειγμα διεπαφής, Relatable
Ας δούμε ένα interface το οποίο ορίζει πώς συγκρίνεται το μέγεθος δύο αντικειμένων:
public interface Relatable {
// Το this (αντικείμενο που καλεί την isLargerThan)
// και το other πρέπει να είναι στιγμιότυπα
// της ίδιας κλάσης και να επιστρέφει 1, 0, -1
// αν το this είναι μεγαλύτερο από, ίσο με
// , ή μικρότερο από το other
public int isLargerThan(Relatable other);
}
Αν θέλουμε να μπορούμε να συγκρίνουμε το μέγεθος 2 παρομοίων αντικειμένων, ότι και αν αυτά είναι, η κλάση από της οποίας είναι στιγμιότυπα πρέπει να υλοποιεί το Relatable.
Οποιαδήποτε κλάση μπορεί να υλοποιεί το Relatable εάν υπάρχει τρόπος να συγκριθεί το σχετικό «μέγεθος» του στιγμιότυπου της. Για String μπορεί να μετράμε τους χαρακτήρες που περιέχει, για βιβλία τον αριθμό των σελίδων, για ανθρώπους μπορεί να είναι η ηλικία κλπ. Αρκεί η σύγκριση να έχει νόημα στην λογική της εφαρμογής μας.
Όλες οι κλάσεις που θέλουμε να συγκρίνουμε με αυτών τον τρόπο αρκεί να υλοποιούν την isLargerThan(). Αν γνωρίζουμε ότι μια κλάση υλοποιεί την Relatable, τότε ξέρουμε ότι μπορούμε να συγκρίνουμε το μέγεθος των στιγμιότυπων της. then you know that you can compare the size of the objects instantiated from that class.
Υλοποίηση διεπαφής Relatable
Εδώ έχουμε το παράδειγμα της κλάσης Rectangle που υλοποιεί την Relatable.
public class RectanglePlus implements Relatable {
public int width = 0;
public int height = 0;
public Point origin;
// 4 constructors
public RectanglePlus() {
origin = new Point(0, 0);
}
public RectanglePlus(Point p) {
origin = p;
}
public RectanglePlus(int w, int h) {
origin = new Point(0, 0);
width = w;
height = h;
}
public RectanglePlus(Point p, int w, int h) {
origin = p;
width = w;
height = h;
}
//μια μέθοδος για την μετακίνηση του Rectangle
public void move(int x, int y) {
origin.x = x;
origin.y = y;
}
// μια μέθοδος υπολογισμού
// του εμβαδού του Rectangle
public int getArea() {
return width * height;
}
// η μέθοδος υλοποίησης της
// διεπαφής Relatable
public int isLargerThan(Relatable other) {
RectanglePlus otherRect
= (RectanglePlus)other;
if (this.getArea() < otherRect.getArea())
return -1;
else if (this.getArea() > otherRect.getArea())
return 1;
else
return 0;
}
}
Επειδή το RectanglePlus υλοποιεί την Relatable, το μέγεθος των 2 αντικειμένων RectanglePlus μπορεί να συγκριθεί.
Type Casting
Η μέθοδος isLargerThan, όπως αυτή ορίζεται από την διεπαφή Relatable, δέχεται ως παράμετρο μια αναφορά σε αντικείμενο τύπου Relatable. Η έντονα τονισμένη γραμμή κώδικα του προηγούμενου παραδείγματος, μεταμορφώνει - μετασχηματίζει (casting) το other σε στιγμιότυπο της RectanglePlus. Η τεχνική «Type casting» γνωστοποιεί στον μεταγλωττιστή της Java τη είναι το κάθε αντικείμενο. Στη διάρκεια της μεταγλώττισης καλώντας την getArea απευθείας από το στιγμιότυπο other (other.getArea()) οδηγεί σε σφάλμα γιατί ο μεταγλωττιστής (compiler) δεν κατανοεί ότι το στιγμιότυπο other είναι στην πραγματικότητα στιγμιότυπο της RectanglePlus.
Όταν ορίζουμε μια νέα διεπαφή στην ουσία ορίζουμε ένα νέα αναφορά τύπου δεδομένων . Αν ορίσουμε μια μεταβλητή με τύπο κάποια διεπαφή, όποιο τα αντικείμενο ανατεθεί στην μεταβλητή αυτή πρέπει να είναι στιγμιότυπο μιας κλάσης που υλοποιεί την διεπαφή αυτή.
Για παράδειγμα, εδώ έχουμε τη μέθοδο που βρίσκει το μεγαλύτερο από 2 αντικείμενα, οποιαδήποτε αντικείμενα υλοποιούν την διεπαφή Relatable.
public Object findLargest(Object object1, Object object2) {
Relatable obj1 = (Relatable)object1;
Relatable obj2 = (Relatable)object2;
if ((obj1).isLargerThan(obj2) > 0)
return object1;
else
return object2;
}
Εμφανίζοντας (casting) το object1 σε Relatable αυτό μπορεί να καλέσει την μέθοδο isLargerThan
Εφόσον υλοποιηθεί η Relatable σε διάφορες κλάσεις, τα αντικείμενα που δημιουργούνται από αποιαδήποτε από αυτές τις κλάσεις μπορουν να συγκριθούν με την μέθοδο findLargest() – δεδομένου ότι και τα 2 αντικείμενα είναι της ίδιας κλάσης. Με τον ίδιο τρόπο μπορούμε να κάνουμε συγκρίσεις με τις ακόλουθες μεθόδους:
public Object findSmallest(Object object1, Object object2) {
Relatable obj1 = (Relatable)object1;
Relatable obj2 = (Relatable)object2;
if ((obj1).isLargerThan(obj2) < 0)
return object1;
else
return object2;
}
public boolean isEqual(Object object1, Object object2) {
Relatable obj1 = (Relatable)object1;
Relatable obj2 = (Relatable)object2;
if ( (obj1).isLargerThan(obj2) == 0)
return true;
else
return false;
}
Οι μέθοδοι αυτές «δουλεύουν» με οποιοδήποτε αντικείμενο Relatable χωρίς να έχει σημασία από ποια κλάση αυτό προέρχεται. Όταν αυτό υλοποιεί την Relatable μπορεί να εμφανιστεί και ως Relatable και ως ο εαυτός του. Η δυνατότητα αυτή παρέχει ιδιότητες πολλαπλής κληρονομικότητας εφόσον ένα αντικείμενο μπορεί να υιοθετεί λειτουργικότητα και από την superclass του και από μία ή περισσότερες διεπαφές.
Κληρονομικότητα
Πιο πριν είδαμε να αναφέρεται πολλές φορές η έννοια της κληρονομικότητας. Στην Java οι κλάσεις μπορούν να προέλθουν από άλλες κλάσεις κληρονομώντας έτσι πεδία και μεθόδους από αυτές τις κλάσεις.
Ορισμός: Μία κλάση που προέρχεται από μία άλλη ονομάζεται υπο-κλάση (ή επίσης προέκταση ή παιδί). Η κλάση από την οποία αυτή προέρχεται ονομάζεται σουπερ-κλάση (ή επίσης βάση ή μητρική).
Εκτός από την κλάση Object, που δεν έχει μητρική κλάση, όλες οι άλλες κλάσεις έχουν μία και μόνη μητρική (μοναδική κληρονομικότητα). Κάθε κλάση που ορίζουμε εάν δεν έχει ορισμένη μητρική εννοείται ότι είναι παιδί της Object.
Μια κλάση μπορεί να προέρχεται από μία κλάση που προέρχεται από μία άλλη ή οποία προέρχεται από μια άλλη κ.ο.κ., ώσπου να φτάσουμε στην κορυφαία που είναι η Object. Αυτή η κλάση λέμε ότι κληρονομεί από όλες τις κλάσεις στην αλυσίδα της κληρονομικότητας μέχρι την Object.
Η ιδέα της κληρονομικότητας είναι απλή αλλά πολύ δυνατή. Όταν θέλουμε να κατασκευάσουμε μία νέα κλάση και υπάρχει ήδη μία που περιλαμβάνει μερικό από τον κώδικα που χρειαζόμαστε, μπορούμε να την δημιουργήσουμε προεκτείνοντας την υπάρχουσα. Έτσι μπορούμε να ξαναχρησιμοποιήσουμε τα υπάρχοντα πεδία και μεθόδους της υπάρχουσας κλάσης χωρίς να πρέπει να τα ξαναγράψουμε και εκσφαλματώσουμε πάλι.
Οι υποκλάση κληρονομεί όλα τα μέλη της μητρικής (πεδία, μεθόδους και ενθυλακωμένες κλάσεις). Οι constructors δεν είναι μέλη και έτσι δεν κληρονομούνται, αλλά θα δούμε ότι μπορούμε να τους καλέσουμε από την υπο-κλάση
Ιεραρχία της Java
Η κλάση Object ορίζεται στο package java.lang και υλοποιεί συμπεριφορά κοινή για όλες τις κλάσεις. Πολλές κλάσεις κληρονομούν έμμεσα από την Object ενώ άλλες κληρονομούν από αυτές κ.ο.κ. δημιουργώντας την ιεραρχία των κλάσεων.
Τα interface ως έννοια στην Java μοιάζουν στον τρόπο ορισμού με τις κλάσεις αλλά περιλαμβάνουν μόνο σταθερές, ορισμούς μεθόδων (δεν περιλαμβάνουν υλοποίηση μεθόδων) και φωλιασμένους τύπους. Από τα interface δεν μπορούμε να δημιουργήσουμε στιγμιότυπο. Μπορούμε μόνο να τα υλοποιήσουμε μέσα σε μια κλάση (χρήση “implements”) ή να τα προεκτείνουμε (χρήση extends βλέπε κληρονομικότητα). Ουσιαστικά ένα interface αποτελεί ένα αφηρημένο σκελετό – σχέδιο μιας κλάσης και αποτελεί ένα είδος συμβολαίου -υπόσχεσης ότι η κλάση που το υλοποιεί παρέχει την υλοποίηση του σχεδίου του δηλαδή των μεθόδων. Ένα interface αποτελεί τύπο αναφοράς. Όταν μια κλάση το υλοποιεί μπορούμε να αναφερθούμε σε στιγμιότυπο της με τον τύπο του interface που υλοποιεί
Εδώ βλέπουμε τον ορισμό ενός interface
public interface OperateCar {
// δήλωση σταθερών
// ορισμών μεθόδων
// τύπος enum με τιμές RIGHT, LEFT
int turn(Direction direction,
double radius,
double startSpeed,
double endSpeed);
int changeLanes(Direction direction,
double startSpeed,
double endSpeed);
int signalTurn(Direction direction, boolean signalOn);
int getRadarFront(double distanceToCar, double speedOfCar);
int getRadarRear(double distanceToCar, double speedOfCar);
......
// περισσότερες μέθοδοι
}
Και εδώ την χρήση του
public class OperateBMW760i implements OperateCar {
// the OperateCar method signatures, with implementation --
// for example:
int signalTurn(Direction direction, boolean signalOn) {
// κώδικας για στροφή της BMW LEFT φλας on
// κώδικας για στροφή της BMW LEFT φλας off
// κώδικας για στροφή της BMW RIGHT φλας on
// κώδικας για στροφή της BMW RIGHT φλας off
}
// υπόλοιπη υλοποίηση
}
Σημείωση:
Όλες οι μέθοδοι ενός interface είναι εξ ορισμού public και δεν έχουν σώμα μέσα σε αγκύλες
Όλες οι σταθερές ενός interface είναι εξ ορισμού public static final.
Επειδή τα παραπάνω εννοούνται μπορούν να παραλειφθούν.
Παράδειγμα Χρήσης Interface (Διεπαφής) - Τεχνική Casting
Όπως είδαμε επιγραμματικά σε προηγούμενο κεφάλαιο, για να υλοποιήσουμε ένα interface πρέπει να χρησιμοποιήσουμε την έκφραση implements στο όρισμα μίας κλάσης. Μία κλάση μπορεί να υλοποιεί περισσότερα από ένα interface τα ονόματα των οποίων ορίζονται μετά το implements χωρισμένα με κόμμα. Το implements ακολουθεί πάντα το extends αν αυτό ορίζεται.
Παράδειγμα διεπαφής, Relatable
Ας δούμε ένα interface το οποίο ορίζει πώς συγκρίνεται το μέγεθος δύο αντικειμένων:
public interface Relatable {
// Το this (αντικείμενο που καλεί την isLargerThan)
// και το other πρέπει να είναι στιγμιότυπα
// της ίδιας κλάσης και να επιστρέφει 1, 0, -1
// αν το this είναι μεγαλύτερο από, ίσο με
// , ή μικρότερο από το other
public int isLargerThan(Relatable other);
}
Αν θέλουμε να μπορούμε να συγκρίνουμε το μέγεθος 2 παρομοίων αντικειμένων, ότι και αν αυτά είναι, η κλάση από της οποίας είναι στιγμιότυπα πρέπει να υλοποιεί το Relatable.
Οποιαδήποτε κλάση μπορεί να υλοποιεί το Relatable εάν υπάρχει τρόπος να συγκριθεί το σχετικό «μέγεθος» του στιγμιότυπου της. Για String μπορεί να μετράμε τους χαρακτήρες που περιέχει, για βιβλία τον αριθμό των σελίδων, για ανθρώπους μπορεί να είναι η ηλικία κλπ. Αρκεί η σύγκριση να έχει νόημα στην λογική της εφαρμογής μας.
Όλες οι κλάσεις που θέλουμε να συγκρίνουμε με αυτών τον τρόπο αρκεί να υλοποιούν την isLargerThan(). Αν γνωρίζουμε ότι μια κλάση υλοποιεί την Relatable, τότε ξέρουμε ότι μπορούμε να συγκρίνουμε το μέγεθος των στιγμιότυπων της. then you know that you can compare the size of the objects instantiated from that class.
Υλοποίηση διεπαφής Relatable
Εδώ έχουμε το παράδειγμα της κλάσης Rectangle που υλοποιεί την Relatable.
public class RectanglePlus implements Relatable {
public int width = 0;
public int height = 0;
public Point origin;
// 4 constructors
public RectanglePlus() {
origin = new Point(0, 0);
}
public RectanglePlus(Point p) {
origin = p;
}
public RectanglePlus(int w, int h) {
origin = new Point(0, 0);
width = w;
height = h;
}
public RectanglePlus(Point p, int w, int h) {
origin = p;
width = w;
height = h;
}
//μια μέθοδος για την μετακίνηση του Rectangle
public void move(int x, int y) {
origin.x = x;
origin.y = y;
}
// μια μέθοδος υπολογισμού
// του εμβαδού του Rectangle
public int getArea() {
return width * height;
}
// η μέθοδος υλοποίησης της
// διεπαφής Relatable
public int isLargerThan(Relatable other) {
RectanglePlus otherRect
= (RectanglePlus)other;
if (this.getArea() < otherRect.getArea())
return -1;
else if (this.getArea() > otherRect.getArea())
return 1;
else
return 0;
}
}
Επειδή το RectanglePlus υλοποιεί την Relatable, το μέγεθος των 2 αντικειμένων RectanglePlus μπορεί να συγκριθεί.
Type Casting
Η μέθοδος isLargerThan, όπως αυτή ορίζεται από την διεπαφή Relatable, δέχεται ως παράμετρο μια αναφορά σε αντικείμενο τύπου Relatable. Η έντονα τονισμένη γραμμή κώδικα του προηγούμενου παραδείγματος, μεταμορφώνει - μετασχηματίζει (casting) το other σε στιγμιότυπο της RectanglePlus. Η τεχνική «Type casting» γνωστοποιεί στον μεταγλωττιστή της Java τη είναι το κάθε αντικείμενο. Στη διάρκεια της μεταγλώττισης καλώντας την getArea απευθείας από το στιγμιότυπο other (other.getArea()) οδηγεί σε σφάλμα γιατί ο μεταγλωττιστής (compiler) δεν κατανοεί ότι το στιγμιότυπο other είναι στην πραγματικότητα στιγμιότυπο της RectanglePlus.
Όταν ορίζουμε μια νέα διεπαφή στην ουσία ορίζουμε ένα νέα αναφορά τύπου δεδομένων . Αν ορίσουμε μια μεταβλητή με τύπο κάποια διεπαφή, όποιο τα αντικείμενο ανατεθεί στην μεταβλητή αυτή πρέπει να είναι στιγμιότυπο μιας κλάσης που υλοποιεί την διεπαφή αυτή.
Για παράδειγμα, εδώ έχουμε τη μέθοδο που βρίσκει το μεγαλύτερο από 2 αντικείμενα, οποιαδήποτε αντικείμενα υλοποιούν την διεπαφή Relatable.
public Object findLargest(Object object1, Object object2) {
Relatable obj1 = (Relatable)object1;
Relatable obj2 = (Relatable)object2;
if ((obj1).isLargerThan(obj2) > 0)
return object1;
else
return object2;
}
Εμφανίζοντας (casting) το object1 σε Relatable αυτό μπορεί να καλέσει την μέθοδο isLargerThan
Εφόσον υλοποιηθεί η Relatable σε διάφορες κλάσεις, τα αντικείμενα που δημιουργούνται από αποιαδήποτε από αυτές τις κλάσεις μπορουν να συγκριθούν με την μέθοδο findLargest() – δεδομένου ότι και τα 2 αντικείμενα είναι της ίδιας κλάσης. Με τον ίδιο τρόπο μπορούμε να κάνουμε συγκρίσεις με τις ακόλουθες μεθόδους:
public Object findSmallest(Object object1, Object object2) {
Relatable obj1 = (Relatable)object1;
Relatable obj2 = (Relatable)object2;
if ((obj1).isLargerThan(obj2) < 0)
return object1;
else
return object2;
}
public boolean isEqual(Object object1, Object object2) {
Relatable obj1 = (Relatable)object1;
Relatable obj2 = (Relatable)object2;
if ( (obj1).isLargerThan(obj2) == 0)
return true;
else
return false;
}
Οι μέθοδοι αυτές «δουλεύουν» με οποιοδήποτε αντικείμενο Relatable χωρίς να έχει σημασία από ποια κλάση αυτό προέρχεται. Όταν αυτό υλοποιεί την Relatable μπορεί να εμφανιστεί και ως Relatable και ως ο εαυτός του. Η δυνατότητα αυτή παρέχει ιδιότητες πολλαπλής κληρονομικότητας εφόσον ένα αντικείμενο μπορεί να υιοθετεί λειτουργικότητα και από την superclass του και από μία ή περισσότερες διεπαφές.
Κληρονομικότητα
Πιο πριν είδαμε να αναφέρεται πολλές φορές η έννοια της κληρονομικότητας. Στην Java οι κλάσεις μπορούν να προέλθουν από άλλες κλάσεις κληρονομώντας έτσι πεδία και μεθόδους από αυτές τις κλάσεις.
Ορισμός: Μία κλάση που προέρχεται από μία άλλη ονομάζεται υπο-κλάση (ή επίσης προέκταση ή παιδί). Η κλάση από την οποία αυτή προέρχεται ονομάζεται σουπερ-κλάση (ή επίσης βάση ή μητρική).
Εκτός από την κλάση Object, που δεν έχει μητρική κλάση, όλες οι άλλες κλάσεις έχουν μία και μόνη μητρική (μοναδική κληρονομικότητα). Κάθε κλάση που ορίζουμε εάν δεν έχει ορισμένη μητρική εννοείται ότι είναι παιδί της Object.
Μια κλάση μπορεί να προέρχεται από μία κλάση που προέρχεται από μία άλλη ή οποία προέρχεται από μια άλλη κ.ο.κ., ώσπου να φτάσουμε στην κορυφαία που είναι η Object. Αυτή η κλάση λέμε ότι κληρονομεί από όλες τις κλάσεις στην αλυσίδα της κληρονομικότητας μέχρι την Object.
Η ιδέα της κληρονομικότητας είναι απλή αλλά πολύ δυνατή. Όταν θέλουμε να κατασκευάσουμε μία νέα κλάση και υπάρχει ήδη μία που περιλαμβάνει μερικό από τον κώδικα που χρειαζόμαστε, μπορούμε να την δημιουργήσουμε προεκτείνοντας την υπάρχουσα. Έτσι μπορούμε να ξαναχρησιμοποιήσουμε τα υπάρχοντα πεδία και μεθόδους της υπάρχουσας κλάσης χωρίς να πρέπει να τα ξαναγράψουμε και εκσφαλματώσουμε πάλι.
Οι υποκλάση κληρονομεί όλα τα μέλη της μητρικής (πεδία, μεθόδους και ενθυλακωμένες κλάσεις). Οι constructors δεν είναι μέλη και έτσι δεν κληρονομούνται, αλλά θα δούμε ότι μπορούμε να τους καλέσουμε από την υπο-κλάση
Ιεραρχία της Java
Η κλάση Object ορίζεται στο package java.lang και υλοποιεί συμπεριφορά κοινή για όλες τις κλάσεις. Πολλές κλάσεις κληρονομούν έμμεσα από την Object ενώ άλλες κληρονομούν από αυτές κ.ο.κ. δημιουργώντας την ιεραρχία των κλάσεων.
Στην κορυφή της ιεραρχίας η Object είναι η πιο γενική κλάση. Από όλες. Οι κλάσεις πιο χαμηλά στην ιεραρχία παρέχουν πιο ειδική λειτουργικότητα.
Παράδειγμα κληρονομικότητας
Θυμηθείτε το παράδειγμα της Bicycle:
public class Bicycle {
// η κλάση Bicycle έχει τρία πεδία
public int cadence;
public int gear;
public int speed;
// η κλάση Bicycle έχει ένα constructor
public Bicycle(int startCadence, int startSpeed, int startGear) {
gear = startGear;
cadence = startCadence;
speed = startSpeed;
}
// η κλάση Bicycle έχει 4 μεθόδους
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 παιδί της Bicycle μπορεί να είναι κάπως έτσι:
public class MountainBike extends Bicycle {
// η υποκλάση MountainBike προσθέτει ένα πεδίο
public int seatHeight;
// η υποκλάση MountainBike έχει ένα constructor
public MountainBike(int startHeight,
int startCadence,
int startSpeed,
int startGear) {
super(startCadence, startSpeed, startGear);
seatHeight = startHeight;
}
// η υποκλάση MountainBike προσθέτει μία μέθοδο
public void setHeight(int newValue) {
seatHeight = newValue;
}
}
Η MountainBike κληρονομεί όλα τα πεδία και μεθόδους της Bicycle και προσθέτει ένα πεδίο seatHeight και μια μέθοδο για να του δίνει τιμή. Εκτός από έναν constructor, όλα τα μέλη της Bicycle υπάρχουν ως μέλη της MountainBike αυτόματα χωρίς να τα ξαναγράψουμε. Αυτό έχει μεγαλύτερη ακόμη αξία αν οι μέθοδοι της Bicycle ήταν ιδιαίτερα πολύπλοκοι και δύσκολο να έλεγχουν για λογικά σφάλματα.
Τι μπορούμε να κάνουμε με μια υποκλάση
Μια υποκλάση κληρονομεί αυτόματα όλα τα public και protected μέλη της μητρικής. Εάν η κλάση ορίζεται στο ίδιο package με την μητρική κληρονομεί και τα package-private μέλη. Μπορούμε να χρησιμοποιήσουμε τα μέλη αυτά ως έχουν ή να τα αντικαταστήσουμε ή να τα αποκρύψουμε ή και να τα συμπληρώσουμε με νέα μέλη:
Private μέλη της μητρικής κλάσης
Μια υποκλάση δεν κληρονομεί τα private πεδία της μητρικής της. Όμως εάν η μητρική κλάση έχει public ή protected μεθόδους που έχουν πρόσβαση στα private πεδία της τότε αυτά μπορούν να χρησιμοποιηθούν από την υποκλάση.
Μια φωλιασμένη κλάση μπορεί να έχει πρόσβαση σε όλα τα private μέλη της κλάσης που την περικλείει. Ετσι μία public ή protected φωλιασμένη κλάση που κληρονομείται από την υποκλάση έχει πρόσβαση στα private μέλη της μητρικής.
Casting Objects
Έχουμε δει ότι ένα αντικείμενο είναι «τύπου» της κλάσης από την οποία δημιουργήθηκε. Για παράδειγμα μπορούμε να γράψουμε
public MountainBike myBike = new MountainBike();
τότε το myBike είναι τύπου MountainBike.
Η MountainBike κληρονομεί από την Bicycle και την Object. Για αυτό η MountainBike είναι και Bicycle και επίσης Object, και μπορεί να χρησιμοποιηθεί όπου χρειαζόμαστε ένα Bicycle ή Object.
Το αντίστροφο δεν είναι απαραίοτητα αληθές. ένα Bicycle μπορεί να είναι ένα MountainBike, αλλά όχι πάντα. Παρομοίως, ένα Object μπορεί να είναι ένα Bicycle ή ένα MountainBike, αλλά έχι απαραίτητα.
Η τεχνική Casting περιλαμβάνει την χρήση ενός αντικειμένου ενός τύπου στην θέση ενός άλλου τύπου, ανάμεσα στα αντικείμενα που επιτρέπονται από την κληρονομικότητα και την υλοποίηση του κώδικα.
Για παράδειγμα αν έχουμε
Object obj = new MountainBike();
τότε η obj είναι και τύπου Object και MountainBike (εώς ότου στην obj ανατεθεί κάποιο άλλο αντικείμενο που δεν είναι τύπου MountainBike). Η τεχνική αυτή λέγεται εννοούμενο casting.
Αν όμως δηλώσουμε
MountainBike myBike = obj;
θα έχουμε ως αποτέλεσμα σφάλμα στον κώδικα μας κατά την μεταγλωττιστή διότι η obj δεν είναι γνωστή στον μεταγλωττιστή ως MountainBike. Όμως μπορούμε να πούμε στον μεταγλωττιστή ότι υποσχόμαστε να δώσουμε στην obj τιμή τύπου MountainBike με την τεχνική δηλούμενου casting:
MountainBike myBike = (MountainBike)obj;
Αυτό το cast εισαγάγει έναν runtime έλεγχο ώστε ο μεταγλωττιστής να μπορεί να υποθέσει ασφαλώς ότι η obj παίρνει τιμή τύπου MountainBike. Εάν η obj δεν πάρει τιμή τύπου MountainBike κατά την εκτέλεση του κώδικα, θα έχουμε την εκδήλωση μίας εξαίρεσης (exception thrownιng).
Σημειώστε ότι μπορούμε να κάνουμε έναν λογικό έλεγχο για τον τύπο ένος object χρησιμοποιώντας τον τελεστή instanceof. Αυτό μπορεί να αποτρέψει την εκδήλωση σφάλματος από μη επιτρεπόμενο cast. Για παράδειγμα:
if (obj instanceof MountainBike) {
MountainBike myBike = (MountainBike)obj;
}
Εδώ ο τελεστής instanceof επιβεβαιώνει ότι η obj αναφέρεται σε MountainBike έτσι ώστε να κάνουμε το casting χωρίς φόβο να προκαλέσουμε εξαίρεση.
Τεχνική override (παράκαμψης) και απόκρυψης μεθόδων και πεδίων, χρήση super και final
Μέθοδοι στιγμιότυπου Μια μέθοδος στιγμιότυπου μέσα σε μια υπο-κλάση με το ίδιο αποτύπωμα (όνομα, αριθμό και τύπο παραμέτρων) και τύπο επιστροφής ίδιο με μια μέθοδο στην μητρική της κλάση, παρακάμπτει την μέθοδο αυτή.
Η δυνατότητα μιας υποκλάσης να παρακάμπτει μεθόδους επιτρέπει σε μία υποκλάση να κληρονομεί από την μητρική όλες τις μεθόδους που είναι κοινές και να τροποποιεί εκείνες που την διαφοροποιούν από την μητρική.
Η μέθοδος που παρακάμπτει μια μέθοδο της μητρικής κλάσης πρέπει να έχει το ίδιο αποτύπωμα και τύπο επιστροφής με την μητρική μέθοδο.
Μέθοδοι κλάσης Όταν μια υποκλάση έχει το ίδιο αποτύπωμα και τύπο επιστροφής με μία μέθοδο στην υπερ-κλάση της η μέθοδος της υποκλάσης αποκρύπτει την μέθοδο της μητρικής
Η διαφοροποίηση μεταξύ παράκαμψης και απόκρυψης έχει σημαντικές επιπτώσεις. Η έκδοση της παρακαμωμένης μεθόδου που καλείται είναι αυτή της υποκλάσης, Αλλά με την αποκρυμμένες μεθόδους επειδή είναι επιπέδου κλάσεις (static) η έκδοση που καλείται εξαρτάται αν τις καλούμε μέσω της μητρικής ή της υποκλάσης. Για παράδειγμα:
public class Animal {
public static void testClassMethod() {
System.out.println("The class" + " method in Animal.");
}
public void testInstanceMethod() {
System.out.println("The instance " + " method in Animal.");
}
}
Και η υπο-κλάση της Animal, με όνομα Cat:
public class Cat extends Animal {
public static void testClassMethod() {
System.out.println("The class method" + " in Cat.");
}
public void testInstanceMethod() {
System.out.println("The instance method" + " in Cat.");
}
public static void main(String[] args) {
Cat myCat = new Cat();
Animal myAnimal = myCat;
Animal.testClassMethod();
myAnimal.testInstanceMethod();
}
}
Η Cat παρακάμπτει την μέθοδο στιγμιότυπου της Animal και αποκρύπτει την στατική μέθοδο επιπέδου κλάσης της Animal. Η main μέθοδος μέσα στην Cat δημιουργεί ένα στιγμιότυπο της Cat και ψαλεί την testClassMethod() σε επίπεδο κλάσης και την testInstanceMethod()σε επίπεδο στιγμιότυπου.
Η έξοδος του προγράμματος αυτού εχει ως εξής:
The class method in Animal.
The instance method in Cat.
Μετατροπής πρόσβασης Η μέθοδοι που παρακάμπτουν άλλες μπορούν να επιτρέπουν το ίδιο επίπεδο πρόσβασης ή μεγαλύτερο αλλά όχι μικρότερο. Π.χ. μία private μέθοδος μπορεί να γίνει protected ή public αλλά μία public δεν γίνεται protected ή και private στην υπο-κλάση.
Αλλάζοντας μία μέθοδο στιγμιότυπου σε μέθοδο κλάσης στην υπο-κλάση ή το αντίστροφο προκαλεί runtime σφάλμα.
Σημείωση: Μια υπο-κλάση μπορεί να υπερφορτώνει την μέθοδο της μητρικής της. Η μέθοδοι υπερφόρτωσης δεν παρακάμπτουν την μέθοδο της μητρικής εφόσον δεν ορίζονται με το ίδιο αποτύπωμα και επιστροφή σε αυτή. Αντιμετωπίζονται ως νέες μέθοδοι.
Χρήση super
Το παρακάτω παράδειγμα δείχνει πώς χρησιμοποιούμε την λέξη κλειδί super για να καλέσουμε τον constructor της μητρικής κλάσης. Στο παράδειγμα της Bicycle βλέπουμε ότι η υπο-κλάση MountainBike καλεί τον constructor της μητρικής της και μετά αρχικοποιεί της μεταβλητές της:
public MountainBike(int startHeight,
int startCadence,
int startSpeed,
int startGear) {
super(startCadence, startSpeed, startGear);
seatHeight = startHeight;
}
Η κλήση στον constructor της μητρικής κλάσης πρέπει να γίνεται πάντα στην πρώτη γραμμή του constructor της υποκλάσης
Η σύνταξη είναι:
super();
ή και:
super(λίστα παραμέτρων);
Με την super(), καλείται ο constructor της μητρικής χωρίς παραμέτρους ενώ με super(λίστα παραμέτρων), καλείται ο αντίστοιχος constructor που ταιριάζει στο αποτύπωμα των παραμέτρων.
Προσοχή: εάν δεν καλούμε την super στον constructor της υπο-κλάσης τότε εννοείται εξ ορισμού η κλήση super(). Εάν αυτός δεν υπάρχει στην μητρική λαμβάνουμε μήνυμα λάθους κατά την μεταγλώττιση.
Χρήση final
Η final χρησιμοποιείται σε πεδία, μεθόδους και κλάσεις.
Εάν ορίσουμε ένα πεδίο final τότε αυτό σημαίνει ότι θα έχει σταθερή τιμή σε όλη την εκτέλεση του προγράμματος μας. Πρέπει η τιμή αυτή να του δοθεί εντός του constructor.
public class MyClass {
private final int myField = 3;
public MyClass() {
...
}
}
ή,
public class MyClass {
private final int myField;
public MyClass() {
myField = 3;
}
}
Επίσης εάν δηλώσουμε μία μέθοδο final τότε αυτή δεν μπορεί αν παρακαμθεί από καμία μητρική
class ChessAlgorithm {
enum ChessPlayer { WHITE, BLACK }
...
final ChessPlayer getFirstPlayer() {
return ChessPlayer.WHITE;
}
...
}
Τέλος εάν δηλώσουμε μία κλάση final αυτό σημαίνει ότι η κλάση αυτή δεν μπορεί να κληρονομηθεί.
Πολυμορφισμός
Οι υποκλάσης στην Java ορίζουν την δικιά τους μοναδική συμπεριφορά αλλά παράλληλα μοιράζονται κάποια λειτουργικότητα με την μητρική τους.
Ένα παράδειγμα πολυμορφισμού μπορεί να παρουσιαστεί με μια μικρή τροποποίηση στην κλάση Bicycle. Για παράδειγμα, μια μέθοδος printDescription μπορεί να προστεθεί για να τυπώνει στην κονσόλα τα δεδομένα που κρατάει υποθηκευμένα το στιγμιότυπο.
public void printDescription(){
System.out.println("\nBike is " + "in gear " + this.gear
+ " with a cadence of " + this.cadence +
" and travelling at a speed of " + this.speed + ". ");
}
Για να δούμε τα στοιχεία του πολυμορφισμού προεκτείνουμε την Bicycle με τις MountainBike και RoadBike. Για MountainBike, προσθέτουμε ένα πεδίο suspension, το οποίο είναι τύπου String και η τιμή του εάν το ποδήλατο έχει μπροστινό αμορτισέρ είναι Front. Αλλιώς εάν έχει και στους 2 τροχούς Dual.
Εδώ έχουμε την κλάση:
public class MountainBike extends Bicycle {
private String suspension;
public MountainBike(
int startCadence,
int startSpeed,
int startGear,
String suspensionType){
super(startCadence,
startSpeed,
startGear);
this.setSuspension(suspensionType);
}
public String getSuspension(){
return this.suspension;
}
public void setSuspension(String suspensionType) {
this.suspension = suspensionType;
}
public void printDescription() {
super.printDescription();
System.out.println("The " + "MountainBike has a" +
getSuspension() + " suspension.");
}
}
Προσέξτε την παράκαμψη της printDescription μεθόδου για να τυπώνει τα καινούρια δεδομένα.
Έπειτα δημιουργούμε την κλάση RoadBike. Επειδή τα ποδήλατα δρόμου ή τα αγωνιστικά έχουν λεπτά λάστιχα προσθέτουμε ένα πεδίο για να μετράμε το πάχος τους. Εδώ έχουμε την κλάση RoadBike:
public class RoadBike extends Bicycle{
// In millimeters (mm)
private int tireWidth;
public RoadBike(int startCadence,
int startSpeed,
int startGear,
int newTireWidth){
super(startCadence,
startSpeed,
startGear);
this.setTireWidth(newTireWidth);
}
public int getTireWidth(){
return this.tireWidth;
}
public void setTireWidth(int newTireWidth){
this.tireWidth = newTireWidth;
}
public void printDescription(){
super.printDescription();
System.out.println("The RoadBike" + " has " + getTireWidth() +
" MM tires.");
}
}
Σημειώστε πάλι ότι η printDescription έχει παρακαμθεί για να δείχνει πληροφορίες για το πάχος των λάστιχων.
Και εδώ έχουμε το τεστ πρόγραμμα για τις 3 κλάσεις ποδηλάτου.
public class TestBikes {
public static void main(String[] args){
Bicycle bike01, bike02, bike03;
bike01 = new Bicycle(20, 10, 1);
bike02 = new MountainBike(20, 10, 5, "Dual");
bike03 = new RoadBike(40, 20, 8, 23);
bike01.printDescription();
bike02.printDescription();
bike03.printDescription();
}
}
Η ακόλουθη έξοδος παράγεται:
Bike is in gear 1 with a cadence of 20 and travelling at a speed of 10.
Bike is in gear 5 with a cadence of 20 and travelling at a speed of 10.
The MountainBike has a Dual suspension.
Bike is in gear 8 with a cadence of 40 and travelling at a speed of 20.
The RoadBike has 23 MM tires.
Η Java virtual machine (JVM) καλεί κάθε φορά την μέθοδο που αντιστοιχεί στο αντικείμενο στο οποίο αναφέρεται η κάθε μεταβλητή τύπου Bicycle (virtual method invocation) και αποτελεί μία πτυχή της ιδιότητας του πολυμορφισμού στην Java.
Abstract κλάσεις
(Java Tutorials)
Παράδειγμα κληρονομικότητας
Θυμηθείτε το παράδειγμα της Bicycle:
public class Bicycle {
// η κλάση Bicycle έχει τρία πεδία
public int cadence;
public int gear;
public int speed;
// η κλάση Bicycle έχει ένα constructor
public Bicycle(int startCadence, int startSpeed, int startGear) {
gear = startGear;
cadence = startCadence;
speed = startSpeed;
}
// η κλάση Bicycle έχει 4 μεθόδους
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 παιδί της Bicycle μπορεί να είναι κάπως έτσι:
public class MountainBike extends Bicycle {
// η υποκλάση MountainBike προσθέτει ένα πεδίο
public int seatHeight;
// η υποκλάση MountainBike έχει ένα constructor
public MountainBike(int startHeight,
int startCadence,
int startSpeed,
int startGear) {
super(startCadence, startSpeed, startGear);
seatHeight = startHeight;
}
// η υποκλάση MountainBike προσθέτει μία μέθοδο
public void setHeight(int newValue) {
seatHeight = newValue;
}
}
Η MountainBike κληρονομεί όλα τα πεδία και μεθόδους της Bicycle και προσθέτει ένα πεδίο seatHeight και μια μέθοδο για να του δίνει τιμή. Εκτός από έναν constructor, όλα τα μέλη της Bicycle υπάρχουν ως μέλη της MountainBike αυτόματα χωρίς να τα ξαναγράψουμε. Αυτό έχει μεγαλύτερη ακόμη αξία αν οι μέθοδοι της Bicycle ήταν ιδιαίτερα πολύπλοκοι και δύσκολο να έλεγχουν για λογικά σφάλματα.
Τι μπορούμε να κάνουμε με μια υποκλάση
Μια υποκλάση κληρονομεί αυτόματα όλα τα public και protected μέλη της μητρικής. Εάν η κλάση ορίζεται στο ίδιο package με την μητρική κληρονομεί και τα package-private μέλη. Μπορούμε να χρησιμοποιήσουμε τα μέλη αυτά ως έχουν ή να τα αντικαταστήσουμε ή να τα αποκρύψουμε ή και να τα συμπληρώσουμε με νέα μέλη:
- Τα κληρονομούμενα πεδία χρησιμοποιούνται απευθείας ως έχουν.
- Μπορούμε να ορίσουμε ένα πεδίο με το ίδιο όνομα όπως ένα στην μητρική κλάση κρύβοντας το. (Δεν συνίσταται).
- Μπορούμε να ορίσουμε νέα πεδία που δεν υπάρχουν στην μητρική.
- Οι κληρονομούμενες μέθοδοι χρησιμοποιούνται απευθείας ως έχουν
- Μπορούμε να γράψουμε μία μέθοδο με το ίδιο αποτύπωμα όπως στην μητρική παρακάμπτοντας την.( overriding)
- Μπορούμε να γράψουμε μία στατική μέθοδο με την ιδία υπογραφή όπως στην μητρική ώστε να την αποκρύψουμε.
- Μπορούμε να γράψουμε νέες μεθόδους που δεν υπάρχουν στην μητρική.
- Μπορούμε να γράψουμε νέους constructor οι οποίοι καλούν αυτούς της μητρικής εξ ορισμού ή με την χρήση της λέξης κλειδί super.
Private μέλη της μητρικής κλάσης
Μια υποκλάση δεν κληρονομεί τα private πεδία της μητρικής της. Όμως εάν η μητρική κλάση έχει public ή protected μεθόδους που έχουν πρόσβαση στα private πεδία της τότε αυτά μπορούν να χρησιμοποιηθούν από την υποκλάση.
Μια φωλιασμένη κλάση μπορεί να έχει πρόσβαση σε όλα τα private μέλη της κλάσης που την περικλείει. Ετσι μία public ή protected φωλιασμένη κλάση που κληρονομείται από την υποκλάση έχει πρόσβαση στα private μέλη της μητρικής.
Casting Objects
Έχουμε δει ότι ένα αντικείμενο είναι «τύπου» της κλάσης από την οποία δημιουργήθηκε. Για παράδειγμα μπορούμε να γράψουμε
public MountainBike myBike = new MountainBike();
τότε το myBike είναι τύπου MountainBike.
Η MountainBike κληρονομεί από την Bicycle και την Object. Για αυτό η MountainBike είναι και Bicycle και επίσης Object, και μπορεί να χρησιμοποιηθεί όπου χρειαζόμαστε ένα Bicycle ή Object.
Το αντίστροφο δεν είναι απαραίοτητα αληθές. ένα Bicycle μπορεί να είναι ένα MountainBike, αλλά όχι πάντα. Παρομοίως, ένα Object μπορεί να είναι ένα Bicycle ή ένα MountainBike, αλλά έχι απαραίτητα.
Η τεχνική Casting περιλαμβάνει την χρήση ενός αντικειμένου ενός τύπου στην θέση ενός άλλου τύπου, ανάμεσα στα αντικείμενα που επιτρέπονται από την κληρονομικότητα και την υλοποίηση του κώδικα.
Για παράδειγμα αν έχουμε
Object obj = new MountainBike();
τότε η obj είναι και τύπου Object και MountainBike (εώς ότου στην obj ανατεθεί κάποιο άλλο αντικείμενο που δεν είναι τύπου MountainBike). Η τεχνική αυτή λέγεται εννοούμενο casting.
Αν όμως δηλώσουμε
MountainBike myBike = obj;
θα έχουμε ως αποτέλεσμα σφάλμα στον κώδικα μας κατά την μεταγλωττιστή διότι η obj δεν είναι γνωστή στον μεταγλωττιστή ως MountainBike. Όμως μπορούμε να πούμε στον μεταγλωττιστή ότι υποσχόμαστε να δώσουμε στην obj τιμή τύπου MountainBike με την τεχνική δηλούμενου casting:
MountainBike myBike = (MountainBike)obj;
Αυτό το cast εισαγάγει έναν runtime έλεγχο ώστε ο μεταγλωττιστής να μπορεί να υποθέσει ασφαλώς ότι η obj παίρνει τιμή τύπου MountainBike. Εάν η obj δεν πάρει τιμή τύπου MountainBike κατά την εκτέλεση του κώδικα, θα έχουμε την εκδήλωση μίας εξαίρεσης (exception thrownιng).
Σημειώστε ότι μπορούμε να κάνουμε έναν λογικό έλεγχο για τον τύπο ένος object χρησιμοποιώντας τον τελεστή instanceof. Αυτό μπορεί να αποτρέψει την εκδήλωση σφάλματος από μη επιτρεπόμενο cast. Για παράδειγμα:
if (obj instanceof MountainBike) {
MountainBike myBike = (MountainBike)obj;
}
Εδώ ο τελεστής instanceof επιβεβαιώνει ότι η obj αναφέρεται σε MountainBike έτσι ώστε να κάνουμε το casting χωρίς φόβο να προκαλέσουμε εξαίρεση.
Τεχνική override (παράκαμψης) και απόκρυψης μεθόδων και πεδίων, χρήση super και final
Μέθοδοι στιγμιότυπου Μια μέθοδος στιγμιότυπου μέσα σε μια υπο-κλάση με το ίδιο αποτύπωμα (όνομα, αριθμό και τύπο παραμέτρων) και τύπο επιστροφής ίδιο με μια μέθοδο στην μητρική της κλάση, παρακάμπτει την μέθοδο αυτή.
Η δυνατότητα μιας υποκλάσης να παρακάμπτει μεθόδους επιτρέπει σε μία υποκλάση να κληρονομεί από την μητρική όλες τις μεθόδους που είναι κοινές και να τροποποιεί εκείνες που την διαφοροποιούν από την μητρική.
Η μέθοδος που παρακάμπτει μια μέθοδο της μητρικής κλάσης πρέπει να έχει το ίδιο αποτύπωμα και τύπο επιστροφής με την μητρική μέθοδο.
Μέθοδοι κλάσης Όταν μια υποκλάση έχει το ίδιο αποτύπωμα και τύπο επιστροφής με μία μέθοδο στην υπερ-κλάση της η μέθοδος της υποκλάσης αποκρύπτει την μέθοδο της μητρικής
Η διαφοροποίηση μεταξύ παράκαμψης και απόκρυψης έχει σημαντικές επιπτώσεις. Η έκδοση της παρακαμωμένης μεθόδου που καλείται είναι αυτή της υποκλάσης, Αλλά με την αποκρυμμένες μεθόδους επειδή είναι επιπέδου κλάσεις (static) η έκδοση που καλείται εξαρτάται αν τις καλούμε μέσω της μητρικής ή της υποκλάσης. Για παράδειγμα:
public class Animal {
public static void testClassMethod() {
System.out.println("The class" + " method in Animal.");
}
public void testInstanceMethod() {
System.out.println("The instance " + " method in Animal.");
}
}
Και η υπο-κλάση της Animal, με όνομα Cat:
public class Cat extends Animal {
public static void testClassMethod() {
System.out.println("The class method" + " in Cat.");
}
public void testInstanceMethod() {
System.out.println("The instance method" + " in Cat.");
}
public static void main(String[] args) {
Cat myCat = new Cat();
Animal myAnimal = myCat;
Animal.testClassMethod();
myAnimal.testInstanceMethod();
}
}
Η Cat παρακάμπτει την μέθοδο στιγμιότυπου της Animal και αποκρύπτει την στατική μέθοδο επιπέδου κλάσης της Animal. Η main μέθοδος μέσα στην Cat δημιουργεί ένα στιγμιότυπο της Cat και ψαλεί την testClassMethod() σε επίπεδο κλάσης και την testInstanceMethod()σε επίπεδο στιγμιότυπου.
Η έξοδος του προγράμματος αυτού εχει ως εξής:
The class method in Animal.
The instance method in Cat.
Μετατροπής πρόσβασης Η μέθοδοι που παρακάμπτουν άλλες μπορούν να επιτρέπουν το ίδιο επίπεδο πρόσβασης ή μεγαλύτερο αλλά όχι μικρότερο. Π.χ. μία private μέθοδος μπορεί να γίνει protected ή public αλλά μία public δεν γίνεται protected ή και private στην υπο-κλάση.
Αλλάζοντας μία μέθοδο στιγμιότυπου σε μέθοδο κλάσης στην υπο-κλάση ή το αντίστροφο προκαλεί runtime σφάλμα.
Σημείωση: Μια υπο-κλάση μπορεί να υπερφορτώνει την μέθοδο της μητρικής της. Η μέθοδοι υπερφόρτωσης δεν παρακάμπτουν την μέθοδο της μητρικής εφόσον δεν ορίζονται με το ίδιο αποτύπωμα και επιστροφή σε αυτή. Αντιμετωπίζονται ως νέες μέθοδοι.
Χρήση super
Το παρακάτω παράδειγμα δείχνει πώς χρησιμοποιούμε την λέξη κλειδί super για να καλέσουμε τον constructor της μητρικής κλάσης. Στο παράδειγμα της Bicycle βλέπουμε ότι η υπο-κλάση MountainBike καλεί τον constructor της μητρικής της και μετά αρχικοποιεί της μεταβλητές της:
public MountainBike(int startHeight,
int startCadence,
int startSpeed,
int startGear) {
super(startCadence, startSpeed, startGear);
seatHeight = startHeight;
}
Η κλήση στον constructor της μητρικής κλάσης πρέπει να γίνεται πάντα στην πρώτη γραμμή του constructor της υποκλάσης
Η σύνταξη είναι:
super();
ή και:
super(λίστα παραμέτρων);
Με την super(), καλείται ο constructor της μητρικής χωρίς παραμέτρους ενώ με super(λίστα παραμέτρων), καλείται ο αντίστοιχος constructor που ταιριάζει στο αποτύπωμα των παραμέτρων.
Προσοχή: εάν δεν καλούμε την super στον constructor της υπο-κλάσης τότε εννοείται εξ ορισμού η κλήση super(). Εάν αυτός δεν υπάρχει στην μητρική λαμβάνουμε μήνυμα λάθους κατά την μεταγλώττιση.
Χρήση final
Η final χρησιμοποιείται σε πεδία, μεθόδους και κλάσεις.
Εάν ορίσουμε ένα πεδίο final τότε αυτό σημαίνει ότι θα έχει σταθερή τιμή σε όλη την εκτέλεση του προγράμματος μας. Πρέπει η τιμή αυτή να του δοθεί εντός του constructor.
public class MyClass {
private final int myField = 3;
public MyClass() {
...
}
}
ή,
public class MyClass {
private final int myField;
public MyClass() {
myField = 3;
}
}
Επίσης εάν δηλώσουμε μία μέθοδο final τότε αυτή δεν μπορεί αν παρακαμθεί από καμία μητρική
class ChessAlgorithm {
enum ChessPlayer { WHITE, BLACK }
...
final ChessPlayer getFirstPlayer() {
return ChessPlayer.WHITE;
}
...
}
Τέλος εάν δηλώσουμε μία κλάση final αυτό σημαίνει ότι η κλάση αυτή δεν μπορεί να κληρονομηθεί.
Πολυμορφισμός
Οι υποκλάσης στην Java ορίζουν την δικιά τους μοναδική συμπεριφορά αλλά παράλληλα μοιράζονται κάποια λειτουργικότητα με την μητρική τους.
Ένα παράδειγμα πολυμορφισμού μπορεί να παρουσιαστεί με μια μικρή τροποποίηση στην κλάση Bicycle. Για παράδειγμα, μια μέθοδος printDescription μπορεί να προστεθεί για να τυπώνει στην κονσόλα τα δεδομένα που κρατάει υποθηκευμένα το στιγμιότυπο.
public void printDescription(){
System.out.println("\nBike is " + "in gear " + this.gear
+ " with a cadence of " + this.cadence +
" and travelling at a speed of " + this.speed + ". ");
}
Για να δούμε τα στοιχεία του πολυμορφισμού προεκτείνουμε την Bicycle με τις MountainBike και RoadBike. Για MountainBike, προσθέτουμε ένα πεδίο suspension, το οποίο είναι τύπου String και η τιμή του εάν το ποδήλατο έχει μπροστινό αμορτισέρ είναι Front. Αλλιώς εάν έχει και στους 2 τροχούς Dual.
Εδώ έχουμε την κλάση:
public class MountainBike extends Bicycle {
private String suspension;
public MountainBike(
int startCadence,
int startSpeed,
int startGear,
String suspensionType){
super(startCadence,
startSpeed,
startGear);
this.setSuspension(suspensionType);
}
public String getSuspension(){
return this.suspension;
}
public void setSuspension(String suspensionType) {
this.suspension = suspensionType;
}
public void printDescription() {
super.printDescription();
System.out.println("The " + "MountainBike has a" +
getSuspension() + " suspension.");
}
}
Προσέξτε την παράκαμψη της printDescription μεθόδου για να τυπώνει τα καινούρια δεδομένα.
Έπειτα δημιουργούμε την κλάση RoadBike. Επειδή τα ποδήλατα δρόμου ή τα αγωνιστικά έχουν λεπτά λάστιχα προσθέτουμε ένα πεδίο για να μετράμε το πάχος τους. Εδώ έχουμε την κλάση RoadBike:
public class RoadBike extends Bicycle{
// In millimeters (mm)
private int tireWidth;
public RoadBike(int startCadence,
int startSpeed,
int startGear,
int newTireWidth){
super(startCadence,
startSpeed,
startGear);
this.setTireWidth(newTireWidth);
}
public int getTireWidth(){
return this.tireWidth;
}
public void setTireWidth(int newTireWidth){
this.tireWidth = newTireWidth;
}
public void printDescription(){
super.printDescription();
System.out.println("The RoadBike" + " has " + getTireWidth() +
" MM tires.");
}
}
Σημειώστε πάλι ότι η printDescription έχει παρακαμθεί για να δείχνει πληροφορίες για το πάχος των λάστιχων.
Και εδώ έχουμε το τεστ πρόγραμμα για τις 3 κλάσεις ποδηλάτου.
public class TestBikes {
public static void main(String[] args){
Bicycle bike01, bike02, bike03;
bike01 = new Bicycle(20, 10, 1);
bike02 = new MountainBike(20, 10, 5, "Dual");
bike03 = new RoadBike(40, 20, 8, 23);
bike01.printDescription();
bike02.printDescription();
bike03.printDescription();
}
}
Η ακόλουθη έξοδος παράγεται:
Bike is in gear 1 with a cadence of 20 and travelling at a speed of 10.
Bike is in gear 5 with a cadence of 20 and travelling at a speed of 10.
The MountainBike has a Dual suspension.
Bike is in gear 8 with a cadence of 40 and travelling at a speed of 20.
The RoadBike has 23 MM tires.
Η Java virtual machine (JVM) καλεί κάθε φορά την μέθοδο που αντιστοιχεί στο αντικείμενο στο οποίο αναφέρεται η κάθε μεταβλητή τύπου Bicycle (virtual method invocation) και αποτελεί μία πτυχή της ιδιότητας του πολυμορφισμού στην Java.
Abstract κλάσεις
(Java Tutorials)