Προγραμματισμός

Πολυμορφισμός Java και οι τύποι του

Πολυμορφισμός αναφέρεται στην ικανότητα ορισμένων οντοτήτων να εμφανίζονται σε διαφορετικές μορφές. Αντιπροσωπεύεται ευρέως από την πεταλούδα, η οποία μεταμορφώνεται από την προνύμφη έως την χρυσαλίδα στο imago. Ο πολυμορφισμός υπάρχει επίσης στις γλώσσες προγραμματισμού, ως τεχνική μοντελοποίησης που σας επιτρέπει να δημιουργήσετε μια ενιαία διεπαφή σε διάφορους τελεστές, επιχειρήματα και αντικείμενα. Ο πολυμορφισμός Java έχει ως αποτέλεσμα έναν κώδικα που είναι πιο περιεκτικός και ευκολότερος στη συντήρηση.

Ενώ αυτό το σεμινάριο επικεντρώνεται στον πολυμορφισμό υποτύπων, υπάρχουν πολλοί άλλοι τύποι που πρέπει να γνωρίζετε. Θα ξεκινήσουμε με μια επισκόπηση και των τεσσάρων τύπων πολυμορφισμού.

λήψη Λήψη του κώδικα Λήψη του πηγαίου κώδικα για παράδειγμα εφαρμογές σε αυτό το σεμινάριο. Δημιουργήθηκε από τον Jeff Friesen για το JavaWorld.

Τύποι πολυμορφισμού στην Ιάβα

Υπάρχουν τέσσερις τύποι πολυμορφισμού στην Ιάβα:

  1. Εξαναγκασμός είναι μια πράξη που εξυπηρετεί πολλαπλούς τύπους μέσω μετατροπής σιωπηρού τύπου. Για παράδειγμα, διαιρείτε έναν ακέραιο με έναν άλλο ακέραιο ή μια τιμή κυμαινόμενου σημείου με μια άλλη τιμή κυμαινόμενου σημείου. Εάν ο ένας τελεστής είναι ακέραιος και ο άλλος τελεστής είναι τιμή κινητής υποδιαστολής, ο μεταγλωττιστής συνεργάζεται (σιωπηρά μετατρέπει) τον ακέραιο σε τιμή κυμαινόμενου σημείου για την αποφυγή σφάλματος τύπου. (Δεν υπάρχει καμία λειτουργία διαίρεσης που υποστηρίζει έναν ακέραιο τελεστή και έναν τελεστή κινητής υποδιαστολής.) Ένα άλλο παράδειγμα είναι η μετάδοση μιας αναφοράς αντικειμένου υποκατηγορίας σε μια παράμετρο superclass μιας μεθόδου. Ο μεταγλωττιστής εξαναγκάζει τον τύπο της υποκατηγορίας στον τύπο της υπερκλαστικής για να περιορίσει τις λειτουργίες σε εκείνες της υπερκλαστικής.
  2. Υπερφόρτωση αναφέρεται στη χρήση του ίδιου συμβόλου χειριστή ή ονόματος μεθόδου σε διαφορετικά περιβάλλοντα. Για παράδειγμα, μπορείτε να χρησιμοποιήσετε + για εκτέλεση ακέραιας προσθήκης, προσθήκης κυμαινόμενου σημείου ή συνδυασμού συμβολοσειρών, ανάλογα με τους τύπους των τελεστών του. Επίσης, πολλαπλές μέθοδοι με το ίδιο όνομα μπορούν να εμφανιστούν σε μια τάξη (μέσω δήλωσης ή / και κληρονομιάς).
  3. Παραμετρική Ο πολυμορφισμός ορίζει ότι σε μια δήλωση τάξης, ένα όνομα πεδίου μπορεί να συσχετιστεί με διαφορετικούς τύπους και ένα όνομα μεθόδου μπορεί να συσχετιστεί με διαφορετικούς τύπους παραμέτρων και επιστροφών. Το πεδίο και η μέθοδος μπορούν στη συνέχεια να λάβουν διαφορετικούς τύπους σε κάθε παρουσία κλάσης (αντικείμενο). Για παράδειγμα, ένα πεδίο μπορεί να είναι τύπου Διπλό (μέλος της τυπικής βιβλιοθήκης τάξης της Java που τυλίγει α διπλό τιμή) και μια μέθοδος μπορεί να επιστρέψει a Διπλό σε ένα αντικείμενο και το ίδιο πεδίο μπορεί να είναι τύπου Σειρά και η ίδια μέθοδος μπορεί να επιστρέψει a Σειρά σε άλλο αντικείμενο. Η Java υποστηρίζει τον παραμετρικό πολυμορφισμό μέσω γενικών, το οποίο θα συζητήσω σε ένα μελλοντικό άρθρο.
  4. Υποτύπος σημαίνει ότι ένας τύπος μπορεί να χρησιμεύσει ως υποτύπος άλλου τύπου. Όταν μια παρουσία υποτύπου εμφανίζεται σε περιβάλλον supertype, η εκτέλεση λειτουργίας supertype στην παρουσία υποτύπου οδηγεί στην εκτέλεση της έκδοσης του υποτύπου αυτής της λειτουργίας. Για παράδειγμα, σκεφτείτε ένα κομμάτι κώδικα που σχεδιάζει αυθαίρετα σχήματα. Μπορείτε να εκφράσετε αυτόν τον κωδικό σχεδίασης πιο συγκεκριμένα εισάγοντας ένα Σχήμα τάξη με ένα σχεδιάζω() μέθοδος; με την εισαγωγή Κύκλος, Ορθογώνιο παραλληλόγραμμοκαι άλλες υποκατηγορίες που παρακάμπτουν σχεδιάζω(); εισάγοντας μια σειρά τύπων Σχήμα των οποίων τα στοιχεία αποθηκεύουν αναφορές Σχήμα υποκατηγορίες παρουσίες και τηλεφωνώντας Σχήμα'μικρό σχεδιάζω() μέθοδο σε κάθε παρουσία. Όταν καλείτε σχεδιάζω(), είναι το Κύκλος'μικρό, Ορθογώνιο παραλληλόγραμμοή άλλο Σχήμα παράδειγμα σχεδιάζω() μέθοδος που καλείται. Λέμε ότι υπάρχουν πολλές μορφές Σχήμα'μικρό σχεδιάζω() μέθοδος.

