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

Οι ρήτρες δοκιμαστικής επιδιόρθωσης καθορίστηκαν και αποδείχθηκαν

Καλώς ήλθατε σε άλλη δόση του Κάτω από την κουκούλα. Αυτή η στήλη δίνει στους προγραμματιστές Java μια ματιά στους μυστηριώδεις μηχανισμούς που κάνουν κλικ και στροβιλίζονται κάτω από τα τρέχοντα προγράμματα Java. Το άρθρο αυτού του μήνα συνεχίζει τη συζήτηση του συνόλου εντολών bytecode της εικονικής μηχανής Java (JVM). Το επίκεντρό του είναι ο τρόπος με τον οποίο χειρίζεται το JVM τελικά ρήτρες και τους bytecodes που σχετίζονται με αυτές τις ρήτρες.

Τέλος: Κάτι για να χαροποιήσω

Καθώς η εικονική μηχανή Java εκτελεί τους κωδικούς bytec που αντιπροσωπεύουν ένα πρόγραμμα Java, ενδέχεται να εξέλθει από ένα μπλοκ κώδικα - τις δηλώσεις μεταξύ δύο αντίστοιχων σγουρών τιράντες - με έναν από πολλούς τρόπους. Πρώτον, το JVM θα μπορούσε απλώς να εκτελέσει μετά το κλείσιμο σγουρού στηρίγματος του μπλοκ κώδικα. Ή, θα μπορούσε να αντιμετωπίσει μια δήλωση διακοπής, συνέχισης ή επιστροφής που την αναγκάζει να βγει από το μπλοκ κώδικα από κάπου στη μέση του μπλοκ. Τέλος, θα μπορούσε να υπάρξει μια εξαίρεση που αναγκάζει το JVM είτε να μεταβεί σε μια αντίστοιχη ρήτρα catch, είτε, εάν δεν υπάρχει αντίστοιχη ρήτρα catch, να τερματίσει το νήμα. Με αυτά τα πιθανά σημεία εξόδου που υπάρχουν μέσα σε ένα μόνο τμήμα κώδικα, είναι επιθυμητό να υπάρχει ένας εύκολος τρόπος να εκφραστεί ότι κάτι συνέβη ανεξάρτητα από το πώς εξέρχεται ένα μπλοκ κώδικα. Στην Java, μια τέτοια επιθυμία εκφράζεται με ένα δοκιμάστε τελικά ρήτρα.

Για να χρησιμοποιήσετε ένα δοκιμάστε τελικά ρήτρα:

  • εσωκλείονται σε α δοκιμάστε μπλοκάρει τον κωδικό που έχει πολλά σημεία εξόδου και

  • βάλτε σε ένα τελικά μπλοκάρει τον κώδικα που πρέπει να συμβεί ανεξάρτητα από το πώς δοκιμάστε το μπλοκ έχει κλείσει.

Για παράδειγμα:

δοκιμάστε {// Αποκλεισμός κώδικα με πολλά σημεία εξόδου} επιτέλους {// Αποκλεισμός κώδικα που εκτελείται πάντα κατά την έξοδο του δοκιμαστικού μπλοκ, // ανεξάρτητα από τον τρόπο εξόδου του μπλοκ δοκιμής} 

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

δοκιμάστε {// Αποκλεισμός κώδικα με πολλά σημεία εξόδου} catch (Cold e) {System.out.println ("Caught cold!"); } catch (APopFly e) {System.out.println ("Πιάστηκε μια pop fly!"); } catch (SomeonesEye e) {System.out.println ("Έπιασε το μάτι κάποιου!"); } τέλος {// Μπλοκ κώδικα που εκτελείται πάντα κατά την έξοδο του μπλοκ δοκιμής, // ανεξάρτητα από το πώς εξέρχεται το μπλοκ δοκιμής. System.out.println ("Είναι κάτι για να χαροποιήσεις;"); } 

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

Κρύωσα! Είναι κάτι για να το ενθαρρύνεις; 

Τελικά δοκιμάστε ρήτρες σε bytecodes

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

Ο κώδικας που προκαλεί το JVM να μεταβεί σε μια μικρογραφία υπορουτίνας είναι το jsr εντολή. ο jsr Η εντολή παίρνει έναν τελεστή δύο byte, την μετατόπιση από τη θέση του jsr οδηγίες από όπου ξεκινά η μικρογραφία υπορουτίνας. Μια δεύτερη παραλλαγή του jsr η οδηγία είναι jsr_w, η οποία εκτελεί την ίδια λειτουργία με jsr αλλά παίρνει έναν τεράστιο τελεστή (τεσσάρων byte). Όταν η JVM συναντά a jsr ή jsr_w οδηγίες, σπρώχνει μια διεύθυνση επιστροφής στη στοίβα και μετά συνεχίζει την εκτέλεση στην αρχή της μικροσκοπικής υπορουτίνας. Η διεύθυνση επιστροφής είναι η μετατόπιση του bytecode αμέσως μετά το jsr ή jsr_w οδηγίες και τους τελεστές της.

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

Τέλος ρήτρες
Κώδικας πράξηςOperand (ες)Περιγραφή
jsrbranchbyte1, branchbyte2ωθεί τη διεύθυνση επιστροφής, τα υποκαταστήματα να αντισταθμίσουν
jsr_wbranchbyte1, branchbyte2, branchbyte3, branchbyte4ωθεί τη διεύθυνση επιστροφής, τα κλαδιά σε ευρεία αντιστάθμιση
μουσκεύωδείκτηςεπιστρέφει στη διεύθυνση που είναι αποθηκευμένη στο τοπικό ευρετήριο μεταβλητών

Μην συγχέετε μια μικρογραφία υπορουτίνας με μια μέθοδο Java. Οι μέθοδοι Java χρησιμοποιούν ένα διαφορετικό σύνολο οδηγιών. Οδηγίες όπως invokevirtual ή invokenonvirtual προκαλέσει μια μέθοδο Java για επίκληση, και οδηγίες όπως ΕΠΙΣΤΡΟΦΗ, μια επιστροφή, ή επιστρέφω προκαλέσει επιστροφή μιας μεθόδου Java. ο jsr Η οδηγία δεν προκαλεί την επίκληση μιας μεθόδου Java. Αντίθετα, προκαλεί ένα άλμα σε διαφορετικό opcode με την ίδια μέθοδο. Ομοίως, το μουσκεύω η οδηγία δεν επιστρέφει από μια μέθοδο. Αντίθετα, επιστρέφει στον opcode με την ίδια μέθοδο που ακολουθεί αμέσως την κλήση jsr οδηγίες και τους τελεστές της. Οι κωδικοί bytec που εφαρμόζουν a τελικά Ο όρος ονομάζεται μικροσκοπική υπορουτίνα επειδή ενεργεί σαν μια μικρή υπορουτίνα εντός της ροής bytecode μιας μεμονωμένης μεθόδου.

Ίσως νομίζετε ότι το μουσκεύω Η εντολή θα πρέπει να εμφανίσει τη διεύθυνση επιστροφής από τη στοίβα, γιατί εκεί προωθήθηκε από το jsr εντολή. Όμως όχι. Αντ 'αυτού, στην αρχή κάθε υπορουτίνας, η διεύθυνση επιστροφής αναδύεται από την κορυφή της στοίβας και αποθηκεύεται σε μια τοπική μεταβλητή - την ίδια τοπική μεταβλητή από την οποία μουσκεύω οδηγίες παίρνει αργότερα. Αυτός ο ασύμμετρος τρόπος εργασίας με τη διεύθυνση επιστροφής είναι απαραίτητος επειδή τελικά οι ρήτρες (και επομένως, οι μικροσκοπικές υπορουτίνες) μπορούν να ρίξουν εξαιρέσεις ή να περιλαμβάνουν ΕΠΙΣΤΡΟΦΗ, Διακοπή, ή να συνεχίσει δηλώσεις. Λόγω αυτής της δυνατότητας, η επιπλέον διεύθυνση επιστροφής που ωθήθηκε στη στοίβα από το jsr οδηγίες πρέπει να αφαιρεθούν από τη στοίβα αμέσως, οπότε δεν θα είναι ακόμα εκεί αν το τελικά η ρήτρα εξέρχεται με ένα Διακοπή, να συνεχίσει, ΕΠΙΣΤΡΟΦΗή εξαίρεση. Επομένως, η διεύθυνση επιστροφής αποθηκεύεται σε μια τοπική μεταβλητή στην αρχή οποιασδήποτε τελικά μινιατούρα υπορουτίνας του clause.

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

 static boolean surpriseTheProgrammer (boolean bVal) {ενώ (bVal) {δοκιμάστε {return true; } τέλος {διάλειμμα; }} επιστροφή false; } 

Το παραπάνω παράδειγμα δείχνει γιατί η διεύθυνση επιστροφής πρέπει να αποθηκευτεί σε μια τοπική μεταβλητή στην αρχή του τελικά ρήτρα. Επειδή η τελικά η ρήτρα βγαίνει με ένα διάλειμμα, δεν εκτελεί ποτέ το μουσκεύω εντολή. Ως αποτέλεσμα, το JVM δεν επιστρέφει ποτέ για να ολοκληρώσει το "επιστροφή αληθινήΑντίθετα, απλώς προχωρά με το Διακοπή και πέφτει κάτω από το κλείσιμο σγουρό στήριγμα του ενώ δήλωση. Η επόμενη δήλωση είναι "επιστροφή ψευδής, "αυτό ακριβώς κάνει το JVM.

Η συμπεριφορά που φαίνεται από το a τελικά ρήτρα που βγαίνει με ένα Διακοπή εμφανίζεται επίσης από τελικά ρήτρες που εξέρχονται με ένα ΕΠΙΣΤΡΟΦΗ ή να συνεχίσει, ή ρίχνοντας μια εξαίρεση. Αν ένα τελικά η ρήτρα εξέρχεται για οποιονδήποτε από αυτούς τους λόγους, το μουσκεύω οδηγίες στο τέλος του τελικά η ρήτρα δεν εκτελείται ποτέ. Επειδή η μουσκεύω Η εντολή δεν είναι εγγυημένη για εκτέλεση, δεν μπορεί να βασιστεί για να αφαιρέσετε τη διεύθυνση επιστροφής από τη στοίβα. Επομένως, η διεύθυνση επιστροφής αποθηκεύεται σε μια τοπική μεταβλητή στην αρχή του τελικά μινιατούρα υπορουτίνας του clause.

Για ένα πλήρες παράδειγμα, εξετάστε την ακόλουθη μέθοδο, η οποία περιέχει ένα δοκιμάστε μπλοκ με δύο σημεία εξόδου. Σε αυτό το παράδειγμα, και τα δύο σημεία εξόδου είναι ΕΠΙΣΤΡΟΦΗ δηλώσεις:

 static int giveMeThatOldFashionedBoolean (boolean bVal) {δοκιμάστε {if (bVal) {return 1; } επιστροφή 0; } τέλος {System.out.println ("Έχουμε παλιομοδίτικο."); }} 

Η παραπάνω μέθοδος μεταγλωττίζεται στους ακόλουθους bytecodes:

// Η ακολουθία bytecode για το μπλοκ δοκιμής: 0 iload_0 // Προώθηση τοπικής μεταβλητής 0 (arg πέρασε ως διαιρέτης) 1 ifeq 11 // Push τοπική μεταβλητή 1 (arg πέρασε ως μέρισμα) 4 iconst_1 // Push int 1 5 istore_3 // Πληκτρολογήστε ένα int (το 1), αποθηκεύστε στην τοπική μεταβλητή 3 6 jsr 24 // Μετάβαση στη μίνι υπορουτίνα για το τέλος ρήτρα 9 iload_3 // Πιέστε την τοπική μεταβλητή 3 (το 1) 10 ireturn // Επιστροφή int στην κορυφή του stack (the 1) 11 iconst_0 // Push int 0 12 istore_3 // Pop an int (the 0), αποθηκεύστε στην τοπική μεταβλητή 3 13 jsr 24 // Μεταβείτε στη μίνι υπορουτίνα για τον τελευταίο όρο 16 iload_3 // Push local μεταβλητή 3 (το 0) 17 ireturn // Επιστροφή int στην κορυφή της στοίβας (το 0) // Η ακολουθία bytecode για μια ρήτρα catch που λαμβάνει κάθε είδους εξαίρεση // ρίχνεται μέσα από το μπλοκ δοκιμής. 18 astore_1 // Βάλτε την αναφορά στην εξαιρούμενη εξαίρεση, αποθηκεύστε // στην τοπική μεταβλητή 1 19 jsr 24 // Μεταβείτε στη μίνι υπορουτίνα για την τελευταία ρήτρα 22 aload_1 // Πιέστε την αναφορά (στην εξαίρεση που ρίχτηκε) από // τοπική μεταβλητή 1 23 athrow // Rethrow η ίδια εξαίρεση // Η μικροσκοπική υπορουτίνα που εφαρμόζει το τέλος μπλοκ. 24 astore_2 // Βάλτε τη διεύθυνση επιστροφής, αποθηκεύστε την στην τοπική μεταβλητή 2 25 getstatic # 8 // Λάβετε μια αναφορά στο java.lang.System.out 28 ldc # 1 // Πιέστε από τη συνεχή ομάδα 30 invokevirtual # 7 // Invoke System.out.println () 33 ret 2 // Επιστροφή στη διεύθυνση επιστροφής που είναι αποθηκευμένη στην τοπική μεταβλητή 2 

Οι κωδικοί bytec για το δοκιμάστε μπλοκ περιλαμβάνουν δύο jsr οδηγίες. Αλλο jsr οδηγίες περιέχονται στο σύλληψη ρήτρα. ο σύλληψη Η ρήτρα προστίθεται από τον μεταγλωττιστή επειδή εάν υπάρχει εξαίρεση κατά την εκτέλεση του δοκιμάστε μπλοκ, το τελευταίο μπλοκ πρέπει να εκτελεστεί. Επομένως, ο σύλληψη η ρήτρα επικαλείται απλώς τη μικρογραφία της υπορουτίνας που αντιπροσωπεύει το τελικά ρήτρα, ρίχνει ξανά την ίδια εξαίρεση. Ο πίνακας εξαιρέσεων για το giveMeThatOldFashionedBoolean () μέθοδος, που φαίνεται παρακάτω, υποδεικνύει ότι οποιαδήποτε εξαίρεση δημιουργείται μεταξύ και συμπεριλαμβανομένων των διευθύνσεων 0 και 17 (όλοι οι κωδικοί bytec που εφαρμόζουν το δοκιμάστε χειρίζεται το μπλοκ) σύλληψη ρήτρα που ξεκινά από τη διεύθυνση 18.

Πίνακας εξαίρεσης: από τον τύπο στόχου 0 18 18 οποιοδήποτε 

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

HopAround: Μια προσομοίωση εικονικής μηχανής Java

Η παρακάτω εφαρμογή δείχνει μια εικονική μηχανή Java που εκτελεί μια ακολουθία bytecodes. Η ακολουθία bytecode στην προσομοίωση δημιουργήθηκε από το javac μεταγλωττιστής για το hopAround () μέθοδος της τάξης που φαίνεται παρακάτω:

τάξη Κλόουν {static int hopAround () {int i = 0; ενώ (true) {try {try {i = 1; } τελικά {// η πρώτη επιτέλους ρήτρα i = 2; } i = 3; επιστροφή i; // αυτό δεν ολοκληρώνεται ποτέ, λόγω της συνέχισης} επιτέλους {// του δεύτερου επιτέλους όρου εάν (i == 3) {συνέχεια; // αυτό συνεχίζει να αντικαθιστά τη δήλωση επιστροφής}}}}} 

Οι κωδικοί bytes που δημιουργούνται από javac για το hopAround () Η μέθοδος φαίνεται παρακάτω: