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

Χρησιμοποιήστε == (ή! =) Για να συγκρίνετε τα αθροίσματα Java

Οι περισσότεροι νέοι προγραμματιστές Java μαθαίνουν γρήγορα ότι πρέπει γενικά να συγκρίνουν Java Strings χρησιμοποιώντας String.equals (Object) αντί να χρησιμοποιούν ==. Αυτό τονίζεται και ενισχύεται επανειλημμένα στους νέους προγραμματιστές επειδή αυτοί σχεδόν πάντα σημαίνει να συγκρίνουμε το περιεχόμενο String (τους πραγματικούς χαρακτήρες που σχηματίζουν το String) και όχι την ταυτότητα του String (η διεύθυνση του στη μνήμη). Υποστηρίζω ότι πρέπει να ενισχύσουμε την ιδέα ότι == μπορεί να χρησιμοποιηθεί αντί για Enum.equals (Object). Παρέχω το σκεπτικό μου για αυτόν τον ισχυρισμό στο υπόλοιπο αυτής της ανάρτησης.

Υπάρχουν τέσσερις λόγοι που πιστεύω ότι χρησιμοποιώ == να συγκρίνετε τα Java enums είναι σχεδόν πάντα προτιμότερο από τη χρήση της μεθόδου "ισούται με":

  1. ο == on enums παρέχει την ίδια αναμενόμενη σύγκριση (περιεχόμενο) με ισούται
  2. ο == στο enums είναι αναμφισβήτητα πιο ευανάγνωστο (λιγότερο ρήξη) από ισούται
  3. ο == στα enums είναι πιο μηδενικό από ισούται
  4. ο == on enums παρέχει έλεγχο μεταγλώττισης (στατικό) αντί για έλεγχο χρόνου εκτέλεσης

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

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

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

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

Το τέταρτο πλεονέκτημα == πάνω από . ισότιμα κατά τη σύγκριση των αριθμών έχει να κάνει με την ασφάλεια του χρόνου μεταγλώττισης. Η χρήση του == αναγκάζει έναν αυστηρότερο έλεγχο χρόνου μεταγλώττισης από αυτόν για . ισότιμα επειδή το Object.equals (Object) πρέπει, με σύμβαση, να λάβει αυθαίρετο Αντικείμενο. Όταν χρησιμοποιώ μια στατικά δακτυλογραφημένη γλώσσα όπως η Java, πιστεύω ότι εκμεταλλεύομαι όσο το δυνατόν περισσότερο τα πλεονεκτήματα αυτής της στατικής πληκτρολόγησης. Διαφορετικά, θα χρησιμοποιούσα μια δυναμικά δακτυλογραφημένη γλώσσα. Πιστεύω ότι ένα από τα επαναλαμβανόμενα θέματα του Effective Java είναι ακριβώς αυτό: προτιμήστε τον έλεγχο στατικού τύπου όποτε είναι δυνατόν.

Για παράδειγμα, ας υποθέσουμε ότι είχα ένα προσαρμοσμένο enum Καρπός και προσπάθησα να το συγκρίνω με την κλάση java.awt.Color. Χρησιμοποιώντας το == Ο χειριστής μου επιτρέπει να λάβω ένα σφάλμα χρόνου μεταγλώττισης (συμπεριλαμβανομένης της ειδοποίησης εκ των προτέρων στο αγαπημένο μου Java IDE) του προβλήματος. Ακολουθεί μια λίστα κωδικών που προσπαθεί να συγκρίνει ένα προσαρμοσμένο enum με μια κλάση JDK χρησιμοποιώντας το == χειριστής:

/ ** * Αναφέρατε εάν παρέχεται Το χρώμα είναι καρπούζι. * * Η εφαρμογή αυτής της μεθόδου σχολιάζεται για να αποφευχθεί ένα σφάλμα μεταγλωττιστή * που νόμιμα δεν επιτρέπει == να συγκρίνει δύο αντικείμενα που δεν είναι και * δεν μπορεί να είναι το ίδιο πράγμα ποτέ. * * @param اميدوارColor Color που δεν θα είναι ποτέ καρπούζι. * @ return Δεν πρέπει ποτέ να είναι αλήθεια. * / public boolean isColorWatermelon (java.awt.Color اميدوارColor) {// Αυτή η σύγκριση του Fruit to Color θα οδηγήσει σε σφάλμα μεταγλωττιστή: // σφάλμα: ασύγκριτοι τύποι: Fruit and Color Return Fruit.WATERMELON == اميدوارColor; } 

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

Παρόλο που δεν είμαι λάτρης των σφαλμάτων, προτιμώ να πιάνονται στατικά κατά τη μεταγλώττιση και όχι ανάλογα με την κάλυψη χρόνου εκτέλεσης. Αν είχα χρησιμοποιήσει το ισούται μέθοδος για αυτήν τη σύγκριση, ο κώδικας θα είχε συντάξει πρόστιμο, αλλά η μέθοδος θα επέστρεφε πάντα ψευδής ψευδές γιατί δεν υπάρχει τρόπος α dustin. παραδείγματα. Φρούτα Το enum θα είναι ίσο με ένα java.awt.Χρώμα τάξη. Δεν το προτείνω, αλλά εδώ χρησιμοποιείται η μέθοδος σύγκρισης . ισότιμα:

/ ** * Αναφέρετε εάν το παρεχόμενο χρώμα είναι σμέουρο. Αυτό είναι απολύτως ανοησία * επειδή ένα χρώμα δεν μπορεί ποτέ να είναι ίσο με ένα φρούτο, αλλά ο μεταγλωττιστής επιτρέπει αυτόν τον έλεγχο * και μόνο ο προσδιορισμός του χρόνου εκτέλεσης μπορεί να δείξει ότι δεν είναι * ίσοι, παρόλο που δεν μπορούν ποτέ να είναι ίσοι. Έτσι δεν πρέπει να κάνουμε πράγματα. * * @param اميدوارColor Color που δεν θα είναι ποτέ βατόμουρο. * @return {@code false}. Πάντα. * / public boolean isColorRaspberry (java.awt.Color اميدوارColor) {// // ΜΗΝ ΚΑΝΕΤΕ ΑΥΤΟ: Σπατάλη προσπάθειας και παραπλανητικός κώδικας !!!!!!!! // επιστροφή Fruit.RASPBERRY.equals (اميدوارColor); } 

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

Το τελικό πλεονέκτημα που ανέφερα από τη χρήση == προκειμένου Σύνολο Κατά τη σύγκριση των αριθμών είναι η αποφυγή του φοβισμένου NullPointerException. Όπως ανέφερα στο Effective Java NullPointerException Handling, γενικά θέλω να αποφύγω απρόσμενα NullPointerExceptionμικρό. Υπάρχει ένα περιορισμένο σύνολο καταστάσεων στις οποίες πραγματικά θέλω η ύπαρξη ενός μηδενικού να αντιμετωπίζεται ως εξαιρετική περίπτωση, αλλά συχνά προτιμώ μια πιο χαριτωμένη αναφορά ενός προβλήματος. Ένα πλεονέκτημα της σύγκρισης των αριθμών με == είναι ότι ένα μηδέν μπορεί να συγκριθεί με ένα μη μηδενικό enum χωρίς να συναντηθεί a NullPointerException (NPE). Το αποτέλεσμα αυτής της σύγκρισης, προφανώς, είναι ψευδής.

Ένας τρόπος για να αποφύγετε το NPE κατά τη χρήση . ισοδύναμα (Αντικείμενο) είναι να επικαλεστεί το ισούται μέθοδος εναντίον μιας σταθεράς enum ή ενός γνωστού μη μηδενικού enum και, στη συνέχεια, μεταβίβαση του δυνητικού αριθμού αμφισβητήσιμου χαρακτήρα (πιθανώς null) ως παράμετρο ισούται μέθοδος. Αυτό έχει γίνει συχνά για χρόνια στην Java με Strings για να αποφευχθεί το NPE. Ωστόσο, με το == χειριστής, η σειρά σύγκρισης δεν έχει σημασία. Μου αρεσει αυτο.

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

Fruit.java

πακέτο dustin.example; public enum Φρούτα {APPLE, BANANA, BLACKBERRY, BLUEBERRY, CHERRY, GRAPE, KIWI, MANGO, ORANGE, RASPBERRY, STRAWBERRY, TOMATO, WATERMELON} 

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

EnumComparisonMain.java

πακέτο dustin.example; δημόσια τάξη EnumComparisonMain {/ ** * Υποδείξτε αν τα φρούτα που παρέχονται είναι καρπούζι ({@code true} ή όχι * ({@code false})). * * @param اميدوارFruit Fruit που μπορεί να είναι καρπούζι ή όχι, το null είναι * απολύτως αποδεκτό (φέρετέ το!). * @return {@code true} εάν τα φρούτα είναι καρπούζι; {@code false} εάν * τα φρούτα ΔΕΝ είναι καρπούζι. * / δημόσια boolean isFruitWatermelon (Fruit اميدوارFruit) {return اميدوارFruit = = Fruit.WATERMELON;} / ** * Υποδείξτε εάν το παρεχόμενο αντικείμενο είναι Fruit.WATERMELON ({@code true}) ή * όχι ({@code false}). * * @Param اميدوارObject Αντικείμενο που μπορεί να είναι ή όχι καρπούζι και μπορεί * να μην είναι καν Φρούτα! * @ return {@code true} αν το αντικείμενο που παρέχεται είναι φρούτο. WATERMELON; * {@code false} εάν το αντικείμενο που παρέχεται δεν είναι Fruit.WATERMELON. * / public boolean isObjectWatermelon (Object اميدوارObject ) {return اميدوارObject == Fruit.WATERMELON;} / ** * Υποδείξτε εάν παρέχεται Το χρώμα είναι καρπούζι. * * Η εφαρμογή αυτής της μεθόδου σχολιάζεται στο αποφύγετε ένα σφάλμα μεταγλωττιστή * που δεν επιτρέπει νόμιμα == να συγκρίνετε δύο αντικείμενα που δεν είναι και * δεν μπορούν να είναι το ίδιο πράγμα ποτέ. * * @param اميدوارColor Color που δεν θα είναι ποτέ καρπούζι. * @ return Δεν πρέπει ποτέ να είναι αλήθεια. * / public boolean isColorWatermelon (java.awt.Color اميدوارColor) {// Έπρεπε να σχολιάσω τη σύγκριση του Fruit to Color για να αποφευχθεί το σφάλμα του μεταγλωττιστή: // σφάλμα: ασύγκριτοι τύποι: Επιστροφή φρούτων και χρωμάτων /*Fruit.WATERMELON == اميدوارColor * / ψευδές; } / ** * Υποδείξτε εάν τα φρούτα που παρέχονται είναι φράουλα ({@code true}) ή όχι * ({@code false}). * * @param اميدوارFruit Fruit που μπορεί να είναι ή να μην είναι φράουλα. Το null είναι * απολύτως αποδεκτό (συνεχίστε!). * @return {@code true} εάν παρέχονται φρούτα είναι φράουλα. {@code false} εάν * τα φρούτα ΔΕΝ είναι φράουλα. * / public boolean isFruitStrawberry (Fruit اميدوارFruit) {return Fruit.STRAWBERRY == اميدوارFruit; } / ** * Υποδείξτε εάν τα φρούτα που παρέχονται είναι βατόμουρο ({@code true}) ή όχι * ({@code false}). * * @param اميدوارFruit Fruit που μπορεί να είναι ή να μην είναι βατόμουρο. το null είναι * εντελώς και εντελώς απαράδεκτο. παρακαλώ μην περάσετε null, παρακαλώ, * παρακαλώ, παρακαλώ. * @return {@code true} εάν παρέχονται φρούτα είναι βατόμουρο. {@code false} εάν * τα φρούτα ΔΕΝ είναι βατόμουρο. * / public boolean isFruitRaspberry (Fruit اميدوارFruit) {return اميدوارFruit.equals (Fruit.RASPBERRY); } / ** * Υποδείξτε εάν το παρεχόμενο Αντικείμενο είναι Φρούτα.RASPBERRY ({@code true}) ή * όχι ({@code false}). * * @param اميدوارObject Αντικείμενο που μπορεί να είναι ή δεν είναι σμέουρο και μπορεί * ή μπορεί να μην είναι καν Φρούτα! * @return {@code true} εάν παρέχεται Αντικείμενο είναι φρούτο.RASPBERRY; {@code false} * εάν δεν είναι φρούτο ή δεν είναι βατόμουρο. * / public boolean isObjectRaspberry (Object اميدوارObject) {return اميدوارObject.equals (Fruit.RASPBERRY); } / ** * Υποδείξτε εάν το παρεχόμενο χρώμα είναι σμέουρο. Αυτό είναι απολύτως ανοησία * επειδή ένα χρώμα δεν μπορεί ποτέ να είναι ίσο με ένα φρούτο, αλλά ο μεταγλωττιστής επιτρέπει αυτόν τον έλεγχο * και μόνο ο προσδιορισμός χρόνου εκτέλεσης μπορεί να δείξει ότι δεν είναι * ίσοι, παρόλο που δεν μπορούν ποτέ να είναι ίσοι. Έτσι δεν πρέπει να κάνουμε πράγματα. * * @param اميدوارColor Color που δεν θα είναι ποτέ βατόμουρο. * @return {@code false}. Πάντα. * / public boolean isColorRaspberry (java.awt.Color اميدوارColor) {// // ΜΗΝ ΚΑΝΕΤΕ ΑΥΤΟ: Σπατάλη προσπάθειας και παραπλανητικός κώδικας !!!!!!!! // επιστροφή Fruit.RASPBERRY.equals (اميدوارColor); } / ** * Υποδείξτε εάν τα φρούτα που παρέχονται είναι σταφύλι ({@code true}) ή όχι * ({@code false}). * * @param اميدوارFruit Fruit που μπορεί να είναι ή όχι σταφύλι. Το null είναι * απολύτως αποδεκτό (συνεχίστε!). * @return {@code true} εάν τα φρούτα είναι σταφύλι. {@code false} εάν * τα φρούτα ΔΕΝ είναι σταφύλι. * / public boolean isFruitGrape (Fruit اميدوارFruit) {return Fruit.GRAPE.equals (اميدوارFruit); }} 

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

EnumComparisonTest.groovy