Αυτό το σεμινάριο εισάγει πολυμορφισμό υποτύπων. Θα μάθετε για την αναβάθμιση και την καθυστερημένη δέσμευση, αφηρημένες τάξεις (που δεν μπορούν να τεκμηριωθούν) και αφηρημένες μεθόδους (που δεν μπορούν να κληθούν). Θα μάθετε επίσης σχετικά με την αναγνώριση downcasting και τον τύπο χρόνου εκτέλεσης και θα ρίξετε μια πρώτη ματιά στους τύπους επιστροφής συνδιαλλαγής. Θα σώσω τον παραμετρικό πολυμορφισμό για ένα μελλοντικό σεμινάριο.

Ad-hoc εναντίον καθολικού πολυμορφισμού

Όπως πολλοί προγραμματιστές, ταξινομώ τον εξαναγκασμό και την υπερφόρτωση ως ad-hoc πολυμορφισμό, και παραμετρικός και υποτύπος ως καθολικός πολυμορφισμός. Αν και πολύτιμες τεχνικές, δεν πιστεύω ότι ο εξαναγκασμός και η υπερφόρτωση είναι πραγματικός πολυμορφισμός. μοιάζουν περισσότερο με μετατροπές τύπου και συντακτική ζάχαρη.

Πολυμορφισμός υποτύπου: Αναβάθμιση και καθυστερημένη σύνδεση

