| Both sides previous revision
Previous revision
Next revision
|
Previous revision
Next revision
Both sides next revision
|
java:type_casting [2017/01/30 15:43] gthanos |
java:type_casting [2021/04/05 15:59] gthanos |
| |
| <code java> | <code java> |
| MountainBike myBike = new MountainBike(); | Rectangle r1 = new Rectangle(100,100,5,-5); |
| </code> | </code> |
| |
| Από την παραπάνω δήλωση η μεταβλητή ''myBike'' είναι τύπου ''MountainBike''. Επειδή όμως ο τύπος ''MountainBike'' κληρονομεί από την μεταβλητή ''Bicycle'' **__η συγκεκριμένη μεταβλητή είναι και τύπου Bicycle__**. Επομένως, θα μπορούσαμε να γράψουμε | Από την παραπάνω δήλωση η μεταβλητή ''r1'' είναι τύπου ''Rectangle''. Επειδή όμως ο τύπος ''Rectangle'' κληρονομεί την κλάση ''BasicRectangle'' **η μεταβλητή r1 είναι και τύπου BasicRectangle**. Επομένως, θα μπορούσαμε να γράψουμε |
| |
| <code java> | <code java> |
| Bicycle myBicycle = myBike; | BasicRectangle r2 = r1; |
| //ή | //ή ισοδύναμα |
| Βicycle yourBicycle = new MountainBike(); | BasicRectangle r2 = new Rectangle(100,100,5,-5); |
| </code> | </code> |
| |
| Την παραπάνω ανάθεση την ονομάζουμε άρρητη μετατροπή (//implicit casting//) διότι αναθέτουμε μία μεταβλητή ενός τύπου δεδομένων (''myBike'') σε μία μεταβλητή γονικού τύπου δεδομένων (''myBicycle''), χωρίς να ορίζουμε μία μετατροπή τύπου (//type cast//). | Την παραπάνω ανάθεση την ονομάζουμε άρρητη μετατροπή (//implicit casting//) διότι αναθέτουμε ένα αντικείμενο του κληρονομούμενου τύπου δεδομένων (''r1'') σε μία μεταβλητή του γονικού τύπου (''r2''), χωρίς να ορίζουμε κάποια μετατροπή τύπου. |
| | |
| | Αν θέλουμε να ζωγραφίσουμε τον κώδικα του προηγούμενου παραδείγματος μπορούμε να το κάνουμε όπως στο παρακάτω σχήμα. Παρατηρήστε ότι η μεταβλητές **r1** και **r2** δείχνουν στο ίδιο αντικείμενο. Η διαφορά είναι ότι η **r2** αν και δείχνει σε ένα αντικείμενο τύπου **Rectangle**, έχει πρόσβαση μόνο στο τμήμα του αντικειμένου που είναι του τύπου **BasicRectangle**. Ο λόγος για αυτόν τον περιορισμό είναι ότι λόγο του τύπου της ο //compiler// υποθέτει ότι δείχνει σε ένα αντικείμενο τύπου **BasicRectangle** με αποτέλεσμα να περιορίζει τι μπορούμε να προσπελάσουμε μέσω της μεταβλητής αυτής. |
| | |
| | {{:java:implicit-casting.png|}} |
| | |
| | === Ρητή μετατροπή === |
| |
| Ας δοκιμάσουμε το ανάποδο παράδειγμα τώρα | Ας δοκιμάσουμε το ανάποδο παράδειγμα τώρα |
| |
| <code java> | <code java> |
| Bycycle myBicycle = new Bicycle(); | BasicRectangle r3 = new Rectangle(100,200,5,-5); |
| MountainBike myBike = myBicycle; | Rectangle r4 = r3; |
| </code> | </code> |
| |
| Σε αυτή την περίπτωση ο compiler διαμαρτύρεται, διότι η μεταβλητή ''myBicycle'' είναι τύπου ''Bicycle'' και δεν είναι απαραίτητο ότι είναι και τύπου ''MountainBike''. Αν θέλουμε να ξεπεράσουμε το παραπάνω μήνυμα λάθους του compiler θα πρέπει να γράψουμε το εξής: | Σε αυτή την περίπτωση ο //compiler// διαμαρτύρεται, διότι η μεταβλητή ''r3'' είναι τύπου ''BasicRectangle'' και όχι τύπου ''Rectangle''. Αν θέλουμε να ξεπεράσουμε το παραπάνω μήνυμα λάθους του compiler θα πρέπει να γράψουμε το εξής: |
| |
| <code java> | <code java> |
| Bycycle myBicycle = new Bicycle(); | BasicRectangle r3 = new Rectangle(100,200,5,-5); |
| MountainBike myBike = (MountainBike) myBicycle; | Rectangle r4 = (Rectangle)r3; |
| </code> | </code> |
| |
| Εδώ ενημερώνουμε τον compiler ότι η μεταβλητή ''myBicycle'' είναι και τύπου ''MountainBike''. Ο προγραμματιστής βεβαιώνει ότι αυτό ισχύει. Την παραπάνω ανάθεση την ονομάζουμε ρητή μετατροπή (//explicit casting//). Εάν δεν ισχύει η παραπάνω μετατροπή, κατά την εκτέλεση του προγράμματος θα παραχθεί μία εξαίρεση (//exception//). Θα δούμε πιο κάτω τι είναι και πως διαχειριζόμαστε τις εξαιρέσεις. | Σε αυτή την περίπτωση ο προγραμματιστής βεβαιώνει τον //compiler// ότι η μεταβλητή ''r4'' είναι και τύπου ''Rectangle''. Την παραπάνω ανάθεση την ονομάζουμε ρητή μετατροπή (//explicit casting//). Επειδή δεν ισχύει η παραπάνω μετατροπή, κατά την εκτέλεση του προγράμματος θα παραχθεί μία εξαίρεση (//exception//). Θα δούμε πιο κάτω τι είναι οι εξαιρέσεις και πως τις διαχειριζόμαστε. |
| |
| Ένας πιο ασφαλής τρόπος για να επαναλάβουμε τον παραπάνω κώδικα, χωρίς να παραχθεί εξαίρεση είναι ο εξής: | Ένας πιο ασφαλής τρόπος για να επαναλάβουμε τον παραπάνω κώδικα, χωρίς να παραχθεί εξαίρεση είναι ο παρακάτω. Εδώ η μετατροπή γίνεται αφού πρώτα γίνει έλεγχος ότι το αντικείμενο για το οποίο θα γίνει μετατροπή τύπου είναι και του νέου τύπου. Η μετατροπή θα γίνει μόνο εφόσον ο έλεγχος επιτύχει. |
| |
| <code java> | <code java> |
| Bycycle myBicycle = new Bicycle(); | BasicRectangle r3 = new Rectangle(100,200,5,-5); |
| MountainBike myBike; | Rectangle r4; |
| if (myBicycle instanceof MountainBike) { | if (r3 instanceof Rectangle) { |
| myBike = (MountainBike)myBicycle; | r4 = (Rectangle)r3; |
| } | } |
| </code> | </code> |
| |
| ===== Κριτήρια χρήσης της κληρονομικότητας ως εργαλείο ανάπτυξης λογισμικού ===== | |Προηγούμενο: [[ :java:inheritance | Κληρονομικότητα ]] | [[:toc | Περιεχόμενα]] | Επόμενο: [[ :java:inheritance_subclassing_vs_encapsulating | Κριτήριo χρήσης της κληρονομικότητας ]]| |
| | |
| Ας επανέλθουμε στο παράδειγμα του ορθογωνίου παραλληλογράμμου το οποίο έχουμε συναντήσει αρκετές φορές στο παρελθόν και ας προσπαθήσουμε να δημιουργήσουμε την κλάση ενός [[wp>Cuboid|κυβοειδούς]] με χρήση της υφιστάμενης κλάσης του [[java:class|ορθογωνίου παραλληλογράμμου]]. | |
| | |
| Πριν ξεκινήσουμε την υλοποίηση θα πρέπει να αποφασίσουμε εάν θέλουμε να δημιουργήσουμε το κυβοειδές χρησιμοποιώντας ως μεταβλητή της νέας κλάσης ένα αντικείμενο της κλάσης //Rectangle// ή επιθυμούμε να επεκτείνουμε την κλάση //Rectangle// μέσω της νέας κλάσης. Δείτε παρακάτω τις δύο παραλλαγές της κλάσης //Cuboid// με και χωρίς κληρονομικότητα. | |
| | |
| <code java Cuboid.java> | |
| public class Cuboid { | |
| Rectangle rec; | |
| int length; | |
| | |
| public Cuboid(int l, int w, int h) { | |
| rec = new Rectangle(w,h); | |
| length = l; | |
| } | |
| | |
| public int getLength() { return length; } | |
| public void setLength(int l) { length = l; } | |
| | |
| public int volume() { return length * rec.area(); } | |
| } | |
| </code> | |
| | |
| ή | |
| | |
| <code java Cuboid.java> | |
| public class Cuboid extends Rectangle { | |
| int length; | |
| | |
| public Cuboid(int l, int w, int h) { | |
| super(w,h); | |
| length = l; | |
| } | |
| | |
| public int getLength() { return length; } | |
| public void setLength(int l) { length = l; } | |
| | |
| public int volume() { return length * area(); } | |
| } | |
| </code> | |
| | |
| Οι παραπάνω δύο κλάσεις μεταγλωττίζονται και παράγουν το ίδιο λειτουργικό αποτέλεσμα. Το ερώτημα είναι ποια από τις δύο προσεγγίσεις θα προτιμήσουμε. Η απάντηση είναι απλή και συνίσταται στο εξής: | |
| | |
| Χρησιμοποιώντας την ιδιότητα της κληρονομικότητας, εάν ο νέος τύπος δεδομένων που προκύπτει αποτελεί εξειδίκευση ή επέκταση της γονικής κλάσης αλλά εξακολουθεί να είναι και τύπος δεδομένων της γονικής κλάσης τότε μπορούμε να χρησιμοποιήσουμε την κληρονομικότητα ως μέθοδο δημιουργίας της νέας κλάσης. Εάν όμως ο νέος τύπος δεδομένων δεν εξακολουθεί να είναι τύπος δεδομένων και της γονικής του κλάσης τότε είναι καλύτερα ο νέος τύπος να ενσωματώνει την παραπάνω κλάση ως πεδίο, αντί να την επεκτείνει μέσω της κληρονομικότητας. | |
| | |
| Στο παραπάνω παράδειγμα, εάν η κλάση **Cuboid** κληρονομεί την κλάση **Rectangle** θα πρέπει η κλάση **Cuboid** να είναι και τύπου **Rectangle**. Αν και η κλάση του κυβοειδούς μπορεί να ενσωματώσει στην περιγραφή της και ένα ορθογώνιο παραλληλόγραμμο, το κυβοειδές δεν είναι ορθογώνιο παραλληλόγραμμο. Γενικότερα, ένα τρισδιάστατο σχήμα δεν μπορεί να είναι και διδιάστατο. Δεν μπορούμε να πάμε από το κυβοειδές προς το ορθογώνιο παραλληλόγραμμο διατηρώντας τις βασικές ιδιότητες του κυβοειδούς. Το κυβοειδές αποτελεί μία εντελώς νέα οντότητα σε σχέση με το ορθογώνιο παραλληλόγραμμο. Μπορούμε να εκφράσουμε το κυβοειδές με την χρήση της κλάσης ενός ορθογώνιου παραλληλογράμμου, χωρίς όμως η χρήση του ορθογωνίου παραλληλογράμμου να είναι απαραίτητη ή να συνδέεται άμεσα με το κυβοειδές. Επομένως, η χρήση της κληρονομικότητας στο παραπάνω παράδειγμα εισάγει λογική αντίφαση. | |
| | |
| Αντίθετα, [[java:inheritance|στο αρχικό παράδειγμα της κληρονομικότητας]], από την κλάση Bicycle προκύπτουν οι κλάσεις //MountainBike//, //RoadBike// και //TandemBike//. Και οι 3 νέες κλάσεις είναι τύπου Bicycle, καθώς αποτελούν εξειδικεύσεις της γενικής κλάσης του ποδηλάτου. Επομένως, εδώ η κληρονομικότητα συνιστάται ως μέθοδος δημιουργίας των νέων τύπων δεδομένων. | |
| | |
| | |
| |Προηγούμενο: [[ :java:inheritance | Κληρονομικότητα ]] | Επόμενο: [[ :java:final | Final Κλάσεις και Μέθοδοι]]| | |