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

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

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

Θα περάσουμε δεξιά, οπότε αν δεν έχετε διαβάσει ήδη το Μέρος 1, σας προτείνω να ξεκινήσετε από εκεί.

Παραδείγματα API για παράβαση

Για το πρώτο μας παράδειγμα, σκεφτείτε το Συγκριτής έκδοση του java.util.Collections.sort (), από το Java Συλλογές API. Η υπογραφή αυτής της μεθόδου είναι:

  άκυρη ταξινόμηση (Λίστα λίστας, Συγκριτής γ) 

ο είδος() μέθοδος ταξινομεί οποιαδήποτε Λίστα. Συνήθως είναι πιο εύκολο να χρησιμοποιήσετε την υπερφορτωμένη έκδοση, με την υπογραφή:

 ταξινόμηση (Λίστα) 

Σε αυτήν την περίπτωση, επεκτείνεται συγκρίσιμο εκφράζει ότι το είδος() μπορεί να κληθεί μόνο εάν τα απαραίτητα στοιχεία σύγκρισης μεθόδων (δηλαδή σύγκρισηΤο) έχουν οριστεί στον τύπο στοιχείου (ή στον υπερτύπο του, χάρη στο ? σούπερ Τ):

 ταξινόμηση (integerList); // Ο Integer εφαρμόζει συγκρίσιμο είδος (λίστα πελατών). // λειτουργεί μόνο εάν ο Πελάτης εφαρμόζει το συγκρίσιμο 

Χρήση γενικών για σύγκριση

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

Ωστόσο, αυτός ο τύπος στοιχείου μπορεί να ταξινομηθεί με έναν τρόπο. Για παράδειγμα, μπορείτε να ταξινομήσετε ένα Πελάτης από την ταυτότητά τους, αλλά όχι από τα γενέθλια ή τον ταχυδρομικό κώδικα. Χρησιμοποιώντας το Συγκριτής έκδοση του είδος() είναι πιο ευέλικτο:

 είδος δημόσιου κενού (Λίστα λίστας, Συγκριτής γ) 

Τώρα συγκρίνουμε στοιχεία όχι στην κλάση του στοιχείου, αλλά σε ένα επιπλέον Συγκριτής αντικείμενο. Αυτή η γενική διεπαφή έχει μια μέθοδο αντικειμένου:

 int σύγκριση (T o1, T o2); 

Παράνομες παράμετροι

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

class DateComparator εφαρμόζει το Comparator {public int membandingkan (Ημερομηνία d1, Ημερομηνία d2) {return ...} // συγκρίνει τα δύο αντικείμενα Date} List dateList = ...; // Λίστα ταξινόμησης αντικειμένων ημερομηνίας (dateList, new DateComparator ()); // ταξινόμηση ημερομηνίας Λίστα 

Χρησιμοποιώντας την πιο περίπλοκη έκδοση της μεθόδου Collection.sort () ετοιμαστείτε για πρόσθετες περιπτώσεις χρήσης, ωστόσο. Η παράμετρος τύπου παραλλαγής του Συγκρίσιμος καθιστά δυνατή την ταξινόμηση μιας λίστας τύπου Λίστα, επειδή java.util. Ημερομηνία είναι ένα supertype του java.sql. Ημερομηνία:

 Λίστα sqlList = ...; ταξινόμηση (sqlList, new DateComparator ()); 

Εάν παραλείψουμε την αντίφαση στο είδος() υπογραφή (μόνο με χρήση ή το μη καθορισμένο, μη ασφαλές ), τότε ο μεταγλωττιστής απορρίπτει την τελευταία γραμμή ως σφάλμα τύπου.

Για να καλέσετε

 ταξινόμηση (sqlList, νέο SqlDateComparator ()); 

θα έπρεπε να γράψεις μια επιπλέον κατηγορία χωρίς χαρακτηριστικά:

