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

Εξάρτηση τύπου σε Java, Μέρος 1

Η κατανόηση της συμβατότητας τύπου είναι θεμελιώδης για τη σύνταξη καλών προγραμμάτων Java, αλλά η αλληλεπίδραση των διαφορών μεταξύ των στοιχείων γλώσσας Java μπορεί να φαίνεται εξαιρετικά ακαδημαϊκή για τους άγνωστους. Αυτό το άρθρο είναι για προγραμματιστές λογισμικού έτοιμους να αντιμετωπίσουν την πρόκληση! Το Μέρος 1 αποκαλύπτει τις συσχετιστικές και παραβατικές σχέσεις μεταξύ απλούστερων στοιχείων όπως τύπων συστοιχιών και γενικών τύπων, καθώς και του ειδικού στοιχείου γλώσσας Java, του μπαλαντέρ. Το μέρος 2 διερευνά την εξάρτηση τύπου και τη διακύμανση σε κοινά παραδείγματα API και σε εκφράσεις λάμδα.

λήψη Λήψη της πηγής Λήψη του πηγαίου κώδικα για αυτό το άρθρο, "Εξάρτηση τύπου σε Java, Μέρος 1." Δημιουργήθηκε για το JavaWorld από τον Δρ. Andreas Solymosi.

Έννοιες και ορολογία

Πριν μπουν στις σχέσεις συνδιακύμανσης και αντίθεσης μεταξύ διαφόρων στοιχείων της γλώσσας Java, ας είμαστε σίγουροι ότι έχουμε ένα κοινό εννοιολογικό πλαίσιο.

Συμβατότητα

Σε αντικειμενοστρεφή προγραμματισμό, συμβατότητα αναφέρεται σε μια κατευθυνόμενη σχέση μεταξύ των τύπων, όπως φαίνεται στο Σχήμα 1.

Ανδρέας Σολυμόσι

Λέμε ότι είναι δύο τύποι σύμφωνος στην Java εάν είναι δυνατή η μεταφορά δεδομένων μεταξύ μεταβλητών των τύπων. Η μεταφορά δεδομένων είναι δυνατή εάν ο μεταγλωττιστής το αποδεχτεί και γίνεται μέσω ανάθεσης ή μετάδοσης παραμέτρων. Για παράδειγμα, μικρός είναι συμβατό με int επειδή η ανάθεση intVariable = shortVariable; είναι δυνατόν. Αλλά boolean δεν είναι συμβατό με int επειδή η ανάθεση intVariable = booleanVariable; δεν είναι δυνατόν; ο μεταγλωττιστής δεν θα το αποδεχτεί.

Επειδή η συμβατότητα είναι μια κατευθυνόμενη σχέση, μερικές φορές Τ1 είναι συμβατό με Τ2 αλλά Τ2 δεν είναι συμβατό με Τ1, ή όχι με τον ίδιο τρόπο. Θα το δούμε περαιτέρω όταν συζητάμε για ρητή ή σιωπηρή συμβατότητα.

Αυτό που έχει σημασία είναι ότι η συμβατότητα μεταξύ των τύπων αναφοράς είναι δυνατή μόνο μέσα σε μια ιεραρχία τύπου. Όλοι οι τύποι τάξεων είναι συμβατοί με Αντικείμενο, για παράδειγμα, επειδή όλες οι τάξεις κληρονομούνται σιωπηρά από Αντικείμενο. Ακέραιος αριθμός δεν είναι συμβατό με Φλοτέρ, ωστόσο, επειδή Φλοτέρ δεν είναι ένα superclass της Ακέραιος αριθμός. Ακέραιος αριθμόςείναι συμβατό με Αριθμός, επειδή Αριθμός είναι ένα (αφηρημένο) superclass της Ακέραιος αριθμός. Επειδή βρίσκονται στην ίδια ιεραρχία τύπου, ο μεταγλωττιστής αποδέχεται την εκχώρηση numberReference = integerReference;.

Μιλάμε για σιωπηρή ή σαφής συμβατότητα, ανάλογα με το αν η συμβατότητα πρέπει να επισημανθεί ρητά ή όχι. Για παράδειγμα, το σύντομο είναι σιωπηρά συμβατό με int (όπως φαίνεται παραπάνω) αλλά όχι το αντίστροφο: η ανάθεση shortVariable = intVariable; δεν είναι δυνατόν. Ωστόσο, το σύντομο είναι ρητά συμβατό με int, επειδή η ανάθεση shortVariable = (σύντομο) αμετάβλητο; είναι δυνατόν. Εδώ πρέπει να επισημάνουμε τη συμβατότητα έως χύσιμο, επίσης γνωστή ως μετατροπή τύπου.

Ομοίως, μεταξύ των τύπων αναφοράς: integerReference = numberReference; δεν είναι αποδεκτή, μόνο integerReference = (Integer) numberReference; θα ήταν αποδεκτό. Ως εκ τούτου, Ακέραιος αριθμός είναι σιωπηρά συμβατό με Αριθμός αλλά Αριθμός είναι μόνο ρητά συμβατό με Ακέραιος αριθμός.

Εξάρτηση

Ένας τύπος μπορεί να εξαρτάται από άλλους τύπους. Για παράδειγμα, ο τύπος πίνακα int [] εξαρτάται από τον πρωτόγονο τύπο int. Ομοίως, ο γενικός τύπος Λίστα Array εξαρτάται από τον τύπο Πελάτης. Οι μέθοδοι μπορούν επίσης να εξαρτώνται από τον τύπο, ανάλογα με τους τύπους των παραμέτρων τους. Για παράδειγμα, η μέθοδος αύξηση κενού (ακέραιος αριθμός); εξαρτάται από τον τύπο Ακέραιος αριθμός. Ορισμένες μέθοδοι (όπως μερικοί γενικοί τύποι) εξαρτώνται από περισσότερους από έναν τύπους - όπως μεθόδους που έχουν περισσότερους από μία παραμέτρους.

Συνδιακύμανση και αντίφαση

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

Ετσι, συνδιακύμανση σημαίνει ότι η συμβατότητα δύο τύπων συνεπάγεται τη συμβατότητα των τύπων που εξαρτώνται από αυτούς. Λαμβάνοντας υπόψη τη συμβατότητα τύπου, κάποιος υποθέτει ότι οι εξαρτώμενοι τύποι είναι συνδυαστικοί, όπως φαίνεται στο Σχήμα 2.

Ανδρέας Σολυμόσι

Η συμβατότητα του Τ1 προς την Τ2 συνεπάγεται τη συμβατότητα του ΣΤΟ1) προς την ΣΤΟ2). Ο εξαρτώμενος τύπος ΣΤΟ) λέγεται συνδιαλλακτική; ή πιο συγκεκριμένα, ΣΤΟ1) είναι συνδιαλλακτική για ΣΤΟ2).

Για ένα άλλο παράδειγμα: επειδή η ανάθεση numberArray = integerArray; είναι δυνατή (τουλάχιστον στην Java), οι τύποι συστοιχιών Ακέραιος αριθμός[] και Αριθμός[] είναι συνωμοτικοί. Έτσι, μπορούμε να το πούμε αυτό Ακέραιος αριθμός[] είναι σιωπηρά συνδιαλλακτική προς την Αριθμός[]. Και ενώ το αντίθετο δεν ισχύει - η ανάθεση integerArray = numberArray; δεν είναι δυνατή - η ανάθεση με χύτευση τύπου (integerArray = (Integer []) numberArray;) είναι δυνατόν; επομένως, λέμε, Αριθμός[] είναι ρητά συνδιαλλακτική προς την Ακέραιος αριθμός[] .

Να συνοψίσουμε: Ακέραιος αριθμός είναι σιωπηρά συμβατό με Αριθμός, ως εκ τούτου Ακέραιος αριθμός[] είναι σιωπηρά συνήθης Αριθμός[], και Αριθμός[] είναι σαφώς διακύμανση Ακέραιος αριθμός[] . Το σχήμα 3 απεικονίζει.

Ανδρέας Σολυμόσι

Σε γενικές γραμμές, μπορούμε να πούμε ότι οι τύποι συστοιχιών είναι συνδυαστικοί στην Java. Θα δούμε παραδείγματα συνδιακύμανσης μεταξύ γενικών τύπων αργότερα στο άρθρο.

Σύγκριση

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

Ένας εξαρτώμενος τύπος όπως ΣΤΟ) λέγεται αντιφατικό εάν η συμβατότητα του Τ1 προς την Τ2 συνεπάγεται τη συμβατότητα του ΣΤΟ2) προς την ΣΤΟ1). Το σχήμα 4 απεικονίζει.

Ανδρέας Σολυμόσι

Στοιχείο γλώσσας (τύπος ή μέθοδος) ΣΤΟ) εξαρτάται από Τ είναι συνδιαλλακτική εάν η συμβατότητα του Τ1 προς την Τ2 συνεπάγεται τη συμβατότητα του ΣΤΟ1) προς την ΣΤΟ2). Εάν η συμβατότητα του Τ1 προς την Τ2 συνεπάγεται τη συμβατότητα του ΣΤΟ2) προς την ΣΤΟ1), τότε ο τύπος ΣΤΟ) είναι αντιφατικό. Εάν η συμβατότητα του Τ1 μεταξύ Τ2 δεν συνεπάγεται καμία συμβατότητα μεταξύ ΣΤΟ1) και ΣΤΟ2), έπειτα ΣΤΟ) είναι αμετάβλητο.

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

