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

Πώς να χρησιμοποιήσετε τα γενικά Java για να αποφύγετε το ClassCastExceptions

Το Java 5 έφερε γενικά στη γλώσσα Java. Σε αυτό το άρθρο, σας παρουσιάζω γενόσημους και συζητάμε γενικούς τύπους, γενικές μεθόδους, γενικά και συμπεράσματα, γενικές αντιπαραθέσεις και γενικά και σωρούς ρύπανση.

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

Τι είναι τα γενόσημα;

Generics είναι μια συλλογή σχετικών γλωσσικών χαρακτηριστικών που επιτρέπουν σε τύπους ή μεθόδους να λειτουργούν σε αντικείμενα διαφόρων τύπων παρέχοντας ταυτόχρονα ασφάλεια τύπου μεταγλώττισης. Οι λειτουργίες Generics αντιμετωπίζουν το πρόβλημα του java.lang.ClassCastExceptionρίχνονται κατά το χρόνο εκτέλεσης, που είναι το αποτέλεσμα ενός κώδικα που δεν είναι ασφαλής τύπου (δηλαδή, η μεταφορά αντικειμένων από τους τρέχοντες τύπους τους σε ασύμβατους τύπους).

Generics και το Java Συλλογές Πλαίσιο

Τα γενικά χρησιμοποιούνται ευρέως στο Java Συλλογές Πλαίσιο (επίσημα εισάγονται στο μέλλον Java 101 άρθρα), αλλά δεν είναι αποκλειστικά σε αυτό. Τα γενικά χρησιμοποιούνται επίσης σε άλλα μέρη της τυπικής βιβλιοθήκης κατηγορίας της Java, όπως java.lang.Class, java.lang. Συγκρίσιμο, java.lang.ThreadLocal, και java.lang.ref.WeakReference.

Εξετάστε το ακόλουθο τμήμα κώδικα, το οποίο καταδεικνύει την έλλειψη ασφάλειας τύπου (στο πλαίσιο του Java Συλλογές Πλαισίου java.util.LinkedList class) που ήταν κοινό στον κώδικα Java πριν από την εισαγωγή γενικών:

Λίστα doubleList = new LinkedList (); doubleList.add (νέο Double (3.5)); Double d = (Double) doubleList.iterator (). Επόμενο ();

Αν και ο στόχος του παραπάνω προγράμματος είναι μόνο η αποθήκευση java.lang. Διπλό αντικείμενα στη λίστα, τίποτα δεν εμποδίζει την αποθήκευση άλλων ειδών. Για παράδειγμα, μπορείτε να καθορίσετε doubleList.add ("Γεια"); για να προσθέσετε ένα java.lang.String αντικείμενο. Ωστόσο, κατά την αποθήκευση ενός άλλου είδους αντικειμένου, η τελική γραμμή είναι (Διπλό) αιτίες χειριστή cast ClassCastException να πεταχτούν όταν έρχονται αντιμέτωποι με μηΔιπλό αντικείμενο.

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

Λίστα doubleList = new LinkedList (); doubleList.add (νέο Double (3.5)); Double d = doubleList.iterator (). Επόμενο ();

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

Ανακαλύπτοντας γενικούς τύπους

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