 Η κλάση SqlDateComparator επεκτείνει το DateComparator {} 

Πρόσθετες μέθοδοι

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

Συλλογές μεθόδους όπως Μέγιστη() και ελάχ. () προσφέρουν τύπους παραβατικών αποτελεσμάτων:

 δημόσιο στατικό  T max (Συλλογή συλλογής) {...} 

Όπως βλέπετε εδώ, μπορεί να ζητηθεί μια παράμετρος τύπου για να ικανοποιήσει περισσότερες από μία προϋποθέσεις, απλά χρησιμοποιώντας &. ο επεκτείνει το αντικείμενο μπορεί να φαίνεται περιττή, αλλά το ορίζει αυτό Μέγιστη() επιστρέφει ένα αποτέλεσμα τύπου Αντικείμενο και όχι της σειράς Συγκρίσιμος στο bytecode. (Δεν υπάρχουν παράμετροι τύπου στο bytecode.)

Η υπερφορτωμένη έκδοση του Μέγιστη() με Συγκριτής είναι ακόμη πιο αστείο:

 δημόσια στατική T max (Συλλογή συλλογής, Comparator comp) 

Αυτό Μέγιστη() και τα δύο είναι παράφορα και παράμετροι συνδυαστικού τύπου. Ενώ τα στοιχεία του Συλλογή πρέπει να είναι (ενδεχομένως διαφορετικών) υποτύπων ενός συγκεκριμένου τύπου (δεν αναφέρεται ρητά), Συγκριτής πρέπει να είναι instantiated για ένα supertype του ίδιου τύπου. Πολλά απαιτούνται από τον αλγόριθμο συμπερασμάτων του μεταγλωττιστή, προκειμένου να διαφοροποιηθεί αυτός ο ενδιάμεσος τύπος από μια κλήση όπως αυτή:

 Συλλογή συλλογής = ...; Συγκριτικός συγκριτής = ...; max (συλλογή, συγκριτής); 

Πλαίσιο δέσμευσης παραμέτρων τύπου

Ως το τελευταίο μας παράδειγμα εξάρτησης τύπου και διακύμανσης στο Java Συλλογές API, ας επανεξετάσουμε την υπογραφή του είδος() με Συγκρίσιμος. Σημειώστε ότι χρησιμοποιεί και τα δύο εκτείνεται και σούπερ, τα οποία είναι συσκευασμένα:

 στατικός  άκυρη ταξινόμηση (Λίστα λιστών) {...} 

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

 ταξινόμηση (DateList); // java.util.Date υλοποιεί συγκρίσιμο είδος (sqlList); // java.sql. Εφαρμογές ημερομηνίας συγκρίσιμο 

Ωστόσο, το κάτω όριο της παραμέτρου τύπου επιτρέπει επιπλέον ευελιξία. Συγκρίσιμος δεν χρειάζεται απαραίτητα να εφαρμοστεί στην κατηγορία στοιχείων. αρκεί να το έχουμε εφαρμόσει στο superclass. Για παράδειγμα:

 Η κλάση SuperClass υλοποιεί Συγκρίσιμα {public int membandingkanTo (SuperClass s) {...}} Η κλάση SubClass επεκτείνει το SuperClass {} // χωρίς υπερφόρτωση του σύγκρισηTo () List superList = ...; ταξινόμηση (superList); Λίστα δευτερεύουσας λίστας = ...; ταξινόμηση (subList); 

Ο μεταγλωττιστής αποδέχεται την τελευταία γραμμή με

