3. Εκφράσεις και έλεγχος ροής Τελειώνοντας αυτό το κεφάλαιο θα μπορείτε: Να διακρίνετε ανάμεσα σε μεταβλητές μέλη και αυτόματες μεταβλητές Να περιγράφετε την αρχικοποίηση των μεταβλητών μελών Να αναγνωρίζετε και να διορθώνετε το λάθος μεταγλώττισης «Possible reference before assignment» Να αναγνωρίζετε, να περιγράφετε και να χρησιμοποιείτε τους τελεστές της Java (εκτός από τον instanceof) Να διακρίνετε ανάμεσα σε επιτρεπτές και μη επιτρεπτές αναθέσεις σε βασικούς τύπους Να αναγνωρίζετε boolean εκφράσεις και να δηλώνετε τις απαιτήσεις για αυτές σε δομές ελέγχου Να αναγνωρίζετε συμβατότητα ανάθεσης και τις απαιτούμενες μετατροπές (cast) σε τύπους υποδομής Να κάνετε ορθή χρήση των δομών if, switch, for, while και do και των μορφών των break και continue με χρήση ετικέτας. 3.1 Εκφράσεις 3.1.1. Μεταβλητές και χρόνος ζωής Έχουμε ήδη δει δύο τρόπους περιγραφής μεταβλητών: με χρήση απλών τύπων όπως int και float ή με χρήση τύπων κλάσεων, που ορίζονται από τον προγραμματιστή. Επίσης έχουμε δει δύο μέρη στα οποία μπορούμε να ορίσουμε μεταβλητές: μέσα σε μία μέθοδο ή μέσα στον συνολικό ορισμό μίας κλάσης. Οι μεταβλητές που ορίζονται μέσα σε μία μέθοδο (θα δούμε στη συνέχεια ότι η main() δεν είναι η μόνη μέθοδος που μπορείτε να ορίσετε) ονομάζονται αυτόματες (automatic) μεταβλητές, τοπικές μεταβλητές, προσωρινές μεταβλητές ή μεταβλητές στοίβας, ανάλογα με την ορολογία που χρησιμοποιεί ο ομιλών. Οι μεταβλητές μέλη δημιουργούνται όταν φτιάχνεται το αντικείμενο με τη χρήση της κλήσης new Xxxx(). Οι μεταβλητές αυτές συνεχίζουν να υφίστανται όσο απαιτείται το αντικείμενο. Σημείωση Ένα μέρος του συστήματος της Java που ονομάζεται «αυτόματος συλλέκτης απορριμμάτων» (automatic garbage collector) καταστρέφει τα αντικείμενα που δεν απαιτούνται πλέον. Αυτό εν γένει συμβαίνει χωρίς την εμπλοκή του προγραμματιστή. Οι αυτόματες μεταβλητές δημιουργούνται όταν η εκτέλεση μπαίνει στη μέθοδο και καταστρέφονται κατά την έξοδο από τη μέθοδο. Αυτός είναι και ο λόγος που ορισμένες φορές αναφέρονται ως «προσωρινές».
3.1.2. Αρχικοποίηση μεταβλητών Καμία μεταβλητή σε ένα πρόγραμμα Java δεν επιτρέπεται να έχει μία μη ορισμένη τιμή. Όταν δημιουργείται ένα αντικείμενο, οι μεταβλητές μέλη αρχικοποιούνται με τις ακόλουθες τιμές όταν δεσμεύεται ο αποθηκευτικός χώρος. byte 0 short 0 int 0 long 0L float 0.0f double 0.0d char boolean Όλοι οι τύποι αναφορών \u0000 (NULL) false null Σημείωση Μία αναφορά που έχει τιμή null αναφέρεται σε μη υπάρχον αντικείμενο. Μία προσπάθεια χρήσης του αντικειμένου στο οποίο αναφέρεται θα προκαλέσει εξαίρεση. Οι εξαιρέσεις είναι λάθη που λαμβάνουν χώρα κατά την εκτέλεση και θα τα δούμε στη συνέχεια. Οι αυτόματες μεταβλητές πρέπει να αρχικοποιούνται πριν τη χρήση τους. Ο μεταγλωττιστής μελετά τον κώδικα για να καθορίσει ότι κάθε μεταβλητή έχει σίγουρα αρχικοποιηθεί πριν την πρώτη χρήση της. Αν ο μεταγλωττιστής δεν μπορεί να το καθορίσει έχουμε λάθος μεταγλώττισης. int x = (int) (Math.random() * 100); int y; int z; if (x > 50) { y = 90; } z = y + x; // Πιθανή χρήση πριν αρχικοποίηση θα προκαλέσει // λάθος μεταγλώττισης 3.1.3. Τελεστές Οι τελεστές της Java είναι παρόμοιοι σε στυλ και λειτουργία με αυτούς της C και της C++. Ο πίνακας που ακολουθεί παρουσιάζει τους τελεστές με σειρά προτεραιότητας. ( L to R σημαίνει ότι πρόκειται για προσεταιριστικότητα από αριστερά στα δεξιά, ενώ R to L σημαίνει προσεταιριστικότητα από δεξιά στα αριστερά): Διαχωριστές. [ ] ( ) ;, R to L ++ -- + - ~! (τελεστής cast)
L to R * / % L to R + - L to R << >> >>> L to R < > <= >= instanceof L to R ==!= L to R L to R L to R L to R L to R L to R?: L to R = *= /= %= += -= <<= >>= >>>= &= ^= = Σημείωση Ο τελεστής instanceof είναι μοναδικός στη Java και θα παρουσιαστεί στη συνέχεια. 3.1.4. Λογικές εκφράσεις Οι περισσότεροι τελεστές είναι οικείοι από άλλες γλώσσες προγραμματισμού και συμπεριφέρονται όπως αναμένεται. Οι λογικοί και σχεσιακοί τελεστές επιστρέφουν αποτέλεσμα τύπου boolean. Δεν υπάρχει αυτόματη μετατροπή μεταξύ int και boolean. if (i) // παράγει λάθος μεταγλώττισης if (i!= 0) // Ορθό 3.1.5. Συνένωση συμβολοσειρών με + Ο τελεστής + πραγματοποιεί συνένωση αντικειμένων τύπου String παράγοντας ένα νέο αντικείμενο String: String handle = Prof ; String name = First + Last ; String title = handle + name; Σημείωση Αν ένας από τους τελεσταίους του + είναι αντικείμενο τύπου String, τότε ο άλλος μετατρέπεται επίσης σε String. Όλα τα αντικείμενα μπορούν να μετατραπούν σε String αυτόματα, αν και το αποτέλεσμα σε ορισμένες περιπτώσεις μπορεί να είναι «παράξενο». 3.1.6. Λογικοί τελεστές «βραχυκυκλώματος» Οι τελεστές && και πραγματοποιούν λογικές εκφράσεις βραχυκυκλώματος (shortcircuit logical expressions). Έστω το ακόλουθο παράδειγμα: String unset = null; if ((unset!= null) && (unset.length() > 5)) { & ^ &&
} // do something with unset Η λογική έκφραση που αποτελεί το όρισμα της εντολής if είναι ασφαλής και επιτρεπτή. Αυτό συμβαίνει γιατί η πρώτη υπο-έκφραση (unset!= null) αποτιμάται σε false και αυτό είναι αρκετό για να αποδειχτεί ότι ολόκληρη η έκφραση είναι false. Συνεπώς, ο τελεστής && ξεπερνά την μη αναγκαία αποτίμηση του (unset.length() > 5), και από τη στιγμή που αυτή δεν αποτιμάται αποφεύγεται μία εξαίρεση null δείκτη. Παρόμοια χρησιμοποιείται ο τελεστής και αν η πρώτη έκφραση επιστρέφει true η δεύτερη δεν αποτιμάται εφόσον είναι ήδη γνωστό ότι όλη η έκφραση είναι true. Σημείωση Αν θέλετε να αποφύγετε τη συμπεριφορά βραχυκυκλώματος χρησιμοποιείστε απλά του τελεστές & και 3.1.7. Τελεστές δεξιάς ολίσθησης >> και >>> Η Java παρέχει δύο τελεστές δεξιάς ολίσθησης. Ο συνήθης τελεστής >> πραγματοποιεί μία αριθμητική δεξιά ολίσθηση. Το αποτέλεσμα αυτής της ολίσθησης είναι γενικά η διαίρεση του πρώτου τελεσταίου με το διπλάσιο του αριθμού που καθορίζεται ως ο δεύτερος τελεσταίος. Για παράδειγμα: 128 >> 1 έχει ως αποτέλεσμα 64 256 >> 4 έχει ως αποτέλεσμα 32-256 >> 4 έχει ως αποτέλεσμα -32 Σημείωση Αν κατανοείτε την αναπαράσταση με συμπλήρωμα ως προς 2, ο τελεστής >> έχει ως αποτέλεσμα την αντιγραφή του bit που δηλώνει το πρόσημο κατά την ολίσθηση. Ο λογικός ή μη προσημασμένος τελεστής δεξιάς ολίσθησης >>> λειτουργεί περισσότερο με βάση τα bits και όχι με την αριθμητική έννοια και πάντα τοποθετεί μηδενικά στα περισσότερο σημαντικά ψηφία. Για παράδειγμα: 1010... >> 2 δίνει 111010... 1010... >>> 2 δίνει 001010... Σημείωση Ο τελεστής ολίσθησης μειώνει το δεξί τελεσταίο κατά modulo 32 αν είναι τύπου int, και κατά modulo 64 αν είναι τύπου long. Συνεπώς, για κάθε int x το αποτέλεσμα της x >> 32 είναι το ίδιο το x και όχι το 0 όπως θα περιμένατε. Σημείωση Είναι σημαντικό να κατανοήσετε ότι ο τελεστής >>> επιτρέπεται μόνο σε ακέραιες τιμές και είναι αποτελεσματικός μόνο σε int ή long. Αν εφαρμοστεί σε short ή byte η τιμή προάγεται, με την κατάλληλη επέκταση προσήμου, σε int προτού εφαρμοστεί ο >>>. 3.1.8. Προαγωγή και μετατροπή εκφράσεων Η γλώσσα Java δεν υποστηρίζει την αυθαίρετη μετατροπή (casting) τύπων μεταβλητών. Πρέπει να χρησιμοποιήσετε ρητή μετατροπή (explicit cast) για τη μετατροπή ανάμεσα σε τύπους μεταβλητών. Οι μεταβλητές και οι εκφράσεις μπορούν να μετατραπούν σε ευρύτερες μορφές, αλλά όχι σε στενότερες. Συνεπώς μία έκφραση τύπου int μπορεί να
αντιμετωπιστεί ως long, αλλά μία έκφραση τύπου long δεν μπορεί να αντιμετωπιστεί ως int χωρίς να γίνει ρητή μετατροπή. long bigval = 6; // Το 6 είναι τύπου int, OK int smallval = 99L; // Το 99L είναι τύπου long, μη επιτρεπτή float z = 12.414F; // το 12.414F είναι τύπου float, OK float z1 = 12.414; // το 12.414 είναι τύπου double, μη επιτρεπτή Εν γένει, μπορείτε να σκέφτεστε ότι μία έκφραση είναι συμβατή ως προς την ανάθεση (assignment compatible) αν ο τύπος της μεταβλητής είναι τουλάχιστον τόσο μεγάλος (σε πλήθος bits) όσο ο τύπος της έκφρασης. 3.1.9. Μετατροπή Όταν οι τιμές δεν είναι συμβατές ως προς την ανάθεση ορισμένες φορές χρησιμοποιείται η μετατροπή (cast) για να πεισθεί ο μεταγλωττιστής να αναγνωρίσει την ανάθεση. Αυτό μπορεί να γίνει για παράδειγμα ώστε να «χωρέσουμε» μία τιμή τύπου long σε μία μεταβλητή τύπου int. Η ρητή μετατροπή γίνεται ως εξής: long bigvalue = 99L; int squashed = (int)(bigvalue); Παρατηρούμε ότι ο επιθυμητός τύπος στόχος τοποθετείται σε παρενθέσεις και χρησιμοποιείται ως πρόθεμα στην έκφραση που πρέπει να τροποποιηθεί. Μπορεί να μην είναι απαραίτητο, αλλά συνιστάται, να περικλείεται ολόκληρη η έκφραση προς μετατροπή σε παρενθέσεις. Αυτό ισχύει γιατί η προτεραιότητα της πράξης μετατροπής μπορεί να προκαλέσει προβλήματα σε διαφορετική περίπτωση. Σημείωση Θυμηθείτε ότι το εύρος ενός short είναι 2 15... (2 15 )-1 και το εύρος ενός char είναι 0... (2 16 )-1. Συνεπώς η ανάθεση μεταξύ short και char απαιτεί πάντα ρητή μετατροπή. 3.2 Έλεγχος ροής 3.2.1. Εντολές διακλάδωσης Εντολές if, else Η βασική σύνταξη των εντολών if, else είναι if (boolean_έκφραση) εντολές_ή_μπλοκ_εντολών else εντολές_ή_μπλοκ_εντολών 1 int count; 2 count = getcount(); // μία μέθοδος ορισμένη στο πρόγραμμα 3 if (count < 0) { 4 System.out.println( Σφάλμα: η τιμή του μετρητή είναι αρνητική! ); 5 }
6 else { 7 System.out.println( Θα έχουμε + count + 8 ανθρώπους για φαγητό σήμερα. ); 9 } Η Java διαφέρει από τη C/C+ στο ότι η if δέχεται μία έκφραση τύπου boolean, και όχι αριθμητική τιμή. Να θυμάστε ότι οι τύποι boolean και οι αριθμητικοί τύποι δεν μπορούν να μετατραπούν ο ένας στον άλλο. Συνεπώς αντί του : if (x) // x is int θα πρέπει να χρησιμοποιείτε if (x!= 0) Εντολή switch Η σύνταξη της εντολής switch είναι: switch (expr1) { case expr2: statements; break; case expr3: statements; break; default: statements; break } Σημείωση Στην εντολή switch (expr1), η expr1 πρέπει να είναι συμβατή ως προς την ανάθεση με τύπο int. Λαμβάνει χώρα προαγωγή για τύπους byte, short ή char. Εκφράσεις τύπου κινητής υποδιαστολής ή long δεν είναι επιτρεπτές. Σημείωση Σημειώστε ότι μπορούμε να έχουμε switch μόνο σε τύπους δεδομένων short, int, byte ή char. 1 switch (colornum) { 2 case 0: 3 setbackground(color.red); 4 break; 5 case 1: 6 setbackground(color.green); 7 break; 8 default: 9 setbackground(color.black); 10 break; 11 }
3.2.2. Εντολές βρόχων Βρόχοι for Η σύνταξη των βρόχων for είναι for (init_expr; boolean; alter_expr3) statement_or_block for (int i = 0; i < 10; i++) { System.out.println( Are you finished yet? ); } System.out.println( Finally! ); Σημείωση Η Java επιτρέπει τον τελεστή κόμμα σε εντολές for, αλλά πουθενά αλλού. Για παράδειγμα το for (i=0, j=0; j < 10; i++, j++) είναι επιτρεπτή. Βρόχοι while Η σύνταξη των βρόχων while είναι while (boolean) statement_or_block 1 int i; 2 while (i < 10) { 3 System.out.println( Are you finished yet? ); 4 i++; 5 } 6 System.out.println( Finally! ); Βρόχοι do Η σύνταξη των βρόχων do είναι do statement_or_block while (boolean) 1 int i; 2 do { 3 System.out.println( Are you finished yet? ); 4 i++; 5 } while (i<10); 6 System.out.println( Finally! );
3.2.3. Ειδικός έλεγχος ροής βρόχων Οι ακόλουθες εντολές μπορούν να χρησιμοποιηθούν για επιπρόσθετο έλεγχο σε εντολές που βρίσκονται σε βρόχους. break [label]; continue [label]; label: statement; 1 loop: while (true) { 2 for (int i=0; i<100; i++) { 3 switch (c=in.read()) { 4 case 1: 5 case \n : 6 //jumps out of while-loop line 12 7 break loop; 8... 9 } 10 } // end for 11 } // end while 12 13 test: for (...) { 14... 15 while (...) { 16 if (j > 10) { 17 //jumps to next iteration for line 13 18 continue test; 19 } 20 } 21 } 3.3 Ασκήσεις 3.3.1. Πρώτο επίπεδο: Παραγοντικό Παραγοντικό είναι ένας αριθμός που υπολογίζεται ως μία επαναλαμβανόμενη ακολουθία πολλαπλασιασμών. Ο παραγοντικός αριθμός γράφεται ως ένας αριθμός με θαυμαστικό. Για παράδειγμα 4! που είναι ίσο με 1 * 2 * 3 * 4 = 24. Γράψτε μία εφαρμογή που θα εκτυπώνει τα παραγοντικά των αριθμών 2, 4, 6 και 10. 3.3.2. Δεύτερο επίπεδο: Πρόγραμμα γεωμετρίας Για κάθε δεδομένο ορθογώνιο τρίγωνο το μήκος της υποτείνουσας δίνεται από τον τύπο c + τις δύο άλλες πλευρές ενός ορθογωνίου τριγώνου. Υπόδειξη: δείτε την κλάση java.lang.math 2 2 = a b. Γράψτε ένα πρόγραμμα Java που να υπολογίζει την υποτείνουσα με βάση