τάξη αναγνωριστικό<formalTypeParameterList> διεπαφή {// class body} αναγνωριστικό<formalTypeParameterList> {// σώμα διεπαφής}

Το Java Συλλογές Πλαίσιο προσφέρει πολλά παραδείγματα γενικών τύπων και τις λίστες παραμέτρων τους (και τα αναφέρομαι σε αυτό το άρθρο). Για παράδειγμα, java.util.Set είναι ένας γενικός τύπος, είναι ο επίσημος κατάλογος παραμέτρων τύπου, και μι είναι η μοναδική παράμετρος τύπου της λίστας. Ένα άλλο παράδειγμα είναιjava.util.Map.

Σύμβαση ονομασίας παραμέτρων τύπου Java

Η σύμβαση προγραμματισμού Java υπαγορεύει ότι τα ονόματα παραμέτρων τύπου είναι με κεφαλαία γράμματα, όπως μι για στοιχείο, κ για κλειδί, Β για τιμή, και Τ για τον τύπο. Εάν είναι δυνατόν, αποφύγετε τη χρήση ενός χωρίς νόημα ονόματος Πjava.util. Λίστα σημαίνει μια λίστα στοιχείων, αλλά τι θα μπορούσατε να εννοείτε Λίστα

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

Η γλώσσα Java υποστηρίζει τα ακόλουθα είδη ορισμάτων πραγματικού τύπου:

  • Τύπος σκυροδέματος: Ένα όνομα κλάσης ή άλλου τύπου αναφοράς μεταβιβάζεται στην παράμετρο τύπου. Για παράδειγμα, στο Λίστα, Ζώο μεταβιβάζεται στο μι.
  • Παραμετροποιημένος τύπος σκυροδέματος: Ένα παραμετροποιημένο όνομα τύπου μεταβιβάζεται στην παράμετρο τύπου. Για παράδειγμα, στο Σειρά, Λίστα μεταβιβάζεται στο μι.
  • Τύπος συστοιχίας: Ένας πίνακας μεταβιβάζεται στην παράμετρο τύπου. Για παράδειγμα, στο Χάρτης, Σειρά μεταβιβάζεται στο κ και Σειρά[] μεταβιβάζεται στο Β.
  • Παράμετρος τύπου: Μια παράμετρος τύπου μεταφέρεται στην παράμετρο τύπου. Για παράδειγμα, στο class Container {Ορισμός στοιχείων; }, μι μεταβιβάζεται στο μι.
  • Μπαλαντέρ: Το ερωτηματικό (?) μεταφέρεται στην παράμετρο τύπου. Για παράδειγμα, στο Τάξη, ? μεταβιβάζεται στο Τ.

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

Δήλωση και χρήση γενικών τύπων στην Java

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

Λίστα 1:GenDemo.java (έκδοση 1)

class Container {private E [] στοιχεία; ιδιωτικός δείκτης int Κοντέινερ (μέγεθος int) {element = (E []) νέο αντικείμενο [μέγεθος]; ευρετήριο = 0; } άκυρη προσθήκη (στοιχείο E) {στοιχεία [index ++] = element; } E get (int index) {στοιχεία επιστροφής [index]; } int μέγεθος () {index index; }} δημόσια κλάση GenDemo {public static void main (String [] args) {Container con = new Container (5); con.add ("Βόρεια"); con.add ("Νότια"); con.add ("Ανατολή"); con.add ("Δύση"); για (int i = 0; i <con.size (); i ++) System.out.println (con.get (i)); }}

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

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

ο Δοχείο (μέγεθος int) ο κατασκευαστής δημιουργεί τον πίνακα μέσω στοιχεία = (E []) νέο αντικείμενο [μέγεθος];. Αν αναρωτιέστε γιατί δεν το έκανα στοιχεία = νέο E [μέγεθος];, ο λόγος είναι ότι δεν είναι δυνατόν. Κάτι τέτοιο θα μπορούσε να οδηγήσει σε α ClassCastException.

Μεταγλώττιση καταχώρισης 1 (javac GenDemo.java). ο (ΜΙ[]) Το cast κάνει τον μεταγλωττιστή να εμφανίσει μια προειδοποίηση σχετικά με το ότι το cast δεν είναι επιλεγμένο. Επισημαίνει την πιθανότητα απόκλισης από Αντικείμενο[] προς την ΜΙ[] μπορεί να παραβιάζει την ασφάλεια τύπου επειδή Αντικείμενο[] μπορεί να αποθηκεύσει οποιοδήποτε είδος αντικειμένου.

Σημειώστε, ωστόσο, ότι δεν υπάρχει τρόπος παραβίασης της ασφάλειας τύπου σε αυτό το παράδειγμα. Απλώς δεν είναι δυνατόν να αποθηκεύσετε ένα μημι αντικείμενο στον εσωτερικό πίνακα. Πρόθεμα του Δοχείο (μέγεθος int) κατασκευαστής με @SuppressWarnings ("μη επιλεγμένο") θα καταστείλει αυτό το προειδοποιητικό μήνυμα.

Εκτέλεση java GenDemo για να εκτελέσετε αυτήν την εφαρμογή. Πρέπει να παρατηρήσετε την ακόλουθη έξοδο:

Βορράς Νότος Ανατολή Δύση

Οριακές παράμετροι τύπου στην Java

ο μι σε Σειρά είναι ένα παράδειγμα ενός μη περιορισμένη παράμετρος τύπου επειδή μπορείτε να μεταβιβάσετε οποιοδήποτε πραγματικό όρισμα τύπου μι. Για παράδειγμα, μπορείτε να καθορίσετε Σειρά, Σειρά, ή Σειρά.

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

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

Για παράδειγμα, Εργαζόμενοι στην τάξη περιορίζει τους τύπους στους οποίους μπορεί να περάσει Υπαλλήλους προς την Υπάλληλος ή μια υποκατηγορία (π.χ. Λογιστής). Καθορισμός νέοι υπάλληλοι θα ήταν νόμιμο, ενώ νέοι υπάλληλοι θα ήταν παράνομο.

Μπορείτε να αντιστοιχίσετε περισσότερα από ένα άνω όρια σε μια παράμετρο τύπου. Ωστόσο, το πρώτο όριο πρέπει πάντα να είναι μια κλάση και τα πρόσθετα όρια πρέπει πάντα να είναι διασυνδέσεις. Κάθε όριο χωρίζεται από τον προκάτοχό του με ένα συμπλεκτικό σύμβολο (&). Δείτε την καταχώριση 2.

Λίστα 2: GenDemo.java (έκδοση 2)

εισαγωγή java.math.BigDecimal; εισαγωγή java.util.Arrays; αφηρημένη τάξη Υπάλληλος {ιδιωτικό BigDecimal hourlySalary; ιδιωτικό όνομα συμβολοσειράς; Υπάλληλος (Όνομα συμβολοσειράς, BigDecimal hourlySalary) {this.name = name; this.hourlySalary = hourlySalary; } δημόσια BigDecimal getHourlySalary () {return hourlySalary; } δημόσια συμβολοσειρά getName () {return name; } δημόσια συμβολοσειρά toString () {return name + ":" + hourlySalary.toString (); }} class Accountant επεκτείνει υπαλλήλους υπαλλήλων συγκρίσιμα {Accountant (String name, BigDecimal hourlySalary) {super (name, hourlySalary) } public int membandingkanTo (Λογιστικός λογαριασμός) {return getHourlySalary (). membandingkanTo (acct.getHourlySalary ()); }} ταξινόμηση υπαλλήλων κατηγορίας {ιδιωτικό E [] υπαλλήλους. ιδιωτικός δείκτης int @SuppressWarnings ("unchecked") ΤαξινόμησηEm Employees (int μέγεθος) {υπαλλήλους = (E []) νέο Υπάλληλος [μέγεθος]; int index = 0; } void add (E emp) {εργαζόμενοι [index ++] = emp; Arrays.sort (υπάλληλοι, 0, ευρετήριο); } E get (int index) {επιστροφή υπαλλήλων [index]; } int μέγεθος () {index index; }} δημόσια τάξη GenDemo {public static void main (String [] args) {SortedEm Employees se = new SortedEm Employees (10); se.add (νέος λογιστής ("John Doe", νέο BigDecimal ("35.40"))); se.add (νέος λογιστής ("George Smith", νέο BigDecimal ("15.20"))); se.add (νέος λογιστής ("Jane Jones", νέο BigDecimal ("25.60"))); για (int i = 0; i <se.size (); i ++) System.out.println (se.get (i)); }}

Λίστα 2 Υπάλληλος Η τάξη αφαιρεί την έννοια ενός υπαλλήλου που λαμβάνει ωριαίο μισθό. Αυτή η τάξη υποκατηγορείται από Λογιστής, το οποίο εφαρμόζει επίσης Συγκρίσιμος να το δείξω αυτό ΛογιστήςΤο s μπορεί να συγκριθεί σύμφωνα με τη φυσική τους σειρά, η οποία συμβαίνει να είναι ωριαία αμοιβή σε αυτό το παράδειγμα.

ο java.lang. Συγκρίσιμο διεπαφή δηλώνεται ως γενικός τύπος με την ονομασία μιας παραμέτρου ενός τύπου Τ. Αυτή η διεπαφή παρέχει ένα int membandingkanTo (T o) μέθοδος που συγκρίνει το τρέχον αντικείμενο με το όρισμα (του τύπου Τ), επιστρέφοντας αρνητικό ακέραιο, μηδέν ή θετικό ακέραιο, καθώς αυτό το αντικείμενο είναι μικρότερο από, ίσο ή μεγαλύτερο από το καθορισμένο αντικείμενο.

ο Ταξινομημένοι υπάλληλοι τάξη σας επιτρέπει να αποθηκεύσετε Υπάλληλος υποκατηγορίες παρουσίες που εφαρμόζονται Συγκρίσιμος σε έναν εσωτερικό πίνακα. Αυτός ο πίνακας ταξινομείται (μέσω του java.util.Arays της τάξης κενή ταξινόμηση (Object [] a, int fromIndex, int toIndex) μέθοδος τάξης) σε αύξουσα σειρά του ωριαίου μισθού μετά από Υπάλληλος Προστίθεται η υποκατηγορία.

Συλλογή καταχώρισης 2 (javac GenDemo.java) και εκτελέστε την εφαρμογή (java GenDemo). Πρέπει να παρατηρήσετε την ακόλουθη έξοδο:

Τζορτζ Σμιθ: 15,20 Τζέιν Τζόουνς: 25,60 Τζον Ντο: 35,40

Χαμηλότερα όρια και παράμετροι γενικού τύπου

Δεν μπορείτε να καθορίσετε ένα κατώτατο όριο για μια παράμετρο γενικού τύπου. Για να καταλάβω γιατί προτείνω να διαβάσετε τις Συχνές Ερωτήσεις της Java της Angelika Langer σχετικά με το θέμα των χαμηλότερων ορίων, την οποία λέει "θα ήταν συγκεχυμένη και όχι ιδιαίτερα χρήσιμη".

Λαμβάνοντας υπόψη μπαλαντέρ

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

Λίστα 3: GenDemo.java (έκδοση 3)

εισαγωγή java.util.ArrayList; εισαγωγή java.util.Iterator; εισαγωγή java.util.List; δημόσια τάξη GenDemo {public static void main (String [] args) {List direction = new ArrayList (); direction.add ("βόρεια"); direction.add ("νότος"); direction.add ("ανατολικά"); direction.add ("δυτικά"); printList (οδηγίες); Βαθμοί λίστας = νέο ArrayList (); grades.add (νέος ακέραιος (98)); grades.add (νέος ακέραιος (63)); grades.add (νέος ακέραιος (87)); printList (βαθμοί); } static void printList (Λίστα λιστών) {Iterator iter = list.iterator (); ενώ (iter.hasNext ()) System.out.println (iter.next ()); }}

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

Το μήνυμα σφάλματος που λάβατε σχετίζεται με τον θεμελιώδη κανόνα των γενικών:

Copyright el.verticalshadows.com 2021