Ο υποτύπος πολυμορφισμός βασίζεται σε αναβάθμιση και καθυστερημένη δέσμευση. Αναβάθμιση είναι μια μορφή μετάδοσης όπου ρίχνετε την ιεραρχία κληρονομιάς από έναν δευτερεύοντα τύπο σε έναν υπερτύπο. Δεν συμμετέχει κανένας τελεστής επειδή ο δευτερεύων τύπος είναι μια εξειδίκευση του υπερτύπου. Για παράδειγμα, Σχήμα s = νέος κύκλος (); upcasts από Κύκλος προς την Σχήμα. Αυτό έχει νόημα επειδή ένας κύκλος είναι ένα είδος σχήματος.

Μετά την αναβάθμιση Κύκλος προς την Σχήμα, δεν μπορείτε να καλέσετε Κύκλος- ειδικές μέθοδοι, όπως getRadius () μέθοδος που επιστρέφει την ακτίνα του κύκλου, επειδή Κύκλος- οι ειδικές μέθοδοι δεν αποτελούν μέρος Σχήμαδιεπαφή. Η απώλεια πρόσβασης σε χαρακτηριστικά υποτύπου μετά τη μείωση της υποκατηγορίας στο superclass της φαίνεται άσκοπη, αλλά είναι απαραίτητη για την επίτευξη πολυμορφισμού υποτύπων.

Υποθετω πως Σχήμα δηλώνει α σχεδιάζω() μέθοδος, του Κύκλος η υποκατηγορία παρακάμπτει αυτήν τη μέθοδο, Σχήμα s = νέος κύκλος (); μόλις εκτελέστηκε και καθορίζει η επόμενη γραμμή s.draw ();. Οι οποίες σχεδιάζω() η μέθοδος καλείται: Σχήμα'μικρό σχεδιάζω() μέθοδος ή Κύκλος'μικρό σχεδιάζω() μέθοδος? Ο μεταγλωττιστής δεν ξέρει ποιο σχεδιάζω() μέθοδος κλήσης. Το μόνο που μπορεί να κάνει είναι να επαληθεύσει ότι υπάρχει μια μέθοδος στο superclass και να επαληθεύσει ότι η λίστα ορισμάτων και ο τύπος επιστροφής κλήσης της μεθόδου ταιριάζουν με τη δήλωση μεθόδου του superclass. Ωστόσο, ο μεταγλωττιστής εισάγει επίσης μια εντολή στον μεταγλωττισμένο κώδικα που, κατά το χρόνο εκτέλεσης, ανακτά και χρησιμοποιεί οποιαδήποτε αναφορά υπάρχει μικρό για να καλέσετε το σωστό σχεδιάζω() μέθοδος. Αυτή η εργασία είναι γνωστή ως αργά δεσμευτική.

Καθυστερημένη δέσμευση έναντι πρώιμης δέσμευσης

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

Έχω δημιουργήσει μια εφαρμογή που δείχνει πολυμορφισμό υποτύπων όσον αφορά την αναβάθμιση και την καθυστερημένη δέσμευση. Αυτή η εφαρμογή αποτελείται από Σχήμα, Κύκλος, Ορθογώνιο παραλληλόγραμμο, και Σχήματα τάξεις, όπου κάθε τάξη αποθηκεύεται στο δικό της αρχείο προέλευσης. Η λίστα 1 παρουσιάζει τις τρεις πρώτες τάξεις.

Λίστα 1. Δήλωση μιας ιεραρχίας σχημάτων