Στοιχεία που εξαρτώνται από τον τύπο: Μέθοδοι και τύποι

Στην Java, οι μέθοδοι, οι τύποι συστοιχιών και οι γενικοί (παραμετρικοί) τύποι είναι τα εξαρτώμενα από τον τύπο στοιχεία. Οι μέθοδοι εξαρτώνται από τους τύπους των παραμέτρων τους. Ένας τύπος πίνακα, Τ [], εξαρτάται από τους τύπους των στοιχείων του, Τ. Ένας γενικός τύπος σολ εξαρτάται από την παράμετρο τύπου, Τ. Το σχήμα 5 απεικονίζει.

Ανδρέας Σολυμόσι

Κυρίως αυτό το άρθρο επικεντρώνεται στη συμβατότητα τύπου, αν και θα θίξω τη συμβατότητα μεταξύ των μεθόδων προς το τέλος του Μέρους 2.

Σιωπηρή και ρητή συμβατότητα τύπου

Νωρίτερα, είδατε τον τύπο Τ1 να εισαι σιωπηράρητά) συμβατό με Τ2. Αυτό ισχύει μόνο εάν η εκχώρηση μιας μεταβλητής τύπου Τ1 σε μια μεταβλητή τύπου Τ2 επιτρέπεται χωρίς (ή με) επισήμανση. Η μετάδοση τύπου είναι ο πιο συχνός τρόπος για να επισημάνετε ρητή συμβατότητα:

 variableOfTypeT2 = variableOfTypeT1; // σιωπηρή συμβατή μεταβλητήOfTypeT2 = (T2) μεταβλητήOfTypeT1; // ρητή συμβατή 

Για παράδειγμα, int είναι σιωπηρά συμβατό με μακρύς και ρητά συμβατό με μικρός:

 int intVariable = 5; long longVariable = αμετάβλητο; // σιωπηρή συμβατή short shortVariable = (short) intVariable; // ρητή συμβατή 

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

Σημειώστε ότι boolean δεν είναι συμβατό με οποιονδήποτε άλλο τύπο, ούτε μπορεί να είναι συμβατός ένας πρωτόγονος και ένας τύπος αναφοράς.

Παράμετροι μεθόδου

Λέμε, μια μέθοδος διαβάζει παραμέτρους εισόδου και γράφει παραμέτρους εξόδου. Οι παράμετροι των πρωτόγονων τύπων είναι πάντα παράμετροι εισαγωγής. Η τιμή επιστροφής μιας συνάρτησης είναι πάντα παράμετρος εξόδου. Οι παράμετροι των τύπων αναφοράς μπορούν να είναι και οι δύο: εάν η μέθοδος αλλάξει την αναφορά (ή μια αρχική παράμετρος), η αλλαγή παραμένει εντός της μεθόδου (που σημαίνει ότι δεν είναι ορατή εκτός της μεθόδου μετά την κλήση - αυτό είναι γνωστό ως κλήση ανά αξία). Εάν η μέθοδος αλλάξει το αναφερόμενο αντικείμενο, ωστόσο, η αλλαγή παραμένει μετά την επιστροφή της από τη μέθοδο - αυτό είναι γνωστό ως καλέστε με αναφορά.

Ένας δευτερεύων τύπος (αναφοράς) είναι σιωπηρά συμβατός με τον υπόθετό του και ένας υπερτύπος είναι ρητός συμβατός με τον υπότυπο του. Αυτό σημαίνει ότι οι τύποι αναφοράς είναι συμβατοί μόνο εντός του κλάδου της ιεραρχίας τους - προς τα πάνω έμμεσα και προς τα κάτω ρητά:

 ReferenceOfSuperType = αναφοράOfSubType; // σιωπηρή συμβατή ReferenceOfSubType = (SubType) ReferenceOfSuperType; // ρητή συμβατή 

Ο μεταγλωττιστής Java συνήθως επιτρέπει τη σιωπηρή συμβατότητα για μια ανάθεση μόνο εάν δεν υπάρχει κίνδυνος απώλειας πληροφοριών κατά το χρόνο εκτέλεσης μεταξύ των διαφόρων τύπων. (Σημειώστε, ωστόσο, ότι αυτός ο κανόνας δεν ισχύει για απώλεια ακρίβειας, όπως σε μια ανάθεση από int για να επιπλέουν.) Για παράδειγμα, int είναι σιωπηρά συμβατό με μακρύς γιατί α μακρύς μεταβλητή κρατά κάθε int αξία. Αντίθετα, α μικρός η μεταβλητή δεν περιέχει κανένα int αξίες; Επομένως, επιτρέπεται μόνο ρητή συμβατότητα μεταξύ αυτών των στοιχείων.

Ανδρέας Σολυμόσι