 στατικός  άκυρη ταξινόμηση (Λίστα λιστών) {...} 

και το απορρίπτει με

στατικός  άκυρη ταξινόμηση (Λίστα λιστών) {...} 

Ο λόγος για αυτήν την απόρριψη είναι ότι ο τύπος Υποδιαίρεση τάξεως (που ο μεταγλωττιστής θα καθορίσει από τον τύπο Λίστα στην παράμετρο δευτερεύουσα λίστα) δεν είναι κατάλληλο ως παράμετρος τύπου για Το T επεκτείνεται συγκρίσιμο. Ο τύπος Υποδιαίρεση τάξεως δεν εφαρμόζει Συγκρίσιμος; εφαρμόζει μόνο Συγκρίσιμος. Ωστόσο, τα δύο στοιχεία δεν είναι συμβατά λόγω της έλλειψης σιωπηρής συνδιακύμανσης Υποδιαίρεση τάξεως είναι συμβατό με SuperClass.

Από την άλλη πλευρά, αν χρησιμοποιούμε , ο μεταγλωττιστής δεν περιμένει Υποδιαίρεση τάξεως να εφαρμόσει Συγκρίσιμος; είναι αρκετό αν SuperClass το κάνει. Είναι αρκετό γιατί η μέθοδος σύγκρισηΤο () κληρονομείται από SuperClass και μπορεί να ζητηθεί Υποδιαίρεση τάξεως αντικείμενα: το εκφράζει, προκαλώντας αντίφαση.

Μεταβλητές πρόσβασης μεταβλητές μιας παραμέτρου τύπου

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

Ισχύουν διαφορετικοί κανόνες για την παράμετρο και τον τύπο αποτελέσματος μιας μεθόδου (όπως για το εισαγωγή και παραγωγή τύποι παραμέτρων ενός γενικού τύπου). Ένα αυθαίρετο αντικείμενο συμβατό με Υποτύπος μπορεί να περάσει ως παράμετρος της μεθόδου γράφω(), όπως ορίζεται παραπάνω.

 contravariantReference.write (νέος υποτύπος ()); // OK contravariantReference.write (νέο SubSubType ()); // Εντάξει πάρα πολύ contrariantReference.write (νέο SuperType ()); // type error ((Generic) contravariantReference) .write (νέο SuperType ()); // ΕΝΤΑΞΕΙ 

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

Η κατάσταση δεν αλλάζει για τον τύπο αποτελέσματος δεσμεύοντας: ανάγνωση() εξακολουθεί να παρέχει αποτέλεσμα τύπου ?, συμβατό μόνο με Αντικείμενο:

 Object o = contravariantReference.read (); Υποτύπος st = contravariantReference.read (); // τυπογραφικό λάθος 

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

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

 SuperSuperType sst = ((Generic) contravariantReference) .read (); sst = (SuperSuperType) contravariantReference.read (); // μη ασφαλής εναλλακτική λύση 

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

Ανάγνωση και εγγραφή σε μεταβλητές της παραμέτρου τύπου

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

Πίνακας 1. Πρόσβαση ανάγνωσης και γραφής σε μεταβλητές παραμέτρου τύπου

ΑΝΑΓΝΩΣΗ

(εισαγωγή)

ανάγνωση

Αντικείμενο

γράφω

Αντικείμενο

ανάγνωση

υπερτύπος

γράφω

υπερτύπος

ανάγνωση

δευτερεύων τύπος

γράφω

δευτερεύων τύπος

Μπαλαντέρ

?

Εντάξει Λάθος Εκμαγείο Εκμαγείο Εκμαγείο Εκμαγείο

Συνδιακύμανση

επεκτείνεται

Εντάξει Λάθος Εντάξει Εκμαγείο Εκμαγείο Εκμαγείο

Παραβατική

?σούπερ

Εντάξει Εκμαγείο Εκμαγείο Εκμαγείο Εκμαγείο Εντάξει

Οι σειρές στον Πίνακα 1 αναφέρονται στο είδος αναφοράς, και τις στήλες στο τύπος δεδομένων για πρόσβαση. Οι επικεφαλίδες του "supertype" και "subtype" δείχνουν τα όρια μπαλαντέρ. Η καταχώριση "cast" σημαίνει ότι η αναφορά πρέπει να είναι cast. Ένα παράδειγμα "ΟΚ" στις τέσσερις τελευταίες στήλες αναφέρεται στις τυπικές περιπτώσεις συνδιακύμανσης και παράβασης.

Δείτε το τέλος αυτού του άρθρου για ένα συστηματικό πρόγραμμα δοκιμών για τον πίνακα, με λεπτομερείς εξηγήσεις.

Δημιουργία αντικειμένων

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