class Shape {void draw () {}} Η κατηγορία Circle επεκτείνει το Shape {private int x, y, r; Κύκλος (int x, int y, int r) {this.x = x; αυτό.y = y; αυτό.r = r; } // Για συντομία, παρέλειψα τις μεθόδους getX (), getY () και getRadius (). @Override void draw () {System.out.println ("Κύκλος σχεδίασης (" + x + "," + y + "," + r + ")"); }} Το ορθογώνιο κλάσης επεκτείνει το σχήμα {ιδιωτικό int x, y, w, h; Ορθογώνιο (int x, int y, int w, int h) {this.x = x; αυτό.y = y; αυτό.w = w; this.h = h; } // Για συντομία, παραλείψαμε τις μεθόδους getX (), getY (), getWidth () και getHeight () //. @Override void draw () {System.out.println ("Σχέδιο ορθογωνίου (" + x + "," + y + "," + w + "," + h + ")"); }}

Η λίστα 2 παρουσιάζει το Σχήματα τάξη εφαρμογής του οποίου κύριος() Η μέθοδος οδηγεί την εφαρμογή.

Λίστα 2. Αναβάθμιση και καθυστερημένη δέσμευση στον πολυμορφότυπο υποτύπων

class Shapes {public static void main (String [] args) {Shape [] σχήματα = {νέο κύκλο (10, 20, 30), νέο ορθογώνιο (20, 30, 40, 50)}; για (int i = 0; i <σχήματα. μήκος; i ++) σχήματα [i] .draw (); }}

Η δήλωση του σχήματα Ο πίνακας δείχνει την αναβάθμιση. ο Κύκλος και Ορθογώνιο παραλληλόγραμμο οι αναφορές αποθηκεύονται σε σχήματα [0] και σχήματα [1] και είναι αναβαθμισμένοι στον τύπο Σχήμα. Καθένα από σχήματα [0] και σχήματα [1] θεωρείται ως Σχήμα παράδειγμα: σχήματα [0] δεν θεωρείται ως Κύκλος; σχήματα [1] δεν θεωρείται ως Ορθογώνιο παραλληλόγραμμο.

Η καθυστερημένη δέσμευση αποδεικνύεται από το σχήματα [i] .draw (); έκφραση. Πότε Εγώ ισούται 0, οι εντολές που δημιουργούνται από τον μεταγλωττιστή προκαλούν Κύκλος'μικρό σχεδιάζω() μέθοδος που καλείται. Πότε Εγώ ισούται 1, ωστόσο, αυτή η οδηγία προκαλεί Ορθογώνιο παραλληλόγραμμο'μικρό σχεδιάζω() μέθοδος που καλείται. Αυτή είναι η ουσία του υποτύπου πολυμορφισμού.

Υποθέτοντας ότι και τα τέσσερα αρχεία προέλευσης (Shapes.java, Shape.java, Rectangle.java, και Circle.java) βρίσκονται στον τρέχοντα κατάλογο, μεταγλωττίστε τους μέσω μιας από τις ακόλουθες γραμμές εντολών:

javac * .java javac Shapes.java

Εκτελέστε την εφαρμογή που προκύπτει:

σχήματα java

Πρέπει να παρατηρήσετε την ακόλουθη έξοδο:

Κύκλος σχεδίασης (10, 20, 30) ορθογώνιο σχεδίασης (20, 30, 40, 50)

Περίληψη τάξεις και μέθοδοι

Όταν σχεδιάζετε ιεραρχίες τάξης, θα διαπιστώσετε ότι οι τάξεις που βρίσκονται πιο κοντά στην κορυφή αυτών των ιεραρχιών είναι πιο γενικές από τις τάξεις που είναι χαμηλότερες. Για παράδειγμα, α Οχημα Το superclass είναι πιο γενικό από ένα Φορτηγό υποδιαίρεση τάξεως. Ομοίως, α Σχήμα Το superclass είναι πιο γενικό από ένα Κύκλος ή α Ορθογώνιο παραλληλόγραμμο υποδιαίρεση τάξεως.

Δεν έχει νόημα να δημιουργηθεί μια γενική τάξη. Μετά από όλα, τι θα α Οχημα το αντικείμενο περιγράφει; Ομοίως, τι είδους σχήμα αντιπροσωπεύεται από ένα Σχήμα αντικείμενο? Αντί να κωδικοποιήσετε ένα κενό σχεδιάζω() μέθοδος στο Σχήμα, μπορούμε να αποτρέψουμε την κλήση αυτής της μεθόδου και να μην τεθεί σε λειτουργία αυτή η τάξη δηλώνοντας ότι και οι δύο οντότητες είναι αφηρημένες.

Η Java παρέχει το αφηρημένη δεσμευμένη λέξη για να δηλώσει μια τάξη που δεν μπορεί να δημιουργηθεί. Ο μεταγλωττιστής αναφέρει ένα σφάλμα κατά την προσπάθεια δημιουργίας αυτής της κλάσης. αφηρημένη χρησιμοποιείται επίσης για να δηλώσει μια μέθοδο χωρίς σώμα. ο σχεδιάζω() Η μέθοδος δεν χρειάζεται σώμα επειδή δεν είναι σε θέση να σχεδιάσει ένα αφηρημένο σχήμα. Η λίστα 3 δείχνει.

Λίστα 3. Περίληψη της κλάσης Shape και της μεθόδου draw ()

αφηρημένη κλάση Σχήμα {abstract void draw (); // απαιτείται ερωτηματικό}

Περίληψη προειδοποιήσεις

Ο μεταγλωττιστής αναφέρει ένα σφάλμα όταν προσπαθείτε να δηλώσετε μια τάξη αφηρημένη και τελικός. Για παράδειγμα, ο μεταγλωττιστής παραπονιέται αφηρημένη τελική τάξη Σχήμα επειδή μια αφηρημένη τάξη δεν μπορεί να τεθεί σε λειτουργία και μια τελική τάξη δεν μπορεί να επεκταθεί. Ο μεταγλωττιστής αναφέρει επίσης ένα σφάλμα όταν δηλώνετε μια μέθοδο αφηρημένη αλλά μην δηλώνετε την τάξη του αφηρημένη. Αφαίρεση αφηρημένη από το Σχήμα Για παράδειγμα, η κεφαλίδα της κατηγορίας στην καταχώριση 3 θα είχε ως αποτέλεσμα ένα σφάλμα. Αυτό θα ήταν ένα σφάλμα επειδή μια μη αφηρημένη (συγκεκριμένη) κλάση δεν μπορεί να τεκμηριωθεί όταν περιέχει μια αφηρημένη μέθοδο. Τέλος, όταν επεκτείνετε μια αφηρημένη τάξη, η εκτεταμένη τάξη πρέπει να παρακάμψει όλες τις αφηρημένες μεθόδους, διαφορετικά η επέκταση τάξης πρέπει να δηλωθεί ότι είναι αφηρημένη. Διαφορετικά, ο μεταγλωττιστής θα αναφέρει ένα σφάλμα.

Μια αφηρημένη τάξη μπορεί να δηλώσει πεδία, κατασκευαστές και μη αφηρημένες μεθόδους επιπλέον ή αντί για αφηρημένες μεθόδους. Για παράδειγμα, μια περίληψη Οχημα η τάξη μπορεί να δηλώσει πεδία που περιγράφουν τη μάρκα, το μοντέλο και το έτος. Επίσης, μπορεί να δηλώσει ότι ένας κατασκευαστής θα προετοιμάσει αυτά τα πεδία και συγκεκριμένες μεθόδους για να επιστρέψει τις τιμές του. Δείτε την καταχώριση 4.

Λίστα 4. Περίληψη οχήματος

abstract class Vehicle {private String make, μοντέλο; ιδιωτικό έτος int? Όχημα (String make, String model, int year) {this.make = make; this.model = μοντέλο; αυτό. έτος = έτος; } String getMake () {return make; } String getModel () {model επιστροφής; } int getYear () {έτος επιστροφής; } αφηρημένη κενή κίνηση (); }

Θα το σημειώσεις αυτό Οχημα δηλώνει περίληψη κίνηση() μέθοδος για την περιγραφή της κίνησης ενός οχήματος. Για παράδειγμα, ένα αυτοκίνητο κυλάει στο δρόμο, ένα σκάφος πλέει πέρα ​​από το νερό και ένα αεροπλάνο πετάει στον αέρα. ΟχημαΟι υποκατηγορίες θα παρακάμψουν κίνηση() και παρέχετε την κατάλληλη περιγραφή. Θα κληρονομούσαν επίσης τις μεθόδους και οι κατασκευαστές τους θα καλούσαν ΟχημαΚατασκευαστής.

Downcasting και RTTI

Η αύξηση της ιεραρχίας της τάξης, μέσω αναβάθμισης, συνεπάγεται απώλεια πρόσβασης σε λειτουργίες υποτύπων. Για παράδειγμα, εκχώρηση α Κύκλος διαμαρτύρομαι Σχήμα μεταβλητός μικρό σημαίνει ότι δεν μπορείτε να χρησιμοποιήσετε μικρό για να καλέσετε Κύκλος'μικρό getRadius () μέθοδος. Ωστόσο, μπορείτε να αποκτήσετε ξανά πρόσβαση Κύκλος'μικρό getRadius () μέθοδος εκτελώντας ένα ρητή λειτουργία cast σαν αυτό: Κύκλος c = (Κύκλος) s;.

Αυτή η εργασία είναι γνωστή ως υποβιβασμός επειδή ρίχνετε την ιεραρχία κληρονομιάς από ένα supertype σε έναν subtype (από το Σχήμα superclass στο Κύκλος υποδιαίρεση τάξεως). Αν και ένα upcast είναι πάντα ασφαλές (η διεπαφή του superclass είναι ένα υποσύνολο της διεπαφής της υποκατηγορίας), ένα downcast δεν είναι πάντα ασφαλές. Η λίστα 5 δείχνει τι είδους πρόβλημα θα μπορούσε να προκύψει εάν χρησιμοποιείτε λανθασμένη μετάδοση.

Λίστα 5. Το πρόβλημα με το downcasting

class Superclass {} class Subclass επεκτείνει το Superclass {void method () {}} public class BadDowncast {public static void main (String [] args) {Superclass superclass = new Superclass (); Subclass subclass = (Subclass) superclass; subclass.method (); }}

Η λίστα 5 παρουσιάζει μια ιεραρχία τάξης που αποτελείται από Σούπερ γυαλί και Υποδιαίρεση τάξεως, το οποίο επεκτείνεται Σούπερ γυαλί. Επί πλέον, Υποδιαίρεση τάξεως δηλώνει μέθοδος(). Μια τρίτη τάξη με το όνομα BadDowncast παρέχει ένα κύριος() μέθοδος που δημιουργεί Σούπερ γυαλί. BadDowncast στη συνέχεια προσπαθεί να υποβιβάσει αυτό το αντικείμενο Υποδιαίρεση τάξεως και αντιστοιχίστε το αποτέλεσμα σε μεταβλητή υποδιαίρεση τάξεως.

Σε αυτήν την περίπτωση, ο μεταγλωττιστής δεν θα παραπονεθεί επειδή η μετάδοση από ένα superclass σε μια υποκατηγορία στην ίδια ιεραρχία τύπου είναι νόμιμη. Τούτου λεχθέντος, εάν επιτρεπόταν η ανάθεση, η εφαρμογή θα διακόπτεται όταν προσπαθούσε να εκτελέσει subclass.method ();. Σε αυτήν την περίπτωση το JVM θα προσπαθούσε να καλέσει μια ανύπαρκτη μέθοδο, επειδή Σούπερ γυαλί δεν δηλώνει μέθοδος(). Ευτυχώς, το JVM επαληθεύει ότι το cast είναι νόμιμο πριν εκτελέσει μια λειτουργία cast. Ανιχνεύοντας αυτό Σούπερ γυαλί δεν δηλώνει μέθοδος(), θα ρίξει ένα ClassCastException αντικείμενο. (Θα συζητήσω εξαιρέσεις σε μελλοντικό άρθρο.)

Συγκεντρώστε την καταχώριση 5 ως εξής:

javac BadDowncast.java

Εκτελέστε την εφαρμογή που προκύπτει:

java BadDowncast