Σημειώστε ότι η σιωπηρή συμβατότητα στο Σχήμα 6 προϋποθέτει ότι η σχέση είναι μεταβατικός: μικρός είναι συμβατό με μακρύς.

Παρόμοιο με αυτό που βλέπετε στο Σχήμα 6, είναι πάντα δυνατό να αντιστοιχίσετε μια αναφορά ενός υποτύπου int μια αναφορά ενός υπερτύπου. Λάβετε υπόψη ότι η ίδια ανάθεση προς την άλλη κατεύθυνση θα μπορούσε να ρίξει ένα ClassCastException, ωστόσο, έτσι ο μεταγλωττιστής Java το επιτρέπει μόνο με μετάδοση τύπου.

Συνδιακύμανση και αντίθεση για τύπους συστοιχιών

Στην Ιάβα, ορισμένοι τύποι συστοιχιών είναι συνωμοτικοί και / ή αντίθετοι. Στην περίπτωση συνδιακύμανσης, αυτό σημαίνει ότι εάν Τ είναι συμβατό με Ε, έπειτα Τ [] είναι επίσης συμβατό με U []. Σε περίπτωση παράβασης, αυτό σημαίνει ότι U [] είναι συμβατό με Τ []. Οι πίνακες πρωτόγονων τύπων είναι αμετάβλητες στην Java:

 longArray = intArray; // τύπος σφάλματος shortArray = (short []) intArray; // τυπογραφικό λάθος 

Οι πίνακες των τύπων αναφοράς είναι σιωπηρά συνδιαλλακτική και σαφώς παραβατικό, ωστόσο:

 SuperType [] superArray; Υποτύπος [] subArray; ... superArray = subArray; // σιωπηρή συνδιαλλακτική subArray = (SubType []) superArray; // ρητή παράβαση 
Ανδρέας Σολυμόσι

Σχήμα 7. Σιωπηρή συνδιακύμανση για συστοιχίες

Αυτό σημαίνει, πρακτικά, ότι μπορεί να ρίξει μια αντιστοίχιση συστατικών στοιχείων πίνακα ArrayStoreException στο χρόνο εκτέλεσης. Εάν ένας πίνακας αναφοράς του SuperType αναφέρεται σε ένα αντικείμενο πίνακα Υποτύπος, και ένα από τα συστατικά του εκχωρείται στη συνέχεια σε ένα SuperType αντικείμενο, τότε:

 superArray [1] = νέο SuperType (); // ρίχνει το ArrayStoreException 

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

Ένα παράδειγμα συνδιακύμανσης

Σε ένα απλό παράδειγμα, η αναφορά πίνακα είναι τύπου Αντικείμενο[] αλλά το αντικείμενο του πίνακα και τα στοιχεία είναι διαφορετικών κατηγοριών:

 Object [] objectArray; // array array objectArray = νέα συμβολοσειρά [3]; // αντικείμενο πίνακα; συμβατό αντικείμενο objectArray [0] = νέο ακέραιο (5); // ρίχνει το ArrayStoreException 

Λόγω της συνδιακύμανσης, ο μεταγλωττιστής δεν μπορεί να ελέγξει την ορθότητα της τελευταίας αντιστοίχισης στα στοιχεία του πίνακα - η JVM το κάνει αυτό και με σημαντικό κόστος. Ωστόσο, ο μεταγλωττιστής μπορεί να βελτιστοποιήσει το κόστος μακριά, εάν δεν υπάρχει χρήση συμβατότητας τύπου μεταξύ τύπων συστοιχιών.

Ανδρέας Σολυμόσι

Να θυμάστε ότι στην Java, για μια μεταβλητή αναφοράς κάποιου τύπου που παραπέμπει ένα αντικείμενο του supertype της: τα βέλη στο Σχήμα 8 δεν πρέπει να κατευθύνονται προς τα πάνω.

Παραλλαγές και μπαλαντέρ σε γενικούς τύπους

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

 Γενικό υπερ-γενικό; Γενικό υπογενές; subGeneric = (Generic) superGeneric; // τύπος σφάλματος superGeneric = (Generic) subGeneric; // τυπογραφικό λάθος 

Ωστόσο, τα σφάλματα τύπου προκύπτουν subGeneric.getClass () == superGeneric.getClass (). Το πρόβλημα είναι ότι η μέθοδος getClass () καθορίζει τον ανεπεξέργαστο τύπο - γι 'αυτό μια παράμετρος τύπου δεν ανήκει στην υπογραφή μιας μεθόδου Έτσι, οι δύο δηλώσεις μεθόδου

 άκυρη μέθοδος (Generic p); άκυρη μέθοδος (Generic p); 

δεν πρέπει να εμφανίζονται μαζί σε έναν ορισμό διεπαφής (ή αφηρημένης κλάσης).

$config[zx-auto] not found$config[zx-overlay] not found