 Generic [] genericArray = νέο Generic [20]; // τύπος σφάλματος Generic [] wildcardArray = new Generic [20]; // OK genericArray = (Generic []) wildcardArray; // μη επιλεγμένη μετατροπή genericArray [0] = new Generic (); genericArray [0] = νέο Generic (); // type error wildcardArray [0] = νέο Generic (); // ΕΝΤΑΞΕΙ 

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

Σε μια γενική κλάση, δεν μπορούμε να δημιουργήσουμε αντικείμενα της παραμέτρου τύπου. Για παράδειγμα, στον κατασκευαστή ενός Λίστα Array υλοποίηση, το αντικείμενο του πίνακα πρέπει να είναι τύπου Αντικείμενο[] κατά τη δημιουργία. Στη συνέχεια μπορούμε να το μετατρέψουμε στον τύπο πίνακα της παραμέτρου type:

 Η κλάση MyArrayList εφαρμόζει Λίστα περιεχομένου {private final E []; MyArrayList (int μέγεθος) {content = new E [size]; // τύπος περιεχομένου σφάλματος = (E []) νέο αντικείμενο [μέγεθος]; // λύση} ...} 

Για ασφαλέστερη λύση, περάστε το Τάξη τιμή της πραγματικής παραμέτρου τύπου στον κατασκευαστή:

 content = (E []) java.lang.reflect.Array.newInstance(myClass, μέγεθος); 

Παράμετροι πολλαπλών τύπων

Ένας γενικός τύπος μπορεί να έχει περισσότερες από μία παραμέτρους τύπου. Οι παράμετροι τύπου δεν αλλάζουν τη συμπεριφορά της συνδιακύμανσης και της αντίθεσης και πολλές παράμετροι τύπου μπορούν να εμφανιστούν μαζί, όπως φαίνεται παρακάτω:

 κλάση G {} G αναφορά; αναφορά = νέο G (); // χωρίς αναφορά διακύμανσης = νέο G (); // με συν- και παράβαση 

Η γενική διεπαφή java.util.Map χρησιμοποιείται συχνά ως παράδειγμα για παραμέτρους πολλαπλών τύπων. Η διεπαφή έχει δύο παραμέτρους τύπου, μία για κλειδί και μία για τιμή. Είναι χρήσιμο να συσχετίζετε αντικείμενα με κλειδιά, για παράδειγμα, ώστε να τα βρίσκουμε πιο εύκολα. Ένα τηλεφωνικό βιβλίο είναι ένα παράδειγμα ενός Χάρτης αντικείμενο που χρησιμοποιεί παραμέτρους πολλαπλών τύπων: το όνομα του συνδρομητή είναι το κλειδί, ο αριθμός τηλεφώνου είναι η τιμή.

Η εφαρμογή της διεπαφής java.util.HashMap έχει έναν κατασκευαστή για τη μετατροπή αυθαίρετου Χάρτης αντικείμενο σε έναν πίνακα συσχέτισης:

 δημόσιο HashMap (Χάρτης m) ... 

Λόγω της συνδιακύμανσης, η παράμετρος τύπου του αντικειμένου παραμέτρου σε αυτήν την περίπτωση δεν χρειάζεται να αντιστοιχεί στις κατηγορίες παραμέτρων ακριβούς τύπου κ και Β. Αντ 'αυτού, μπορεί να προσαρμοστεί μέσω συνδιακύμανσης:

 Πελάτες χαρτών; ... επαφές = νέο HashMap (πελάτες); // συνδιαλλακτική 

Εδώ, Ταυτότητα είναι ένα supertype του Αριθμός των πελατών, και Πρόσωπο είναι υπερτύπος του Πελάτης.

Ποικιλία μεθόδων

Έχουμε μιλήσει για τη διακύμανση των τύπων. τώρα ας στραφούμε σε ένα κάπως πιο εύκολο θέμα.