159
Αντικειμενοστραφής Προγραμματισμός ΙΙ (Java) – Κακαρόντζας Γιώργος Τμήμα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας Κεφάλαιο 1: Τα βασικά της Java Εξοικείωση με το περιβάλλον Αυτό το μάθημα αποσκοπεί σε μία πρώτη γνωριμία με τη Java Για να ξεκινήσουμε... Για να ξεκινήσουμε θα πρέπει να έχουμε εγκαταστήσει στον υπολογιστή μας την τρέχουσα έκδοση της JAVA: Μπορούμε να την κατεβάσουμε από το site της Java της εταιρίας Sun (http://java.sun.com ) Ανεξαρτησία από μηχανή Ο μεταγλωττιστής της Java δεν παράγει εκτελέσιμο κώδικα αλλά παράγει μία μορφή αρχείων γνωστή ως Java Bytecodes που δεν είναι εκτελέσιμη απευθείας από μία μηχανή αλλά εκτελείται από την εικονική μηχανή Java (Java Virtual Machine – JVM). Αυτό δίνει στην Java το πλεονέκτημα της ανεξαρτησίας από την συγκεκριμένη μηχανή στην οποία εκτελείται. Αρκεί κανείς να έχει εγκαταστήσει την κατάλληλη έκδοση της Java και το πρόγραμμα θα εκτελεστεί από την εικονική μηχανή. Αυτή η επινόηση έμεινε γνωστή σαν «Γράψε μια φορά για να τρέχει παντού» (Write Once – Run Everywhere). Εικονική Μηχανή Java (Java Virtual Machine - JVM) Η ακόλουθη εικόνα μας δείχνει την ιδέα της εικονικής μηχανής Java. Όπως δείχνει και η εικόνα το ίδιο πρόγραμμα αφού μεταφραστεί σε bytecodes στη συνέχεια μπορεί να τρέξει σε διαφορετικές μηχανές (π.χ Windows, Solaris ή MacOS). Αρκεί το σύστημα να είναι εφοδιασμένο με την κατάλληλη JVM που θα το εκτελέσει. Γιατί λοιπόν Java; Σ' αυτό το εδάφιο θα εξηγήσουμε τους λόγους που είναι προτιμότερο να χρησιμοποιήσουμε Java από άλλες εναλλακτικές λύσεις, κυρίως για τον προγραμματισμό διαδικτυακών εφαρμογών αλλά και γενικότερα. - 1 -

Σημειώσεις - Java - Κακαρόντζας

Embed Size (px)

Citation preview

Page 1: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

Κεφάλαιο 1: Τα βασικά της Java

Εξοικείωση µε το περιβάλλον Αυτό το µάθηµα αποσκοπεί σε µία πρώτη γνωριµία µε τη Java Για να ξεκινήσουµε... Για να ξεκινήσουµε θα πρέπει να έχουµε εγκαταστήσει στον υπολογιστή µας την τρέχουσα έκδοση της JAVA: Μπορούµε να την κατεβάσουµε από το site της Java της εταιρίας Sun (http://java.sun.com) Ανεξαρτησία από µηχανή Ο µεταγλωττιστής της Java δεν παράγει εκτελέσιµο κώδικα αλλά παράγει µία µορφή αρχείων γνωστή ως Java Bytecodes που δεν είναι εκτελέσιµη απευθείας από µία µηχανή αλλά εκτελείται από την εικονική µηχανή Java (Java Virtual Machine – JVM). Αυτό δίνει στην Java το πλεονέκτηµα της ανεξαρτησίας από την συγκεκριµένη µηχανή στην οποία εκτελείται. Αρκεί κανείς να έχει εγκαταστήσει την κατάλληλη έκδοση της Java και το πρόγραµµα θα εκτελεστεί από την εικονική µηχανή. Αυτή η επινόηση έµεινε γνωστή σαν «Γράψε µια φορά για να τρέχει παντού» (Write Once – Run Everywhere). Εικονική Μηχανή Java (Java Virtual Machine - JVM) Η ακόλουθη εικόνα µας δείχνει την ιδέα της εικονικής µηχανής Java. Όπως δείχνει και η εικόνα το ίδιο πρόγραµµα αφού µεταφραστεί σε bytecodes στη συνέχεια µπορεί να τρέξει σε διαφορετικές µηχανές (π.χ Windows, Solaris ή MacOS). Αρκεί το σύστηµα να είναι εφοδιασµένο µε την κατάλληλη JVM που θα το εκτελέσει.

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

- 1 -

Page 2: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

Από την πηγή... Σύµφωνα µε τα λόγια του Tim Lindholm Senior Staff Engineer της JavaSoft σε άρθρο του τον Απρίλιο του 1996: «Με την Java σαν γλώσσα επέκτασης, ένα πρόγραµµα πλοήγησης του Web θα µπορούσε να έχει απεριόριστες

δυνατότητες. Οι προγραµµατιστές θα µπορούσαν να γράψουν τα applets µια φορά και αυτά µετά θα έτρεχαν σε οποιαδήποτε µηχανή, οπουδήποτε. Οι επισκέπτες σε σελίδες µε Java δυνατότητες, θα µπορούσαν να χρησιµοποιήσουν το περιεχόµενο αυτών των σελίδων εφησυχασµένοι ότι τίποτα δεν θα µπορούσε να φθείρει την µηχανή τους. Με τα applets σαν αρχικό σηµείο εστίασης, η Java επέδειξε ένα νέο τρόπο για την διάθεση λογισµικού µε το Internet. Αυτός ο νέος παραδειγµατισµός προγραµµατισµού πηγαίνει πέρα από τα προγράµµατα πλοήγησης. Πιστεύουµε ότι είναι ένας νεωτερισµός µε την δυνατότητα να αλλάξει την πορεία της Πληροφορικής.» Λόγοι για να χρησιµοποιήσουµε τη Java Η Java είναι σχεδιασµένη για το Internet. Προγράµµατα της Java µπορούν να τρέχουν σε προγράµµατα πλοήγησης εφοδιασµένα µε την κατάλληλη JVM. Αφού το πρόγραµµα κατέβει από το Internet εκτελείται στη µηχανή του πελάτη και όχι στο Server. Αυτά τα προγράµµατα της Java είναι γνωστά σαν Java Applets. Η Java είναι σχεδιασµένη µε στόχο την εξασφάλιση ασφάλειας. Ένα Java Applet εκτελείται µε απόλυτη ασφάλεια στη µηχανή του χρήστη και δεν µπορεί να κάνει τίποτα που να µην επιτρέπεται από την πολιτική ασφάλειας της JVM της µηχανής του πελάτη. Η Java προσφέρει εκτέλεση σε όλους τους υπολογιστές. Με το επίπεδο αφαίρεσης υλικού της JVΜ ένα πρόγραµµα Java εκτελείται σε όλους τους υπολογιστές ανεξαρτήτως Λειτουργικού Συστήµατος. Αρκεί αυτές να είναι εφοδιασµένες µε την κατάλληλη JVM (Java Virtual Machine) Η Java είναι µια πλούσια γλώσσα προγραµµατισµού. Η Java είναι πολύ ισχυρή αντικειµενοστραφής γλώσσα προγραµµατισµού για το Internet (και όχι µόνο) και όχι µία απλή script γλώσσα για την σύνθεση συστατικών (π.χ. VBScript µε ActiveX controls). Αυτό µας επιτρέπει να γράφουµε προγράµµατα τα οποία είναι εύκολο να συντηρηθούν και να εξελιχτούν στο χρόνο και όχι απλά σενάρια (scripts) τα οποία παίζουν το ρόλο της κόλλας για components που έχουν γίνει µε άλλες γλώσσες αλλά γενικά δεν µπορούµε να τα αλλάξουµε ή να ελέγξουµε την εξέλιξή τους. Η Java προσφέρει την ευελιξία της δυναµικής σύνδεσης. Ένα πρόγραµµα Java εκτελείται από µία εικονική µηχανή και οι τάξεις (τα αρχεία .class που περιέχουν τα bytecodes) φορτώνονται από την JVM όταν εκτελείται το πρόγραµµα. Αυτό σηµαίνει ότι µπορεί κανείς να αντικαταστήσει µία παλιά τάξη µε µία νέα ακόµα και την στιγµή που εκτελείται το πρόγραµµα. Αυτό είναι ένα βασικό χαρακτηριστικό των components. Έτσι οι τάξεις Java πάνε πέρα από τον αντικειµενοστραφή προγραµµατισµό, στον προγραµµατισµό βασισµένο σε συστατικά (component-based programming). Είναι επίσης σηµαντικό ότι η Java είναι σχεδιασµένη γι’ αυτό και δεν απαιτείται η προσθήκη κάποιου πολύπλοκου µοντέλου (όπως το COM) της Microsoft, πάνω από µία γλώσσα προγραµµατισµού που δεν είναι σχεδιασµένη µε αυτό τον τρόπο (π.χ. Visual C++). Αρκεί κανείς να µελετήσει το specification του COM και να το συγκρίνει µε το specification των Java Beans για να πειστεί γι’ αυτό! Πιθανά µειονεκτήµατα της Java Τα προγράµµατα πλοήγησης ενδέχεται να µην µπορούν να εκτελέσουν το Applet µας. Αυτό διότι τα προγράµµατα πλοήγησης ενδέχεται να µην είναι εφοδιασµένα µε την τρέχουσα έκδοση της Java. Ο κώδικας που είναι µεταγλωττισµένος για µια συγκεκριµένη µηχανή είναι ταχύτερος από τον κώδικα που εκτελείται από ένα µεταφραστή (interpreter). Πάντως αξίζει να σηµειωθεί ότι µία τεχνική που ονοµάζεται Just-In Time µεταγλώττιση (JIT compilation) έχει βελτιώσει σηµαντικά τους χρόνους εκτέλεσης των Java προγραµµάτων. Αν και ακόµα τα προγράµµατα

- 2 -

Page 3: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

της Java δεν έχουν γίνει ταχύτερα από τον µεταγλωττισµένο κώδικα της C++, πολλοί είναι αυτοί που υποστηρίζουν ότι είναι αρκετά γρήγορα για να µην γίνει η ταχύτητα η αιτία να µην πετύχει η Java.

Eφαρµογές Java Σ' αυτό το µάθηµα θα κάνουµε την πρώτη µας εφαρµογή σε Java η οποία (φυσικά) θα είναι το πρόγραµµα HelloWorld! και θα δούµε πως είναι µία εφαρµογή στη Java. Τι είναι µία εφαρµογή στη Java Μία Java εφαρµογή είναι ένα πρόγραµµα σχεδιασµένο για να εκτελεστεί από µία JVM στη µηχανή του χρήστη και όχι από ένα πρόγραµµα πλοήγησης. Μία Java εφαρµογή θα πρέπει να έχει και µία συνάρτηση main (κύρια) που είναι και το σηµείο εισόδου στην εφαρµογή (δηλαδή από εκεί ξεκινά το πρόγραµµα). Φυσικά µια συνάρτηση main θα πρέπει να βρίσκεται µέσα σε µία τάξη (class). Η Java είναι µία αντικειµενοστραφής γλώσσα προγραµµατισµού και εποµένως δεν µπορούµε να έχουµε καµία συνάρτηση που δεν είναι τµήµα µιας τάξης στη Java. Θα πρέπει να έχουµε µία έστω τάξη στην οποία θα περιέχεται η main ακόµα και αν δεν την χρειαζόµαστε! Η πρώτη µας εφαρµογή: Το πρόγραµµα "Hello World!" Σ’ αυτό το σηµείο θα κατασκευάσουµε την πρώτη µας εφαρµογή Java που ο κώδικάς της είναι ο ακόλουθος:

public class HelloWorld public static void main(String[] args) System.out.println("Hello World!");

Θα πρέπει να αποθηκεύσουµε το αρχείο µε το όνοµα HelloWorld.java να το µεταγλωττίσουµε σε bytecodes δίνοντας javac HelloWorld.java και αν δεν έχει λάθη να το εκτελέσουµε πληκτρολογώντας java HelloWorld

Java Applets Σ' αυτό το εδάφιο θα κάνουµε µία πρώτη επαφή µε τα Java Applets και θα κάνουµε το πρόγραµµα HelloWorld σε µορφή Applet. Επίσης θα κάνουµε µία HTML σελίδα που είναι απαραίτητη για να εισάγουµε σ' αυτήν το Applet. Τί είναι ένα Java Applet Τα Java Applets είναι προγράµµατα Java τα οποία είναι σχεδιασµένα για να εκτελούνται από ένα πρόγραµµα πλοήγησης (browser). Για λόγους διευκόλυνσης των προγραµµατιστών, η Sun παρέχει µε κάθε JDK (Java Development Kit) και ένα πρόγραµµα εκτέλεσης των Applets το οποίο λέγεται appletviewer. Κάθε Applet θα πρέπει να επεκτείνει την τάξη java.applet.Applet και να υλοποιεί κάποιες µεθόδους (τουλάχιστον τη µέθοδο paint). Για να εκτελέσουµε ένα applet µέσα από ένα browser ή από τον appletviewer θα πρέπει να συµπεριλάβουµε την τάξη του applet σε µια HTML σελίδα χρησιµοποιώντας το tag <Applet>. Στη συνέχεια µπορούµε να φορτώσουµε τη σελίδα στο browser ή να δώσουµε appletviewer <όνοµα HTML αρχείου> για να δούµε το Applet να εκτελείται Το πρώτο µας Applet: Το Applet HelloWorldApplet

- 3 -

Page 4: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

Στη συνέχεια θα κατασκευάσουµε το Hello World πρόγραµµα σαν Applet, θα το µεταφράσουµε σε bytecodes και θα κατασκευάσουµε µια µινιµαλιστική HTML σελίδα για να µπορέσουµε να εκτελέσουµε το Applet µέσα από τον appletviewer: Το java πρόγραµµα είναι το ακόλουθο:

import java.applet.Applet; import java.awt.Graphics; public class HelloWorldApplet extends Applet public void paint(Graphics g) g.drawString("Hello world!", 50, 25);

To HTML αρχείο είναι το ακόλουθο:

<applet code="HelloWorldApplet.class" width=300 height=300> </applet> Για να εκτελέσουµε το applet θα πρέπει να φορτώσουµε την HTML σελίδα είτε σε ένα browser (π.χ. τον Internet Explorer) ή να εκτελέσουµε το applet µε τον appletviewer, δίνοντας την εντολή: appletviewer <όνοµα HTML σελίδας>

Έννοιες αντικειµενοστραφούς προγραµµατισµού Η Java είναι µία αντικειµενοστραφής (object-oriented) γλώσσα προγραµµατισµού. Εποµένως είναι σηµαντικό να έχουµε από νωρίς µία γνωριµία µε τις βασικές έννοιες του αντικειµενοστραφούς προγραµµατισµού. Αυτό το εδάφιο αποσκοπεί σ' αυτή τη πρώτη γνωριµία µε τις έννοιες που διέπουν τον αντικειµενοστραφή προγραµµατισµό, ενώ αυτές οι έννοιες θα αναπτυχθούν αναλυτικά σε επόµενα µαθήµατα. Η έννοια της τάξης (class) Μία τάξη (class) είναι ένα καλούπι από το οποίο δηµιουργούνται αντικείµενα. Μία τάξη είναι µια µονάδα: (α) Αφαίρεσης: ∆ιότι παριστάνει µία οντότητα του χώρου λύσης του προβλήµατος (β) Συγκέντρωσης: ∆ιότι συγκεντρώνει δεδοµένα και µεθόδους κάτω από τον ίδιο τύπο. (γ) Απόκρυψης: ∆ιότι επιτρέπει τον απόλυτο έλεγχο στο ποιες άλλες τάξεις µπορούν να προσπελάσουν τα δεδοµένα της (δ) Επέκτασης: ∆ιότι µπορεί να αποτελέσει την βάση ή την επέκταση ή και τα δύο κάποιας ή κάποιων άλλων τάξεων. (ε) Εξέλιξης: ∆ιότι δεδοµένων κάποιων προϋποθέσεων µπορεί να διευκολύνει στην εξέλιξη του λογισµικού µε συστηµατικό τρόπο Τα τµήµατα της δήλωσης µιας τάξης στη Java Τα τµήµατα αυτά είναι όπως φαίνονται στην ακόλουθη εικόνα:

- 4 -

Page 5: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

Η έννοια του αντικειµένου (object) Ένα αντικείµενο δεν είναι παρά µία περίσταση (instance) µιας τάξης. Όπως είδαµε µία τάξη είναι ένα καλούπι για να δηµιουργήσουµε αντικείµενα. Άρα αν δηµιουργήσουµε ένα αντικείµενο της τάξης Person που είδαµε στο προηγούµενο παράδειγµα, τότε αυτό θα έχει ένα ιδιωτικό ακέραιο µέλος (το age), ένα default κατασκευαστή που µόλις δηµιουργήσουµε το αντικείµενο θα θέσει την ηλικία του σε 33, και δύο µεθόδους την setAge που µας επιτρέπει να θέσουµε την ηλικία κάποιου σε κάποια συγκεκριµένη τιµή και την getAge που µας επιστρέφει την ηλικία του αντικειµένου. Σε ένα πρόγραµµα Java η δήλωση και η δηµιουργία αντικειµένου µπορεί να γίνει µε µία απλή δήλωση ενός αντικειµένου και την αρχικοποίησή του σε ένα νέο αντικείµενο του τύπου της τάξης. Για παράδειγµα για να δηλώσουµε ένα νέο Person το citizen θα γράφαµε: Person citizen = new Person(); Συγκέντρωση (encapsulation) και υπερφόρτωση (overloading) Η συγκέντρωση των δεδοµένων και των µεθόδων µίας τάξης είναι ένα από τα βασικά χαρακτηριστικά του αντικειµενοστραφούς προγραµµατισµού. Ένα αντικείµενο τύπου Person µπορεί να αναφερθεί σε µεθόδους της τάξης του χρησιµοποιώντας τον τελεστή 'τελεία', όπως στην ακόλουθη εντολή: citizen.setAge(40); Αυτό µας επιτρέπει να έχουµε και άλλες τάξεις στις οποίες θα µπορούσαµε να έχουµε µία µέθοδο µε το ίδιο όνοµα. Για παράδειγµα θα µπορούσαµε να έχουµε και µία τάξη Animal που θα είχε µία µέθοδο setAge. Η εικονική µηχανή Java δεν µπερδεύεται διότι γνωρίζει την τάξη του κάθε αντικειµένου. Αυτό το χαρακτηριστικό, η χρήση δηλαδή των ίδιων ονοµάτων για διαφορετικές τάξεις, ονοµάζεται υπερφόρτωση (overloading). Κληρονοµικότητα (Inheritance) Μία τάξη Java µπορεί να επεκταθεί (extend) από µία άλλη τάξη. Αυτή η άλλη τάξη ονοµάζεται υποτάξη της αρχικής που είναι η υπερτάξη της. Μια υποτάξη κληρονοµεί από την υπερτάξη της: (α) Όλα τα µέλη ιδιωτικά, προστατευµένα και δηµόσια, αν και µπορεί απευθείας να αναφερθεί µόνο στα προστατευµένα και στα δηµόσια.

- 5 -

Page 6: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

(β) Όλες τις µεθόδους ιδιωτικές, προστατευµένες και δηµόσιες αν και µπορεί απευθείας να αναφερθεί µόνο στις προστατευµένες και στις δηµόσιες. (γ) Τον τύπο της υπερτάξης της. Για το σύστηµα τύπων της Java αν η τάξη Β είναι υποτάξη της τάξης Α, τότε αντικείµενα της Β θα γίνουν δεκτά οπουδήποτε θα απαιτούνταν αντικείµενα της Α. Αυτή η συµβατότητα τύπων είναι µία πολύ ισχυρή ιδιότητα του αντικειµενοστραφούς προγραµµατισµού. Στη συνέχεια σαν παράδειγµα δίνουµε µία τάξη WorkingPerson η οποία επεκτείνει το Person. Αφού είναι Person τα αντικείµενα της τάξης WorkingPerson θα έχουν όλες τις µεθόδους και τα δεδοµένα της τάξης Person συν τα δικά τους:

public class WorkingPerson extends Person String job; public WorkingPerson() super(); job= "Ανεργος"; //Η εξ’ ορισµού τιµή για την Ελλάδα! public void setJob(String j) job = j; public String getJob() return job;

Πολυµορφισµός (polymorphism) Ένα αντικείµενο µιας υποτάξης είναι συµβατό για το σύστηµα τύπων µε την υπερτάξη διότι σηµασιολογικά το αντικείµενο της υποτάξης είναι και αντικείµενο της υπερτάξης. Αυτή η ιδιότητα µας επιτρέπει να καταχωρίσουµε σε αντικείµενα που έχουν δηλωθεί σαν αντικείµενα της υπερτάξης, αντικείµενα της υποτάξης. Αν η υπερτάξη και η υποτάξη έχουν λοιπόν µια κοινή µέθοδο, δηλαδή η µέθοδος ορίζεται εκ νέου στην υποτάξη, ποια µέθοδος θα κληθεί σε ένα αντικείµενο που έχει δηλωθεί σαν αντικείµενο της υπερτάξης; Η απάντηση σ’ αυτό το ερώτηµα είναι «Εξαρτάται». Αν το αντικείµενο είναι πράγµατι της υπερτάξης θα κληθεί η µέθοδος της υπερτάξης, αν όµως είναι της υποτάξης θα κληθεί η µέθοδος της υποτάξης. Αυτή η σηµαντική ιδιότητα ονοµάζεται πολυµορφισµός, διότι η ίδια µέθοδος για αντικείµενα του ιδίου τύπου φαίνεται να έχει πολλές µορφές, και είναι µία πολύ ισχυρή τεχνική στην οποία τα αντικειµενοστραφή συστήµατα οφείλουν µεγάλο µέρος της φήµης τους. Ο πολυµορφισµός, για παράδειγµα, µας δίνει το δικαίωµα να διατηρούµε δοµές δεδοµένων µε αντικείµενα µιας υπερτάξης τα οποία µπορεί να είναι στη πραγµατικότητα αντικείµενα των υποτάξεων και να διατρέχουµε αυτή τη δοµή καλώντας τη κοινή µέθοδο. Αυτό µας δίνει πολλές δυνατότητες τις οποίες θα διερευνήσουµε σε επόµενα µαθήµατα και κυρίως διευκολύνει την επεκτασιµότητα των εφαρµογών!

Mεταβλητές (variables) Οι µεταβλητές είναι οι θέσεις µνήµης στις οποίες ένα πρόγραµµα τοποθετεί τα δεδοµένα του κατά τη διάρκεια της λειτουργίας του. Σ' αυτό το εδάφιο θα µάθουµε πως δηλώνουµε µεταβλητές, ποιοί είναι οι τύποι των µεταβλητών στη γλώσσα Java, πως πρέπει να είναι το όνοµα µιας µεταβλητής και ποια είναι η εµβέλεια της µεταβλητής. ∆ήλωση µεταβλητών Η δήλωση µιας µεταβλητής έχει πάντοτε την µορφή: <Τύπος Μεταβλητής> <Όνοµα Μεταβλητής>;

- 6 -

Page 7: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

Για παράδειγµα η ακόλουθη δήλωση αφορά µία ακέραια µεταβλητή που ονοµάζεται count: int count; Προαιρετικά µπορεί να υπάρχει και µία αρχικοποίηση της µεταβλητής ταυτόχρονα µε την δήλωσή της. Για παράδειγµα η ακόλουθη δήλωση αρχικοποιεί την count στη τιµή 0. int count = 0; Στη συνέχεια και για την περιοχή εµβέλειας της συγκεκριµένης µεταβλητής µπορούµε να χρησιµοποιήσουµε την count όπου επιτρέπεται να χρησιµοποιηθούν ακέραιες τιµές. Ο τύπος εποµένως της µεταβλητής καθορίζει τις επιτρεπτές ενέργειες που µπορούν να γίνουν σε αυτή τη µεταβλητή. Τύποι µεταβλητών Οι τύποι των µεταβλητών της Java δίνονται από τον ακόλουθο πίνακα

Τύπος Μέγεθος και µορφή Περιγραφή Ακέραιες µεταβλητές

byte 8 bit σε µορφή συµπληρώµατος ως προς 2

Ακέραιος µήκους ενός byte

short 16 bit σε µορφή συµπληρώµατος ως προς 2

Ακέραιος µικρού µήκους

int 32 bit σε µορφή συµπληρώµατος ως προς 2

Ακέραιος

long 64 bit σε µορφή συµπληρώµατος ως προς 2

Ακέραιος µεγάλου µήκους

Πραγµατικές µεταβλητές float 32 bit σε µορφή IEEE 754 Πραγµατικός απλής ακρίβειας

double 64 bit σε µορφή IEEE 754 Πραγµατικός διπλής ακρίβειαςΆλλοι Τύποι

char 16-bit Unicode χαρακτήρας Ένας χαρακτήρας boolean true ή false Μία boolean τιµή αληθής ή

ψευδής Ονόµατα µεταβλητών Ένα πρόγραµµα Java αναφέρεται σε µία µεταβλητή µε το όνοµά της. Ένα όνοµα µιας µεταβλητής θα πρέπει να ικανοποιεί τους ακόλουθους περιορισµούς. (α) Πρέπει να είναι ένα έγκυρο αναγνωριστικό της Java αποτελούµενο από µία σειρά Unicode χαρακτήρων. (β) ∆εν πρέπει να είναι δεσµευµένη λέξη της Java (keyword) (γ) ∆εν πρέπει να υπάρχει άλλο ίδιο όνοµα µεταβλητής στην περιοχή εµβέλειας της µεταβλητής. Στη Java συνηθίζονται τα ακόλουθα:

− Τα ονόµατα των τάξεων ξεκινάνε µε κεφαλαία γράµµατα − Τα ονόµατα των µεταβλητών ξεκινάνε µε πεζά γράµµατα − Στη περίπτωση που έχουµε αναγνωριστικό πολλών λέξεων τότε ενώνουµε τις λέξεις

γράφοντας µε κεφαλαίο το πρώτο γράµµα κάθε λέξης: π.χ. aVeryLongIdentifier −

- 7 -

Page 8: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

Εµβέλεια µεταβλητών (scope) Η εµβέλεια µίας µεταβλητής είναι η περιοχή εκείνη του προγράµµατός µας στην οποία η µεταβλητή είναι ορατή και καθορίζει πότε η µεταβλητή δηµιουργείται και καταστρέφεται. Υπάρχουν τέσσερα ήδη µεταβλητών µε αντίστοιχα τέσσερις περιοχές εµβέλειας:

− Μεταβλητές µέλη µε εµβέλεια την τάξη − Τοπικές µεταβλητές µε εµβέλεια την οµάδα εντολών στην οποία δηλώνονται − Παράµετροι µεθόδων µε εµβέλεια την µέθοδο µέσα στην οποία δηλώνονται, και − Παράµετροι χειριστών εξαιρέσεων µε εµβέλεια την οµάδα εντολών του χειριστή

εξαιρέσεων Η εµβέλεια των µεταβλητών φαίνεται πιο παραστατικά στην ακόλουθη εικόνα:

Τελικές µεταβλητές και παράµετροι Μπορούµε να δηλώσουµε µία µεταβλητή ή µία παράµετρος σε µία µέθοδο ως τελική (final).Η δήλωση final έχει ως αποτέλεσµα την απαγόρευση της αλλαγής της τιµής στην εµβέλεια στην οποία ανήκει η µεταβλητή. ∆ηλαδή η µεταβλητή είναι στη πραγµατικότητα σταθερά για την εµβέλεια αυτή. Στην περίπτωση της µεταβλητής η λέξη final θα πρέπει λογικά να χρησιµοποιηθεί µε κάποια αρχικοποίηση για την απόδοση της αρχικής και ταυτόχρονα οριστικής τιµής στη µεταβλητή, όπως στο ακόλουθο παράδειγµα:

Final int interestRate = 0.15; Μπορούµε όµως να αναβάλλουµε την απόδοση της αρχικής τιµής και γι’ αργότερα αν αυτό είναι αναγκαίο. Η αρχική τιµή φυσικά µπορεί να αποδοθεί µόνο µία φορά. Μία τελική µεταβλητή στην οποία δεν έχει αποδοθεί ακόµα τιµή ονοµάζεται κενή (blank). Στην περίπτωση µιας παραµέτρου σε µία µέθοδο η δήλωση final έχει το νόηµα ότι η συγκεκριµένη παράµετρος δεν µπορεί να αλλάξει τιµή µέσα στη µέθοδο αλλά θα διατηρηθεί η τιµή που θα περαστεί από το καλούν τµήµα του προγράµµατος. Για παράδειγµα η µέθοδος withdraw δεν µπορεί να µεταβάλλει τη παράµετρο money , διότι η παράµετρος αυτή έχει δηλωθεί ως τελική (final). Μπορεί µόνο να την χρησιµοποιήσει.:

public class Account

- 8 -

Page 9: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

… public void withdraw(final double money) if (money > 0 && money<=balance) balance -= money; …

Τελεστές (operators) Σ' αυτό το µάθηµα θα αναφερθούµε στους τελεστές της Java και στις έγκυρες χρήσεις τους. Τι είναι οι τελεστές Οι τελεστές είναι σύµβολα τα οποία συµβολίζουν την τέλεση µιας λειτουργίας σε ένα, δύο ή και τρεις τελεστέους (operators). Υπάρχουν λοιπόν τρία είδη τελεστών ως προς τον αριθµό των τελεστέων στους οποίους επενεργούν: Μοναδιαίοι (unary) Τελεστές: Αυτοί επενεργούν σε ένα και µόνο τελεστέο. Για παράδειγµα ο τελεστής ++ αυξάνει κατά 1 τον τελεστέο του όπως στην έκφραση ++count. ∆υαδικοί (binary) Τελεστές: Αυτοί επενεργούν σε δύο τελεστέους. Για παράδειγµα ο τελεστής + της αριθµητικής πρόσθεσης απαιτεί δύο τελεστέους όπως στην έκφραση op1 + op2. Τριαδικοί (ternary) Τελεστές: Αυτοί επενεργούν σε τρεις τελεστέους. Η Java έχει µόνο ένα τέτοιο τελεστή την έκφραση if που είναι ο :?, όπως στην ακόλουθη έκφραση a>4?a-4:4 Αριθµητικοί τελεστές Οι αριθµητικοί τελεστές χρησιµοποιούνται για τις αριθµητικές πράξεις και είναι αυτοί που φαίνονται στον ακόλουθο πίνακα. Τελεστής Χρήση Περιγραφή

+ n1+n2 Αποτιµάται στο άθροισµα των n1 και n2 - n1-n2 Αποτιµάται στη διαφορά των n1 και n2 * n1*n2 Αποτιµάται στο γινόµενο των n1 και n2 / n1 / n2 Αποτιµάται στο πηλίκο των n1 και n2 % n1%n2 Αποτιµάται στο υπόλοιπο της διαίρεσης του n1 δια του n2 ++ ++n ή ++n Αυξάνει την µεταβλητή n κατά 1 -- -- n ή n-- Μειώνει την µεταβλητή n κατά 1

Μερικές παρατηρήσεις για τους αριθµητικούς τελεστές είναι οι ακόλουθες: Ο τελεστής + στη Java έχει επεκταθεί έτσι ώστε να έχει και το νόηµα την συνένωσης για τα Strings. Έτσι για παράδειγµα οι ακόλουθες εντολές θα εµφανίσουν στην οθόνη τo µήνυµα των «Αρ. Χαρκτήρων: 10». Παρατηρείστε επίσης την σιωπηλή µετατροπή του count από int σε String για να συνενωθεί µε τη σταθερά «Αρ. Χαρακτήρων»

int count = 10; System.out.println("Αρ. Χαρακτήρων: "+count);

Ο τελεστής ++ όπως και ο --, µπορούν να εφαρµοστούν είτε από τα αριστερά (προ-αύξηση & προ-µείωση) είτε από τα δεξιά (µετά-αύξηση & µετά-µείωση).

- 9 -

Page 10: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

Προ-αύξηση και προ-µείωση σηµαίνει ότι αν η συγκεκριµένη έκφραση αποτελεί τµήµα µιας ευρύτερης έκφρασης πρώτα θα υπολογιστεί η νέα τιµή της µεταβλητής που αυξάνουµε ή µειώνουµε και µετά θα υπολογιστεί η έκφραση µε τη χρήση της νέας τιµής, όπως στο παράδειγµα:

int x=10; y = ++x; //Στο y θα εισαχθεί η τιµή 11, και φυσικά το x θα γίνει 11

Μετά-αύξηση και µετά-µείωση σηµαίνει ότι αν η συγκεκριµένη έκφραση αποτελεί τµήµα µιας ευρύτερης έκφρασης πρώτα θα υπολογιστεί η έκφραση µε βάση τη τιµή εκείνη τη στιγµή που έχει η µεταβλητή που αυξάνουµε ή µειώνουµε και µετά θα αυξηθεί ή θα µειωθεί η µεταβλητή όπως στο παράδειγµα:

int x=10; y = x++; //Στο y θα εισαχθεί η τιµή 10, και φυσικά το x θα γίνει 11

Τελεστές σύγκρισης Οι τελεστές σύγκρισης που δίνονται στη συνέχεια συγκρίνουν δύο τιµές και καθορίζουν τη σχέση µεταξύ τους. Η έκφραση που περιέχει ένα τελεστή σύγκρισης µπορεί να είναι true ή false. Τελεστής Χρήση Περιγραφή

> n1>n2 true αν το n1 είναι µεγαλύτερο του n >= n1>=n2 true αν το n1 είναι µεγαλύτερο ή ίσο του n2 < n1<n2 true αν το n1 είναι µικρότερο του n2

<= n1<=n2 true αν το n1 είναι µικρότερο ή ίσο του n2 == n1==n2 true αν το n1 είναι ίσο µε το n2 != n1!=n2 true αν το n1 είναι διάφορο του n2

Λογικοί τελεστές Τους λογικούς τελεστές συνήθως τους χρησιµοποιούµε µε τους τελεστές σύγκρισης για την κατασκευή ποιο πολύπλοκων λογικών εκφράσεων, εκφράσεων δηλαδή που µπορεί να είναι true ή false. Για παράδειγµα η ακόλουθη έκφραση θα είναι true αν η τιµή του x είναι µεταξύ 10 και 100: x>=10 && x<=100 Οι λογικοί τελεστές είναι αυτοί που φαίνονται στον ακόλουθο πίνακα: Τελεστής Χρήση Περιγραφή

&& e1 && e2 true αν και η e1 και η e2 είναι true. H e2 δεν αποτιµάται αν η e1 είναι false

|| e1 || e2 false αν και η e1 και η e2 είναι false. Η e2 δεν αποτιµάται αν η e1 είναι true

! !e H λογική άρνηση της έκφρασης e & e1&e2 Το ίδιο µε τον && µόνο που η e2 θα αποτιµηθεί ακόµα και

αν η e1 είναι false | e1|e2 Το ίδιο µε τον || µόνο που η e2 θα αποτιµηθεί ακόµα και

αν η e1 είναι true Μερικές φορές είναι επιθυµητό να αποτιµηθεί και το δεύτερο µέρος µιας σύνθετης λογικής έκφρασης ακόµα και αν έχει υπολογιστεί η τιµή αλήθειας της έκφρασης. Για παράδειγµα φανταστείτε την ακόλουθη έκφραση:

x>0 && someFunc(x)>10

- 10 -

Page 11: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

Αν θέλουµε σ’ αυτό το παράδειγµα να κληθεί οπωσδήποτε η συνάρτηση someFunc τότε θα πρέπει να χρησιµοποιήσουµε τον & αντί του &&, γιατί µε τον && αν το x <=0 δεν θα αποτιµηθεί το δεύτερο µέρος της έκφρασης µια και η τιµή αλήθειας της έκφρασης θα είναι ούτως ή άλλως false, αφού το πρώτο µέρος της έκφρασης θα είναι false. Ανάλογη είναι και η χρήση του τελεστή | σε σχέση µε τον ||. Για παράδειγµα στην έκφραση

x>0 | someFunc(x)>10

θα κληθεί η someFunc ακόµα και αν το x είναι θετικό ∆υαδικοί τελεστές Με τους τελεστές αυτούς µπορούµε και κάνουµε πράξεις σε επίπεδο bit. Τελεστής Χρήση Περιγραφή

>> n1 >> n2 Κάνει ολίσθηση n2 bits δεξιά στο n1 << n1<<n2 Κάνει ολίσθηση n2 bits αριστερά στο n1

>>> n1 >>> n2 Το ίδιο µε τον >> αλλά χωρίς πρόσηµο & n1&n2 ∆υαδικό ΚΑΙ των n1 και n2 | n1 | n2 ∆υαδικό Ή των n1 και n2 ^ n1 ^ n2 Αποκλειστικό Ή (XOR) των n1 και n2 ~ ~n ∆υαδικό συµπλήρωµα του n

Οι τελεστές δυαδικών πράξεων χρησιµοποιούνται όταν θέλουµε να επέµβουµε στην τιµή µιας µεταβλητής σε επίπεδο bit. Για παράδειγµα έστω ότι έχουµε δηλώσει τις ακόλουθες σταθερές:

final int TURBO = 1; final int TWO_DOORS = 2; final int CHEAP = 4;

που έστω ότι αφορούν τα χαρακτηριστικά ενός αυτοκινήτου. Θα µπορούσαµε στη συνέχεια να δηλώσουµε µία µεταβλητή int έστω την int characteristics = 0 και στη συνέχεια να αποδώσουµε σε αυτή τη µεταβλητή ταυτόχρονα δύο ή και τρεις ιδιότητες χρησιµοποιώντας τον τελεστή | όπως παρακάτω:

characteristics = TURBO | TWO_DOORS; Σε άλλο σηµείο του προγράµµατος θα µπορούσαµε να ελέγξουµε µε την χρήση του δυαδικού & αν ένα αυτοκίνητο είναι για παράδειγµα TURBO ως εξής:

if ((characteristics & TURBO) == TURBO) … Tελεστές καταχώρισης Ο τελεστής καταχώρισης τιµής σε µεταβλητή είναι ο =. Ο = αποδίδει την τιµή της έκφρασης στα δεξιά του στη µεταβλητή που βρίσκεται στα αριστερά του, όπως στην εντολή:

x = y+10; όπου θα υπολογιστεί η έκφραση y+10 και στη συνέχεια θα καταχωρηθεί η τιµή αυτή στη µεταβλητή x. Στη Java χρησιµοποιούνται και άλλοι τελεστές απόδοσης τιµής όπου είναι ουσιαστικά συντοµεύσεις για απόδοση τιµής σε µεταβλητή µε ταυτόχρονη αύξηση, µείωση κτλ αυτής της µεταβλητής. Για παράδειγµα η έκφραση µε τον τελεστή +=:

- 11 -

Page 12: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

x += 10;

είναι ταυτόσηµη µε την έκφραση: x = x+10; Αντίστοιχη σηµασία έχουν και οι τελεστές: -= *= /= %= &= |= ^= <<= >>= >>>=

∆οµές ελέγχου ροής προγράµµατος Σ' αυτό το µάθηµα θα ασχοληθούµε µε τις δοµές ροής ελέγχου προγράµµατος της Java, όπως η if και η while. ∆οµές ελέγχου ροής στη Java Υπάρχουν γενικά δύο ήδη δοµών ελέγχου ροής (control flow):

− Οι δοµές επιλογής και − Οι δοµές επανάληψης

Ο ακόλουθος πίνακας συνοψίζει αυτές τις δοµές για τη Java Είδος δοµής ελέγχου ροής ∆οµή ελέγχου ροής

if-else ∆οµές Επιλογής switch-case for while

∆οµές Επανάληψης

do-while Πέρα από τις εντολές του πιο πάνω πίνακα ο έλεγχος ροής σε ένα πρόγραµµα Java µπορεί να µεταφερθεί και σε κάποιο άλλο σηµείο εξαιτίας της πρόκλησης µιας εξαίρεσης. Αλλά για τις εξαιρέσεις (exceptions) και τους χειριστές τους (exception handlers) θα µιλήσουµε σε επόµενα µαθήµατα. Επίσης κάποιες άλλες εντολές πέρα από τις ίδιες τις δοµές ελέγχου ροής που είδαµε προηγουµένως είναι οι: break, continue και return που µεταφέρουν το έλεγχο ροής σε άλλα σηµεία του προγράµµατος. Την χρήση αυτών των εντολών θα τη δούµε στη συνέχεια. Η Java επιτρέπει και τη χρήση ετικετών αλλά δεν υποστηρίζει την εντολή goto. Αντί αυτής µπορούν και µε τις ετικέτες να χρησιµοποιηθούν οι εντολές break και continue. Η δοµή επιλογής if-else Η εντολή if µας βοηθάει στον έλεγχο µίας λογικής έκφρασης (της συνθήκης) και αν είναι αληθής εκτελούνται µία ή περισσότερες εντολές ενώ αν είναι ψευδής δεν εκτελούνται. Αν οι εντολές είναι περισσότερες από µία τότε θα πρέπει οι εντολές να περικλειστούν ανάµεσα από άγκιστρα (τα οποία οµαδοποιούν εντολές). Το συντακτικό της εντολής if είναι το ακόλουθο

if (συνθήκη) εντολές

- 12 -

Page 13: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

π.χ.

if (x!=0) System.out.println("To x δεν είναι 0"); y = 1/x;

Η εντολή if έχει επίσης και τη φράση else µε την οποία επιτρέπεται η εκτέλεση µίας οµάδας εντολών εναλλακτικά αν δεν ισχύει η συνθήκη. Φυσικά µπορούµε να έχουµε και εµφωλευµένα if δηλαδή if µέσα σε άλλα if όσες φορές θέλουµε. Το συντακτικό της if επαυξηµένο µε τη φράση else είναι το ακόλουθο:

if (συνθήκη) οµάδα-εντολών-1 else οµάδα-εντολών-2

π.χ.

if (x!=0) System.out.println("Το x δεν είναι 0"); y=1/x; else System.out.println("Το x είναι 0"); y=0;

Η δοµή επιλογής switch-case Όταν οι εναλλακτικές περιπτώσεις που πρέπει να ελέγξουµε µε την if είναι πάρα πολλές και αφορούν τον έλεγχο για ισότητα της τιµής µιας µεταβλητής ή µιας έκφρασης µε κάποιες τιµές προτιµάται η switch-case η οποία έχει την ακόλουθη γενική µορφή:

switch (έκφραση) case τιµή-1: εντολές-1; [break;] … case τιµή-ν: εντολές-ν; [break;] [default: εντολές; [break;]]

Η switch-case αποτιµά την τιµή της έκφρασης που ελέγχεται και στη συνέχεια διατρέχει µε τη σειρά όλες τις περιπτώσεις που δίνονται. Αν κάποια περίπτωση βρεθεί αληθής τότε εκτελούνται οι εντολές που δίνονται µετά την άνω-κάτω τελεία γι’ αυτή τη περίπτωση. Αν καµία περίπτωση δεν βρεθεί αληθής τότε εκτελείται η default περίπτωση αν υπάρχει. Προσοχή χρειάζεται στη χρήση του break που είναι µεν προαιρετική αλλά απαιτείται τις περισσότερες φορές, µια και αν δεν υπάρχει τότε θα εκτελεστούν και οι εντολές τις επόµενης περίπτωσης (χωρίς να ελεγχθεί η τιµή της) και πιθανώς και άλλων, µέχρι να βρεθεί το επόµενο break ή να τελειώσει η switch-case. Παρόλα αυτά ενδέχεται να υπάρχουν κάποιες περιπτώσεις που αυτό θα ήταν βολικό, όπως δείχνει το ακόλουθο τµήµα κώδικα που υπολογίζει τις µέρες ενός µήνα ανάλογα µε το ποιος µήνας είναι (η τιµή της µεταβλητής month) και το αν το έτος (year) είναι δίσεκτο:

- 13 -

Page 14: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

switch (month) case 1: case 3: case 5: case 7: case 8: case 10: case 12: numDays = 31; break; case 4: case 6: case 9: case 11: numDays = 30; break; case 2: if ( ((year % 4 == 0) && !(year % 100 == 0)) || (year % 400 == 0) ) numDays = 29; else numDays = 28; break; …

Η δοµή επανάληψης for Η εντολή for είναι χρήσιµη για την επανάληψη µιας σειράς εντολών όταν είναι γνωστό εκ των προτέρων πόσες φορές θέλουµε να επαναληφθούν. Έτσι συνήθως το for ελέγχεται από ένα µετρητή ο οποίος µεταβάλλεται από µία αρχική τιµή µέχρι να ξεπεράσει µία τελική τιµή. Στη for επίσης καθορίζεται το πόσο θα µεταβάλλεται αυτός ο µετρητής σε κάθε βήµα. Η γενική µορφή της for είναι η ακόλουθη:

for (αρχικοποίηση; τερµατισµός; αύξηση) εντολές;

Για παράδειγµα το ακόλουθο τµήµα κώδικα εµφανίζει στην οθόνη τους αριθµούς από το 1 µέχρι το 10:

... for (int i=1; i<=10; i++) System.out.println(i); ...

Στη φράση της αρχικοποίησης το i δηλώνεται (η εµβέλειά του είναι το for loop) και αρχικοποιείται στο1. Στη φράση του τερµατισµού το i ελέγχεται για το αν έχει ξεπεράσει το 10. Άρα ο συγκεκριµένος βρόχος θα επαναληφθεί µέχρι το i να ξεπεράσει το 10. Στη φράση της µεταβολής το i αυξάνεται κατά 1. Αυτό σηµαίνει ότι το i θα πάρει διαδοχικά τις τιµές 1, 2, …, 10 Σε κάθε βήµα της επανάληψης ελέγχεται η τιµή του i. Aν το i ξεπεράσει το 10 ο βρόχος τερµατίζεται, αν όχι αυξάνεται κατά 1 και εκτελείται ξανά η µία και µοναδική εντολή αυτού του βρόχου. Η δοµή επανάληψης while

- 14 -

Page 15: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

Η εντολή for που είδαµε προηγουµένως είναι κατάλληλη όταν γνωρίζουµε πόσες επαναλήψεις θα γίνουν. Αν δεν γνωρίζουµε πόσες επαναλήψεις θα γίνουν µία καλύτερη εντολή είναι η while. H while έχει την ακόλουθη γενική µορφή:

while (συνθήκη) εντολές;

Η εντολή ή εντολές που ακολουθούν το while θα εκτελεστούν όσο η συνθήκη είναι αληθής. Ο βρόχος δηλαδή θα τερµατιστεί όταν η συνθήκη - που είναι µία boolean έκφραση - γίνει ψευδής Στο ακόλουθο τµήµα κώδικα ελέγχεται αν η τιµή x βρίσκεται µέσα στο πίνακα numbers. Κάνουµε δηλαδή µία σειριακή αναζήτηση στα στοιχεία του πίνακα. Αν το στοιχείο βρεθεί η µεταβλητή found γίνεται true και βγαίνουµε από το βρόχο. Επίσης από το βρόχο θα βγούµε αν ελεγχθούν και τα δέκα στοιχεία του πίνακα και δεν έχει βρεθεί ακόµα το x.

… boolean found = false; int i=0; while (!found && i!=10) if (numbers[i++] == x) found = true; …

Η δοµή επανάληψης do-while Υπάρχουν κάποιες περιπτώσεις στις οποίες θα θέλαµε οι εντολές µέσα στο βρόχο να εκτελεστούν τουλάχιστον µία φορά και στη συνέχεια να ελεγχθεί η συνθήκη εξόδου. Σ’ αυτές τις περιπτώσεις προτιµάται η χρήση της do-while αντί της while. Η do-while έχει την ακόλουθη γενική µορφή:

do εντολές; while (συνθήκη);

Στην do-while πρώτα εκτελούνται οι εντολές και στη συνέχεια ελέγχεται η συνθήκη. Ο βρόχος τερµατίζεται αν η συνθήκη βρεθεί ψευδής. H do-while δεν χρησιµοποιείται πολύ συχνά αλλά έχει κι αυτή τις χρήσεις της. Για παράδειγµα όταν διαβάζουµε χαρακτήρες από ένα αρχείο µέχρι να διαπιστώσουµε το τέλος του αρχείου θα πρέπει να διαβάσουµε τουλάχιστον ένα χαρακτήρα, όπως δείχνει και το ακόλουθο τµήµα κώδικα:

... int c; Reader in; ... do c = in.read(); ... while (c != 1); ...

Εντολές διακλάδωσης Η Java έχει τρεις εντολές διακλάδωσης οι οποίες είναι βολικές σε αρκετές περιπτώσεις και ιδιαίτερα µε τις επαναληπτικές δοµές που έχουµε συζητήσει. Οι εντολές διακλάδωσης είναι οι: break, continue και return

- 15 -

Page 16: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

Την εντολή break ήδη την είδαµε σε µία χρήση της µε την switch-case. Μία ακόµα αρκετά συνήθη χρήση της break είναι η χρήση της για άµεση έξοδο από κάποιο βρόχο. Για παράδειγµα η σειριακή αναζήτηση που είδαµε µε το while θα µπορούσε να γίνει ως εξής:

int i; boolean found = false; for (i=0; i<10; i++) if (numbers[i]==x) found = true; break;

Η χρήση της continue µέσα σε ένα βρόχο προκαλεί την άµεση αποτίµηση και πάλι της συνθήκης εξόδου. Για να γίνει αυτό στις εντολές for και while ο έλεγχος µεταφέρεται στη πρώτη γραµµή του βρόχου ενώ στο do-while στη τελευταία. Στη συνέχεια αποτιµάται και πάλι η συνθήκη τερµατισµού και η επαναληπτική διαδικασία τερµατίζεται ή συνεχίζεται ανάλογα µε την τιµή της συνθήκης (true ή false) ως συνήθως. Οι εντολές break και continue έχουν και µία µορφή για ετικέτες (labeled break και labeled continue) που δεν θα συζητήσουµε εδώ µια και στη πράξη οι ετικέτες δεν χρησιµοποιούνται αφού οι υπόλοιπες δοµές που είδαµε αρκούν για όλες τις πιθανές χρήσεις, χωρίς να εµφανίζουν πολλά από τα προβλήµατα που πιθανώς να εµφανιστούν µε την χρήση των ετικετών. Η συζήτηση για την εντολή return αναβάλλεται µέχρι να συζητήσουµε για τις µεθόδους των τάξεων σε επόµενα µαθήµατα.

Πίνακες Σ' αυτό το µάθηµα θα ασχοληθούµε µε τους πίνακες οµοειδών στοιχείων (π.χ. ακεραίων) στη Java. ∆ήλωση και δηµιουργία πινάκων Οι πίνακες, όπως και στις άλλες γλώσσες προγραµµατισµού, είναι δοµές δεδοµένων για την συλλογή και διαχείριση δεδοµένων του ιδίου τύπου. Η διαφορά µε τις περισσότερες από τις άλλες γλώσσες είναι ότι οι πίνακες στη Java είναι αντικείµενα και έχουν και ιδιότητες όπως η length που µας δείχνει ποιο είναι το µέγεθος ενός πίνακα. Η δήλωση ενός πίνακα γίνεται ως εξής:

τύπος[] όνοµα; όπως για παράδειγµα στην ακόλουθη δήλωση:

int[] arrayOfInts; που δηλώνει ένα πίνακα ακεραίων µε το όνοµα arrayOfInts. Η δήλωση του πίνακα δεν αρκεί για τη χρησιµοποίησή του. Γι’ αυτό θα πρέπει να δεσµευτεί ο χώρος µνήµης που απαιτείται για το πίνακα. Επειδή ο πίνακας είναι αντικείµενο απαιτείται – όπως και µε όλα τα αντικείµενα στη Java – να δηµιουργήσουµε ένα αντικείµενο (instantiation) για τον πίνακα µε την εντολή new. Έτσι συνήθως η δήλωση ενός πίνακα συνδυάζεται µε την δηµιουργία του αντικειµένου µε την new, στην οποία προσδιορίζεται και το µέγεθος του πίνακα. Αυτό γίνεται ως εξής:

- 16 -

Page 17: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

τύπος[] όνοµα = new τύπος[µέγεθος]; όπως για παράδειγµα στην ακόλουθη δήλωση:

int[] arrayOfInts = new int[10]; που δηµιουργεί ένα πίνακα 10 ακεραίων µε το όνοµα arrayOfInts. Η δηµιουργία του πίνακα δεν είναι απαραίτητο να γίνει ταυτόχρονα µε την δήλωσή του µπορεί να γίνει και αργότερα, όπως δείχνει το ακόλουθο τµήµα κώδικα:

int[] arrayOfInts; ... arrayOfInts = new int[10]; ...

Σε κάθε περίπτωση πάντως δεν επιτρέπεται η αναφορά σε στοιχεία του πίνακα πριν να δηµιουργηθεί ο πίνακας µε την εντολή new. ΠΡΟΣΟΧΗ: Ένα σηµείο που προκαλεί σύγχυση στους νέους προγραµµατιστές στη Java είναι η χρήση των πινάκων για αντικείµενα και όχι για τύπους της γλώσσας όπως ο int. Σ’ αυτή τη περίπτωση πέρα από τη δηµιουργία του πίνακα απαιτείται και η δηµιουργία των ίδιων των αντικειµένων τα οποία βρίσκονται στον πίνακα. Χρήση πινάκων Το ακόλουθο τµήµα κώδικα δηµιουργεί ένα πίνακα 10 ακεραίων και στη συνέχεια τοποθετεί µέσα σ’ αυτόν τους ακεραίους από το 0 µέχρι το 9.

... int[] arrayOfInts = new int[10]; for (int i=0; i<10; i++) arrayOfInts[i] = i; ...

Το ακόλουθο τµήµα κώδικα εµφανίζει τα στοιχεία που βρίσκονται στο πίνακα arrayOfInts. Παρατηρείστε τη χρήση της ιδιότητας length για να αναφερθούµε στο µήκος του πίνακα:

... for (int i=0; i<arrayOfInts.length; i++) System.out.println(arrayOfInts[i]); ...

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

ενός πίνακα ακεραίων Αναφορά Εύρεση Μήκους

1 int a[] = new int[10] a[i] a.length 2 int a[][]=new int[3][10] a[i][j] a.length

a[i].length

- 17 -

Page 18: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

3 int a[][][]= new int[3][5][10] a[i][j][k] a.length a[i].length a[i][j].length

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

class Array2 public static void main(String[] args) int marks[][] = new int[3][10]; for (int i=0; i<marks.length; i++) for (int j=0; j<marks[i].length; j++) marks[i][j] = i*j; System.out.print(marks[i][j]+" "); System.out.println();

Συµβολοσειρές (Strings) Σ' αυτό το µάθηµα θα αναφερθούµε αναλυτικά στην τάξη java.lang.String. ∆ήλωση και δηµιουργία String Μία συµβολοσειρά είναι µία σειρά από χαρακτήρες. Στη Java µία συµβολοσειρά υλοποιείται µε τη τάξη String του πακέτου java.lang. Ένα String είναι λοιπόν ένα αντικείµενο και όπως όλα τα αντικείµενα πρέπει να δηλωθεί και να δηµιουργηθεί µε την εντολή new, όπως στο παράδειγµα:

String s = new String(); Εναλλακτικά µπορούµε να δηµιουργήσουµε ένα String αν του αποδώσουµε µία τιµή που θα είναι µία σταθερά συµβολοσειρά, όπως στο παράδειγµα:

String s = "George"; Αφού δηµιουργήσουµε το String µπορούµε στη συνέχεια να το χρησιµοποιήσουµε. H τάξη String παρέχει πολλές χρήσιµες µεθόδους γι’ αυτό. ΠΡΟΣΟΧΗ: Μια συνήθη πηγή λαθών για τους νέους στη Java είναι η σύγκριση για ισότητα των String. Επειδή τα String είναι αντικείµενα ο τελεστής == δεν συγκρίνει τη τιµή τους, αλλά τη διεύθυνσή τους στη µνήµη. Αν θέλουµε να δούµε αν δύο String έχουν την ίδια τιµή θα πρέπει να χρησιµοποιήσουµε τη συνάρτηση equals. Για παράδειγµα για να δούµε αν το String s1 έχει την ίδια τιµή µε το String s2, θα πρέπει να αποτιµήσουµε την έκφραση s1.equals(s2) ή s2.equals(s1). Οι παράµετροι της main Όπως ήδη θα έχετε παρατηρήσει στη µέθοδο main περνιέται σαν παράµετρος ένας πίνακας από String που τον ονοµάσαµε args. Αυτός ο πίνακας είναι µία σειρά παράµετροι που µπορούν να περαστούν στην εφαρµογή όταν τη ξεκινάµε, ως εξής:

java <όνοµα-τάξης> <παράµετρος-0> … <παράµετρος-Ν>

- 18 -

Page 19: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

π.χ.

java CheckUser Master Το ακόλουθο παράδειγµα ελέγχει τη πρώτη παράµετρο που δόθηκε στη γραµµή εντολών που ξεκίνησε το πρόγραµµα για να δει αν είναι “Master”. Αν είναι τότε εµφανίζει το µήνυµα “Welcome Master!” διαφορετικά εµφανίζει το µήνυµα “Unauthorized User!”.

class CheckUser public static void main(String[] args) if (args[0].equals("Master")) System.out.println("Welcome Master!"); //Kane kati epitrepto mono gia tous Masters else System.out.println(“Unauthorized User!);

Βασικές µέθοδοι της τάξης String

− int length(): Επιστρέφει το µήκος του String − char charAt(int index): Επιστρέφει τον χαρακτήρα του String στη θέση index. Ο index

πρέπει να είναι από 0 έως length()-1. − String substring(int beginIndex): Επιστρέφει το substring που ξεκινά από την θέση

beginIndex µέχρι το τέλος του String. − String substring(int beginIndex, int endIndex): Επιστρέφει το τµήµα του String από

την θέση beginIndex µέχρι την θέση endIndex-1. − byte[] getBytes(String enc): Μετατρέπει το array των χαρακτήρων που συνιστούν το

String σε ένα array από bytes, χρησιµοποιώντας την κωδικοσελίδα που περιγράφεται από το String της παραµέτρου.

− void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin): Τοποθετεί τους χαρακτήρες του String από τη θέση srcBegin µέχρι την srcEnd-1 στο array dst, ξεκινώντας από την θέση dstBegin

− int indexOf(int ch): Επιστρέφει τη θέση της πρώτης εµφάνισης του ch στο String ή –1 αν ο ch δεν υπάρχει στο String.

− int lastIndexOf(int ch): Επιστρέφει τη θέση της τελευταίας εµφάνισης του ch στο String ή –1 αν ο ch δεν υπάρχει στο String.

− int compareTo(String anotherString): Επιστρέφει θετικό αριθµό αν το anotherString

είναι µεγαλύτερο λεξικογραφικά από αυτό το String, αρνητικό αριθµό αν είναι µικρότερο, και 0 αν τα δύο Strings έχουν την ίδια τιµή

− boolean equals(Object anObject): Επιστρέφει true αν το anObject είναι ένα String που δεν είναι null και που έχει την ίδια τιµή µε αυτό το String.

− int hashCode(): Είναι ουσιαστικά µια συνάρτηση κατακερµατισµού (hashing function) που επιστρέφει την τιµή µιας θέσης για το String σε µια δοµή Hashtable (π.χ. το java.util.Hashtable). Η τιµή της συνάρτησης προκύπτει από τον υπολογισµό της ακόλουθης έκφρασης:

s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1] όπου s[0], s[1] κ.λ.π. είναι οι χαρακτήρες του String, n είναι το µήκος του String και ο χαρακτήρας ^ έχει το νόηµα της ύψωσης σε δύναµη.

Παράδειγµα της τάξης String

- 19 -

Page 20: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

Θα κάνουµε µία τάξη TokenMethods που θα έχει µια static µέθοδο printlnPerToken(String str) η οποία θα δέχεται ως παράµετρο ένα String και θα εκτυπώνει τα tokens µέσα στο String. Ως token θεωρούµε µια σειρά χαρακτήρων που είναι συνεχόµενη και χωρίζεται από ένα άλλο token µε έναν ή περισσότερους από τους χαρακτήρες λευκού διαστήµατος, που είναι ‘\n’, ‘\t’, ‘\r’, ‘ ’ public class TokenMethods public static void printlnPerToken(String str)

int currentPosition = 0; int strLength = str.length(); int inWhitespace = 0; String whitespaceChars = " \n\r\t"; //όσο δεν τελείωσε το String while (currentPosition < strLength) //αγνόησε τους χαρακτήρες λευκού διαστήµατος στην αρχή της λέξης while (whitespaceChars.indexOf(str.charAt(currentPosition)) >= 0) currentPosition++; int placeHolder = currentPosition; //όσο δεν είναι χαρακτήρας λευκού διαστήµατος απλά προχωρά στην //επόµενη θέση στο String while ((currentPosition < strLength) && (whitespaceChars.indexOf(str.charAt(currentPosition))< 0)) currentPosition++; //εκτυπώνει το String από τον τελευταίο µη λευκό χαρακτήρα έως και //πριν την τρέχουσα θέση System.out.println(str.substring(placeHolder, currentPosition));

public static void main(String args[]) TokenMethods.printlnPerToken("Joshua Tree U2"); Στο πιο πάνω παράδειγµα η static µέθοδος printlnPerToken καλείται µε το String “Joshua Tree U2”. Το αποτέλεσµα θα είναι:

Όπως βλέπετε το String εµφανίστηκε λέξη προς λέξη, µε κάθε λέξη σε ξεχωριστή γραµµή, ενώ αγνοήθηκαν οι χαρακτήρες λευκού διαστήµατος.

Η τάξη StringBuffer

- 20 -

Page 21: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

Η τάξη StringBuffer, χρησιµοποιείται για Strings τα οποία θέλουµε να µπορούν να αλλάξουν τιµή. Σηµειωτέον πως ένα String δεν αλλάζει τιµή. Όταν δίνετε µία νέα τιµή σε ένα String τότε παρασκηνιακά δηµιουργείτε ένα νέο String µε την νέα τιµή. Έτσι η χρήση της τάξης StringBuffer συνίσταται όταν τα περιεχόµενα ενός String µπορεί να αλλάξουν συχνά, γιατί δεν απαιτείται η δηµιουργία νέων αντικειµένων και η συλλογή των παλιών από τον συλλέκτη σκουπιδιών. Βασικές µέθοδοι και κατασκευαστές της τάξης StringBuffer Βασικοί constructors

− StringBuffer(): Κατασκευάζει έναν άδειο StringBuffer − StringBuffer(String str): Κατασκευάζει ένα StringBuffer µε αρχικά περιεχόµενα το str

Βασικές µέθοδοι

− public StringBuffer append(char c): Προσαρτά τον c στο τέλος του StringBuffer. Μέθοδοι append υπάρχουν για κάθε βασικό τύπο της Java αλλά και για Strings ή και για οποιοδήποτε Object (String.valueOf(Object)).

− public StringBuffer insert(int offset, char c): Εισάγει τον c στη θέση offset. Μέθοδοι insert υπάρχουν επίσης για όλους τους τύπους.

− public void setCharAt(int index, char ch): Αλλάζει τον χαρακτήρα στην θέση index µε τον ch

− public StringBuffer reverse(): Αντιστρέφει τα περιεχόµενα του StringBuffer. − public char charAt(int index): Επιστρέφει τον χαρακτήρα στη θέση index. − public String toString(): Επιστρέφει ένα νέο String µε τα περιεχόµενα του StringBuffer − public int length(): Επιστρέφει το µήκος του StringBuffer

Παράδειγµα της τάξης StringBuffer Θα κάνουµε τάξη Title µε µιά static µέθοδο της µορφής: public static void makeTitle(StringBuffer sb) η οποία θα δέχεται ένα StringBuffer και θα αλλάζει τα πρώτα γράµµατα των λέξεων που περιέχει µε κεφαλαία. Για την µετατροπή σε κεφαλαία θα χρησιµοποιήσουµε την ακόλουθη static µέθοδο της τάξης Character: public static char toUpperCase(char ch) Επίσης για να διαπιστώσουµε αν ένας χαρακτήρας είναι whitespace, θα χρησιµοποιήσουµε την ακόλουθη static µέθοδο της τάξης Character: public static boolean isWhitespace(char ch) public class Title public static void makeTitle(StringBuffer sb) boolean inWhitespace = false;

for (int i = 0; i < sb.length(); i++) char c = sb.charAt(i); if (Character.isWhitespace(c)) inWhitespace = true; else if (inWhitespace || i==0) sb.setCharAt(i, Character.toUpperCase(c)); inWhitespace = false;

public static void main(String args[]) StringBuffer sb = new StringBuffer("pera apo tin afriki");

- 21 -

Page 22: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

Title.makeTitle(sb); System.out.println(sb); Η µέθοδος makeTitle απλά διασχίζει τον StringBuffer κάνοντας το πρώτο γράµµα κάθε λέξης κεφαλαίο. Το αποτέλεσµα της εκτέλεσης του προγράµµατος θα είναι:

Ασκήσεις Να γίνει πρόγραµµα StringUpper.java το οποίο θα έχει πίνακα µε 10 String µε Αγγλικές λέξεις. Το πρόγραµµα θα πρέπει να διατρέχει τον πίνακα και να µετατρέπει κάθε String στο array αυτό σε ένα String µε κεφαλαία γράµµατα. Να χρησιµοποιήσετε τη συνάρτηση toUpperCase της τάξης String. (Προτεινόµενη λύση: StringUpper.java) Να γίνει πρόγραµµα SortArray.java το οποίο χρησιµοποιεί τον αλγόριθµο της ταξινόµησης µε τη µέθοδο της φυσαλίδας για την αύξουσα ταξινόµηση ενός πίνακα 10 ακεραίων (Προτεινόµενη λύση: SortArray.java) Να γίνει πρόγραµµα SeekNumber.java το οποίο χρησιµοποιεί τον αλγόριθµο της δυαδικής αναζήτησης για να αναζητήσει ένα ακέραιο που δίνεται σαν παράµετρος στο πρόγραµµα σε ένα πίνακα ακεραίων ο οποίος είναι ταξινοµηµένος. (Προτεινόµενη λύση: SeekNumber.java)

Προτεινόµενες Λύσεις class StringUpper public static void main(String[] args) String[] arrayOfWords = "backspace", "deletes", "the", "character", "to", "the", "left", "of", "the", "cursor"; for (int i=0; i<arrayOfWords.length; i++) arrayOfWords[i] = arrayOfWords[i].toUpperCase(); System.out.println(arrayOfWords[i]);

class SortArray public static void main(String[] args) int[] a = 10, 5, 109, 200, 8, 764, 32, 87, 87, 9;

- 22 -

Page 23: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

//bubble sort for (int i=1; i<a.length-1; i++) for (int j=a.length-1; j>=i; j--) if (a[j-1] > a[j]) int k = a[j-1]; a[j-1] = a[j]; a[j] = k; //Emfanisi tou apotelesmatos for (int i=0; i<a.length; i++) System.out.println("a["+i+"] = "+a[i]); class SeekNumber public static void main(String[] args) int a[] = 1, 9, 10, 23, 45, 67, 90, 95, 105, 180 ; int left, right, mid=0; if (args.length != 1) System.out.println("Usage: java SeekNumber int"); System.exit(0); int x = Integer.parseInt(args[0]); left = 0; right = a.length-1; boolean found = false; while (!found && (left<=right)) mid = (left+right)/2; if (a[mid] == x) found = true; else if (a[mid] < x) left = mid + 1; else right = mid - 1; if (found) System.out.println(x+" was found in position a["+mid+"]"); else System.out.println(x+" was not found");

- 23 -

Page 24: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

Κεφάλαιο 2: Αντικειµενοστραφής Προγραµµατισµός µε τη Java Σ' αυτό το εδάφιο θα κάνουµε µία εισαγωγή στις έννοιες που θα αναλύσουµε στα επόµενα.

Σκοπός των επόµενων µαθηµάτων Στα επόµενα µαθήµατα θα µάθουµε τα ακόλουθα:

1. Πως δηµιουργούµε µία τάξη στη Java 2. Ποιος είναι ο κύκλος ζωής ενός αντικειµένου στη Java 3. ∆εδοµένα και µέθοδοι µιας τάξης σε βάθος 4. Προσδιοριστές πρόσβασης 5. Κληρονοµικότητα τελικές, αφηρηµένες και εσωτερικές τάξεις 6. ∆ιεπαφές (Interfaces) 7. Πακέτα (packages)

Τι γνωρίζουµε ήδη Στο πρώτο µάθηµα είδαµε πως δηλώνουµε µία τάξη στη Java και κάναµε µία πρώτη γνωριµία µε τα βασικά στοιχεία της γλώσσας. Συγκεκριµένα είδαµε τα ακόλουθα:

− Πως είναι µία τάξη στη Java − Τι είναι ένα αντικείµενο − Τι είναι συγκέντρωση (encapsulation) και υπερφόρτωση (overloading) − Τι είναι κληρονοµικότητα (inheritance), και − Τι είναι πολυµορφισµός

Αυτά αναφέρονται στο µάθηµα: "Έννοιες αντικειµενοστραφούς προγραµµατισµού" Στη συνέχεια θα εκµεταλλευτούµε αυτές τις βάσεις που θέσαµε για να επεκτείνουµε τις γνώσεις µας σ’ αυτές τις έννοιες Η αρχή είναι η τάξη Η βασική αρχή που διέπει τον αντικειµενοστραφή προγραµµατισµό είναι η έννοια της τάξης (class). Μία τάξη είναι όπως είδαµε ήδη, ένα καλούπι (ένα πρότυπο) από το οποίο δηµιουργούνται τα αντικείµενα αυτής της τάξης. Τα βασικά στοιχεία στον ορισµό µίας τάξης είναι τα ακόλουθα:

− Ένα όνοµα − Κάποια δεδοµένα − Ένα (ή περισσότερους) κατασκευαστές (constructors) − Κάποιες µεθόδους

Στα επόµενα µαθήµατα θα εξετάσουµε αναλυτικά ένα-ένα αυτά τα στοιχεία, έτσι ώστε να είµαστε σε θέση να χειριζόµαστε µε άνεση τις τάξεις.

Η δήλωση της τάξης Η δήλωση µιας τάξης έχει την ακόλουθη γενική µορφή: [public] [abstract][final] class OνοµαΤάξης [extends τάξη] [implements λίστα-διεπαφών]

- 24 -

Page 25: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

<σώµα τάξης> Οι φράσεις µέσα σε αγκύλες είναι προαιρετικές. Το όνοµα µιας τάξης είναι σύµβαση να ξεκινά µε κεφαλαίο γράµµα. Αυτό δεν σηµαίνει ότι είστε υποχρεωµένοι να τηρήσετε αυτή τη σύµβαση, απλά οι «καλοί τρόποι» επιβάλλουν να το πράξετε! Αν το όνοµα της τάξης αποτελείται από πολλές λέξεις, µπορείτε είτε να ξεκινάτε την κάθε λέξη µε κεφαλαίο γράµµα ή να χωρίζεται τις λέξεις µε τον χαρακτήρα υπογράµµισης (underscore): π..χ ThisIsAVeryLongClassName ή This_is_a_very_long_class_name. Mία τάξη είναι καλό να περιέχεται µόνη της σε ένα και µόνο αρχείο, το δε όνοµα αυτού του αρχείου θα πρέπει να είναι το ίδιο µε το όνοµα της τάξης και µε την επέκταση java. Η πρόσβαση σε µία τάξη Εξ ορισµού σε µία τάξη έχουν πρόσβαση τάξεις του ιδίου πακέτου (package). Αν και δεν µιλήσαµε ακόµα για πακέτα, µπορείτε µε ασφάλεια να θεωρήσετε τη λέξη συνώνυµη µε την υποδιεύθυνση ή το φάκελο (directory, folder). Με την τοποθέτηση της λέξης public µπροστά από το όνοµα της τάξης, έχουν και άλλες τάξεις πρόσβαση σε αυτή και µπορούν να τη χρησιµοποιήσουν, ασχέτως από το αν ανήκουν στο ίδιο ή σε διαφορετικά πακέτα. Η δήλωση abstract Για αφηρηµένες (abstract) τάξεις και για τη χρήση τους θα µιλήσουµε σε επόµενα µαθήµατα. Προς το παρόν πάντως αρκεί να πούµε ότι µία τάξη που έχει δηλωθεί ως abstract δεν µπορεί να χρησιµοποιηθεί για τη δηµιουργία αντικειµένων. ∆ηλαδή δεν µπορούµε να δηµιουργήσουµε αντικείµενα µιας abstract τάξης. Η δήλωση final Οι τάξεις που δηλώνονται ως final, δεν µπορούν να χρησιµοποιηθούν σαν υπερτάξεις άλλων τάξεων. Έτσι θα δηλώνετε µία τάξη ως final µόνο αν θέλετε να µην επεκταθεί. Η φράση extends Μία τάξη µπορεί να επεκτείνει (extend) µία άλλη τάξη µε τη χρήση της φράσης extends, όπως είδαµε και στο µάθηµα "Έννοιες αντικειµενοστραφούς προγραµµατισµού". Σηµειώστε ότι στη Java µια τάξη δεν µπορεί να επεκτείνει περισσότερες από µία τάξεις. ∆εν υποστηρίζεται δηλαδή η πολλαπλή κληρονοµικότητα (multiple inheritance). Αλλά γι' αυτό και όλα τα σχετικά µε την κληρονοµικότητα θα µιλήσουµε σε επόµενα µαθήµατα. Η φράση implements Μία διεπαφή (interface) είναι ένα σύνολο δηλώσεων µεθόδων χωρίς ορισµούς. Μία τάξη µπορεί να υλοποιεί µία ή περισσότερες διεπαφές, µε τη χρήση της φράσης implements. Όταν µία τάξη δηλώνει πως υλοποιεί (implements) µία διεπαφή, θα πρέπει να ορίσει όλες τις µεθόδους που έχουν δηλωθεί στη διεπαφή αυτή. Για διεπαφές και τη φράση implements θα µιλήσουµε αναλυτικά σε επόµενα µαθήµατα. Παράδειγµα: Μία τάξη τραπεζικού λογαριασµού Για να αποσαφηνιστούν καλύτερα τα παραπάνω, ας δώσουµε σ' αυτό το σηµείο µία τάξη ενός τραπεζικού λογαριασµού. Η τάξη Account, όπως θα τη πούµε, θα είναι πολύ απλή. Θα αποτελείται από τα εξής:

− ένα ιδιωτικό µέλος balance το οποίο θα είναι το υπόλοιπο του λογαριασµού

- 25 -

Page 26: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

− ένα constructor (κατασκευαστή) που θα καλείται όταν δηµιουργούνται αντικείµενα τύπου Account και θα θέτει την ιδιότητα balance ίσο µε 0. Αρχικά το υπόλοιπο κάθε λογαριασµού είναι µηδέν.

− Μία µέθοδο getBalance που θα επιστρέφει το τρέχον υπόλοιπο του λογαριασµού − Μία µέθοδο withdraw (ανάληψη) για την ανάληψη χρηµάτων από τον λογαριασµό − Μία µέθοδο deposit (κατάθεση) για την κατάθεση χρηµάτων στο λογαριασµό

Η τάξη Account έχει ως εξής: public class Account private double balance; //constructor public Account() balance = 0.0; //µέθοδος getBalance public double getBalance() return balance; //µέθοδος withdraw //Αν η ανάληψη γίνει επιστρέφει true αλλιώς false public boolean withdraw(double money) if (money <= balance ) balance -= money; return true; return false; //Αν το ποσό είναι θετικό γίνεται η κατάθεση και //επιστρέφεται true, διαφορετικά επιστρέφεται false public boolean deposit(double money) if (money >= 0) balance += money; return true; return false; Η τάξη Account περιέχεται µόνη της στο αρχείο Account.java. Στη συνέχεια θα κάνουµε µία συνάρτηση main που θα περιέχεται στην τάξη µε το όνοµα Main. Αυτή θα είναι µία σύµβαση που θα ακολουθήσουµε, δηλαδή να βάζουµε την κύρια τάξη µιας εφαρµογής µόνη της σε µία τάξη µε το όνοµα Main. Η συνάρτηση main θα δηµιουργήσει ένα αντικείµενο της τάξης Account το a. Στη συνέχεια θα κάνει µία κατάθεση 10.000 δραχµών. Μετά θα κάνει µία ανάληψη 5.000 δραχµών και τέλος θα προσπαθήσει να κάνει µία

- 26 -

Page 27: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

ανάληψη 6.000 δραχµών που όµως δεν θα πετύχει γιατί το υπόλοιπο του λογαριασµού θα είναι 5.000 δραχµές. Η main θα εµφανίσει και τα ανάλογα µηνύµατα στην οθόνη. public class Main public static void main (String[] args) Account a = new Account(); boolean succeeded = a.deposit(10000); if (succeeded) System.out.println("Η κατάθεση έγινε. Νέο υπόλοιπο: "+a.getBalance()); else System.out.println("Η κατάθεση δεν έγινε"); succeeded = a.withdraw(5000); if (succeeded) System.out.println("Η ανάληψη έγινε. Νέο υπόλοιπο : "+a.getBalance()); else System.out.println("Η ανάληψη δεν έγινε"); succeeded = a.withdraw(6000); if (succeeded) System.out.println("Κάτι δεν πάει καλά!!!"); else System.out.println("Η ανάληψη 6000 δρ. δεν έγινε. Υπόλοιπο:"+a.getBalance());

Κύκλος Ζωής Αντικειµένου Σ' αυτό το µάθηµα θα εξετάσουµε τις φάσεις που περνάει ένα αντικείµενο στη (σύντοµη) ζωή του, από την δήλωσή του µέχρι και την συλλογή του από τον συλλέκτη σκουπιδιών (garbage collector). Η δήλωση ενός αντικειµένου Αφού δηµιουργήσουµε µία τάξη, στη συνέχεια µπορούµε να δηµιουργήσουµε αντικείµενα αυτής της τάξης. Αυτό γίνεται πολύ συχνά µε µία εντολή της µορφής: Account a = new Account(); όπως είδαµε και στο παράδειγµα µε την τάξη του λογαριασµού στο προηγούµενο µάθηµα. Η πιο πάνω εντολή στη πραγµατικότητα συνεπάγεται τρεις ενέργειες:

1. Την δήλωση (declaration) του αντικειµένου: ∆ηλώνεται ένα αντικείµενο µε το όνοµα a το οποίο είναι αντικείµενο της τάξης Account

2. Την δηµιουργία (instantiation) του αντικειµένου: ∆ηµιουργείται το αντικείµενο µε το

όνοµα a. Αυτό έχει σαν συνέπεια την δέσµευση µνήµης γι' αυτό το αντικείµενο.

3. Την αρχικοποίηση (initialization) του αντικειµένου: Αυτό γίνεται µε την κλήση του constructor (του κατασκευαστή του αντικειµένου) που θα αρχικοποιήσει το ιδιωτικό δεδοµένο balance σε µηδέν.

- 27 -

Page 28: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

Τα πιο πάνω δεν είναι απαραίτητο να γίνουν µε µία εντολή. Θα µπορούσε η δήλωση να γίνει ξεχωριστά και στη συνέχεια να δηµιουργηθεί το αντικείµενο. Για παράδειγµα θα µπορούσε η δήλωση ενός αντικειµένου τύπου Account να δηλωθεί σε ένα σηµείο του προγράµµατος και η δηµιουργία και αρχικοποίηση σε άλλο όπως δείχνει το ακόλουθο τµήµα κώδικα:

Account a; ... a = new Account();

Φυσικά δεν µπορεί να υπάρξει αναφορά σε µέλη (µεθόδους, ιδιότητες κλπ) πριν να δηµιουργηθεί το αντικείµενο. Χρήση του αντικειµένου Αφού, λοιπόν, δηµιουργηθεί το αντικείµενο στη συνέχεια µπορούµε να το χρησιµοποιήσουµε, να αναφερθούµε δηλαδή στις µεθόδους και στα µέλη του αντικειµένου. Γενικά δεν θεωρείται σωστό να δίνεται η δυνατότητα απευθείας πρόσβασης στα δεδοµένα (δηλαδή στις µεταβλητές) της τάξης. Αντί αυτού θα πρέπει να παρέχονται µέθοδοι set (που θέτουν τις τιµές των µεταβλητών και get που επιστρέφουν τις τιµές των µεταβλητών). Αυτό ευνοεί την εξέλιξη των τάξεων αφού ελέγχεται ο τρόπος µε τον οποίο υλοποιούν τις πληροφορίες που παρέχουν. Έτσι, για παράδειγµα, στην τάξη Account που είδαµε προηγουµένως, δώσαµε µία µέθοδο get η οποία επέστρεφε το υπόλοιπο του λογαριασµού και δεν δώσαµε απευθείας πρόσβαση στο ιδιωτικό (private) δεδοµένο balance. Επίσης, το υπόλοιπο αυξάνεται και µειώνεται (δηλαδή τίθεται - set) µε τις µεθόδους withdraw και deposit, άρα στο συγκεκριµένο παράδειγµα δεν θα ήταν σωστή η χρήση µιας ξεχωριστής µεθόδου set γι' αυτή τη δουλειά. Αφού δηµιουργήσαµε το αντικείµενο a τύπου Account στη συνέχεια το χρησιµοποιήσαµε, καλώντας τις µεθόδους του. Όπως φαίνεται στο ακόλουθο τµήµα κώδικα από τη main που επαναλαµβάνουµε εδώ, η αναφορά γίνεται µε τον τελεστή της τελείας. Έτσι µε την εντολή a.deposit(10000) κατατίθενται 10000 στον λογαριασµό a αφού καλείται η µέθοδος deposit. Το αποτέλεσµα της deposit που έχει boolean τιµή επιστροφής εισάγεται στην µεταβλητή succeeded και στη συνέχεια µπορούµε να το ελέγξουµε. Account a = new Account(); boolean succeeded = a.deposit(10000); ∆ιευκρινίζεται ότι αν το ιδιωτικό δεδοµένο balance δεν ήταν ιδιωτικό (private), αλλά ήταν public, θα µπορούσε ένα πρόγραµµα να κάνει το εξής:

a.balance = 10000;

Αυτό όµως δεν είναι σωστό. Για παράδειγµα στη συγκεκριµένη περίπτωση οι µέθοδοι withdraw και deposit µπορούν να χρησιµοποιηθούν για να ελεγχθεί ποικιλοτρόπως αν θα επιτραπεί ή όχι η κατάθεση ή η ανάληψη σε λογαριασµό. Για παράδειγµα θα µπορούσαµε να προσθέσουµε τη δυνατότητα πίστωσης µέχρι ένα ποσό. Αυτό δεν θα µπορούσε να γίνει αν οι χρήστες είχαν απευθείας πρόσβαση στο δεδοµένο balance, µια και ο οποιοσδήποτε θα µπορούσε να πάρει ή να βάλει χρήµατα παρακάµπτοντας τις µεθόδους withdraw και deposit, που επιβάλλουν την πολιτική µας. Συλλογή αντικειµένου από τον συλλέκτη σκουπιδιών Στην Java δεν χρειάζεται ο προγραµµατιστής να ανησυχεί για την απελευθέρωση της µνήµης όταν τα αντικείµενά του δεν χρειάζονται πλέον. Αυτό είναι ένας διαρκής "βραχνάς" σε άλλες γλώσσες. Για παράδειγµα στη C++ οι destructors µιας τάξης χρησιµοποιούνται για την

- 28 -

Page 29: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

απελευθέρωση της µνήµης που κατέλαβε το αντικείµενο, λίγο πριν καταστραφεί. Αν και η C++ προσφέρει µεγαλύτερη ευελιξία στη διαχείριση της µνήµης, αφού αυτή γίνεται από το πρόγραµµα, τις περισσότερες φορές αυτή η ευελιξία δεν είναι απαραίτητη και αποτελεί µάλλον πηγή λαθών παρά ευλογία. Στη Java η κατάσταση απλοποιείται ως εξής: Όταν ένα αντικείµενο δεν αναφέρεται πια από κανένα άλλο αντικείµενο, δηλαδή δεν χρειάζεται πια, συλλέγεται αυτοµάτως από ένα νήµα εκτέλεσης χαµηλής προτεραιότητας (low-priority thread) της εικονικής µηχανής Java, τον συλλέκτη σκουπιδιών (garbage collector). Ο συλλέκτης σκουπιδιών, διατρέχει κατά διαστήµατα την δυναµική µνήµη στην οποία δηµιουργούνται τα αντικείµενα και όταν αυτά δεν χρειάζονται, δηλαδή δεν αναφέρονται από κανένα άλλο εν ζωή αντικείµενο, απελευθερώνει τη µνήµη που κατέλαβαν. Μπορεί ένα πρόγραµµα να επιβάλλει την εκτέλεση του garbage collector καλώντας την System.gc που είναι µια στατική µέθοδος (δηλαδή µέθοδος της τάξης System και όχι αντικειµένων της τάξης System). Για να είµαστε πιο ακριβολόγοι το ακόλουθο απόσπασµα από το Java API (Application Programming Interface) εγχειρίδιο αναφοράς εξηγεί ακριβώς ποιά είναι η σηµασία της κλήσης του gc: "... Περιγραφή Τρέχει τον συλλέκτη σκουπιδιών. Η κλήση της µεθόδου gc προτείνει ότι η Εικονική Μηχανή Java θα έπρεπε να επεκτείνει την προσπάθειά της στην κατεύθυνση της ανακύκλωσης αχρησιµοποίητων αντικειµένων έτσι ώστε να κάνει τη µνήµη που απασχολούν τώρα διαθέσιµη για γρήγορη επαναχρησιµοποίηση. Όταν ο έλεγχος επιστρέφει από τη κλήση της µεθόδου, η Εικονική Μηχανή Java έκανε την καλύτερη προσπάθεια για την διεκδίκηση του χώρου όλων των αχρησιµοποίητων αντικειµένων. ..." Επίσης µπορούµε να δηλώσουµε τη µέθοδο finalize. Η µέθοδος finalize εκτελείται πριν να συλλεγεί το αντικείµενο από τον συλλέκτη σκουπιδιών. Σπανίως ένα πρόγραµµα θα χρησιµοποιήσει τη µέθοδο finalize. Η µόνη ίσως χρήση της µεθόδου θα ήταν η απελευθέρωση πόρων που δεν βρίσκονται κάτω από τον έλεγχο του συλλέκτη σκουπιδιών της Java. ∆ιευκρινίζεται ότι η µέθοδος finalize είναι µέθοδος της τάξης Object που είναι η υπερτάξη όλων των αντικειµένων στη Java. Η υπέρβαση της µεθόδου από τις υποτάξεις θα έχει ως συνέπεια να εκτελεστεί η µέθοδος finalize της υποτάξης, η οποία θα πρέπει να καλεί την µέθοδο της υπερτάξης µε την εντολή

super.finalize(); πριν να τερµατισθεί (τελευταία εντολή).

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

- 29 -

Page 30: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

Όπως ήδη είδαµε στα βασικά στοιχεία της Java, τα δεδοµένα µπορεί να είναι δεδοµένα ενός από τους προκαθορισµένους τύπους δεδοµένων της Java ή να είναι ακόµα και άλλα αντικείµενα. Τα δεδοµένα είναι σύµβαση να δηλώνονται πριν από τις µεθόδους σε µία τάξη Java και είναι συνήθως ιδιωτικά (private) ή προστατευµένα (protected).

− Αν ο προσδιοριστής πρόσβασης της µεταβλητής είναι private τότε δεν έχει καµία άλλη τάξη πρόσβαση στις µεταβλητές αυτές απευθείας παρά µόνο η ίδια η τάξη στην οποία δηλώνονται.

− Αν ο προσδιοριστής πρόσβασης της µεταβλητής είναι protected τότε παραχωρείται το δικαίωµα της απευθείας πρόσβασης και στις υποτάξεις της τάξης, αν αυτές δηλωθούν στο µέλλον. Οι τάξεις του ιδίου πακέτου µπορούν να καλέσουν τις protected µεθόδους σε αντικείµενα των τάξεων του ίδιου πακέτου.

− Όπως εξηγήσαµε και στο προηγούµενο µέρος δεν θα πρέπει, συνήθως, τα δεδοµένα µιας τάξης να είναι δηµόσια, δηλαδή να δηλώνονται ως public, γιατί αυτό θα ακύρωνε τη δυνατότητα ελέγχου στην προσπέλασή τους, αλλά και γιατί θα "µαρτυρούσε" την συγκεκριµένη υλοποίησή τους και δεν θα µπορούσαµε να αλλάξουµε αυτή την υλοποίηση στο µέλλον.

− Αν ο προσδιοριστής πρόσβασης δεν προσδιοριστεί τότε θα έχουν πρόσβαση απευθείας σ' αυτή τη µεταβλητή όλες οι τάξεις του ιδίου πακέτου. Αν και δεν µιλήσαµε ακόµα για πακέτα θεωρείστε προς το παρόν ότι αυτό σηµαίνει "όλες οι τάξεις στον ίδιο κατάλογο (directory)".

Έτσι η δήλωση µιας µεταβλητής σε µία τάξη έχει τη µορφή:

<προσδιοριστής πρόσβασης> <τύπος> <όνοµα> <αρχικοποίηση>; π.χ.

private double balance = 0; private Person holder = new Person();

Φυσικά, η αρχικοποίηση είναι προαιρετική και µπορεί να γίνει και αργότερα, όπως δείχνουν οι ακόλουθες εντολές.

private double balance; private Person holder; ... balance = 0; ... holder = new Person();

Καθαρές αξίες ή Αντικείµενα Τα δεδοµένα µιας τάξης όπως είπαµε είναι δυνατόν να είναι κάποιες µεταβλητές των προκαθορισµένων τύπων της Java (π.χ. int, double, long) κ.λ.π., αλλά θα µπορούσε να ήταν και κάποιο άλλο αντικείµενο. Οι ιδιότητες ενός αντικειµένου που ανήκουν σε προκαθορισµένους τύπους της Java λέγονται πολλές φορές καθαρές αξίες (pure values), διότι αυτό που µας ενδιαφέρει είναι η τιµή τους και µόνο. Για παράδειγµα το 3 είναι 3 και δεν είναι µία διεύθυνση µνήµης που περιέχει το 3. Αν ίσχυε αυτό θα µπορούσαµε να καταλήξουµε σε παραλογισµούς του στυλ να ελέγχουµε αν το 3 είναι ίσο µε το 3 (όσο και αν σας φαίνεται παράξενο αυτό συνέβαινε µε κάποιες πρώτες εκδόσεις της Fortran!). Μπορούµε να φανταστούµε µία µεταβλητή που είναι καθαρή αξία σαν ένα κουτί µε το όνοµα που δίνουµε και περιεχόµενα αυτά που έχει κάθε φορά, όπως δείχνει και η ακόλουθη εικόνα.

- 30 -

Page 31: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

Σε αντίθεση µε τις καθαρές αξίες είναι δυνατόν να έχουµε δηλώσεις µεταβλητών που είναι αντικείµενα. Εδώ υπάρχει η έννοια της αναφοράς (reference) στο αντικείµενο. ∆ηλαδή το όνοµα του αντικειµένου δεν αναφέρεται σε κάποια ή κάποιες συγκεκριµένες ιδιότητες του αντικειµένου, αλλά στην διεύθυνση του αντικειµένου στη µνήµη. Έτσι όταν ελέγχουµε για ισότητα δύο αντικειµένων ουσιαστικά ελέγχουµε αν χρησιµοποιούµε δύο ονόµατα για την αναφορά του ιδίου αντικειµένου στη µνήµη. Αυτό είναι σηµασιολογικά σωστό γιατί ένα αντικείµενο είναι µία οντότητα (entity) και εποµένως διακρίνεται από τις άλλες οντότητες ακόµα και αν έχουν ακριβώς τις ίδιες τιµές στις ιδιότητές τους. Για παράδειγµα δύο αυτοκίνητα εξακολουθούν να είναι δύο διαφορετικά αυτοκίνητα ακόµα και αν έχουν όλες τις ιδιότητές τους ίδιες. Μπορείτε να φανταστείτε ένα αντικείµενο σαν ένα κουτί στη µνήµη που περιέχει ένα δείκτη σε µία άλλη περιοχή µνήµης η οποία περιέχει τα δεδοµένα του αντικειµένου, όπως δείχνει και η ακόλουθη εικόνα.

Έτσι ένα αντικείµενο έχει κάποιες ιδιότητες που είναι καθαρές αξίες και κάποιες άλλες που είναι άλλα αντικείµενα. Για παράδειγµα ένα αντικείµενο τύπου Person θα µπορούσε να έχει µια ιδιότητα int age για την ηλικία του, αλλά επίσης θα µπορούσε να έχει και µία ιδιότητα Account a για τον τραπεζικό του λογαριασµό. Μάλιστα αν το άτοµο µπορούσε να έχει περισσότερους λογαριασµούς από έναν, θα µπορούσε να έχει η τάξη Person ένα πίνακα από Accounts. Αντίστοιχα αν θέλαµε να δώσουµε τη δυνατότητα πλοήγησης (navigability) από το Account στο Person, θα έπρεπε να διατηρούµε και ένα πίνακα από Persons στα αντικείµενα της τάξης Account µια και γενικά ένας λογαριασµός µπορεί να ανήκει σε περισσότερα από ένα άτοµα. Αυτού του είδους οι συσχετίσεις (associations) µεταξύ των τάξεων ενός προγράµµατος, µπορούν να παρασταθούν καλύτερα µε µία "γλώσσα" σχεδίασης µοντέλων που ονοµάζεται UML (Unified Modeling Language). Για παράδειγµα η συσχέτιση που περιγράψαµε πιο πάνω σε UML θα ήταν κάπως έτσι:

Το παραπάνω διάγραµµα δείχνει δύο τάξεις. Για κάθε τάξη έχουµε ένα κουτί µε τρία διαµερίσµατα. Στο πάνω διαµέρισµα γράφουµε το όνοµα της τάξης, στο µεσαίο τις ιδιότητές της και στο κάτω µέρος τις µεθόδους της τάξης. Οι ιδιότητες της τάξης που είναι αντικείµενα άλλων τάξεων δεν παριστάνονται µε µεταβλητές στο µεσαίο διαµέρισµα αλλά σαν γραµµές συσχετίσεων µε τις τάξεις των αντικειµένων. Έτσι για παράδειγµα ένα Person έχει µία

- 31 -

Page 32: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

συσχέτιση µε µηδέν ή περισσότερους Accounts µε το όνοµα ρόλου (accounts). Αντίστοιχα ένας Account έχει µία συσχέτιση µε έναν ή περισσότερα Persons τα οποία τα ονοµάζει µε το όνοµα ρόλου holders. Για περισσότερες πληροφορίες για την UML αλλά και για προγράµµατα παραγωγής µοντέλων UML (µε δυνατότητες µάλιστα παραγωγής κώδικα από µοντέλο και αντίστροφα) µπορείτε να επισκεφτείτε την Rational (www.rational.com). ∆εδοµένα τάξης (static data) Ενδέχεται κάποιες φορές να θέλουµε κάποια δεδοµένα τα οποία θα αφορούν όλα τα αντικείµενα µιας τάξης και δεν θα αναπαράγονται για κάθε αντικείµενο. Για παράδειγµα µπορούµε να φανταστούµε µία εφαρµογή όπου η τάξη Person θα χρειαζόταν να παρακολουθήσει τον αριθµό των αντικειµένων που δηµιουργούνται από αυτήν. Ο αριθµός αυτός είναι δεδοµένο της τάξης και όχι των αντικειµένων της τάξης. Για να υλοποιήσουµε τέτοιες µεταβλητές χρησιµοποιούµε τη δήλωση static ως εξής: class Person ... private static noOfPersons=0; ... Ένα παράδειγµα static µεταβλητών και static µεθόδων µπορείτε να βρείτε στο επόµενο µάθηµα. Ένα παράδειγµα Για να αποσαφηνίσουµε καλύτερα τα όσα είπαµε για τα δεδοµένα της τάξης θα δώσουµε ένα απλό παράδειγµα το οποίο ουσιαστικά θα υλοποιεί το UML διάγραµµα που είδαµε σε Java. Το πρόγραµµά µας θα αποτελείται από τρεις τάξεις:

− Την τάξη Person − Την τάξη Account, και − Την τάξη Main µε το κυρίως πρόγραµµα

Η τάξη Person έχει τρεις ιδιότητες: την ηλικία, το όνοµα και ένα πίνακα τραπεζικών λογαριασµών που αντιπροσωπεύουν τους τραπεζικούς λογαριασµούς του Person. Επίσης έχει µεθόδους set και get για το όνοµα και την ηλικία, ενώ έχει δύο µεθόδους setter και δύο µεθόδους getter για την ιδιότητα του λογαριασµού. Η πρώτη µέθοδος setter παίρνει σαν πρώτη παράµετρο ένα δείκτη και ένα αντικείµενο τύπου Account και θέτει το αντίστοιχο στοιχείο του πίνακα accounts στο Account που δίνεται σαν παράµετρο. Η δεύτερη µέθοδος setter µας δίνει τη δυνατότητα να θέσουµε ολόκληρο το πίνακα accounts στον πίνακα που δίνεται σαν παράµετρος. Αντίστοιχα η πρώτη µέθοδος getter παίρνει σαν παράµετρο ένα δείκτη και επιστρέφει το στοιχείο του πίνακα accounts που βρίσκεται στη συγκεκριµένη θέση του πίνακα. Η δεύτερη µέθοδος getter επιστρέφει ολόκληρο το πίνακα accounts. Οι µορφές των µεθόδων getter και setter που δέχονται ένα δείκτη σαν παράµετρο, ενδέχεται να προκαλέσουν την εξαίρεση java.lang.ArrayIndexOutOfBoundsException, αν ο δείκτης που δίνεται σαν παράµετρος δεν είναι µέσα στα όρια του πίνακα accounts. Προς το παρόν δεν θα χειριστούµε αυτή την εξαίρεση η οποία αν προκληθεί θα διακόψει το πρόγραµµα. Σε επόµενα µαθήµατα θα µάθουµε πως να αντιδρούµε µε κάπως ποιο κοµψό τρόπο στην πρόκληση εξαιρέσεων. Ο κώδικας της τάξης Person έχει ως εξής:

- 32 -

Page 33: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

public class Person //Ena atomo theoroyme oti borei na exei to poli 10 //logariasmoys. //Gia megaliteri evelixia tha boroysame na xrisimopoiisoyme //ena java.util.Vector anti pinaka. private final int MAX_ACCOUNTS=10; private int age; private String name; private Account accounts[] = new Account[MAX_ACCOUNTS]; public Person() age = 0; name = ""; //set kai get methods gia to age public void setAge(int a) age = a; public int getAge() return age; //set kai get methods gia to name public void setName(String s) name = s; public String getName() return name; //indexed setter kai getter methods gia ton pinaka accounts public void setter(int index, Account a) throws ArrayIndexOutOfBoundsException //an o index einai metaxy 0 kai MAX_ACCOUNTS //vale sti thesi index toy accounts ton a if (index>=0 && index <MAX_ACCOUNTS) accounts[index] = a; //diaforetika prokalese mia exairesi deikti ektos oriwn else throw new ArrayIndexOutOfBoundsException(); public Account getter(int index) throws ArrayIndexOutOfBoundsException //an o index einai metaxy 0 kai MAX_ACCOUNTS //epestrepse ton account pou vrisketai sti thesi index

- 33 -

Page 34: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

if (index>=0 && index <MAX_ACCOUNTS) return accounts[index]; //diaforetika prokalese mia exairesi deikti ektos oriwn else throw new ArrayIndexOutOfBoundsException(); //oxi indexed setter kai getter methods gia ton pinaka accounts public void setter(Account[] a) accounts = a; public Account[] getter() return accounts; Η τάξη Account είναι ακριβώς όπως την είδαµε στο µάθηµα "Η δήλωση της τάξης" µε την προσθήκη όµως ενός πίνακα από Persons (του πίνακα holders) που είναι οι συνδικαιούχοι ενός λογαριασµού. Αυτοί στη υλοποίησή µας µπορεί να είναι µέχρι και 5 (η σταθερά MAX_PERSONS). Όπως και στην τάξη Person έχουµε indexed και όχι indexed setter και getter µεθόδους για τον πίνακα holders. Η σηµασία αυτών των µεθόδων είναι ανάλογη µε αυτή που δόθηκε για τις αντίστοιχες µεθόδους της τάξης Person. Ακολουθεί ο κώδικας της τάξης Account: public class Account //To poli 5 atoma boroyn na exoyn mazi ena logariasmo private final int MAX_PERSONS=5; private double balance; Person holders[] = new Person[MAX_PERSONS]; //constructor public Account() balance = 0.0; //methodos getBalance public double getBalance() return balance; //methodos withdraw //An i analipsi ginei epistrefei true alliws false public boolean withdraw(double money) if (money <= balance ) balance -= money; return true;

- 34 -

Page 35: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

return false; //An to poso einai thetiko ginetai i katathesi kai //epistrefetai true, diaforetika epistrefetai false public boolean deposit(double money) if (money >= 0) balance += money; return true; return false; //indexed setter kai getter methods gia ton pinaka holders public void setter(int index, Person p) throws ArrayIndexOutOfBoundsException //an o index einai metaxy 0 kai MAX_PERSONS //vale sti thesi index toy holders ton p if (index>=0 && index <MAX_PERSONS) holders[index] = p; //diaforetika prokalese mia exairesi deikti ektos oriwn else throw new ArrayIndexOutOfBoundsException(); public Person getter(int index) throws ArrayIndexOutOfBoundsException //an o index einai metaxy 0 kai MAX_PERSONS //epestrepse to person pou vrisketai sti thesi index if (index>=0 && index <MAX_PERSONS) return holders[index]; //diaforetika prokalese mia exairesi deikti ektos oriwn else throw new ArrayIndexOutOfBoundsException(); //setter kai getter methods gia ton pinaka holders xwris deikti public void setter(Person[] p) holders = p; public Person[] getter() return holders; Για να δοκιµάσουµε τις δύο αυτές τάξεις θα τρέξουµε ένα κυρίως πρόγραµµα το οποίο βρίσκεται στη τάξη Main. Η τάξη Main δηµιουργεί δύο άτοµα τον George και την Natalia και δύο τραπεζικούς λογαριασµούς ένα της CityBank και ένα της Εµπορικής. Στον λογαριασµό της CityBank είναι συνδικαιούχοι ο George και η Natalia ενώ στο λογαριασµό της Εµπορικής

- 35 -

Page 36: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

δικαιούχος είναι ο George. Οι συνδέσεις µεταξύ αυτών των αντικειµένων δηµιουργούνται µε τη χρήση των µεθόδων setter των δυο τάξεων. Επίσης για να έχουµε και κάποια χρήµατα κάνουµε και κάποιες καταθέσεις στους λογαριασµούς. Τέλος µε τη χρήση των µεθόδων getter των δύο τάξεων βρίσκουµε το σύνολο των χρηµάτων που έχει o George σε καταθέσεις στους λογαριασµούς του και εµφανίζουµε το σύνολο των δικαιούχων στον λογαριασµό της CityBank. Ο κώδικας της Main έχει ως εξής: public class Main public static void main (String[] args) Person george = new Person(); george.setName("George"); george.setAge(33); Person natalia = new Person(); natalia.setName("Natalia"); natalia.setAge(31); Account cityBank = new Account(); Account emporiki = new Account(); //Sto logariasmo tis city bank o George kai h //Natalia einai syndikaiouxoi. //Ton logariasmo tis emporikis ton exei monos toy o George //Oi akolouthes kliseis dhmiourgoun aytes tis syndeseis george.setter(0, cityBank); george.setter(1, emporiki); cityBank.setter(0, george); emporiki.setter(0, george); natalia.setter(0, cityBank); cityBank.setter(1, natalia); cityBank.deposit(5000); emporiki.deposit(250000); //To array a tha exei toys logariasmous toy George Account[] a = george.getter(); System.out.println("Total on all bank accounts for George:"); int total=0; for (int i=0; i<a.length; i++) if (a[i] != null) total += a[i].getBalance(); System.out.println(total); //To array p tha exei oloys toys dikaiouxous sto //logariasmo tis cityBank Person p[] = cityBank.getter(); System.out.println("All holders of City Bank account"); for (int i=0; i<p.length; i++)

- 36 -

Page 37: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

if (p[i] != null) System.out.println(p[i].getName());

Οι µέθοδοι της τάξης Σ' αυτό το µάθηµα θα εξετάσουµε αναλυτικά τις µεθόδους µιας τάξης. Θα εξετάσουµε τα δικαιώµατα πρόσβασης σε µία µέθοδο, την τιµή επιστροφής από µία µέθοδο και θα µιλήσουµε για τη λίστα παραµέτρων της µεθόδου και για το πως µπορούµε να ξεπεράσουµε το εµπόδιο του περάσµατος των παραµέτρων µε τιµή (pass by value). Θα µιλήσουµε ακόµα για τις τοπικές µεταβλητές της µεθόδου. Τέλος, θα µιλήσουµε για static µεθόδους και θα εξετάσουµε ένα παράδειγµα της χρήσης τους. Ορισµός µεθόδου Η µέθοδος µιας τάξης έχει στο ελάχιστο την ακόλουθη µορφή: πρόσβαση τύπος_µεθόδου όνοµα_µεθόδου(λίστα_παραµέτρων) σώµα_µεθόδου για παράδειγµα η ακόλουθη µέθοδος getAge είναι µία δηµόσια µέθοδος που επιστρέφει την ηλικία κάποιου αντικειµένου της τάξης Person. Αυτές οι µέθοδοι συνήθως αναφέρονται ως getters και µαζί µε τις µεθόδους setters, επιστρέφουν και θέτουν αντίστοιχα τις τιµές στις ιδιωτικές µεταβλητές µιας τάξης. Αυτό ενισχύει την απόκρυψη µια και ο χρήστης αγνοεί την υλοποίηση. Για παράδειγµα η ηλικία θα µπορούσε να είναι απευθείας ένα δεδοµένο-µέλος της τάξης ή θα µπορούσε να υπολογίζεται από την διαφορά της τρέχουσας ηµεροµηνίας από την ηµεροµηνία γέννησης. Αυτός που κάνει τη κλήση δεν γνωρίζει τι ακριβώς συµβαίνει: public int getAge() return age; Προσδιοριστής πρόσβασης µεθόδου Ο προσδιοριστής πρόσβασης µπροστά από το όνοµα µιας µεθόδου µπορεί να είναι ένας από τους ακόλουθους: public: H µέθοδος µπορεί να κληθεί από οποιαδήποτε άλλη τάξη. protected: H µέθοδος µπορεί να κληθεί από άλλες µεθόδους της τάξης της και των υποτάξεων της τάξης της, αλλά και από οποιαδήποτε άλλη τάξη στο ίδιο πακέτο. private: Η µέθοδος µπορεί να κληθεί µόνο µέσα από την τάξη στην οποία ορίζεται Αν αφεθεί απροσδιόριστος ο προσδιοριστής πρόσβασης τότε εννοείται ότι έχουν δικαίωµα κλήσης της µεθόδου όλες οι µέθοδοι των τάξεων του ιδίου πακέτου (package). Σηµειώστε ότι αν µία υποτάξη δεν ανήκει στο ίδιο πακέτο µε την υπερτάξη της τότε δεν µπορεί να αναφερθεί στα protected µέλη ή να καλέσει τις protected µεθόδους των αντικειµένων της υπερτάξης της. ∆ηλαδή, για παράδειγµα, αν µία µέθοδος της υποτάξης δέχεται σαν παράµετρο µια µεταβλητή τύπου της υπερτάξης της τότε δεν µπορεί να καλέσει σ' αυτή τη παράµετρο την protected µέθοδο, παρόλο που µπορεί να την καλέσει για τον εαυτό της (και σε αντικείµενα του εαυτού της) αφού την κληρονοµεί και έχει το δικαίωµα να την καλέσει στον εαυτό της µια και είναι protected.

- 37 -

Page 38: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

Τιµή επιστροφής από µέθοδο Η µέθοδος µιας τάξης επιστρέφει µία τιµή ο τύπος της οποίας είναι ένας από τους προκαθορισµένους τύπους της Java (int, double κλπ), µία τάξη, ή και ένας πίνακας. Ακόµα ο τύπος επιστροφής µιας µεθόδου µπορεί να είναι void που σηµαίνει ότι η µέθοδος δεν επιστρέφει καµία τιµή. Μερικά παραδείγµατα είναι τα ακόλουθα: //Επιστρέφει int public int getAge() //∆εν επιστρέφει τίποτα public void setName(String s) //Επιστρέφει ένα αντικείµενο της τάξης Account (ή και των υποτάξεων της Account) public Account getter(int index) //Επιστρέφει ένα πίνακα αντικειµένων της τάξης Account //(ή και των υποτάξεων της Account) public Account[] getter() Μερικά παραδείγµατα κλήσης των πιο πάνω µεθόδων είναι τα ακόλουθα: //καλεί την getAge και βάζει την τιµή επιστροφής στην ακέραια µεταβλητή a int a = getAge(); //καλεί την setName και θέτει το όνοµα του αντικειµένου george σε George //επειδή η µέθοδος είναι void δεν επιστρέφει καµία τιµή george.setName("George"); //καλεί την µέθοδο getter µε τον ακέραιο index 0 και βάζει την τιµή που επιστρέφεται //στο αντικείµενο a της τάξης Account Account a=george.getter(0); //καλεί τη µέθοδο getter και βάζει τον πίνακα των αντικειµένων της τάξης Account που //επιστρέφεται στον πίνακα a Account[] a = george.getter(); Η λίστα παραµέτρων της µεθόδου Η µέθοδος µίας τάξης για να επιτελέσει τη λειτουργία της είναι πολλές φορές απαραίτητο να λάβει κάποιες πληροφορίες από την µέθοδο που την καλεί. Αυτές οι πληροφορίες περνιούνται στην µέθοδο µε τη λίστα των παραµέτρων της µεθόδου. Η λίστα των παραµέτρων της µεθόδου έχει την ακόλουθη µορφή: λίστα παραµέτρων := τύπος όνοµα_παραµέτρου [,τύπος όνοµα_παραµέτρου ...] Για παράδειγµα η µέθοδος setter της τάξης Person που είδαµε στο προηγούµενο µάθηµα και επαναλαµβάνουµε εδώ έχει δύο παραµέτρους. Η πρώτη είναι η παράµετρος index που δείχνει στην µέθοδο setter σε ποια θέση του πίνακα accounts θα πρέπει να τοποθετηθεί ο Account aπου είναι η δεύτερη παράµετρος στη µέθοδο. public void setter(int index, Account a) throws ArrayIndexOutOfBoundsException //an o index einai metaxy 0 kai MAX_ACCOUNTS //vale sti thesi index toy accounts ton a if (index>=0 && index <MAX_ACCOUNTS)

- 38 -

Page 39: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

accounts[index] = a; //diaforetika prokalese mia exairesi deikti ektos oriwn else throw new ArrayIndexOutOfBoundsException(); Στο πιο πάνω παράδειγµα η µέθοδος setter δεν χρειάζεται να επιστρέψει καµία τιµή στη µέθοδο που έκανε την κλήση (αν ο δείκτης που δόθηκε είναι άκυρος, απλά προκαλεί την εξαίρεση ArrayIndexOutOfBoundsException - για εξαιρέσεις θα µιλήσουµε στο επόµενο µάθηµα). Γι' αυτό και ο τύπος της µεθόδου setter είναι void, που σηµαίνει ότι η µέθοδος δεν επιστρέφει καµία συγκεκριµένη τιµή. Αν µία µέθοδος πρέπει να επιστρέψει µία τιµή µπορεί να χρησιµοποιήσει τον τύπο_επιστροφής. Τι γίνεται όµως αν µία µέθοδος θέλει να επιστρέψει δύο ή και περισσότερες τιµές, στην µέθοδο που έκανε την κλήση; Όλες οι παράµετροι στη Java περνιούνται µε τιµή (pass-by-value). Αυτό σηµαίνει ότι κάθε µεταβλητή που δίνεται στη θέση µιας παραµέτρου στη µέθοδο που κάνει την κλήση, αντιγράφεται σε µία νέα προσωρινή µεταβλητή που θα χρησιµοποιηθεί ως παράµετρος για την κλήση. Αυτό αρχικά φαίνεται πολύ περιοριστικό διότι η παράµετρος αφού δεν είναι η ίδια µεταβλητή µε αυτή που περνιέται από την µέθοδο που έκανε την κλήση, η µεταβλητή στη µέθοδο που έκανε τη κλήση δεν µπορεί να αλλάξει τιµή σαν αποτέλεσµα της κλήσης της µεθόδου. Πως λοιπόν είναι δυνατόν για µία µέθοδο να επιστρέψει παραπάνω από µία τιµές στη µέθοδο που έκανε την κλήση; Η απάντηση είναι µε τη χρήση αντικειµένων. Και τα αντικείµενα όπως και οι καθαρές αξίες περνιούνται µε τιµή. Εποµένως και για τα αντικείµενα δηµιουργείται µία προσωρινή µεταβλητή-αντίγραφο του αντικειµένου. Αλλά επειδή, όπως εξηγήσαµε στο προηγούµενο µάθηµα, το αντικείµενο ουσιαστικά είναι µία αναφορά στη θέση µνήµης που περιέχεται το αντικείµενο, η αντιγραφή του αντικειµένου έχει σαν αποτέλεσµα τη δηµιουργία µιας παραµέτρου που περιέχει µία διαφορετική αναφορά στο ίδιο όµως αντικείµενο. Εποµένως ότι αλλαγές γίνουν στα δεδοµένα του αντικειµένου από την κληθείσα µέθοδο, θα ισχύουν και για το αντικείµενο που περάστηκε ως παράµετρος στην καλούσα µέθοδο, αφού πρόκειται για το ίδιο αντικείµενο. Ένα απλό παράδειγµα είναι το ακόλουθο: Έστω η τάξη Point (αν και όπως έχουµε πει τα δεδοµένα µιας τάξης δεν πρέπει να είναι δηµόσια, γι' αυτό το απλό παράδειγµα θα αγνοήσουµε αυτή την αρχή):

public class Point public int x; public int y; public Point() x=0; y=0;

Στη συνέχεια θα κάνουµε µία τάξη Program η οποία θα έχει και τη συνάρτηση main και η οποία βρίσκεται στο αρχείο Program.java. Η συνάρτηση main δηµιουργεί ένα νέο αντικείµενο Point το p. Στη συνέχεια εµφανίζει τις τιµές των x και y πριν καλέσει την µέθοδο add5 και αυτές είναι φυσικά 0 και 0, αντίστοιχα. Στη συνέχεια καλεί την µέθοδο add5 περνώντας σαν παράµετρο το αντικείµενο p. Η µέθοδος add5 προσθέτει στην παράµετρο (pParam) 5 pixels και στο x και στο y. Σαν απόδειξη ότι πράγµατι οι τιµές των x και y άλλαξαν στο Point p στο σώµα της main, εµφανίζουµε ξανά τις τιµές των x και y, µετά την κλήση οι οποίες τώρα είναι 5 και 5, αντίστοιχα.

- 39 -

Page 40: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

Η τάξη Program έχει ως εξής: public class Program

public static void main (String[] args)

Point p = new Point(); System.out.println("Times twn x kai y prin tin klish"); System.out.println("\t"+p.x); System.out.println("\t"+p.y); add5(p); System.out.println("Times twn x kai y meta tin klish"); System.out.println("\t"+p.x); System.out.println("\t"+p.y);

public static void add5(Point pParam)

pParam.x += 5; pParam.y += 5;

Τοπικές µεταβλητές µιας µεθόδου Μία µέθοδος µπορεί να δηλώσει µέσα στο σώµα της, όσες µεταβλητές χρειάζεται για να επιτελέσει τη λειτουργία της. Αυτές οι µεταβλητές είναι τοπικές µεταβλητές της µεθόδου και δεν είναι ορατές έξω από την µέθοδο, η εµβέλειά τους δηλαδή είναι το σώµα της µεθόδου. Η διάρκεια ζωής των τοπικών µεταβλητών είναι ο χρόνος εκτέλεσης της µεθόδου στην οποία δηλώνονται. Αν δηλαδή, η µέθοδος δηµιουργήσει κάποιες τοπικές µεταβλητές που είναι αντικείµενα κάποιας τάξης αυτά θα είναι υποψήφια για συλλογή από τον συλλέκτη σκουπιδιών µε το πέρας εκτέλεσης της µεθόδου. public void someMethod() //Η a και η s είναι τοπικές µεταβλητές της someMethod int a=0; String s=new String(); ... Static µέθοδοι Όπως είδαµε υπάρχουν περιπτώσεις στις οποίες θα ήταν χρήσιµο να έχουµε κάποια δεδοµένα τα οποία ανήκουν στη τάξη και όχι στα αντικείµενα της τάξης. Αυτές τις µεταβλητές τις δηλώνουµε ως static. Αντίστοιχα είναι επιθυµητό κάποιες φορές, οι µέθοδοι να µπορούν να κληθούν απλά χρησιµοποιώντας το όνοµα της τάξης. Έτσι αποφεύγουµε τη δηµιουργία ενός αντικειµένου απλά και µόνο για να καλέσουµε τη µέθοδο. Αυτό βεβαίως έχει νόηµα µόνο όταν η µέθοδος δεν χρησιµοποιεί δεδοµένα του αντικειµένου. Έτσι λοιπόν µία µέθοδος που δηλώνεται ως static µπορεί να αναφερθεί µόνο σε δεδοµένα τα οποία έχουν δηλωθεί ως static. Έτσι µπορούµε να δηλώσουµε µια µέθοδο ως static: static τύπος_µεθόδου όνοµα_µεθόδου(λίστα παραµέτρων) σώµα µεθόδου

- 40 -

Page 41: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

Σ' αυτή τη περίπτωση µπορούµε να καλέσουµε τη µέθοδο ως εξής: όνοµα_τάξης.όνοµα_static_µεθόδου(λίστα παραµέτρων); Επισηµαίνεται ότι στη Java µπορούµε να καλέσουµε µια static µέθοδο χρησιµοποιώντας ακόµα και ένα αντικείµενο της τάξης, αν έχουµε κάποιο. ∆ηλαδή, το παρακάτω δεν είναι λάθος. όνοµα_αντικειµένου.όνοµα_static_µεθόδου(λίστα παραµέτρων); Στη συνέχεια θα δώσουµε ένα παράδειγµα µια τάξης Person στην οποία θα χρησιµοποιήσουµε µία static ιδιωτική µεταβλητή, την noOfPersons, για να κρατήσουµε το πλήθος των αντικειµένων τύπου Person που δηµιουργήθηκαν από την εφαρµογή. Για να το καταφέρουµε αυτό κάθε φορά που δηµιουργείτε ένα αντικείµενο στον constructor του θα αυξάνουµε την static µεταβλητή κατά 1. Η µέθοδος howMany που είναι static θα εµφανίζει την τιµή της noOfPersons. Η howMany µπορεί να αναφερθεί στην µεταβλητή noOfPersons διότι είναι και αυτή static. Η τάξη Person έχει ως εξής: public class Person private static int noOfPersons = 0; public Person() noOfPersons++; public static void howMany() System.out.println("Αριθµός Persons που δηµιουργήθηκαν µέχρι τώρα: "+noOfPersons); Ένα παράδειγµα χρήσης της τάξης Person βρίσκεται στο αρχείο Main.java το οποίο, ως συνήθως, έχει την συνάρτηση main. Η συνάρτηση main του παραδείγµατος δηµιουργεί δύο αντικείµενα τύπου Person και στη συνέχεια καλεί στην τάξη, µε την εντολή Person.howMany(), την µέθοδο howMany η οποία και εµφανίζει τον αριθµό των ατόµων που είναι η τιµή της µεταβλητής noOfPersons. Αρχικά θα πρέπει να εµφανιστεί 1 και στη συνέχεια 2, αφού πριν τη πρώτη κλήση της howMany έχει δηµιουργηθεί ένα µόνο άτοµο, ενώ πριν τη δεύτερη κλήση έχουµε δηµιουργήσει και το δεύτερο. public class Main public static void main (String[] args) Person p1 = new Person(); Person.howMany(); //Prepei na emfanisei 1 Person p2 = new Person(); Person.howMany(); //Prepei na emfanisei 2

Εξαιρέσεις (Exceptions) Οι εξαιρέσεις είναι ένας µηχανισµός χειρισµού των λαθών. Σ' αυτό το µάθηµα θα µάθουµε τι είναι οι εξαιρέσεις, ποιες είναι οι σχετικές εντολές της Java και πως µπορούµε να

- 41 -

Page 42: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

δηµιουργούµε τις δικές µας εξαιρέσεις για τις δικές µας τάξεις, πέρα από τις εξαιρέσεις που παρέχει η Java. Τι είναι οι εξαιρέσεις Όταν µία µέθοδος κληθεί, ενδέχεται να βρεθεί αντιµέτωπη µε κάποιες συνθήκες που δεν µπορεί να αντιµετωπίσει µε κάποιο λογικό τρόπο. Αυτό µπορεί να συµβεί είτε γιατί οι παράµετροι που περάστηκαν στη µέθοδο δεν ήταν οι αναµενόµενες, είτε γιατί προέκυψε κάποιο έκτακτο περιστατικό κατά τη διάρκεια της λειτουργίας της. Σ' αυτές τις περιπτώσεις επειδή η µέθοδος δεν δύναται να ολοκληρώσει τη λειτουργία της µπορεί να προκαλέσει µία εξαίρεση, πετώντας ουσιαστικά το "γάντι" στην µέθοδο που την κάλεσε. Η µέθοδος που έκανε τη κλήση µε τη σειρά της µπορεί να χειριστεί την εξαίρεση, αν γνωρίζει το πως, ή αν δεν µπορεί κι αυτή να χειριστεί την εξαίρεση µε κάποιο λογικό τρόπο, τότε µπορεί να την "πετάξει" πιο πάνω στη στοίβα των κλήσεων. Αν τελικά µια εξαίρεση φτάσει στο κατώτερο επίπεδο των κλήσεων και δεν την έχει χειριστεί κανείς, θα προκαλέσει την διακοπή του προγράµµατος. Οι εξαιρέσεις στη Java είναι αντικείµενα τα οποία ανήκουν στην ακόλουθη ιεραρχία των τάξεων (του πακέτου java.lang):

Όπως φαίνεται και από την ιεραρχία των τάξεων αυτών, τα αντικείµενα τύπου Exception κληρονοµούν από την τάξη Throwable την ικανότητα να "πεταχτούν" από την εντολή throw. Όλες οι εξαιρέσεις στη Java θα πρέπει να είναι αντικείµενα υποτάξεων της τάξης Exception. Άρα και οι δικές µας εξαιρέσεις θα πρέπει να επεκτείνουν την τάξη Excpetion ή κάποια από τις υποτάξεις της. Στην ιεραρχία φαίνονται ακόµα κάποιες άλλες τάξεις:

− Η τάξη Error επεκτείνει κι αυτή την τάξη Throwable. Συνήθως αυτά τα λάθη προκαλούνται από την εικονική µηχανή της Java, και είναι κάποια "βαριά" λάθη µε τα οποία ένα συνηθισµένο πρόγραµµα δεν έχει νόηµα να ασχοληθεί. Ένα παράδειγµα είναι τα αντικείµενα της τάξης NoClassDefFoundError που επεκτείνει την τάξη LinkageError που µε τη σειρά της επεκτείνει την τάξη Error και που προκαλείται όταν µία µέθοδος κάλεσε µία άλλη µέθοδος σε ένα αντικείµενο, ή δοκίµασε να δηµιουργήσει ένα νέο αντικείµενο µε την new, η τάξη του οποίου (το αρχείο µε επέκταση .class) δεν µπορεί να βρεθεί για να φορτωθεί. Φυσικά η τάξη αυτή υπήρχε

- 42 -

Page 43: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

όταν µεταφράστηκε το .java αρχείο της τάξης που έκανε την κλήση αλλά δεν µπορεί να βρεθεί πια.

− Η τάξη RuntimeException από την άλλη µεριά είναι υποτάξη της τάξης Exception. Οι εξαιρέσεις που είναι αντικείµενα υποτάξεων της τάξης αυτής, δεν είναι υποχρεωτικό να πεταχτούν από µία µέθοδο, ούτε είναι υποχρεωτικό η µέθοδος να τις χειριστεί. Αυτές οι εξαιρέσεις είναι εξαιρέσεις που είναι αποτέλεσµα της κανονικής λειτουργίας της Εικονικής Μηχανής Java και θεωρήθηκε από τους σχεδιαστές της γλώσσας ότι θα ήταν υπερβολικά επιβαρυντικό να απαιτήσουν τον χειρισµό ή την δήλωση αυτών των εξαιρέσεων. Ένα παράδειγµα υποτάξης της τάξης RuntimeException είναι η εξαίρεση ArrayIndexOutOfBoundsException η οποία προκαλείται όταν µία µέθοδος αναφερθεί σε µια θέση ενός πίνακα που δεν υπάρχει. Θα θυµάστε ίσως ότι στο µάθηµα "Τα δεδοµένα µιας τάξης" είδαµε την µέθοδο setter της τάξης Person:

public void setter(int index, Account a) throws ArrayIndexOutOfBoundsException ...

την οποία η συνάρτηση main χρησιµοποιήσε χωρίς να δώσει καµία σηµασία στο γεγονός ότι µπορεί να προκαλέσει την εξαίρεση ArrayIndexOutOfBoundsException. Αυτό δεν θα µπορούσε να το κάνει η main αν η ArrayIndexOutOfBoundsException ήταν υποτάξη της τάξης Exception αντί της τάξης RuntimeException. Τότε η main θα έπρεπε να χειριστεί την εξαίρεση.

Πρόκληση εξαιρέσεων - Η εντολή throw Μία µέθοδος µπορεί να προκαλέσει µία νέα εξαίρεση, χρησιµοποιώντας την εντολή throw. Η εντολή αυτή έχει την ακόλουθη σύνταξη:

throw αντικείµενο_εξαίρεσης; Επειδή η throw πρέπει να "πετάξει" ένα αντικείµενο εξαίρεσης είναι πολύ συνηθισµένο στα προγράµµατα να τη συναντάµε µε την ακόλουθη µορφή:

throw new κατασκευαστής_τάξης_εξαίρεσης(); όπως στην ακόλουθη εντολή:

throw new ArrayIndexOutOfBoundsException();

Όταν µία µέθοδος έχει στο σώµα της µία εντολή throw και η τάξη της εξαίρεσης που προκαλείται από αυτή δεν είναι υποτάξη της τάξης RuntimeException, θα πρέπει να δηλώσει ότι µπορεί να προκαλέσει αυτή την εξαίρεση. ∆ηλαδή, για παράδειγµα στη τάξη Person που είδαµε στο µάθηµα "Τα δεδοµένα µιας τάξης" είχε τον ακόλουθο κώδικα: public void setter(int index, Account a) throws ArrayIndexOutOfBoundsException //an o index einai metaxy 0 kai MAX_ACCOUNTS //vale sti thesi index toy accounts ton a if (index>=0 && index <MAX_ACCOUNTS) accounts[index] = a; //diaforetika prokalese mia exairesi deikti ektos oriwn else throw new ArrayIndexOutOfBoundsException(); Σ' αυτή τη περίπτωση έχουµε την εντολή throw να δηµιουργεί (προκαλεί) µία εξαίρεση ArrayIndexOutOfBoundsException. Αυτή είναι µία RuntimeException και εποµένως δεν

- 43 -

Page 44: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

υπάρχει από τη πλευρά της setter η υποχρέωση να δηλώσει ότι την προκαλεί. Παρόλα αυτά η setter την δηλώνει µε την φράση throws ArrayIndexOutOfBoundsException µετά τη λίστα των παραµέτρων της. Έτσι αυτός που καλεί τη µέθοδο setter γνωρίζει ότι µπορεί να προκληθεί αυτή η εξαίρεση και µπορεί να λάβει τα µέτρα του, να χειριστεί δηλαδή την εξαίρεση σε περίπτωση που αυτή προκληθεί. Επισηµαίνουµε και πάλι ότι η δήλωση µε τη φράση throws δεν θα ήταν προαιρετική στη περίπτωση που η εξαίρεση δεν ήταν υποτάξη της RuntimeException, αλλά υποχρεωτική. Χειρισµός των εξαιρέσεων: Η οµάδα εντολών try - catch Όπως είδαµε αν µια εξαίρεση προκληθεί σε µία µέθοδο, τότε αυτή θα παραδοθεί από το σύστηµα εκτέλεσης (Runtime System) στη µέθοδο που έκανε τη κλήση. Αυτή η µέθοδος θα πρέπει να χειριστεί την εξαίρεση ή να δηλώσει µε τη σειρά της ότι την προκαλεί µε τη χρήση της φράσης throws στη δήλωση της. Εξαίρεση σ' αυτό το κανόνα αποτελούν µόνο οι εξαιρέσεις που είναι υποτάξεις της τάξης RuntimeException, τις οποίες οι µέθοδοι µπορούν να αγνοήσουν (αλλά όχι µε ασφάλεια!). Οι εξαιρέσεις που δεν είναι υποτάξεις της τάξης RuntimeException επειδή υποχρεωτικά θα πρέπει να τις χειριστούν οι µέθοδοι που θα γίνουν αποδέκτες τους ονοµάζονται ελεγχόµενες εξαιρέσεις (checked exceptions). Μία µέθοδος που καλεί µία άλλη µέθοδος η οποία προκαλεί µία ελεγχόµενη εξαίρεση, οφείλει να χειριστεί αυτή την εξαίρεση. Υπάρχουν δύο τρόποι χειρισµού:

1. Η δήλωση throws µετά τη λίστα παραµέτρων της µεθόδου-αποδέκτης. Αυτό σηµαίνει ότι η εξαίρεση θα παραδοθεί ποιο πάνω στη στοίβα των κλήσεων, στη µέθοδο που κάλεσε τη µέθοδο αποδέκτης. Αυτή µε τη σειρά της ή θα πιάσει (catch) την εξαίρεση ή θα δηλώσει ότι την προκαλεί πάλι µε τη φράση throws. Αν η εξαίρεση δεν "πιαστεί" ούτε από την τελευταία µέθοδο στη στοίβα των κλήσεων, το πρόγραµµα θα διακοπεί.

2. Η οµάδα εντολών try-catch: Η κλήση της µεθόδου που µπορεί να προκαλέσει την εξαίρεση περικλείεται σε ένα block try το οποίο ακολουθείται από ένα block catch ως εξής:

try κλήση_µεθόδου //και πιθανώς και άλλες εντολές catch (τύπος_εξαίρεσης όνοµα_αντικειµένου_εξαίρεσης) εντολές χειρισµού της εξαίρεσης

∆ηµιουργία των δικών µας τάξεων εξαιρέσεων Πέρα από τις εξαιρέσεις που παρέχει η Java µπορούµε να δηλώσουµε τις δικές µας τάξεις εξαιρέσεων. Ουσιαστικά η πιο σηµαντική απόφαση που πρέπει να πάρουµε είναι από το ποια τάξη εξαίρεσης θα κληρονοµήσει η τάξη µας. Θα είναι υποτάξη της τάξης RuntimeException ή της τάξης Exception; Αν ισχύει το πρώτο τότε η εξαίρεση που δηµιουργούµε δεν θα είναι ελεγχόµενη ενώ αν ισχύει το δεύτερο τότε θα είναι.. Επειδή έχουµε ήδη δει ένα παράδειγµα µη ελεγχόµενης εξαίρεσης (της εξαίρεσης ArrayIndexOutOfBoundsException στο µάθηµα "Τα δεδοµένα µιας τάξης"), ας δούµε ένα παράδειγµα ελεγχόµενης εξαίρεσης. Στη τάξη Account που είχαµε δηλώσει στο ίδιο µάθηµα, έχουµε τις µεθόδους withdraw και deposit. Οι δύο αυτές µέθοδοι ήταν boolean και επέστρεφαν false αν δεν µπορούσαν να κάνουν την κατάθεση ή την ανάληψη λόγω του ότι το ποσό δεν ήταν κατάλληλο (δεν καλύπτονταν από το υπόλοιπο στη περίπτωση της withdraw ή ήταν αρνητικό στην περίπτωση της deposit). Σύµφωνα µε τη φιλοσοφία της Java µια ποιο κατάλληλη τεχνική θα ήταν να δηµιουργήσουµε µία τάξη εξαίρεσης που επεκτείνει την Exception. Βεβαίως στο πακέτο java.lang υπάρχει ήδη η µη ελεγχόµενη εξαίρεση

- 44 -

Page 45: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

IllegalArgumentException που είναι υποτάξη της RuntimeException και που θα µπορούσαµε να χρησιµοποιήσουµε και θα ήταν κατάλληλη για δύο λόγους:

1. Το πολύ κατάλληλο όνοµά της για την περίπτωση (µια και η παράµετρος money δεν έχει την κατάλληλη τιµή)

2. Το γεγονός ότι η µέθοδος που έκανε τη κλήση στη µέθοδό µας θα έπρεπε να έχει ελέγξει την παράµετρο και εποµένως δεν είναι δικό µας το λάθος για να έχουµε την υποχρέωση να "σώσουµε" την κατάσταση, υποχρεώνοντάς τη "απρόσεκτη" µέθοδο να χειριστεί την εξαίρεση.

Παρόλα αυτά θα δηµιουργήσουµε (χάριν παραδείγµατος) µία δική µας τάξη εξαίρεσης, την InvalidMoneyException που επεκτείνει την τάξη Exception. Ο κώδικας της τάξης αυτής, που βρίσκεται στο αρχείο InvalidMoneyException.java, είναι ο ακόλουθος:

public class InvalidMoneyException extends Exception public InvalidMoneyException() super(); public InvalidMoneyException(String m) super(m);

Για τους σκοπούς µας εδώ, θα χρησιµοποιήσουµε µια απλή τάξη Account χωρίς τον πίνακα των δικαιούχων του λογαριασµού, που βρίσκεται στο αρχείο Account.java, και ο κώδικάς της δίνεται στη συνέχεια: public class Account private double balance; //constructor public Account() balance = 0.0; //methodos getBalance public double getBalance() return balance; //methodos withdraw //An i analipsi den borei na ginei petaei tin exairesi InvalidMoneyException public void withdraw(double money) throws InvalidMoneyException if (money <= balance ) balance -= money;

- 45 -

Page 46: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

return; throw new InvalidMoneyException(); //methodos deposit //An i katathesi den borei na ginei petaei tin exairesi InvalidMoneyException public void deposit(double money) throws InvalidMoneyException if (money >= 0) balance += money; return; throw new InvalidMoneyException(); Παρατηρείστε ότι οι µέθοδοι deposit και withdraw πετάνε την εξαίρεση InvalidMoneyException και υποχρεωτικά θα πρέπει να δηλώσουν ότι την "πετάνε" µε τη φράση throws µετά τη λίστα παραµέτρων. Αν δεν τη δήλωναν ο Java compiler (javac) δεν θα µετέφραζε την τάξη σε bytecodes. Στη συνέχεια δίνουµε µία τάξη Main που βρίσκεται στο αρχείο Main.javaκαι η οποία περιέχει την main που θα πρέπει να χειριστεί την εξαίρεση InvalidMoneyException µια και καλεί την deposit. Το κάνει αυτό µε ένα block try-catch που πιάνει την εξαίρεση που θα προκληθεί (µια και προσπαθούµε να καταθέσουµε αρνητικό ποσό) public class Main public static void main(String[] args) Account a = new Account(); try a.deposit(-1000); catch (InvalidMoneyException e) e.printStackTrace(); Όπως παρατηρείτε το πρόγραµµα θα χειριστεί την εξαίρεση, καλώντας τη µέθοδο printStackTrace που θα εµφανίσει το όνοµα της εξαίρεσης και τη στοίβα των κλήσεων που οδήγησε σ' αυτήν. ∆ιευκρινίζεται ότι η µέθοδος printStackTrace κληρονοµείται από την τάξη Throwable. Πολλαπλές οµάδες εντολών catch και η εντολή finally Πολλές φορές είναι δυνατόν µια οµάδα εντολών να προκαλέσει περισσότερες από µία εξαιρέσεις. Σ' αυτή τη περίπτωση θα πρέπει για να χειριστούµε τις εξαιρέσεις επιτυχώς, να περικλείσουµε τις εντολές σε ένα try block και µετά το try block να δώσουµε τόσα catch blocks όσες και οι εξαιρέσεις που µπορούν να προκληθούν.

- 46 -

Page 47: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

Στην περίπτωση των πολλαπλών catch blocks που χειρίζονται εξαιρέσεις στην ίδια ιεραρχία τάξεων θα πρέπει να βάζουµε υψηλότερα τις ειδικότερες εξαιρέσεις (αυτές που βρίσκονται πιο χαµηλά στην ιεραρχία) και χαµηλότερα τις γενικότερες. Για παράδειγµα δεν θα ήταν σωστό να γράψουµε κώδικα σαν τον ακόλουθο: try ... catch (java.io.ΙΟException e) //χειρισµός της εξαίρεση Excpetion catch (java.io.FileNotFoundException e) //χειρισµός της εξαίρεσης FileNotFoundException διότι η εξαίρεση java.io.FileNotFoundException είναι υποτάξη της τάξης java.io.IOException και εποµένως αν προκληθεί θα γίνει ο χειρισµός της στο πρώτο catch block, αφού οι υποτάξεις µιας τάξης ανήκουν και στον τύπο της υπερτάξης τους. Η σωστή σειρά λοιπόν, θα ήταν η αντίστροφη. Τέλος, µετά τα catch blocks µπορούµε να βάλουµε την οµάδα εντολών finally η οποία εκτελείται είτε συµβεί κάποια εξαίρεση είτε όχι, ακόµα και αν γίνει ο χειρισµός της εξαίρεσης. Μία πολύ κοινή χρήση της finally είναι η εκτέλεση κάποιων εντολών που είναι κοινές για όλα τα σενάρια εκτέλεσης ενός try block: try ... catch (java.io.ΙΟException e) //χειρισµός της εξαίρεση Excpetion catch (java.io.FileNotFoundException e) //χειρισµός της εξαίρεσης FileNotFoundException finally //εντολές που θα εκτελεστούν σε κάθε περίπτωση //φυσιολογικής εξόδου ή εξόδου µετά από πρόκληση εξαίρεσης

Κατασκευαστές (constructors) Οι κατασκευαστές (constructors) είναι µέθοδοι που εκτελούνται όταν κατασκευάζεται ένα αντικείµενο. Όπως θα δούµε µια τάξη µπορεί να έχει αρκετούς κατασκευαστές, οι οποίοι διαφοροποιούνται από τη λίστα των παραµέτρων τους. Πως είναι ένας κατασκευαστής και τι κάνει Ένας κατασκευαστής θα πρέπει υποχρεωτικά να έχει το ίδιο όνοµα µε αυτό της τάξης, αλλά δεν µπορεί να έχει τύπο επιστροφής, γιατί δεν επιστρέφει καµία τιµή. Έχει, δηλαδή, την ακόλουθη µορφή:

- 47 -

Page 48: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

πρόσβαση Όνοµα_Τάξης(λίστα_παραµέτρων) εντολές αρχικοποίησης

Η πρόσβαση µπορεί να είναι: δηµόσια (public), προστατευµένη (protected), ιδιωτική (private) και αν αφεθεί κενή θεωρείται ότι είναι σε επίπεδο πακέτου (package). Η σηµασία της πρόσβασης είναι η ίδια όπως και για τις άλλες µεθόδους, µόνο που φυσικά εδώ έχει και την ιδιαίτερη σηµασία του να υπονοεί ποιές άλλες τάξεις µπορούν να δηµιουργήσουν αντικείµενα της τάξης που δηλώνουµε, γιατί η δηµιουργία ενός αντικειµένου γίνεται µε τη χρήση κάποιου κατασκευαστή. Ένας κατασκευαστής εκτελείται κάθε φορά που δηµιουργούµε ένα αντικείµενο και ο σκοπός του θα πρέπει να είναι η αρχικοποίηση του αντικειµένου. Η Java υποστηρίζει την ύπαρξη πολλών κατασκευαστών, οι οποίοι θα πρέπει να διακρίνονται µεταξύ τους από τη λίστα των παραµέτρων τους, γιατί φυσικά το όνοµα όλων των κατασκευαστών θα πρέπει να είναι το όνοµα της τάξης. Αν δεν ορίσετε κανένα κατασκευαστή για την τάξη σας τότε η Java θα προµηθεύσει ένα εξ ορισµού κατασκευαστή (default constructor), ο οποίος δεν κάνει τίποτα. Οπότε για να αρχικοποιήσετε τα µέλη της τάξης θα πρέπει να δηλώσετε έναν ή περισσότερους κατασκευαστές. Πολλοί κατασκευαστές Ο εξ ορισµού κατασκευαστής που είδαµε προηγουµένως καλείται για την τάξη σας όταν εσείς δεν παρέχεται ένα κατασκευαστή χωρίς παραµέτρους. Αν παρέχετε κατασκευαστή χωρίς παραµέτρους τότε ο εξ ορισµού κατασκευαστής αντικαθίσταται από αυτόν. Πέρα από ένα κατασκευαστή χωρίς παραµέτρους έχετε το δικαίωµα να δηλώσετε και άλλους κατασκευαστές. Αυτοί οι κατασκευαστές συνήθως χρησιµοποιούνται για την δηµιουργία και την ταυτόχρονη απόδοση τιµών σε κάποιες ιδιωτικές µεταβλητές της τάξης. Για παράδειγµα η τάξη Person θα µπορούσε να είχε ένα κατασκευαστή µε παράµετρο ένα String που θα αρχικοποιούσε τη µεταβλητή name στο δοθέν String:

public Person(String s) name = s;

και ένα κατασκευαστή µε παράµετρο ένα άλλο αντικείµενο τύπου Person ο οποίος θα αντέγραφε τις ιδιότητες του δοθέντος Person στο αντικείµενο που δηµιουργείται:

public Person(Person p) name = p.getName(); age = p.getAge();

Κλήση του κατάλληλου κατασκευαστή Όταν δηµιουργείτε ένα αντικείµενο µίας τάξης η Java θα καλέσει τον κατάλληλο κατασκευαστή ανόλογα µε τη λίστα παραµέτρων που δόθηκε. Παραδείγµατα δηµιουργίας αντικειµένου Person είναι τα ακόλουθα:

… //καλείται ο εξ ορισµού κατασκευαστής

- 48 -

Page 49: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

Person p1 = new Person(); //καλείται ο κατασκευαστής µε παράµετρο ένα String Person p2 = new Person("George"); //καλείται ο κατασκευαστής µε παράµετρο ένα άλλο Person Person p3 = new Person(p2); …

Κληρονοµικότητα (Inheritance) Στο µάθηµα αυτό θα συζητήσουµε για µια από τις ισχυρότερες αλλά και περισσότερο αµφιλεγόµενες τεχνικές του αντικειµενοστραφούς προγραµµατισµού την κληρονοµικότητα (inheritance). Επίσης θα δούµε το πως υλοποιείται η κληρονοµικότητα στη γλώσσα Java. Τι είναι η κληρονοµικότητα Στη θεωρία των γλωσσών προγραµµατισµού γίνεται η διάκριση ανάµεσα σε γλώσσες βασισµένες στα αντικείµενα (object-based) και σε αντικειµενοστραφείς (object-oriented) γλώσσες προγραµµατισµού. Στη πρώτη κατηγορία ανήκουν εκείνες οι γλώσσες προγραµµατισµού που υποστηρίζουν την έννοια του αντικειµένου και τη δηµιουργία αντικειµένων από τον χρήστη και στη δεύτερη εκείνες που, πέρα από τα άλλα, υποστηρίζουν και την δηµιουργία νέων τάξεων αντικειµένων µε την επέκταση των ήδη υπαρχόντων, υποστηρίζουν δηλαδή την κληρονοµικότητα υλοποίησης (implementation inheritance). Το πιο γνωστό παράδειγµα των object-based γλωσσών είναι η Visual Basic 6.0 η οποία, προς το παρόν τουλάχιστον, ενώ υποστηρίζει τη δηµιουργία νέων τάξεων από τον προγραµµατιστή δεν υποστηρίζει την κληρονοµικότητα υλοποίησης . Οι πιο γνωστές αντικειµενοστραφείς γλώσσες προγραµµατισµού είναι η C++, η Smaltalk και η Java. Τώρα τελευταία κερδίζει έδαφος και η Eiffel (κυρίως στον ακαδηµαϊκό χώρο). Ο όρος κληρονοµικότητα προέρχεται από το γεγονός ότι µια τάξη που επεκτείνει µια υπάρχουσα τάξη κληρονοµεί απ' αυτή όλες τις µεθόδους και τα δεδοµένα της, αλλά και τον τύπο της. ∆ηλαδή, η νέα τάξη που δηµιουργείται σαν επέκταση της παλιάς θα µπορεί να χρησιµοποιηθεί οπουδήποτε χρησιµοποιούταν η παλιά αφού το σύστηµα τύπων (type system) δεν θα διαµαρτυρηθεί. Το µεγάλο θέµα εδώ είναι πότε πρέπει να γίνεται αυτό. Στην αρχή η κληρονοµικότητα υλοποίησης θεωρήθηκε απλά και µόνο σαν ένας µηχανισµός επαναχρησιµοποίησης (reuse) του κώδικα. Η κληρονοµικότητα όµως δεν είναι µόνο αυτό! ∆εν είναι σωστό, για παράδειγµα, να κληρονοµείτε από µία στοίβα απλά γιατί τυχαίνει η στοίβα να έχει µία µέθοδο υπολογισµού τόκων που εσείς τυχαίνει να χρειάζεστε για µία νέα τάξη. Αυτό θα ήταν εξωφρενικό γιατί θα µπορούσατε για παράδειγµα να περάσετε αντικείµενα της τάξης σας (που κατά τα άλλα καµία σχέση δεν έχει µε στοίβα) σε µεθόδους που χρειάζονται µία στοίβα. Γιατί θα έπρεπε ένας τραπεζικός λογαριασµός, για παράδειγµα, να είναι ταυτόχρονα και στοίβα! Αυτό που θα πρέπει κανείς να απαντήσει είναι αν η νέα τάξη που δηλώνει (υποτάξη) και κληρονοµεί από την παλιά τάξη (υπερτάξη) ΕΙΝΑΙ µία ειδική περίπτωση της υπερτάξης της. Για παράδειγµα µπορούµε να πούµε πως ένα φορτηγό ΕΙΝΑΙ ένα όχηµα, άρα το όχηµα θα µπορούσε να είναι υπερτάξη της υποτάξης φορτηγό. ∆εν µπορούµε σίγουρα να πούµε πως ένας τραπεζικός λογαριασµός ΕΙΝΑΙ µια στοίβα. Χρησιµοποιώντας τον συµβολισµό της UML για την κληρονοµικότητα, που είναι ένα βέλος από την υποτάξη προς την υπερτάξη:

- 49 -

Page 50: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

Λίγο πιο αυστηρά, για να κάνουµε µία τάξη υποτάξη κάποιας άλλης τάξης θα πρέπει η υποτάξη να είναι και υποτύπος του τύπου της υπερτάξης, ώστε να ισχύει η ιδιότητα της αντικατάστασης (substitution property) της Barbara Liskov που είναι η ακόλουθη: "...Αυτό που θέλουµε είναι κάτι όπως η ακόλουθη ιδιότητα της αντικατάστασης. Αν για κάθε αντικείµενο ο1 τύπου Σ υπάρχει ένα αντικείµενο ο2 τύπου Τ, τέτοιου ώστε για όλα τα προγράµµατα Π τα οποία ορίζονται σε σχέση µε τον Τ, η συµπεριφορά τους να παραµένει αµετάβλητη όταν το ο1 αντικαθίσταται από το ο2, τότε ο Σ είναι υποτύπος του Τ". Κληρονοµικότητα στη Java - Η φράση extends Μία υποτάξη για να επεκτείνει µία υπερτάξη θα πρέπει να χρησιµοποιήσει τη φράση extends στη δήλωσή της. Το σχετικό συντακτικό είναι το ακόλουθο:

class όνοµα_υποτάξης extends όνοµα_υπερτάξης σώµα_υποτάξης

Αφού µία τάξη δηλωθεί ότι επεκτείνει την υπερτάξη της, κληρονοµεί όλα τα µέλη της υπερτάξης της. Το αν µπορεί να αναφερθεί σε όλα είναι µία άλλη ιστορία. Αν η υπερτάξη έχει δηλώσει κάποια µέλη ως ιδιωτικά (private) τότε ναι µεν αυτά τα µέλη κληρονοµούνται από την υποτάξη αλλά όµως η υποτάξη δεν µπορεί να αναφερθεί σε αυτά άµεσα. Από την άλλη µεριά η υποτάξη µπορεί να αναφερθεί στα προστατευµένα (protected) µέλη της (που κληρονοµούνται από την υπερτάξη) άµεσα χωρίς κανένα πρόβληµα σε αντίθεση µε τις τάξεις που ανήκουν σε άλλα πακέτα και δεν έχουν την δυνατότητα αυτή. Τα δηµόσια (public) µέλη της υπερτάξης είναι φυσικά προσπελάσιµα από όλους και από τις υποτάξεις. Για να αποσαφηνίσουµε καλύτερα τα παραπάνω, ας δούµε ένα παράδειγµα. Έστω η ακόλουθη τάξη Person η οποία έχει δύο µέλη δεδοµένων ένα για το όνοµα και ένα για την ηλικία. Το όνοµα είναι protected ενώ η ηλικία είναι private. Η τάξη Person επίσης παρέχει µεθόδους get και set και για τα δύο της µέλη, οι οποίες είναι φυσικά public. Τέλος, παρέχει την µέθοδο sayWhoYouAre η οποία επιστρέφει ένα String που είναι του στυλ "Είµαι ο Γιώργος και είµαι 33 ετών.". Φυσικά το όνοµα και η ηλικία στο µήνυµα εξαρτώνται από τις τιµές που θα έχουν εκείνη τη στιγµή οι µεταβλητές name και age. Η τάξη Person είναι η ακόλουθη:

public class Person

protected String name; private int age; //κατασκευαστής

- 50 -

Page 51: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

public Person() public void setName(String n) name = n; public void setAge(int a) age = a; public String getName() return name; public int getAge() return age; public String sayWhoYouAre() return "Είµαι ο "+name+" και είµαι "+age+" ετών.";

Στη συνέχεια δηλώνουµε την τάξη Student η οποία είναι υποτάξη της τάξης Person και εποµένως χρησιµοποιούµε στην αρχή της δήλωσης της (µετά το όνοµά της) τη φράση extends Person. Η τάξη Student θα είναι λοιπόν Person και κληρονοµεί όλα τα µέλη και τις µεθόδους της τάξης Person. Εποµένως δεν χρειάζεται να δηλώσουµε εκ νέου τις µεθόδους getAge, setAge, getName και setName διότι αυτές κληρονοµούνται. Η τάξη Student έχει µία επιπλέον ιδιότητα σε σχέση µε ένα (γενικό) Person. Πέρα από το όνοµα, έχει και το String fieldOfStudy και τις µεθόδους setField και getField για να θέτει και να παίρνει κανείς τον τοµέα σπουδών του φοιτητή. Τέλος, ορίζουµε και πάλι την µέθοδο sayWhoYouAre στην υποτάξη υπερβαίνοντας (overriding) έτσι τη µέθοδο της υπερτάξης. Αυτή η µέθοδος, λοιπόν, θα καλείται όταν το αντικείµενο είναι Student και όχι η µέθοδος της υπερτάξης. Αυτή η µέθοδος επιστρέφει µία φράση (String) του στυλ "Είµαι ο Γιώργος και είµαι 22 ετών. Σπουδάζω Πληροφορική". Φυσικά οι ιδιότητες name, age και fieldOfStudy θα είναι αυτές που έχει το αντικείµενο κατά τη στιγµή της κλήσης. Το ενδιαφέρον σε σχέση µε τους προσδιοριστές πρόσβασης της υπερτάξης είναι ότι η sayWhoYouAre µπορεί να αναφερθεί άµεσα στην µεταβλητή name διότι αυτή δηλώθηκε ως protected στην υπερτάξη της. ∆εν µπορεί όµως να αναφερθεί άµεσα στην µεταβλητή age διότι αυτή δηλώθηκε ως private στην υπερτάξη της. Εποµένως υποχρεούται (όπως και όλες οι άλλες τάξεις) να βρει την ηλικία καλώντας τη συνάρτηση getAge. Η τάξη Student είναι η ακόλουθη:

public class Student extends Person private String fieldOfStudy; public void setField(String s) fieldOfStudy = s; public String getField() return fieldOfStudy;

- 51 -

Page 52: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

public String sayWhoYouAre() return "Είµαι o "+name+" και είµαι "+getAge()+" ετών. "+ "Σπουδάζω "+fieldOfStudy;

Τέλος, δίνουµε ένα παράδειγµα χρήσης των δύο αυτών τάξεων µε την τάξη Main που έχει και τη συνάρτηση main. Η main δηµιουργεί δύο αντικείµενα, ένα Person (p) και ένα Student (s). Στη συνέχεια καλεί τις µεθόδους setName και setAge και στα δύο αντικείµενα. Παρατηρείστε ότι η main µπορεί να κάνει τη κλήση των δύο αυτών µεθόδων και στο s γιατί τα αντικείµενα Student κληρονοµούν αυτές τις µεθόδους από την υπερτάξη τους. Η κλήση της setField µπορεί να γίνει φυσικά µόνο για το s. Για το p η µέθοδος αυτή δεν ορίζεται. Αφού η main θέσει τις διάφορες ιδιότητες, στη συνέχεια καλεί τις µεθόδους sayWhoYouAre και για τις δύο µεθόδους. Φυσικά καλείται διαφορετική sayWhoYouAre ανάλογα µε το αν το αντικείµενο είναι Person ή Student. Η τάξη Main είναι η ακόλουθη: public class Main public static void main(String[] args) Person p = new Person(); Student s = new Student(); p.setName("Γιώργος"); p.setAge(33); s.setName("Νίκος"); s.setAge(22); s.setField("Πληροφορική"); System.out.println(p.sayWhoYouAre()); System.out.println(s.sayWhoYouAre()); Συµβατότητα τύπων (type compatibility) και πολυµορφικές µέθοδοι Ένα αντικείµενο τύπου Student είναι και αντικείµενο τύπου Person για το σύστηµα τύπων της Java. Αυτό σηµαίνει ότι µπορείτε να χρησιµοποιήσετε ένα αντικείµενο τύπου Student όπου αναµένεται ένα αντικείµενο τύπου Person. Έτσι µπορείτε να κάνετε τα ακόλουθα:

− αν µία µέθοδος περιµένει ένα αντικείµενο τύπου Person ως παράµετρο µπορεί να της περαστεί στην θέση του ένα αντικείµενο τύπου Student, όπως δείχνει το ακόλουθο τµήµα κώδικα: ... Student s = new Student(); ...

//Αυτό είναι σωστό παρότι η someMethod περιµένει αντικείµενα τύπου Person someMethod(s); ... public void someMethod(Person p) ...

- 52 -

Page 53: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

− αν έχετε µία µεταβλητή ή ένα πίνακα τύπου Person µπορείτε να εισάγετε σε αυτήν τη µεταβλητή ή στις θέσεις αυτού του πίνακα αντικείµενα τύπου Student όπως δείχνει το ακόλουθο τµήµα κώδικα:

... Person p = new Student(); ...

− αν έχετε σε µία µεταβλητή τύπου Person ένα αντικείµενο το οποίο γνωρίζετε ότι είναι στη πραγµατικότητα ένα αντικείµενο Student µπορείτε να χρησιµοποιήσετε µία κάστα µετατροπής για να αναφερθείτε σε µία µέθοδος που µόνο οι Students έχουν και όχι τα Persons, όπως δείχνει το ακόλουθο τµήµα κώδικα:

... Person p = new Student(); ... //Εδώ χρησιµοποιούµε τη κάστα γιατί τα αντικείµενα τύπου Person δεν έχουν //µέθοδο setField ((Student) p).setField("Πληροφορική");

Για να γίνουν περισσότερο κατανοητά τα παραπάνω, θα κάνουµε ένα νέο πρόγραµµα µε µία συνάρτηση main αυτή τη φορά στην τάξη Program που βρίσκεται στο αρχείο Program.java. Στο παράδειγµα αυτό δηλώνουµε ένα πίνακα δύο Persons, αλλά ενώ στη πρώτη θέση του πίνακα (θέση 0) βάζουµε πράγµατι ένα νέο αντικείµενο τύπου Person στη δεύτερη βάζουµε ένα αντικείµενο τύπου Student. Στη συνέχεια θέτουµε τις ιδιότητες των δύο αντικειµένων καλώντας τις µεθόδους τους, αλλά στην κλήση της setField απαιτείται να µετατρέψουµε τον τύπο του αντικειµένου Student που βρίσκεται στη δεύτερη θέση από Person σε Student. Αυτό το κάνουµε µε µία κάστα. Τέλος, σε ένα βρόχο for καλούµε για όλα τα αντικείµενα του πίνακα τη µέθοδο SayWhoYouAre, η οποία όµως είναι πολυµορφική, αφού δηλώνεται ξανά στην υποτάξη. Έτσι το ποια µέθοδος θα κληθεί εξαρτάται από το αντικείµενο στο οποίο γίνεται η κλήση. Για το πρώτο αντικείµενο που είναι Person θα κληθεί η sayWhoYouAre της τάξης Person, ενώ για το δεύτερο αντικείµενο που είναι Student θα κληθεί η sayWhoYouAre της τάξης Student. Έτσι λοιπόν το ποια µέθοδος καλείται δεν εξαρτάται από τον τύπο αλλά από την τάξη του αντικειµένου στο οποίο γίνεται η κλήση.

public class Program public static void main (String[] args) final int MAX = 2; Person p[] = new Person[MAX]; //Εδώ βάζουµε ένα Person p[0]=new Person(); p[0].setAge(33); p[0].setName("Γιώργος"); //ενώ εδώ βάζουµε ένα Student p[1]=new Student(); p[1].setAge(22); p[1].setName("Νίκος"); //πρέπει να κάνουµε µετατροπή τύπου (cast) για την κλήση της setField ((Student) p[1]).setField("Πληροφορική"); /* Παρατηρήστε ότι η µέθοδος sayWhoYouAre είναι πολυµορφική και η µέθοδος που θα εκτελεστεί

- 53 -

Page 54: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

εξαρτάται από το αντικείµενο στο οποίο γίνεται η κλήση. Για το πρώτο αντικείµενο που είναι Person θα κληθεί η sayWhoYouAre της τάξης Person ενώ για το δεύτερο αντικείµενο που είναι Student θα κληθεί η sayWhoYouAre της τάξης Student. */ for (int i=0; i<MAX; i++) System.out.println(p[i].sayWhoYouAre());

Κληρονοµικό δένδρο Φυσικά µία τάξη µπορεί να αποτελεί ταυτόχρονα υπερτάξη για κάποια άλλη τάξη αλλά να είναι και υποτάξη κάποιας άλλης τάξης. ∆εν υπάρχει περιορισµός στα επίπεδα που µπορεί να έχει ένα τέτοιο δένδρο κληρονοµικότητας. Προβληµατισµός για την κληρονοµικότητα Μεγάλες ιεραρχίες τάξεων βέβαια, δηµιουργούν µεγάλες εξαρτήσεις. Μην ξεχνάτε ότι µία τάξη κληρονοµεί από µία άλλη τάξη όλες τις µεθόδους και τα δεδοµένα της. Αυτό δηµιουργεί έντονο βαθµό συσχέτισης (cohesion) µεταξύ των τάξεων που συµµετέχουν σε µία τέτοια ιεραρχία, δυσχεραίνοντας έτσι την αυτόνοµη εξέλιξή τους. Το πρόβληµα αυτό φυσικά επιτείνεται όταν η ιεραρχίες αυτές έχουν µεγάλο βάθος. Το πρόβληµα αυτό έχει καταγραφεί στη βιβλιογραφία ως το πρόβληµα της εύθραυστης τάξης βάσης (the fragile base class problem). Το πρόβληµα έχει δύο όψεις την συντακτική (syntax) και την σηµαντική ή σηµασιολογική (semantic).

− Στη συντακτική του µορφή το πρόβληµα εντοπίζεται στο ότι ενδέχεται να µεταβληθεί το συντακτικό µίας µεθόδου σε µία τάξη (για παράδειγµα να γίνει µία αλλαγή στη λίστα των παραµέτρων). Τότε η αλλαγή αυτή αντικατοπτρίζεται άµεσα και στην ή στις υποτάξεις. Έτσι αν αυτές βασίζονταν στο συγκεκριµένο συντακτικό θα αποτύχουν όταν θα προσπαθήσουν να καλέσουν τη µέθοδο µε το παλιό συντακτικό της, µια και αυτή η µέθοδος δεν υπάρχει πια. Χρειάζεται δηλαδή οι υποτάξεις να αλλαχθούν και να µεταγλωττιστούν ξανά.

− Στη σηµασιολογική του µορφή το πρόβληµα εντοπίζεται στο ότι ενδέχεται µία

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

Η συζήτηση του προβλήµατος αυτού εδώ είναι αναγκαστικά σύντοµη λόγω του ότι το συγκεκριµένο πρόβληµα είναι περισσότερο ένα πρόβληµα του πεδίου της Τεχνολογίας Λογισµικού και όχι κάποιας συγκεκριµένης γλώσσας προγραµµατισµού, όπως η Java. Ο αναγνώστης που ενδιαφέρεται για περισσότερα πάνω σε αυτό το θέµα ενθαρρύνεται να διαβάσει το έβδοµο κεφάλαιο (Object versus class composition, or how to avoid inheritance) του βιβλίου του Clemens Szyperski "Component Software - Beyond Object-Oriented Programming".

Αφηρηµένες (abstract) τάξεις Υπάρχουν µερικές τάξεις για τις οποίες δεν έχει νόηµα η δηµιουργία αντικειµένων. Αυτές οι τάξεις θα πρέπει να δηλώνονται ως abstract (αφηρηµένες). Οι τάξεις που δηλώνονται ως abstract δεν µπορούν να χρησιµοποιηθούν για την δηµιουργία αντικειµένων και χρησιµοποιούνται µόνο σαν υπερτάξεις άλλων τάξεων.

- 54 -

Page 55: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

Αφηρηµένες τάξεις και αφηρηµένες µέθοδοι Κάτι που συµβαίνει αρκετά συχνά σε ιεραρχίες τάξεων είναι να δηλώνουµε κάποιες γενικές υπερτάξεις και οι υποτάξεις που επεκτείνουν τις υπερτάξεις αυτές, να κληρονοµούν όλα τα µέλη και τις µεθόδους των υπερτάξεών τους. Οι υποτάξεις µπορούν να επεκτείνουν τις υπερτάξεις τους προσθέτοντας καινούργια µέλη δεδοµένων και µεθόδους. Σε ένα τέτοιο σενάριο είναι πιθανόν να υπάρχει κάποια µέθοδος στην υπερτάξη η οποία δεν µπορεί να οριστεί στην υπερτάξη αλλά µόνο να δηλωθεί. ∆ηλαδή, ενώ µπορούµε να πούµε πως θα είναι αυτή η µέθοδος (τύπος επιστροφής, όνοµα, λίστα παραµέτρων, εξαιρέσεις που προκαλεί), η λειτουργία της στις υποτάξεις θα είναι τόσο διαφορετική που δεν έχει νόηµα η παροχή κάποιας εξ ορισµού λειτουργίας. Αν σ' αυτό το σενάριο, προσθέσουµε και την επιθυµία να µην µπορεί κανείς να δηµιουργήσει αντικείµενα αυτής της υπερτάξης τότε οδηγούµαστε στις abstract µεθόδους. Όταν µία τάξη έχει µία abstract µέθοδο: ∆εν δίνει παρά µόνο την δήλωση της µεθόδου, χωρίς να την ορίζει. Η δήλωση αυτή θα πρέπει να περιέχει και την δήλωση abstract όπως φαίνεται στο ακόλουθο τµήµα κώδικα: ... public abstract void anAbstractMethod(); ... ∆εν µπορούµε να δηµιουργήσουµε αντικείµενα από αυτή τη τάξη η οποία θεωρείται και πρέπει να δηλωθεί ως abstract όπως στο ακόλουθο παράδειγµα κώδικα: public abstract class AClass ... Όλες οι υποτάξεις της τάξης αυτής, θα πρέπει να ορίσουν την abstract µέθοδο, αλλιώς και αυτές µε τη σειρά τους θα είναι abstract και δεν θα µπορούµε να δηµιουργήσουµε αντικείµενα από αυτές. Θα πρέπει να διευκρινιστεί ότι δεν είναι απαραίτητο µία τάξη να περιέχει abstract µεθόδους για να δηλωθεί ως abstract, απλά αυτό είναι το πιο συνηθισµένο. Σηµειώστε, επίσης, ότι:

− Μία αφηρηµένη τάξη δεν µπορεί να είναι ταυτόχρονα και τελική (final), διότι τότε δεν θα είχε κανένα νόηµα η ύπαρξή της µια και δεν θα µπορούσαµε να δηµιουργήσουµε υποτάξεις της

− Μία αφηρηµένη µέθοδος δεν µπορεί ταυτόχρονα να είναι static, private ή final. Αν η µέθοδος µπορούσε να ήταν static θα µπορούσε να κληθεί και χωρίς την δηµιουργία αντικειµένου πράγµα που θα καθιστούσε δυνατή την κλήση της σε µία abstract τάξη. Επίσης, αν η µέθοδος ήταν private δεν θα ήταν ορατή από τις υποτάξεις. Τέλος, αν η µέθοδος ήταν final δεν θα µπορούσε να δηλωθεί στις υποτάξεις.

Ένα παράδειγµα µιας αφηρηµένης τάξης Για παράδειγµα θα µπορούσαµε να έχουµε µία τάξη για την πληρωµή κάποιων αγορών την PaymentMethod η οποία θα ήταν abstract. Μία τέτοια τάξη θα µπορούσε, για παράδειγµα, να χρησιµοποιηθεί για πώληση αγαθών στο Internet. Έστω, για παράδειγµα, η ακόλουθη πολύ απλή PaymentMethod που µας επιτρέπει να θέτουµε και να ανακτούµε το ποσό της πληρωµής, κάτι που χρειάζεται σε όλες τις µεθόδους πληρωµής, αλλά έχει την abstract µέθοδο pay η οποία είναι πολύ διαφορετική για τις πιθανές µεθόδους πληρωµής και έτσι δεν µπορούµε να την δηλώσουµε εδώ. Η τάξη PaymentMethod ακολουθεί:

- 55 -

Page 56: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

abstract class PaymentMethod protected double money; PaymentMethod() money =0; public double getMoney() return money; public void setMoney(double d) money = d; public abstract void pay(); Στη συνέχεια µπορούµε να δηλώσουµε τις συµπαγείς (concrete) τάξεις που θα αποτελέσουν τις υποτάξεις της αφηρηµένης (abstract) τάξης PaymentMethod. Οι δύο τάξεις που θα δηλώσουµε εδώ είναι η CreditCardPayment για πληρωµή µε πιστωτική κάρτα και η CheckPayment για πληρωµή µε επιταγή. Φυσικά εδώ δεν θα υλοποιήσουµε αυτές τις µεθόδους, απλά θα τις βάλουµε να εµφανίζουν κάποιο σχετικό µήνυµα. public class CreditCardPayment extends PaymentMethod public void pay() System.out.println("Πληρωµή "+money+ " δρχ. µε πιστωτική κάρτα"); //ακολουθεί κώδικας διεκπεραίωσης της πληρωµής µε πιστωτική public class CheckPayment extends PaymentMethod public void pay() System.out.println("Πληρωµή "+money+ " δρχ. µε επιταγή που θα σταλεί από τον πελάτη"); //ακολουθεί κώδικας διεκπεραίωσης της πληρωµής µε επιταγή Τέλος, για να δοκιµάσουµε τις τάξεις που κάναµε θα κάνουµε µία τάξη Main µε µία παραµετρική συνάρτηση main. Στη main θα περνάµε σαν παράµετρο τον τρόπο πληρωµής και το ποσό, που θα βρίσκονται στις θέσεις 0 και 1 αντίστοιχα του πίνακα args που είναι οι παράµετροι της main. Θα ακολουθηθεί η σύµβαση ότι το 1 θα σηµαίνει πληρωµή µε πιστωτική κάρτα και το 2 πληρωµή µε επιταγή. Η main ελέγχει αν το πλήθος των παραµέτρων είναι 2. Αν όχι διακόπτεται το πρόγραµµα. Στη συνέχεια µετατρέπει την πρώτη παράµετρο από String σε int και τη δεύτερη από String σε double. Αυτό το κάνει χρησιµοποιώντας τις στατικές µεθόδους parseInt και valueOf- doubleValue των τάξεων Integer και Double αντίστοιχα. Αν δεν µπορούν να γίνουν οι

- 56 -

Page 57: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

µετατροπές αυτές τότε διακόπτεται το πρόγραµµα. Τέλος ελέγχεται ο τύπος πληρωµής. Αν ο τύπος πληρωµής είναι 1 τότε δηµιουργείται ένα αντικείµενο τύπου CreditCardPayment ενώ αν είναι 2 τότε δηµιουργείται ένα αντικείµενο τύπου CheckPayment. Και στις δύο περιπτώσεις αφού δηµιουργηθεί το αντικείµενο καλείται η setMoney της υπερτάξης για να θέσουµε το ποσό της πληρωµής και τέλος καλείται η µέθοδος pay που όµως θα είναι η σωστή pay ανάλογα µε το αντικείµενο που δηµιουργήθηκε. Αν ο τύπος πληρωµής δεν είναι 1 ή 2 τότε καλείται η µέθοδος stop που θα εµφανίσει την υπόδειξη για το πως πρέπει να χρησιµοποιήσουµε το πρόγραµµα και θα διακόψει την εκτέλεσή του. public class Main public static void main (String[] args) if (args.length != 2) stop(); int type=0; double money=0.0; try type = Integer.parseInt(args[0]); money = (Double.valueOf(args[1])).doubleValue(); catch (NumberFormatException e) stop(); PaymentMethod p; switch (type) case 1: p = new CreditCardPayment(); p.setMoney(money); p.pay(); break; case 2: p = new CheckPayment(); p.setMoney(money); p.pay(); break; default: stop(); public static void stop() System.out.println("Χρήση: java Main (1 ή 2) ποσό"); System.exit(0);

- 57 -

Page 58: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

∆ιεπαφές (interfaces) Μία διεπαφή (interface) είναι ένα σύνολο δηλώσεων µεθόδων. Αυτές οι µέθοδοι δεν υλοποιούνται στην διεπαφή, απλά δηλώνονται. Στη συνέχεια η διεπαφή µπορεί να υλοποιηθεί από κάποιες τάξεις. Μία τάξη που υλοποιεί µία διεπαφή θα πρέπει να υλοποιήσει όλες ή κάποιες από τις µεθόδους που δηλώνονται στη διεπαφή. Το µόνο που κληρονοµεί µία τάξη από την διεπαφή που υλοποιεί είναι ο τύπος της. Έτσι αποφεύγονται πολλά από τα προβλήµατα που παρουσιάζονται µε την κληρονοµικότητα υλοποίησης (implementation inheritance) και κυρίως το σηµασιολογικό πρόβληµα της εύθραυστης τάξης βάσης (semantic fragile base class problem) που συζητήσαµε σύντοµα στο τέλος της προηγούµενης ενότητας. Η δηµιουργία µιας διεπαφής Μία διεπαφή έχει την ακόλουθη γενική µορφή:

[public] interface όνοµα_διεπαφής [extends λίστα_διεπαφών] σώµα_διεπαφής

Όπως φαίνεται και από το πιο πάνω συντακτικό, µία διεπαφή ξεκινά µε τη δεσµευµένη λέξη interface και ακολουθεί το όνοµα της διεπαφής. Μπορείτε να δηλώσετε µία διεπαφή ως public, οπότε όλες οι τάξεις έχουν πρόσβαση στη διεπαφή. Αν δεν το κάνετε τότε εννοείται ότι η πρόσβαση θα είναι σε επίπεδο πακέτου (package), δηλαδή θα έχουν πρόσβαση στο interface µόνο οι τάξεις στο ίδιο πακέτο. Συνήθως µία διεπαφή δηλώνεται στο δικό της αρχείο µε όνοµα το όνοµα της διεπαφής και επέκταση java. Μεταφράζουµε µια διεπαφή µε τον ίδιο τρόπο που µεταφράζουµε µια τάξη και παράγεται γι' αυτή ένα αρχείο µε επέκταση class που περιέχει τα bytecodes γι' αυτή τη διεπαφή. Μία διεπαφή, επίσης, µπορεί να επεκτείνει µία ή περισσότερες άλλες διεπαφές που αναγράφονται στη λίστα µετά τη φράση extends. Επισηµαίνεται ότι µία διεπαφή που κάνει κάτι τέτοιο κληρονοµεί όλες τις δηλώσεις µεθόδων και σταθερών που κληρονοµήθηκαν από την ή τις υπερ-διεπαφές της. Πέρα από τις δηλώσεις των µεθόδων µια διεπαφή µπορεί να έχει και δηλώσεις σταθερών. Αυτές θα είναι static και final, και θα κληρονοµηθούν από τις τάξεις που θα υλοποιήσουν τη διεπαφή. Μία τάξη που υλοποιεί την διεπαφή και δεν υλοποιεί όλες τις µεθόδους της, θα είναι αφηρηµένη (abstract) και αντικείµενά της δεν θα µπορούν να δηµιουργηθούν. Τι είναι µια διεπαφή και γιατί τη χρειαζόµαστε Απ' όσα είπαµε προκύπτει το συµπέρασµα ότι ουσιαστικά µία τάξη που υλοποιεί µία διεπαφή κληρονοµεί την υποχρέωση να υλοποιήσει τις µεθόδους που δηλώνονται από την διεπαφή. Μία τάξη που κληρονοµεί από µία τάξη βάσης κληρονοµεί υλοποίηση, δηλαδή κάποιες µεθόδους που δεν χρειάζεται να υλοποιήσει η ίδια, γιατί έχουν υλοποιηθεί στην υπερτάξη της. Εποµένως φαίνεται, εκ πρώτης όψεως, ότι η υλοποίηση κάποιας διεπαφής δεν φαίνεται να οφελεί κάποια τάξη, µια και η τάξη αποκτά την υποχρέωση να υλοποιήσει τις µεθόδους της διεπαφής χωρίς να κερδίζει κάποιες άλλες µεθόδους. Αυτό που η τάξη κερδίζει υλοποιώντας την διεπαφή είναι: το δικαίωµα να χρησιµοποιείται ως αντικείµενο του τύπου της διεπαφής, και το συνακόλουθο δικαίωµα να χρησιµοποιηθεί από άλλες τάξεις ως αντικείµενο αυτού του τύπου. Αυτές οι άλλες τάξεις µπορούν να βασίζονται στην ύπαρξη των µεθόδων της διεπαφής,

- 58 -

Page 59: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

αφού µία τάξη που υλοποιεί την διεπαφή έχει την υποχρέωση να υλοποιήσει τις µεθόδους της διεπαφής. Γιατί όµως χρειαζόµαστε τις διεπαφές; ∆εν θα αρκούσε να είχαµε µία υπερτάξη µε κάποιες αφηρηµένες µεθόδους (abstract methods). Και πάλι η τάξη που θα επέκτεινε την τάξη αυτή, θα κληρονοµούσε και τον τύπο της υπερτάξης της αλλά και την υποχρέωση να υλοποιήσει αυτές τις µεθόδους, δίνοντας έτσι αυτή την εγγύηση στις άλλες τάξεις. Ποια είναι λοιπόν η διαφορά; Η διαφορά βρίσκεται στο ότι δεν γίνεται µία τάξη να επεκτείνει πολλές τάξεις, µια και στη Java δεν υποστηρίζεται η πολλαπλή κληρονοµικότητα (multiple inheritance). Γίνεται, όµως, να υλοποιήσει όσες διεπαφές επιθυµεί. Έτσι όταν µία τάξη υλοποιεί µία ή περισσότερες διεπαφές δεν δεσµεύει την µία και µοναδική υπερτάξη την οποία µπορεί να επεκτείνει. Επίσης, πολλές φορές θέλουµε τελείως ανόµοιες τάξεις να έχουν την υποχρέωση να υλοποιήσουν µία ή δύο µεθόδους, Αυτό δεν σηµαίνει ότι θα πρέπει να ανήκουν και στην ίδια ιεραρχία τάξεων. Πώς υλοποιεί µία τάξη µία διεπαφή - Η φράση implements Η υλοποίηση µιας διεπαφής γίνεται µε τη χρήση της φράσης implements. Η φράση implements ακολουθεί το όνοµα της τάξης στη πρώτη γραµµή δήλωσης µιας τάξης και στη συνέχεια βάζουµε το όνοµα ή τα ονόµατα των διεπαφών που υλοποιεί η τάξη χωρισµένα µε κόµµα. Θα πρέπει να πούµε ότι η χρήση της φράσης implements δεν αποκλείει την χρήση της φράσης extends. Μία τάξη µπορεί και να κληρονοµεί από κάποια άλλη τάξη αλλά και να υλοποιεί κάποια ή κάποιες διεπαφές. Για να κάνουµε τα παραπάνω σαφέστερα ας δούµε ένα παράδειγµα. Έστω ότι θέλουµε να έχουµε τη δυνατότητα να κάνουµε κάποιες τάξεις κατάλληλες για χρήση µε ένα χρονόµετρο το οποίο θα καλεί τη µέθοδο repeatedTask σ' αυτές τις τάξεις µε µία συχνότητα που θα προσδιορίζεται από την ιδιότητα timePeriod. Για να το καταφέρουµε αυτό θα πρέπει να εγγυηθούµε ότι η µέθοδος repeatedTask θα υπάρχει στις τάξεις πελάτες αυτού του Timer και επίσης θα πρέπει να υπάρχει ένας κοινός τύπος των πελατών αυτού του Timer για να µπορεί ο Timer να τοποθετήσει τους πελάτες του σε µία δοµή (π.χ. ένα πίνακα). Θα πρέπει να διευκρινίσουµε ότι ο Timer θα είναι ένα ενεργό αντικείµενο (active object) που θα έχει το δικό του νήµα εκτέλεσης (thread) και γι' αυτό θα υλοποιεί την µέθοδο run. Η µέθοδος αυτή θα αρχίσει να εκτελείται όταν θα κληθεί σε έναν Timer η µέθοδος start. Η µέθοδος start κληρονοµείται από την τάξη Thread που επεκτείνει η τάξη Timer. Αν και δεν έχουµε µιλήσει ακόµα για νήµατα εκτέλεσης, κρίθηκε αναγκαίο να τα χρησιµοποιήσουµε εδώ γιατί είναι δύσκολο να δοθεί ένα έστω και αµυδρά ενδιαφέρον παράδειγµα µε Interfaces χωρίς νήµατα εκτέλεσης. Αυτό διότι η πιο κλασική εφαρµογή των Interfaces είναι για µεθόδους που θα χρησιµοποιηθούν για callbacks από κάποιου είδους Servers στους οποίους εγγράφονται οι πελάτες που επιθυµούν την εξυπηρέτηση του Server. Αυτά τα callbacks (δηλαδή η κλήση των µεθόδων των πελατών από τον Server) γίνεται στο νήµα εκτέλεσης του Server ενώ η εγγραφή των πελατών γίνεται συνήθως στο νήµα εκτέλεσης του προγράµµατος που τους δηµιούργησε. Οι πελάτες λοιπόν εγγράφονται και τους καλεί πίσω (callback) o Server όταν προκύψει το συµβάν για το οποίο έχουν εγγραφεί. Στην περίπτωσή µας ο "Server" είναι ο Timer ο οποίος παρέχει τις ακόλουθες µεθόδους:

− public synchronized void add(TimerClient c): Προσθέτει ένα αντικείµενο που υλοποιεί το TimerClient interface στον πίνακα των πελατών του Timer. Η µέθοδος αυτή ενδέχεται να προκαλέσει την εξαίρεση NotEmptySlotException αν δεν υπάρχει κενή θέση στον πίνακα των πελατών του Timer. Η δήλωση synchronized είναι για να εξασφαλίσουµε την αµοιβαία αποκλειστικότητα στην εκτέλεση της add στην περίπτωση ύπαρξης πολλών νηµάτων που την καλούν ταυτόχρονα.

- 59 -

Page 60: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

− public synchronized void remove(TimerClient c): Όταν κληθεί αφαιρεί έναν εγγεγραµµένο πελάτη που δίνεται ως παράµετρος από τον πίνακα των πελατών.

− public int getTimePeriod() και public void setTimePeriod(int t): Χρησιµοποιούνται για να ανακτούµε και να θέτουµε αντίστοιχα την περίοδο αφύπνισης timePeriod του Timer.

− public void run(): Αυτή η µέθοδος καλείται όταν ξεκινάµε ένα αντικείµενο Timer µε την κλήση της µεθόδου start σε αυτό. Εκτελεί ένα άπειρο βρόχο στον οποίο θα καλεί την µέθοδο repeatedTask κάθε αντικειµένου στον πίνακα των εγγεγραµµένων πελατών theClients. Αφού εκτελέσει τις µεθόδους αυτές στη συνέχεια περιµένει το χρόνο που προσδιορίζεται από την ιδιότητα timePeriod µε την κλήση της µεθόδου sleep(timePeriod). H sleep επίσης κληρονοµείται από την τάξη Thread.

Η τάξη Timer είναι η ακόλουθη: public class Timer extends Thread private static final int MAX_TASKS = 10; /* H default περίοδος επανάληψης του timer είναι κάθε 1 δευτερόλεπτο*/ private int timePeriod = 1000; private TimerClient theClients[] = new TimerClient[MAX_TASKS]; public void Timer() for (int i=0; i<MAX_TASKS; i++) theClients[i] = null; public synchronized void add(TimerClient c) throws NoEmptySlotException for (int i=0; i<MAX_TASKS; i++) if (theClients[i] == null) theClients[i] = c; return; throw new NoEmptySlotException(); public synchronized void remove(TimerClient c) for (int i=0; i<MAX_TASKS; i++) if (theClients[i] == c) theClients[i] = null; public int getTimePeriod() return timePeriod; public void setTimePeriod(int t) timePeriod = t;

- 60 -

Page 61: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

public void run() //για πάντα while (true) //επανέλαβε τις εργασίες for (int i=0; i<MAX_TASKS; i++) if (theClients[i] != null) theClients[i].repeatedTask(); //περίµενε για τον προσδιορισµένο χρόνο try sleep(timePeriod); catch (InterruptedException e) Το interface TimerClient που θα πρέπει να υλοποιούν όλοι οι πελάτες που ενδιαφέρονται να αφυπνιστούν από τον Timer έχει πολύ απλά µία µέθοδο, τη µέθοδο repeatedTask που θα κληθεί πίσω από τον Timer σε όλους τους εγγεγραµµένους πελάτες όταν περάσει η περίοδος του Timer. Το interface αυτό είναι λοιπόν το ακόλουθο:

public interface TimerClient public void repeatedTask();

Είδαµε προηγουµένως ότι η µέθοδος add της τάξης Timer µπορεί να προκαλέσει την εξαίρεση NoEmptySlotException. Αυτή η εξαίρεση θα πρέπει επίσης να δηλωθεί και είναι η ακόλουθη:

public class NoEmptySlotException extends Exception

Σαν πελάτες της τάξης Timer θα χρησιµοποιήσουµε δύο τάξεις την τάξη ClassA και την τάξη ClassB. Αυτές οι τάξεις υλοποιούν το interface TimerClient και εποµένως υλοποιούν τη µέθοδο repeatedTask. Στο απλό µας παράδειγµα οι µέθοδοι αυτοί εµφανίζουν ένα µήνυµα του στυλ "Επαναλαµβανόµενη εργασία της τάξης ClassA". Ο κώδικας για την τάξη ClassA (για την ClassB ο κώδικας είναι εντελώς ανάλογος) είναι ο ακόλουθος: public class ClassA implements TimerClient public void repeatedTask() System.out.println("Επαναλαµβανόµενη εργασία της τάξης ClassA"); Η τελευταία τάξη αυτής της εφαρµογής είναι φυσικά η τάξη Main που περιέχει τη συνάρτηση main που είναι το κυρίως πρόγραµµα της εφαρµογής αυτής. Η main θα δηµιουργήσει δύο

- 61 -

Page 62: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

αντικείµενα - ένα ClassA και ένα ClassB - και ένα Timer. Θα θέσει τη περίοδο επανάληψης του Timer σε 5 δευτερόλεπτα και θα προσθέσει µε τη µέθοδο add τα δύο αντικείµενα-πελάτες στον Timer. Τέλος, θα ξεκινήσει τη µέθοδο run καλώντας την start στον Timer. Παρατηρείστε ότι µετά από αυτό, η συνάρτηση main τερµατίζεται αλλά το πρόγραµµα δεν τερµατίζεται γιατί το αντικείµενο Timer έχει το δικό του νήµα εκτέλεσης το οποίο θα εξακολουθήσει να καλεί τις µεθόδους repeteadTask των δύο εγγεγραµµένων αντικειµένων για πάντα περιµένοντας πέντε δευτερόλεπτα.ανάµεσα από διαδοχικές κλήσεις. Για να τερµατίσετε το πρόγραµµα θα πρέπει να κλείσετε το DOS session στο οποίο το εκτελείτε ή να πατήσετε τον συνδυασµό Ctrl-C. public class Main public static void main(String[] args) //∆ηµιούργησε δύο αντικείµενα ένα ClassA και ένα ClassB ClassA a = new ClassA(); ClassB b = new ClassB(); /*∆ηµιούργησε ένα Timer και θέσε τη περίοδο αφύπνισης σε 5 δευτερόλεπτα */ Timer t = new Timer(); t.setTimePeriod(5000); //πρόσθεσε τους δύο πελάτες στον Timer try t.add(a); t.add(b); catch (NoEmptySlotException e) //ξεκίνησε τον Timer t.start();

Τελικές τάξεις (final classes) Ο χαρακτηρισµός final Μία τάξη µπορεί να δηλωθεί ως final (τελική) µε την προϋπόθεση ότι δεν έχει δηλωθεί ως abstract (αφηρηµένη). Μία τελική τάξη δεν µπορεί να επεκταθεί από άλλες τάξεις, δηλαδή µία τελική τάξη δεν µπορεί να είναι υπερτάξη άλλων τάξεων. ∆ηλώνουµε µία τελική τάξη όπως δείχνει το ακόλουθο τµήµα κώδικα: public final AFinalClass ... Λόγοι για την χρήση τελικών τάξεων Γενικά είναι επιθυµητό να µπορούµε να επεκτείνουµε µία τάξη, διότι είναι γενικά δύσκολο να προβλέψουµε τους τρόπους µε τους οποίους µπορεί να χρησιµοποιηθεί µία τάξη στο µέλλον. ∆ύο λόγοι που θα µπορούσαν να µας οδηγήσουν στη χρήση της δήλωσης final είναι οι ακόλουθοι:

- 62 -

Page 63: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

− Η τάξη µας γνωρίζουµε ότι είναι αδύνατο να επεκταθεί από κάτι άλλο, ή δεν θέλουµε,

για λόγους τεχνολογίας λογισµικού να επεκταθεί από κάτι άλλο − Η τάξη µας είναι πολύ κρίσιµη για την λειτουργία ενός υπολογιστικού συστήµατος και

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

Εσωτερικές τάξεις (inner classes) Τι είναι µία εσωτερική τάξη Μία εσωτερική τάξη είναι µία τάξη που δηλώνεται µέσα σε µία άλλη τάξη και η άλλη τάξη που την περιέχει χρησιµοποιεί ένα αντικείµενο αυτής της τάξης µε κάθε αντικείµενο της, για τους σκοπούς της λειτουργίας της. Συντακτικά ο ορισµός µιας εσωτερικής τάξης είναι πάντοτε εµφωλευµένος (nested) µέσα στον ορισµό µιας άλλης τάξης, όπως φαίνεται και στο ακόλουθο τµήµα κώδικα:

class OuterClass ... class InnerClass ... ...

Η εσωτερική τάξη, έχει πρόσβαση σε όλα τα µέλη της τάξης που την περιέχει ακόµα και αν αυτά είναι ιδιωτικά. Σε τι χρησιµεύει µία εσωτερική τάξη; Μία εσωτερική τάξη θα πρέπει να έχει νόηµα µόνο στο πλαίσιο της τάξης που την περιέχει και αυτή η τάξη που την περιέχει να είναι απαραίτητο να χρησιµοποιήσει την εσωτερική τάξη για τη λειτουργία της. Έτσι από την άποψη της σχεδίασης του λογισµικού (software design), τα αντικείµενα της εσωτερικής τάξης θα πρέπει να είναι µία λεπτοµέρεια της λειτουργικότητας της εξωτερικής τάξης. Ένα παράδειγµα εσωτερικής τάξης Ένα κλασικό παράδειγµα της χρήσης µιας εσωτερικής τάξης θα είναι και το πρώτο "παραθυρικό" µας πρόγραµµα. Θα κάνουµε ένα πρόγραµµα το οποίο θα εµφανίσει ένα παράθυρο µε µία περιοχή εξόδου και ένα κουµπί. Κάθε φορά που ο χρήστης πατάει το κουµπί θα προστίθεται στην περιοχή εξόδου το µήνυµα "Γεια σου γραφικέ κόσµε!".

- 63 -

Page 64: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

Το πρόγραµµα αυτό θα κάνει χρήση της βιβλιοθήκης τάξεων java.awt που περιέχει τάξεις όπως η τάξη Frame που είναι ένα παράθυρο, η τάξη TextArea που είναι µια περιοχή στην οποία µπορούµε να εµφανίσουµε µηνύµατα και η τάξη Button που είναι ένα κουµπί. Η τάξη Application που θα δώσουµε στη συνέχεια επεκτείνει ένα γενικό Frame και ο κατασκευαστής της δηµιουργεί µία TextArea την out και ένα Button το theButton τα οποία θα εµφανιστούν στις προκαθορισµένες θέσεις που καθορίζονται µε την µέθοδο setBounds. Στη συνέχεια τα προσθέτει στη φόρµα µε την add. ∆εν θα επεκταθούµε πιο πολύ στις µεθόδους των τάξεων του πακέτου java.awt εδώ. Το µόνο που θα πούµε ακόµα για την ώρα είναι ότι θα πρέπει να κάνουµε import το πακέτο για να µπορεί η τάξη µας να χρησιµοποιήσει τις τάξεις του και γι' αυτό τοποθετούµε τη δήλωση import java.awt.* στη πρώτη γραµµή του αρχείου (ο αστερίσκος σηµαίνει 'όλα τα περιεχόµενα του πακέτου'). ∆εν αρκεί όµως η εµφάνιση των στοιχείων αλλά θα πρέπει κάθε φορά που κάνουµε κλικ στο κουµπί theButton να εµφανίζεται ένα µήνυµα στην TextArea out. Για να γίνει αυτό θα προσθέσουµε ένα νέο ακροατή συµβάντων στο κουµπί µε την µέθοδο addActionListener. Η µέθοδος αυτή δέχεται σαν παράµετρο ένα αντικείµενο µιας τάξης που υλοποιεί το java.awt.event.ActionListener interface, το οποίο αντικείµενο θα είναι ο ακροατής συµβάντων για τα πατήµατα στο κουµπί αυτό. Οι τάξεις που υλοποιούν το interface αυτό θα πρέπει να υλοποιήσουν την µέθοδο actionPerformed η οποία θα κληθεί και θα εκτελεστεί ο κώδικάς της κάθε φορά που πατάµε το κουµπί. Η τάξη που θα χρησιµοποιήσουµε εδώ για την ακρόαση συµβάντων στο κουµπί θα είναι ο ButtonListener που θα είναι εσωτερική τάξη της τάξης Application µια και ισχύουν τα εξής:

− Η τάξη αυτή δεν χρειάζεται πέρα από τα πλαίσια της τάξης Application και είναι απαραίτητη για την λειτουργία της, και

− Χρειάζεται ο ButtonListener να έχει πρόσβαση στο ιδιωτικό µέλος out µια και χρειάζεται να προσθέσει στην περιοχή εξόδου out ένα µήνυµα.

Βεβαίως είναι προφανές ότι το δεύτερο θα µπορούσαµε να το καταφέρουµε και µε µία µέθοδο αλλά σε συνδυασµό οι δύο λόγοι οδηγούν στη επιλογή της εσωτερικής τάξης. Η τάξη Application µαζί µε την εσωτερική της τάξη ButtonListener θα βρίσκονται στο αρχείο Application.java και έχουν τον ακόλουθο κώδικα: import java.awt.*; public class Application extends Frame private static final int HEIGHT = 400; private static final int WIDTH = 300; private TextArea out; private Button theButton; public Application() setLayout(null); setSize(WIDTH, HEIGHT); Font font = new Font("Arial", Font.PLAIN, 12); out = new TextArea(10, 40); out.setBounds(10, 30, WIDTH-20, HEIGHT-100); out.setFont(font); add(out); theButton = new Button("Κάνε κλικ εδώ");

- 64 -

Page 65: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

theButton.setBounds(10, HEIGHT-50, WIDTH-20, 40); add(theButton); theButton.addActionListener(new ButtonListener()); class ButtonListener implements java.awt.event.ActionListener public void actionPerformed(java.awt.event.ActionEvent e) out.append("Γειά σου γραφικέ κόσµε!\n"); Θα χρειαστούµε, φυσικά, και µία τάξη Main µε µία συνάρτηση main που θα είναι το κυρίως πρόγραµµα. Η main απλά θα δηµιουργήσει και θα εµφανίσει ένα νέο παράθυρο Application. Το αρχείο για το κυρίως πρόγραµµα είναι το Main.java και η τάξη Main είναι η ακόλουθη:

public class Main

public static void main (String[] args) Application newApp = new Application(); newApp.show();

Πακέτα (packages) Σ' αυτό το µάθηµα θα µιλήσουµε για την κατασκευή του πακέτου (package) της Java, γιατί τη χρειαζόµαστε και πως τη χρησιµοποιούµε. Τί είναι ένα πακέτο Ένα πακέτο είναι ένα σύνολο τάξεων και διεπαφών. Αυτές οι τάξεις και διεπαφές θα πρέπει να δηλωθούν ότι ανήκουν στο ίδιο πακέτο µε τη χρήση της δήλωσης:

package όνοµα-πακέτου; στη πρώτη γραµµή του αρχείου που περιέχει τη τάξη ή τη διεπαφή που θα ανήκει σε αυτό το πακέτο. Έτσι για παράδειγµα µπορεί να έχουµε ένα πακέτο σχετικό µε τραπεζικούς λογαριασµούς στο οποίο θα έχουµε τις τάξεις Account και InterestRate. Αυτές οι δύο τάξεις θα πρέπει να περιέχουν στη πρώτη τους γραµµή τη δήλωση package bank, αν για παράδειγµα αποφασίσουµε να χρησιµοποιήσουµε το όνοµα bank γι' αυτό το πακέτο, όπως δείχνουν τα ακόλουθα τµήµατα κώδικα:

package bank; public class Account ... και

- 65 -

Page 66: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

package bank; public class InterestRate ...

Είναι δυνατόν να έχουµε και ένα ακόµα πακέτο σε ένα άλλο πακέτο που είναι σε ένα άλλο πακέτο κ.ο.κ. Τότε θα πρέπει να δηλώνουµε τις τάξεις αυτού του πακέτου σαν να ανήκουν πακέτο µε όνοµα τα ονόµατα των πακέτων από το ποιο εξωτερικό µέχρι αυτό που περιέχει τη τάξη χωρισµένα µε τελείες.

package όνοµα-πακέτου-1.όνοµα-πακέτου-2; Ίσως να απορείτε γιατί στα µέχρι τώρα παραδείγµατα δεν χρειάστηκε να δηλώσουµε κάποια ή κάποιες τάξεις σαν να ανήκουν σε κάποιο πακέτο. Ο λόγος είναι ότι η Java εξ' ορισµού θεωρεί ότι οι τάξεις που χρησιµοποιούµε ανήκουν σε ένα ανώνυµο πακέτο αν δεν ισχύει κάτι άλλο. Για να χρησιµοποιήσουµε τις τάξεις ενός πακέτου από ένα άλλο πακέτο, θα πρέπει αυτές να έχουν δηλωθεί ως public και να εισάγουµε µε import αυτές τις τάξεις στο αρχείο όπου δηλώνουµε την τάξη που θα κάνει χρήση του πακέτου. Αν θέλουµε, για παράδειγµα να µπορούµε να χρησιµοποιήσουµε τις τάξεις και τις διεπαφές του πακέτου java.awt που περιέχει τάξεις και διεπαφές για παραθυρικές εφαρµογές θα πρέπει να εισάγουµε τις τάξεις του java.awt στην αρχή του αρχείου που περιέχει την τάξη, ως εξής: import java.awt.*; Ο αστερίσκος σηµαίνει ότι θα είµαστε σε θέση να χρησιµοποιήσουµε όλες τις τάξεις και τις διεπαφές. Αν θέλουµε να χρησιµοποήσουµε µόνο µία τάξη ή διεπαφή ενός πακέτου, µπορούµε να εισάγουµε µε την import µόνο αυτή τη τάξη ή την διεπαφή. Για παράδειγµα αν θέλουµε να εισάγουµε µόνο την τάξη Account του πακέτου bank στη δήλωση µιας τάξης ενός άλλου πακέτου, µπορούµε να κάνουµε το import ως εξής:

import bank.Account; Γιατί να χρησιµoποιήσουµε ένα πακέτο; Η τοποθέτηση κάποιων τάξεων στο ίδιο πακέτο έχει πολλά πλεονεκτήµατα:

1. Οι τάξεις και οι διεπαφές ενός πακέτου µπορούν να έχουν το ίδιο όνοµα µε τις τάξεις και τις διεπαφές άλλων πακέτων χωρίς προβλήµατα ονοµαστικών συγκρούσεων. Το πακέτο λοιπόν αποτελεί και ένα χώρο ονοµάτων (name space).

2. Υπάρχει έλεγχος δικαιωµάτων πρόσβασης µε την χρήση των πακέτων ως εξής: Οι

τάξεις άλλων πακέτων δεν έχουν πρόσβαση στα προστατευµένα (protected) µέλη αντικειµένων των τάξεων του πακέτου, ακόµα και αν κληρονοµούν από αυτές.Οι τάξεις του ιδίου πακέτου έχουν πρόσβαση στα προστατευµένα (protected) µέλη αντικειµένων των τάξεων του πακέτου, ακόµα και αν δεν κληρονοµούν από αυτές. Μία τάξη ή µέθοδος είναι ορατή εκτός του πακέτου της µόνο αν είναι public (δηµόσια). Αν δεν δηλώσουµε την πρόσβαση είτε σε µεθόδους είτε στις τάξεις εννοείται ότι η πρόσβαση είναι σε επίπεδο πακέτου (package) δηλαδή επιτρέπεται η πρόσβαση στις τάξεις και στις µεθόδους του ιδίου πακέτου.

- 66 -

Page 67: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

3. Οι τάξεις ενός πακέτου είναι συγγενείς κατά κάποια έννοια. Για παράδειγµα αποτελούν

τάξεις που είναι προσανατολισµένες σε τραπεζικές εφαρµογές. Αυτό διευκολύνει τους προγραµµατιστές που θα τις χρησιµοποιήσουν.

Πως υλοποιούνται τα πακέτα Η υλοποίηση των πακέτων είναι κάτι που αφέθηκε έξω από τον προσδιορισµό της γλώσσας Java (Java Specification) αλλά το Java Development Kit (JDK) χρησιµοποιεί το σύστηµα αρχείων γι' αυτό. ∆ηλαδή κάθε πακέτο αντιστοιχεί σε ένα κατάλογο (directory) ή φάκελο (folder) για να χρησιµοποιήσουµε την ορολογία των Windows. Αν έχουµε πακέτα µέσα σε άλλα πακέτα θα πρέπει να χρησιµοποιήσουµε γι' αυτά καταλόγους µέσα σε άλλους καταλόγους κ.ο.κ. Πως όµως ανακαλύπτει η Εικονική Mηχανή Java (JVM) το πακέτο που βρίσκεται στο πρώτο επίπεδο (top-level package); Για παράδειγµα όταν εισάγουµε µε την import τις τάξεις του java.awt πως ξέρει η JVΜ που βρίσκεται το πακέτο java; Αν γνωρίζει που βρίσκεται το πακέτο java στη συνέχεια µπορεί να βρει το πακέτο awt αφού αυτό θα είναι ένας κατάλογος µέσα στον κατάλογο java. Εξ' ορισµού η αναζήτηση του πακέτου του πρώτου επιπέδου γίνεται στον τρέχοντα κατάλογο και στο συµπιεσµένο αρχείο που περιέχει τις τάξεις του Java Development Kit. Εποµένως αν µεταγλωττίσετε από το τρέχον κατάλογο και η ιεραρχία των πακέτων στην οποία αναφέρεστε βρίσκεται στον κατάλογο αυτό, δεν χρειάζεται να κάνετε τίποτα. Για πακέτα που δεν βρίσκονται στον ενεργό κατάλογο κατά τη στιγµή της µεταγλώττισης, ούτε και είναι πακέτα του JDK θα πρέπει να τροποποιήσετε τη µεταβλητή περιβάλλοντος CLASSPATH ή να χρησιµοποιήσετε την επιλογή -classpath του Java compiler (javac) για να θέσετε το classpath γι' αυτή τη µεταγλώττιση και το ίδιο option µε το πρόγραµµα java για να εκτελέσετε το πρόγραµµα στη συνέχεια.. Το CLASSPATH δεν είναι τίποτα άλλο παρά ένα σύνολο διαδροµών (paths) που είναι χωρισµένοι µε ερωτηµατικά (;) για το DOS ή τον χαρακτήρα : για το Unix και είναι εκείνες οι διαδροµές στις οποίες θα αναζητηθούν τα πακέτα που χρειάζονται κατά τη στιγµή της µεταγλώττισης και κατά τη στιγµή της εκτέλεσης.

Ασκήσεις στον Α.Π. µε τη Java Άσκηση 1 Να γίνει µία τάξη Book η οποία θα χρησιµοποιηθεί για τα βιβλία µιας βιβλιοθήκης. Για κάθε βιβλίο να δηλώσετε τις µεταβλητές title (String) µε τον τίτλο του βιβλίου, author (επίσης String) για τον συγγραφέα και firstEdition (int) που είναι το έτος της πρώτης έκδοσης. Όλες οι µεταβλητές θα πρέπει να είναι ιδιωτικές (private). Να δώσετε µεθόδους get και set για όλες τις ιδιότητες που αναφέρθηκαν παραπάνω. Στη συνέχεια δηµιουργείστε ένα πρόγραµµα το οποίο θα δηµιουργήσει ένα πίνακα τριών βιβλίων. Τέλος η main θα πρέπει να ταξινοµήσει τον πίνακα αυτών των βιβλίων χρησιµοποιώντας οποιαδήποτε µέθοδο ταξινόµησης θέλετε, µε αύξουσα σειρά ως προς το έτος πρώτης έκδοσης. Αφού η main κάνει τη ταξινόµηση θα πρέπει να εµφανίσει τα τρία βιβλία µε ένα String του στυλ "Βιβλίο: <όνοµα-βιβλίου>, Συγγραφέας: <όνοµα-συγγραφέα>, Έτος πρώτης έκδοσης: <έτος-πρώτης-έκδοσης>. Άσκηση 2 Στην προηγούµενη άσκηση η τάξη Book έχει την ιδιότητα Author για τον συγγραφέα, την οποία προηγουµένως υλοποιήσαµε ως String. Να δηλώσετε µία τάξη Author η οποία θα έχει την ιδιότητα name για το όνοµα του συγγραφέα που θα είναι String και get και set µεθόδους γι' αυτή την ιδιότητα. Να συµπεριλάβετε στην τάξη του συγγραφέα και ένα πίνακα 20 το πολύ βιβλίων στον οποίο θα τοποθετήσουµε τα βιβλία του συγγραφέα. Τα βιβλία θα είναι αντικείµενα της τάξης Book της προηγούµενης άσκησης. Από την τάξη Book της

- 67 -

Page 68: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

προηγούµενης άσκησης να αλλάξετε την ιδιότητα Author και τις µεθόδους setAuthor και getAuthor έτσι ώστε να είναι και να επενεργούν πάνω σε ένα αντικείµενο τύπου Author. Για την τάξη Author θα πρέπει επίσης να συµπεριλάβετε δύο µεθόδους: Την public void setter(int index, Book b) throws ArrayIndexOutOfBoundsException η οποία θα θέτει στην θέση index του πίνακα των βιβλίων ενός συγγραφέα το βιβλίο που δίνεται σαν δεύτερη παράµετρο. Η µέθοδος θα πρέπει να προκαλεί την εξαίρεση ArrayIndexOutOfBoundsException στην περίπτωση που η θέση index είναι εκτός των ορίων του πίνακα. Την public Book[] getter() η οποία επιστρέφει τον πίνακα των βιβλίων του συγγραφέα. Αφού δηλώσετε τις ποιο πάνω τάξεις κάνετε πρόγραµµα στο οποίο θα δηµιουργήσετε ένα αντικείµενο τύπου Author µε τον αγαπηµένο σας συγγραφέα. Στη συνέχεια θα δηµιουργήσετε δύο βιβλία του και θα κάνετε τις απαραίτητες συσχετίσεις. Τέλος θα εµφανίσετε λίστα µε τα βιβλία του συγγραφέα, χρησιµοποιώντας την getter µέθοδο για τον συγγραφέα αυτό. Άσκηση 3 Να γίνει Interface CryingObject το οποίο θα διαθέτει τη µέθοδο public void cry(). Στη συνέχεια δηµιουργείστε την τάξη Baby που θα υλοποιεί το Interface αυτό. Η µέθοδος cry για το Baby απλά θα εµφανίζει στην οθόνη το µήνυµα "Crying all night long!". Επίσης δηµιουργείστε τάξη BluesSinger η οποία επίσης θα υλοποιεί το InterfaceCryingObject και της οποίας η µέθοδος θα εµφανίζει το µήνυµα "Crying when singing!". Στη συνέχεια δηµιουργείστε τάξη Main µε µέθοδο main η οποία θα δηµιουργεί πίνακα τύπου CryingObject µε δύο θέσεις . Στη πρώτη θέση τοποθετείστε ένα νέο αντικείµενο της τάξης Baby ενώ στη δεύτερη τοποθετείστε ένα αντικείµενο της τάξης BluesSinger. Τέλος, η main θα εκτελεί βρόχο (loop) στον οποίο θα καλεί τη µέθοδο cry σε όλα τα αντικείµενα του πίνακά της. Άσκηση 4 Να γίνει πρόγραµµα µε τις ακόλουθες τάξεις όπως φαίνονται στο UML διάγραµµα που δίνεται:

Η τάξη Employee είναι υπερτάξη των τάξεων SalariedEmployee και HourlyEmployee. Η υπερτάξη έχει τις ιδιότητες name (όνοµα) και afm (ΑΦΜ) και µεθόδους για να θέτουµε και να ανακτούµε τις ιδιότητες αυτές. Επίσης έχει την αφηρηµένη (abstract) µέθοδο payment(). Εποµένως και η τάξη Employee θα πρέπει να δηλωθεί ως abstract.

- 68 -

Page 69: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

Η υποτάξη SalariedEmployee (τακτικός υπάλληλος) έχει επίσης την ιδιότητα salary και µία set µέθοδο για να θέτει αυτή την ιδιότητα. Επίσης η µέθοδος δηλώνει την αφηρηµένη τάξη payment της υπερτάξης έτσι ώστε να επιστρέφει τον salary. Η υποτάξη HourlyEmployee (ωροµίσθιος υπάλληλος) έχει επίσης τις ιδιότητες hoursWorked (ώρες εργασίας) και hourlyPayment (ωροµίσθιο). Επίσης δηλώνει τις µεθόδους get και set γι' αυτές τις ιδιότητες.Τέλος, δηλώνει την µέθοδο payment που είναι ο ορισµός της abstract µεθόδου της υπερτάξης. Η µέθοδος αυτή θα επιστρέφει το γινόµενο των ωρών εργασίας επί του ωροµισθίου. Να κατασκευάσετε την πιο πάνω ιεραρχία τάξεων και στη συνέχεια να κάνετε µία τάξη Main µε µία συνάρτηση main. Η main θα έχει ένα πίνακα δυο θέσεων ο οποίος θα είναι τύπου Employee. Στην θέση 0 του πίνακα να δηµιουργήσετε και να βάλετε ένα αντικείµενο SalariedEmployee και στη θέση 1 του πίνακα να δηµιουργήσετε και να βάλετε ένα αντικείµενο HourlyEmployee. Στη συνέχεια να θέσετε τα στοιχεία του πρώτου υπαλλήλου και τον µισθό του και τα στοιχεία του δεύτερου υπαλλήλου συµπεριλαµβανοµένων και των ωρών εργασίας και του ωροµισθίου. Τέλος, η main θα πρέπει να εκτελεί ένα βρόχο στον οποίο θα εµφανίσει τον µισθό των δύο υπαλλήλων καλώντας τη µέθοδο payment στα αντικείµενα του πίνακά της.

Προτεινόµενες λύσεις Άσκηση 1 public class Book private String title; private String author; private int firstEdition; public Book() title = ""; author=""; firstEdition=0; public void setTitle(String t) title = t; public String getTitle() return title; public void setAuthor(String a) author = a; public String getAuthor() return author; public void setFirstEdition(int fe) firstEdition = fe;

- 69 -

Page 70: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

public int getFirstEdition() return firstEdition; public class Main public static void main (String[] args) //Dimioyrgia toy pinaka 3 books Book books[] = new Book[3]; for (int i=0; i<3; i++) books[i] = new Book(); //Stoixeia gia to 1o vivlio books[0].setAuthor("Jaworski Jamie"); books[0].setTitle("Java 2 Unleashed"); books[0].setFirstEdition(1999); //Stoixeia gia to 2o vivlio books[1].setAuthor("Stroustrup Bjarne"); books[1].setTitle("The C++ Programming Language"); books[1].setFirstEdition(1991); //Stoixeia gia to 3o vivlio books[2].setAuthor("Szyperski Clemens"); books[2].setTitle("Component Software - Beyond O-O Programming"); books[2].setFirstEdition(1997); //Taxinomisi twn vivliwn me ti bubble sort int count = books.length; for (int i=1; i<count; i++) for (int j=count-1; j>=i; j--) //An xreiazetai kane swap if (books[j-1].getFirstEdition() > books[j].getFirstEdition()) Book tempBook = books[j-1]; books[j-1] = books[j]; books[j] = tempBook; //emfanise ta vivlia for (int i=0; i<books.length; i++) System.out.println("Syggrafeas: "+books[i].getAuthor()+ ", Vivlio: "+books[i].getTitle()+ ", Etos Prwtis Ekdosis:"+ books[i].getFirstEdition()); Άσκηση 2 public class Author

- 70 -

Page 71: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

private final static int MAX_BOOKS=20; private String name; private Book books[] = new Book[MAX_BOOKS]; public Author() String name=""; for (int i=0; i<MAX_BOOKS; i++) books[i] = null; public void setName(String n) name=n; public String getName() return name; public void setter(int index, Book b) throws ArrayIndexOutOfBoundsException if (index>=0 && index<MAX_BOOKS) books[index] = b; else throw new ArrayIndexOutOfBoundsException(); public Book[] getter() return books; public class Book private String title; private int firstEdition; private Author author; public Book() title = ""; author=null; firstEdition=0; public void setTitle(String t) title = t; public String getTitle()

- 71 -

Page 72: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

return title; public void setAuthor(Author a) author = a; public Author getAuthor() return author; public void setFirstEdition(int fe) firstEdition = fe; public int getFirstEdition() return firstEdition; public class Main public static void main (String[] args) //Dimiourgia enos siggrafea Author booch = new Author(); booch.setName("Booch G."); //Dimiourgia tou 1ou vivliou Book b1 = new Book(); b1.setTitle("Software Components with Ada"); b1.setFirstEdition(1987); //Dimiourgia tou 2ou vivliou Book b2 = new Book(); b2.setTitle("Object Oriented Analysis and Design, 2nd Ed."); b2.setFirstEdition(1994); //Sysxetisi toy siggrafea me ta vivlia toy booch.setter(0, b1); booch.setter(1, b2); //Sysxetisi toy vivlioy me ton siggrafea toy b1.setAuthor(booch); b2.setAuthor(booch); //Emfanisi twn vivliwn toy siggrafea Book b[] = booch.getter(); for (int i=0; i<b.length; i++) if (b[i] != null) System.out.println(b[i].getTitle());

- 72 -

Page 73: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

Άσκηση 3 public interface CryingObject public void cry(); public class Baby implements CryingObject public void cry() System.out.println("Crying all night long!"); public class BluesSinger implements CryingObject public void cry() System.out.println("Crying when singing!"); public class Main public static void main (String[] args) CryingObject co[] = new CryingObject[2]; //dimiourgia toy Baby Baby b = new Baby(); //dimiourgia toy BluesSinger BluesSinger bs = new BluesSinger(); //topothetisi twn antikeimenwn ston pinaka co[0] = b; co[1] = bs; for (int i=0; i<co.length; i++) co[i].cry(); Άσκηση 4 abstract public class Employee String name; String afm; public Employee()

- 73 -

Page 74: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

name = ""; afm = ""; public void setName(String n) name = n; public String getName() return name; public void setAfm(String a) afm = a; public String getAfm() return afm; abstract public int payment(); public class SalariedEmployee extends Employee public int salary; public SalariedEmployee() super(); salary = 0; public void setSalary(int s) salary = s; public int payment() return salary; public class HourlyEmployee extends Employee public int hoursWorked; public int hourlyPayment; public HourlyEmployee() super(); hoursWorked = hourlyPayment = 0;

- 74 -

Page 75: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

public void setHoursWorked(int hw) hoursWorked = hw; public int getHoursWorked() return hoursWorked; public void setHourlyPayment(int hp) hourlyPayment = hp; public int getHourlyPayment() return hourlyPayment; public int payment() return hoursWorked*hourlyPayment; public class Main public static void main (String[] args) Employee emp[] = new Employee[2]; //O prwtos ypalilos plirwnetai me to mina emp[0] = new SalariedEmployee(); emp[0].setName("Mikelis"); emp[0].setAfm("777777"); ((SalariedEmployee) emp[0]).setSalary(300000); //O deyteros ypallilos einai oromisthios emp[1] = new HourlyEmployee(); emp[1].setName("Karamitros"); emp[1].setAfm("888888"); ((HourlyEmployee) emp[1]).setHourlyPayment(4000); ((HourlyEmployee) emp[1]).setHoursWorked(30); /*Twra anatrexoume ton pinaka kai kaloyme tin methodo payment() se kathe employee gia na emfanisoyme tin plirwmi toy */ for (int i=0; i<emp.length; i++) System.out.println("Employee's #"+i+ " salary is "+emp[i].payment());

- 75 -

Page 76: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

Κεφάλαιο 3: Java Applets

Java Applets: Εισαγωγή Σκοποί αυτού του κεφαλαίου Στα επόµενα µαθήµατα θα γνωρίσουµε την τεχνολογία των Java Applets. Η τεχνολογία των applets είναι µία τεχνολογία που επιτρέπει ένα πρόγραµµα java (το applet) να τρέχει σε µία σελίδα του web. Άρα είναι ένας από τους τρόπους που µπορούν να χρησιµοποιηθούν για να κατασκευαστούν προγράµµατα για το Internet. Στα επόµενα µαθήµατα οι σκοποί µας θα είναι οι ακόλουθοι:

− Να γνωρίσουµε τι είναι τα Applets και πως µπορούµε να κατασκευάσουµε ένα − Να γνωρίσουµε τον κύκλο ζωής ενός Applet και τις µεθόδους-σταθµούς στη ζωή του − Να γνωρίσουµε πως µπορούµε να ενσωµατώσουµε ένα Applet σε µία σελίδα HTML − Να δούµε πως µπορούµε να θέσουµε κάποια παραθυρικά συστατικά (windows

components) σε ένα applet και µέσα από αυτά τα µαθήµατα να γνωρίσουµε το πακέτο java.awt

∆ιευκρινίζεται ότι αυτό είναι το πρώτο µέρος στη χρήση των applets. Άλλα ποιο προχωρηµένα θέµατα θα καλυφτούν σε επόµενο κεφάλαιο. ∆ιευκρινίζεται ότι από αυτό το σηµείο και µετά τα θέµατα που καλύπτονται αφορούν το Java API και ενώ εδώ καλύπτονται οι βασικές τάξεις και οι µέθοδοι αυτών των τάξεων σε καµία περίπτωση η κάλυψη αυτή δεν είναι πλήρης. Το Java API είναι κολοσσιαίο και οποιαδήποτε προσπάθεια πλήρους κάλυψής του θα οδηγούσε σε ένα βιβλίο που θα ζύγιζε µερικές δεκάδες κιλά ή θα οδηγούσε σε µια κανονικού µεγέθους εγκυκλοπαίδεια. Επίσης διατηρώ µια επιφύλαξη ως προς το κατά πόσο βιβλία αυτού του τύπου είναι χρήσιµα µια και επιδίδονται περισσότερο στην επιγραµµατική αναφορά των τάξεων και των µεθόδων χωρίς να είναι ούτε εκπαιδευτικά αλλά ούτε, τελικά, πλήρη. Προσωπικά πάντοτε ήµουν της άποψης ότι ο προγραµµατιστής οποιασδήποτε γλώσσας προγραµµατισµού θα πρέπει να έχει στο γραφείο του µερικά tutorials και ένα (ή περισσότερα) manual. Το παρόν σύγγραµµα φιλοδοξεί να παίξει το ρόλο ενός tutorial για τη Java. Στο ρόλο του manual µπορείτε να χρησιµοποιήσετε το on-line εγχειρίδιο αναφοράς της Sun για το Java 2 API που µπορείτε να το βρείτε στο: http://java.sun.com/products/jdk/1.2/docs/api

Τι είναι ένα Applet Ένα Applet είναι ένα πρόγραµµα Java το οποίο εκτελείται σε µία ιστοσελίδα. Τα βήµατα που πρέπει να κάνετε για να δηµιουργήσετε ένα Java Applet είναι τα ακόλουθα: Πρώτα γράφετε το Applet ακριβώς όπως γράφετε και κάθε άλλο πρόγραµµα Java. Στη συνέχεια µεταφράζετε το Applet για να παράγετε το αρχείο µε επέκταση class µε τα bytecodes Τέλος, ενσωµατώνετε το Applet σε µία ιστοσελίδα χρησιµοποιώντας (κατ' ελάχιστο) την ετικέτα <Applet> η οποία έχει την εξής (ελάχιστη) µορφή: <applet code = όνοµα-αρχείου.class width=πλάτος height=ύψος></applet> Όπως βλέπετε η ετικέτα applet έχει τις υποχρεωτικές φράσεις code, width και height. Το code προσδιορίζει το αρχείο που θα εκτελεστεί όταν θα φορτωθεί η ιστοσελίδα που περιέχει την

- 76 -

Page 77: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

ετικέτα σε ένα browser. Το width προσδιορίζει το πλάτος που θα καταλάβει το applet σε εικονοστοιχεία (pixels) στην περιοχή του browser και το height είναι το ύψος που θα καταλάβει το applet σε pixels στην περιοχή του browser. Τέλος το </applet> τερµατίζει την ετικέτα Applet. Το Applet θα καταλάβει το χώρο αυτό στο σηµείο που βρίσκετε στην ιστοσελίδα. Ένα Applet εκτελείται όταν φορτώνεται η ιστοσελίδα που το περιέχει σε κάποιο browser. Όπως είναι προφανές από την πιο πάνω συζήτηση ένα Applet θα πρέπει να έχει µια οπτική παράσταση για να µπορεί να εµφανιστεί σε µια ιστοσελίδα και να καταλάβει κάποιο χώρο σε αυτή. Γι' αυτό το λόγο τα Applets είναι υποτάξεις της τάξης java.awt.Panel. Σαν υποτάξη της τάξης Panel ένα Applet έχει διαστάσεις που µπορούν να καθοριστούν από την µέθοδο setBounds και µπορεί να λειτουργήσει ως container άλλων παραθυρικών συστατικών (windows components). ∆ηλαδή σε ένα Applet µπορούµε να έχουµε παραθυρικά συστατικά όπως κουµπιά (buttons), πλαίσια ελέγχου (checkboxes) κ.λ.π. Αυτό που δεν µπορούµε να έχουµε είναι µενού και αυτό διότι τα µενού προσαρτώνται µόνο σε παράθυρα (Frames) και ένα Panel δεν είναι Frame. Αυτή η ιεραρχία τάξεων που περιγράφηκε πιο πάνω είναι όπως δείχνει το ακόλουθο UML µοντέλο:

Όπως παρατηρείτε ένα Applet παρότι επεκτείνει τη τάξη java.awt.Panel και εποµένως είναι java.awt.Container για να µπορεί να περιέχει όλα τα παραθυρικά συστατικά που θα αποτελέσουν την γραφική του εικόνα προς το χρήστη δεν ανήκει στο πακέτο java.awt αλλά στο πακέτο java.Applet. Πως γράφουµε ένα Applet. Ένα πρόγραµµα Java το οποίο θα χρησιµοποιηθεί σαν Applet θα πρέπει να επεκτείνει (extend) την τάξη java.applet.Applet. Έτσι, συνήθως, κάνουµε import το πακέτο java.applet όπως και το πακέτο java.awt σε κάθε Applet. Ένα Applet έχει λοιπόν, συνήθως, την ακόλουθη γενική µορφή:

import java.awt.*; import java.applet.*; public class όνοµα-τάξης-Applet extends Applet

Ένα απλό Applet

- 77 -

Page 78: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

Στα πρώτα µαθήµατά µας είδαµε ένα Applet - το applet HelloWorld - το οποίο εµφάνιζε στη περιοχή του Applet το µήνυµα HelloWorld!. Θα επαναλάβουµε αυτό το Applet εδώ για να δούµε λίγο καλύτερα τα συστατικά του τώρα που γνωρίζουµε λίγα περισσότερα. import java.applet.Applet; import java.awt.Graphics; public class HelloWorldApplet extends Applet public void paint(Graphics g) g.drawString("Hello world!", 50, 25); Όπως βλέπουµε λοιπόν το Applet αυτό επεκτείνει τη τάξη java.applet.Applet. Επίσης αντί να εισάγουµε µε import όλο το πακέτο java.awt εισάγουµε µόνο τη τάξη java.awt.Graphics που χρησιµοποιούµε για τη µέθοδο paint. Η µέθοδος paint είναι µια µέθοδος της τάξης Container που είναι υπερτάξη της τάξης Panel που µε τη σειρά της είναι υπερτάξη της τάξης Applet. Η µέθοδος αυτή καλείται αυτόµατα όταν εµφανίζεται το Applet και δουλειά της είναι να εµφανίσει το Container (δηλαδή το Applet) και όλα τα συστατικά του στην οθόνη. Στη συγκεκριµένη περίπτωση αν δεν υπερβαίναµε (override) τη µέθοδο paint το Applet µας θα ήταν ένα πολύ βαρετό Applet µια και δεν θα βλέπαµε απολύτως τίποτα στη περιοχή του. Βεβαίως και τώρα που την υπερβαίνουµε δεν κάνει και τίποτα το συγκλονιστικό. Το µόνο που κάνει είναι να εµφανίσει στην οθόνη τη φράση Hello World!. Παρόλα αυτά µην ξεχνάτε ότι είµαστε ακόµα στην αρχή. Τα πράγµατα θα γίνουνε πιο ενδιαφέροντα στα επόµενα µαθήµατα. Ίσως να απορείτε για το τι είναι η παράµετρος g που είναι ένα αντικείµενο της τάξης java.awt.Graphics. Αυτή είναι, ουσιαστικά η περιοχή σχεδίασης του Applet και διαθέτει µεθόδους που µας επιτρέπουν να γράφουµε, να σχεδιάζουµε, να εµφανίζουµε εικόνες κ.λ.π. πάνω στην επιφάνεια του Applet όπως η drawString που καλείται και εµφανίζει το String που δίνεται σαν πρώτη παράµετρος. Το String αυτό εµφανίζεται χρησιµοποιώντας σαν την πάνω αριστερή γωνία τη θέση που προσδιορίζεται από τις συντεταγµένες x και y που δίνονται σαν δεύτερη και τρίτη παράµετρος αντίστοιχα. Όταν, εποµένως, φορτωθεί η ιστοσελίδα που θα περιέχει το πιο πάνω Applet, θα εµφανιστεί η φράση "Hello World!" στη περιοχή του Applet. Αλλά πως περιλαµβάνουµε ένα Applet σε µία ιστοσελίδα; Η απάντηση αυτή ήδη έχει δοθεί: Θα πρέπει να βάλουµε στην ιστοσελίδα την ετικέτα <Applet> προσδιορίζοντας ως code το αρχείο class του Applet. Η HTML που απαιτείται γι' αυτό δίνεται στη συνέχεια: <applet code="HelloWorldApplet.class" width=200 height=100> </applet>

Μέθοδοι-Σταθµοί για ένα Applet Σ' αυτό το µάθηµα θα εξηγήσουµε τέσσερις µεθόδους-σταθµούς στη ζωή ενός Applet. Τις µεθόδους: init(), start(), stop() και destroy(). Αυτές οι µέθοδοι καλούνται αυτόµατα σε διάφορα σηµεία της ζωής ενός Applet και είναι χρήσιµο να καταλάβουµε τι σηµαίνουν και πως µπορούµε να τις χρησιµοποιήσουµε. Τέλος, θα δούµε ένα παράδειγµα της χρήσης τους. Η µέθοδος init() Στα applets συνήθως δεν έχει νόηµα η κατασκευή κάποιου constructror (κατασκευαστή) δηλαδή κάποιας µεθόδου που θα κατασκευάσει το Applet. Αυτό γιατί τα αντικείµενα της τάξης αυτής δεν τα κατασκευάζουµε εµείς µε την εντολή new αλλά τα κατασκευάζει το σύστηµα εκτέλεσης (java runtime system) σαν αποτέλεσµα της φόρτωσης της σελίδας η οποία περιέχει το Applet σε ένα πρόγραµµα πλοήγησης (browser) µε δυνατότητες Java (java-enabled browser). Παρόλα αυτά είναι πολύ πιθανόν να θέλουµε να κάνουµε κάποιες αρχικοποιήσεις όταν φορτωθεί το Applet για πρώτη φορά. Αυτό επιτυγχάνεται µε την µέθοδο init(), η οποία πρέπει να έχει την ακόλουθη γενική µορφή:

public void init()

- 78 -

Page 79: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

εντολές αρχικοποίησης του Applet

Η κλήση της init όπως ήδη ανέφερα δεν γίνεται από εµάς αλλά από το σύστηµα εκτέλεσης κάθε φορά που φορτώνεται η σελίδα που περιέχει το Applet για πρώτη φορά από ένα πρόγραµµα πλοήγησης. Η µέθοδος start() Η µέθοδος start() µπορείτε να φανταστείτε ότι είναι κάτι ανάλογο µε την main(). Μας δίνει δηλαδή τη δυνατότητα να ξεκινήσουµε κάποιες επεξεργασίες αφού φορτωθεί το Applet. Το Applet βέβαια συνήθως, θα παρέχει µία γραφική διεπαφή χρήστη (ένα Graphical User Interface - GUI) µε τη χρήση της οποίας ο χρήστης θα µπορεί να κάνει κάποιες ενέργειες. Το περιβάλλον εκτέλεσης ενός Applet είναι δηλαδή οδηγούµενο από συµβάντα τα οποία προκαλεί ο χρήστης (event-driven). Έτσι µπορεί η µέθοδος start να µην απαιτείται και καθόλου αν δεν θέλουµε να ξεκινήσει καµία επεξεργασία και θέλουµε να γίνουν όλα µόλις ο χρήστης προκαλέσει τα κατάλληλα συµβάντα (για παράδειγµα πατήσει ένα κουµπί - button). Η µέθοδος start() πρέπει να έχει την ακόλουθη µορφή:

public void start() ενέργειες που γίνονται µόλις ξεκινά το Applet

Όπως και η µέθοδος init έτσι και η µέθοδος start καλείται αυτοµάτως από το σύστηµα εκτέλεσης. Η διαφορά τους βρίσκεται στο ότι η µέθοδος start καλείται και τη πρώτη φορά που θα φορτωθεί το Applet, αλλά και τις επόµενες που ο χρήστης θα επιστρέψει στη σελίδα µε το Applet µε το πλήκτρο "Back" του προγράµµατος πλοήγησης και η σελίδα εµφανιστεί στο παράθυρο του προγράµµατος πλοήγησης και πάλι. Η µέθοδος stop() Η µέθοδος stop() καλείται από το σύστηµα εκτέλεσης κάθε φορά που ο χρήστης φεύγει από τη σελίδα που περιέχει το Applet είτε για να πάει σε κάποια άλλη σελίδα είτε για να πάει σε κάποιο άλλο πρόγραµµα. Η µορφή της µεθόδου stop() είναι η ακόλουθη:

public void stop() ενέργειες που γίνονται µόλις σταµατά το Applet

Η µέθοδος destroy() Η µέθοδος destroy() καλείται κάθε φορά που τερµατίζεται η εκτέλεση του Applet συνήθως σαν αποτέλεσµα του κλεισίµατος του προγράµµατος πλοήγησης στο οποίο έχει φορτωθεί η σελίδα µε το Applet και χρησιµοποιείται για ενδεχόµενες τελικές ενέργειες που µπορεί να θέλουµε να συµβούν λίγο πριν σταµατήσει το Applet να εκτελείται. Η µέθοδος αυτή έχει τη µορφή:

public void destroy()

- 79 -

Page 80: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

εντολές για τελικές ενέργειες πριν σταµατήσει η εκτέλεση του Applet

Ένα παράδειγµα Το Appet που θα κάνουµε εµφανίζει το πλήθος των φορών που κλήθηκαν η init(), η start(), η stop() και η destroy(). Η µορφή του Applet στον browser είναι όπως στην εικόνα που ακολουθεί:

Θα πρέπει να διευκρινίσουµε ότι το τι ακριβώς θα δείτε στο πιο πάνω Applet εξαρτάται και από το πρόγραµµα πλοήγησης στο οποίο βλέπετε τη σελίδα και από το πότε ακριβώς καλεί τις µεθόδους start και stop. ∆εν έχουν όλα τα προγράµµατα πλοήγησης την ίδια συµπεριφορά σ' αυτό το σηµείο. Το σίγουρο είναι ότι θα δείτε ότι η µέθοδοι init και start θα πρέπει να κλήθηκαν από µια φορά όταν φορτώσατε τη σελίδα. Πάντως αν θέλετε να βεβαιωθείτε για την ορθότητα του κώδικά σας µπορείτε να δοκιµάσετε το Applet µε τον appletviewer ο οποίος στο µενού "Applet" έχει τις επιλογές Start και Stop που ξεκινάνε και σταµατάνε το Applet αντίστοιχα, όπως δείχνει και η ακόλουθη εικόνα:

Ο κώδικας του Applet είναι ο ακόλουθος:

import java.applet.*; import java.awt.*; public class Milestones extends Applet int timesInitialized, timesStarted, timesStopped, timesDestroyed; private String composeString() return "Init: "+timesInitialized+ ", Start: "+timesStarted+ ", Stop: "+timesStopped+ ", Destroyed: "+timesDestroyed; public void destroy() timesDestroyed++; repaint();

- 80 -

Page 81: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

public void init() timesInitialized = 1; timesStarted = 0; timesStopped = 0; timesDestroyed = 0; repaint(); public void paint(Graphics g) g.drawString(composeString(), 20, 20); public void start() timesStarted++; repaint(); public void stop() timesStopped++; repaint();

Παρατηρείστε ότι µέσα σε κάθε µέθοδο (init, start, stop και destroy) καλούµε τη µέθοδο repaint αφού αυξήσουµε τον αντίστοιχο µετρητή. Η repaint θα προκαλέσει τη κλήση της paint που µε τη σειρά της "γράφει" στις συντεταγένες (20, 20) το String που επιστρέφει η ιδιωτική µέθοδος composeString που συνθέτει το String που βλέπουµε στην οθόνη. Για να δούµε το Applet να τρέχει σε ένα browser ή µε τον appletviewer θα πρέπει να κατασκευάσουµε και µία HTML σελίδα που να περιέχει την ετικέτα <Applet>. Για παράδειγµα ένα αρχείο µε την ακόλουθη γραµµή και την επέκταση html θα αρκούσε γι' αυτό: <APPLET CODE=Milestones.class WIDTH=300 HEIGHT=100></APPLET>

H HTML ετικέτα Applet Την HTML ετικέτα Applet την έχουµε δει ως τώρα µόνο µε τις απαραίτητες φράσεις της που είναι οι code, width και height. Υπάρχουν όµως και άλλες φράσεις της ετικέτας Applet που µπορούµε να χρησιµοποιήσουµε για να κάνουµε πιο ευέλικτο ένα Applet. Η αποτελεσµατική, εποµένως, χρήση ενός Applet επιβάλλει µια πιο σοβαρή µελέτη της ετικέτας Applet και αυτή θα κάνουµε σ' αυτό το µάθηµα. Πέρασµα παραµέτρων σε ένα Applet µε την ετικέτα param Ίσως η πιο χρήσιµη δυνατότητα της ετικέτας Applet είναι ότι µας επιτρέπει να περάσουµε κάποιες παραµέτρους σε ένα Applet επιτρέποντας έτσι τη διαµόρφωσή του για τους σκοπούς µας κάθε φορά. Αν ένα Applet γίνει αρκετά παραµετρικό τότε θα µπορούµε να το χρησιµοποιήσουµε σε διάφορες περιπτώσεις. Για το πέρασµα των παραµέτρων σε ένα Applet θα πρέπει να χρησιµοποιήσουµε την ετικέτα <param> η οποία θα πρέπει να εµφανίζεται µετά από την ετικέτα <applet> και πριν το κλείσιµό της µε την </applet>. Σε κάθε παράµετρο θα πρέπει να δώσουµε ένα όνοµα (name) και µια τιµή (value). Το Applet τότε, συνήθως κατά την αρχικοποίησή του, θα µπορεί να χρησιµοποιήσει τη µέθοδο getParameter() της τάξης Applet για να βρει ποια τιµή (value) έχει µια παράµετρος µε ένα δοθέν όνοµα (name). Επίσης το

- 81 -

Page 82: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

Applet θα πρέπει να αντιµετωπίσει και τις περιπτώσεις που ο χρήστης δεν έχει δώσει τιµή σε κάποια παράµετρο µε την ετικέτα <parameter> αλλά και την περίπτωση που ο χρήστης έχει δώσει µια τιµή που δεν είναι έγκυρη. Σ' αυτές τις περιπτώσεις το Applet θα πρέπει να δώσει κατά πάσα πιθανότητα µια εξ ορισµού τιµή. Η µορφή που πρέπει να έχει η ετικέτα <param> σε ένα HTML κείµενο είναι η ακόλουθη:

<applet ....> <param name=όνοµα-παραµέτρου-1 value=τιµή-παραµέτρου-1> ... <param name=όνοµα-παραµέτρου-ν value=τιµή-παραµέτρου-ν> </applet>

Η ακόλουθη εικόνα δείχνει πιο παραστατικά τον τρόπο µε τον οποίο ανακτά ένα Applet µια παράµετρο µε τη χρήση της µεθόδου getParameter():

Όπως φαίνεται και απο την εικόνα το πέρασµα παραµέτρων από την HTML σελίδα γίνεται µε την ετικέτα <param> ενώ η ανάκτηση της τιµής της από το Applet γίνεται µε την getParameter(). Αν ο χρήστης δεν έχει καµία παράµετρο µε το name "onoma" τότε το η getParameter() θα επιστρέψει null και στη συγκεκριµένη περίπτωση το Applet απλά θα χρησιµοποιήσει την εξ ορισµού τιµή "Guest". Το ακόλουθο Applet χρησιµοποιεί τη παράµετρο onoma που περνάει ο χρήστης για να πει Hello στο χρήστη. Αν δεν έχει δοθεί παράµετρος µε το όνοµα onoma τότε το Applet εµφανίζει το µήνυµα "Hello Guest". Επίσης ο χρήστης µπορεί να περάσει σαν παράµετρο την ηλικία του. Επειδή η παράµετρος ηλικία είναι String για να χρησιµοποιηθεί σαν int θα χρειαστεί να µετατραπεί σε int. Αυτό γίνεται µε την κλήση της Integer.parseInt(strAge) η οποία όµως µπορεί να προκαλέσει την εξαίρεση NumberFormatException αν ο χρήστης έχει δώσει µεν ηλικία αλλά η ηλικία δεν είναι ένας ακέραιος αριθµός. Όπως µπορείτε να δείτε αν ο χρήστης δεν δώσει παράµετρο ilikia ή αν η τιµή που έδωσε στη παράµετρο αυτή δεν είναι έγκυρη τότε η µεταβλητή age τίθεται ίση µε 0 και δεν εµφανίζεται η ηλικία στο µήνυµα. ∆ιαφορετικά εµφανίζεται. Το applet HelloMister έχει ως εξής:

import java.awt.*; import java.applet.*; public class HelloMister extends Applet private String name;

- 82 -

Page 83: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

private int age; public void init() name = getParameter("onoma"); if (name == null) name = "Guest"; String strAge = getParameter("ilikia"); age=0; if (strAge != null) try age = Integer.parseInt(strAge); catch (NumberFormatException e) public void paint(Graphics g) if (age == 0) g.drawString("Hello "+name, 20, 20); else g.drawString("Hello "+name+". Your age is "+age, 20, 20);

Για να δείτε το applet να τρέχει θα πρέπει να δηµιουργήσετε και µια HTML σελίδα µε την οποία θα αποδώσετε και τιµές στις παραµέτρους onoma και ilikia. Για παράδειγµα αν δώσετε σε µία HTML σελίδα το ακόλουθο κείµενο:

<applet code=HelloMister.class width=400 height=50> <param name="onoma" value="George"> <param name="ilikia" value=33> </applet>

Όταν θα φορτώσετε τη σελίδα θα δείτε κάτι τέτοιο:

Η φράση alt και το εναλλακτικό κείµενο Μπορεί ένα πρόγραµµα πλοήγησης εφοδιασµένο µε δυνατότητες Java (Java-enabled browser) να µην µπορεί να τρέξει το Applet για κάποιο λόγο ή µπορεί το πρόγραµµα πλοήγησης που χρησιµοποιεί ο χρήστης να καταλαβαίνει µεν την ετικέτα <Applet> αλλά να µη µπορεί να εκτελέσει το Applet, να µην είναι δηλαδή εφοδιασµένο µε δυνατότητες Java. Σ' αυτή τη περίπτωση µπορείτε να χρησιµοποιήσετε τη φράση ALT της ετικέτας Applet για να δώσετε τη δυνατότητα στο πρόγραµµα πλοήγησης τουλάχιστον να εµφανίσει ένα µήνυµα εκεί που κανονικά θα έπρεπε να τρέχει το Applet.

<applet code=anApplet.class width=200 height=200 ALT="∆εν µπορώ να τρέξω το Applet anApplet">

- 83 -

Page 84: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

... </applet>

Επίσης για περιπτώσεις που ένα πρόγραµµα πλοήγησης (browser) δεν καταλαβαίνει καθόλου την ετικέτα Applet µπορείτε να χρησιµοποιήσετε το εναλλακτικό κείµενο έτσι ώστε να πληροφορήσετε το χρήστη του προγράµµατος πλοήγησης ότι µάλλον θα έπρεπε να το αλλάξει µε κάποιο άλλο πρόγραµµα πλοήγησης! Το εναλλακτικό κείµενο είναι οποιοδήποτε κείµενο εµφανίζεται µετά από όλες τις ετικέτες <param> και πριν την ετικέτα </applet>. Αυτό το κείµενο θα αγνοηθεί από τα προγράµµατα πλοήγησης τα οποία έχουν τη δυνατότητα να καταλάβουν την ετικέτα <applet> ενώ τα υπόλοιπα θα το εµφανίσουν.

<applet code=anApplet.class width=200 height=200 ALT="∆εν µπορώ να τρέξω το Applet anApplet"> ... ∆εν νοµίζεις ότι είναι καιρός να εκσυγχρονιστείς; </applet>

Η φράση codebase Φυσιολογικά το applet φορτώνεται από την περιοχή από την οποία κατέβηκε και η σελίδα που το περιείχε. Αν το Applet περιέχεται σε κάποιο άλλο κατάλογο ή ακόµα και σε κάποιο άλλο υπολογιστή µπορείτε να χρησιµοποιήσετε την φράση codebase για να προσδιορίσετε που βρίσκετε το Applet (το αρχείο µε την επέκταση class) αλλά και τα άλλα αρχεία στα οποία αυτό αναφέρεται. Η χρήση σχετικής διαδροµής στη φράση codebase έχει τη σηµασία ότι το Applet βρίσκεται στη διαδροµή που δίνεται η οποία βρίσκεται κάτω από τον κατάλογο από τον οποίο κατέβηκε η ιστοσελίδα που το περιέχει. Η χρήση ενός απόλυτου URL µπορεί να έχει σαν αποτέλεσµα το Applet να κατέβει από ένα τελείως διαφορετικό web server. Η φράση codebase της ετικέτας Applet µπορεί να γραφεί ως εξής: <applet code=anApplet.class widht=.. height=... alt=... codebase=σχετικό-ή-απόλυτο-URL> ... </applet> Αν για παράδειγµα δηµιουργήσετε µια HTML σελίδα µε το ακόλουθο κείµενο στο δίσκο σας στο δικό σας προσωπικό υπολογιστή και στη συνέχεια ανοίξετε τη σελίδα αυτή µε κάποιο πρόγραµµα πλοήγησης τότε παρότι η σελίδα φορτώνεται από τον δικό σας προσωπικό Η/Υ το applet HelloWorldApplet.class θα αναζητηθεί στον HTTP server και στη διαδροµή κάτω από αυτό το server που προσδιορίζεται στη φράση codebase. Φυσικά θα πρέπει να έχετε σύνδεση µε το Internet όταν ανοίξετε αυτή την ιστοσελίδα για µπορέσετε να κατεβάσετε το Applet. Μια ακόµα προϋπόθεση είναι να µην έχει αλλάξει η θέση του αρχείου HelloWorldApplet.class και να εξακολουθεί να είναι εκεί που ήταν την ώρα που γράφονταν αυτές οι γραµµές. To HTML κείµενο έχει ως εξής:

<html> <body> <h1>Αυτό το applet κατεβαίνει από έναν άλλον http server</h1><br> <applet code ="HelloWorldApplet.class" width=400 height=100 codebase="http://www.cs.teilar.gr/gkakaron/java/Day1/"> </applet> </body> </html>

Η φράση archive Το κατέβασµα των αρχείων class από το Internet µπορεί να πάρει αρκετό χρόνο αν το Applet έχει πολλές τάξεις. Σε αυτή τη περίπτωση, επειδή όταν χρειάζεται να κατέβει το κάθε αρχείο class θα απαιτείται και η σύνδεση του υπολογιστή σας µε τον αποµακρυσµένο υπολογιστή, ο

- 84 -

Page 85: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

χρόνος εκτέλεσης µπορεί να µεγαλώσει όχι τόσο εξαιτίας του χρόνου µετάδοσης όσο εξαιτίας του χρόνου σύνδεσης. Για να αντιµετωπιστεί το πιο πάνω πρόβληµα µε το Java Development Kit (JDK) η Sun παρέχει το βοηθητικό πρόγραµµα jar που µπορεί να πακετάρει ένα αριθµό class αρχείων αλλά και άλλων πόρων (π.χ. αρχεία εικόνων, ήχων κ.λ.π.) σε ένα αρχείο µε την επέκταση jar. Το ακρωνύµιο jar προέρχεται από τις λέξεις Java ARchive. Πρόκειται για ένα αρχείο συµβατό µε τη µορφή των αρχείων zip. Έτσι δεν έχουµε µόνο οικονοµία χρόνου λόγω του ότι έχουµε µικρότερο συνολικό χρόνο επειδή µειώνεται ο αριθµός των απαιτούµενων συνδέσεων αλλά και επειδή υπάρχει συµπίεση των αρχείων στο αρχείο µε επέκταση jar. Για να προσδιορίσουµε ότι υπάρχει το αρχείο αυτό (ή τα αρχεία αυτά) και ότι θα πρέπει το πρόγραµµα πλοήγησης να το κατεβάσει χρησιµοποιούµε τη φράση archive µε την ετικέτα applet, ως εξής:

<applet ... archive=όνοµα-αρχείου-jar-1[,όνοµα-αρχείου-jar-2...]> ... </applet>

Για παράδειγµα στο ακόλουθο παράδειγµα το πρόγραµµα πλοήγησης θα αναζητήσει τη τάξη HelloWorldApplet.class, αλλά και ότι άλλους πόρους απαιτηθούν στη συνέχεια στο αρχείο hi.jar. Αν δεν βρει τη τάξη στο αρχείο αυτό, τότε θα αναζητήσει τη τάξη στο φάκελο του εξυπηρετητή που περιείχε την HTML σελίδα.

<applet code=HelloWorldApplet.class width=300 height=300 alt="I can't run this applet" archive="hi.jar"> </applet>

Για να δηµιουργήσετε ένα Java Archive όπως αναφέραµε µπορείτε να χρησιµοποιήσετε το βοηθητικό πρόγραµµα jar. Για παράδειγµα το πιο πάνω αρχείο περιέχει µόνο την τάξη HelloWorldApplet.class και για να δηµιουργηθεί δόθηκε η εντολή:

jar cvf hi.jar HelloWorldApplet.class

από το prompt του λειτουργικού συστήµατος. Οι επιλογές c, v και f έχουν τη σηµασία του create, verbose και filename αντίστοιχα και όταν τις δίνουµε τότε δηµιουργούµε (create) ένα νέο αρχείο jar, του οποίου το όνοµα (filename) θα προσδιορίσουµε µετά τις σηµαίες (στη περίπτωσή µας είναι το hi.jar) και θέλουµε να παραχθούν και µηνύµατα (verbose output) από το βοηθητικό πρόγραµµα jar. Για µια πλήρη λίστα των επιλογών του προγράµµατος jar, δώστε jar και πατήστε το enter. Μια ακόµα παρατήρηση είναι ότι η µορφή των αρχείων jar ενδέχεται να µην είναι κατανοητή από κάποια προγράµµατα πλοήγησης, αλλά είναι κατανοητή από τα πιο διαδεδοµένα όπως ο Internet Explorer ή ο Netscape Navigator. Οι φράσεις name, vspace, hspace και align Οι υπόλοιπες φράσεις της ετικέτας applet έχουν την ακόλουθη έννοια:

− name = όνοµα-applet: Με αυτή τη φράση µπορούµε να ονοµάσουµε το συγκεκριµένο αντικείµενο-Applet. Αυτή η δυνατότητα µπορεί να χρησιµοποιηθεί έτσι ώστε να µπορούν να επικοινωνήσουν δύο applets τα οποία µπορεί να βρίσκονται στην ίδια σελίδα

− vspace = αριθµός-pixels και hspace = αριθµός-pixels: Αν καθορίσουµε ένα αριθµό pixels µε τη φράση vspace τότε θα υπάρχει αυτή η απόσταση στο πάνω και κάτω µέρος του applet µε τα υπόλοιπα περιεχόµενα στη ιστοσελίδα. Αν καθορίσουµε ένα αριθµό pixels µε τη φράση hspace τότε θα υπάρχει αυτή η απόσταση στο αριστερό και δεξιό µέρος του applet µε τα υπόλοιπα περιεχόµενα της ιστοσελίδας.

- 85 -

Page 86: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

− align = στοίχιση: Με αυτή τη φράση καθορίζουµε τη στοίχιση του applet σε σχέση µε τα άλλα περιεχόµενα της ιστοσελίδας. Οι δυνατές τιµές αυτής της φράσης είναι: left, right, top, texttop, middle, absmiddle, baseline, bottom, absbottom.

Εισαγωγή στο πακέτο java.awt Τα applets έχουν γραφική διασύνδεση (Graphical User Interface – GUI) µε τα οποία ο χρήστης αλληλεπιδρά µε αυτά. Αυτή η γραφική διασύνδεση µπορεί να δηµιουργηθεί µε τις τάξεις του πακέτου java.awt. Το awt προέρχεται από τις λέξεις Abstract Windows Toolkit. Ορισµένες από τις τάξεις αυτού του πακέτου και τη χρήση τους θα δούµε στα επόµενα µαθήµατα. Τι είναι το πακέτο java.awt Μια από τις αιτίες που η Java έγινε τόσο δηµοφιλής είναι η ευκολία µε την οποία µπορεί κανείς να αναπτύξει παραθυρικές εφαρµογές και applets. Η ευκολία αυτή οφείλεται στο πακέτο java.awt το οποίο περιέχει τάξεις για τα παραθυρικά συστατικά τα οποία µπορούν να χρησιµοποιηθούν για την κατασκευή εφαρµογών και applets. Τα συστατικά αυτά περιλαµβάνουν πλήκτρα (buttons), πλαίσια ελέγχου (check boxes), πλαίσια κειµένου (text fields), περιοχές κειµένου (text areas) κ.ο.κ. Το πακέτο java.awt περιέχει τάξεις τις οποίες ο χρήστης µπορεί να τις χρησιµοποιήσει για την κατασκευή αντικειµένων αυτού του τύπου. Στη συνέχεια αυτά τα αντικείµενα µπορούν να προστεθούν (add) σε άλλα παραθυρικά συστατικά τα οποία παίζουν το ρόλο του container και τα οποία κληρονοµούν από την τάξη java.awt.Container, όπως η τάξη java.awt.Frame και η τάξη java.awt.Panel. Αυτές οι τάξεις µπορούν να εµφανιστούν στην οθόνη και µαζί τους και τα συστατικά που έχουµε προσθέσει σ' αυτά τα containers. Να σηµειώσετε ότι η τάξη java.applet.Applet που είναι η υπερτάξη κάθε applet επεκτείνει την τάξη java.awt.Panel. Αυτό βεβαίως σηµαίνει ότι ένα αντικείµενο της τάξης Applet (δηλαδή ένα Applet) µπορεί να περιέχει τα παραθυρικά συστατικά που προαναφέραµε, αφού είναι container. Έτσι σε ένα Applet µπορούµε να έχουµε πλήκτρα, λίστες, πλαίσια ελέγχου κ.λ.π. Ένα Applet δεν µπορεί να περιέχει µενού και αυτό διότι τα µενού είναι και αυτά παραθυρικά συστατικά τα οποία όµως µπορούν να προστεθούν µόνο σε παραθυρικά συστατικά που επεκτείνουν την τάξη java.awt.Window. Το java.awt.Panel δεν είναι υποτάξη αυτής της τάξης. Αυτή η (υπο-) ιεραρχία απεικονίζεται στο UML διάγραµµα που δώσαµε στο µάθηµα 'Τι είναι ένα Applet'. Έτσι µε αφορµή τα applets θα αναφερθούµε στα επόµενα µαθήµατα στα παραθυρικά συστατικά που µπορείτε να δηµιουργήσετε µε τις τάξεις του πακέτου java.awt και στο τρόπο που µπορείτε να τα χρησιµοποιήσετε για να προσθέσετε διαλογικότητα στα applets. Επίσης κάτι άλλο σηµαντικό είναι ο χειρισµός των συµβάντων που προκαλούνται από τους χρήστες. Για παράδειγµα όταν ένας χρήστης κάνει κλικ σε ένα πλήκτρο θα πρέπει να εκτελεστεί µια µέθοδος που θα χειριστεί αυτό το συµβάν και θα κάνει κάτι. Για να γίνει αντιληπτό ότι προκλήθηκε το συµβάν στο πλήκτρο θα πρέπει να έχει αντιστοιχηθεί στο πλήκτρο ο κατάλληλος ακροατής συµβάντων. Οι ακροατές αυτοί είναι αντικείµενα τάξεων που υλοποιούν τα interfaces που ορίζονται στο πακέτο java.awt.event. Έτσι λοιπόν το πακέτο αυτό καθώς και παραδείγµατα χειριστών συµβάντων θα δούµε στα επόµενα µαθήµατα. Τι θα συζητήσουµε στα επόµενα µαθήµατα Τα κύρια θέµατα που θα διαπραγµατευτούµε στα επόµενα µαθήµατα είναι τα ακόλουθα:

− Πλήκτρα (buttons) − Χειρισµός συµβάντων − Παραθυρικά συστατικά κειµένου − Πλαίσια ελέγχου − Λίστες

- 86 -

Page 87: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

− ∆ιάταξη των παραθυρικών συστατικών

Σηµειώστε ότι το πακέτο java.awt εξακολουθεί να υπάρχει και στη Java 2, αν και η Sun ενθαρρύνει τελευταία τη χρήση ενός άλλου πακέτου του swing το οποίο θα µελετήσουµε σε επόµενα µαθήµατα. Το swing έχει ορισµένα πλεονεκτήµατα σε σχέση µε το awt στα οποία θα αναφερθούµε όταν θα µιλήσουµε για το swing. Παρόλα αυτά η γνώµη µου είναι ότι η µελέτη του java.awt είναι χρήσιµη για τουλάχιστον δυο λόγους:

− Το swing βασίζεται στο java.awt και πολλά από τα συστατικά του java.awt που θα δούµε έχουν ακριβώς αντίστοιχα συστατικά στο swing

− Τα παραθυρικά συστατικά του swing είναι λίγο "βαριά" για αργές dial-up συνδέσεις µε αποτέλεσµα να επιδεινώνεται ο χρόνος εκτέλεσης του applet όταν εκτελείται σε υπολογιστές µε τέτοιες συνδέσεις.

Πλήκτρα εντολών: Η τάξη Button Σ' αυτό το µάθηµα θα ασχοληθούµε µε τα πλήκτρα εντολών (command buttons). Τα πλήκτρα εντολών για τα οποία θα µιλήσουµε είναι αντικείµενα της τάξης java.awt.Button. Θα δούµε τις κυριότερες µεθόδους και ιδιότητες αυτής της τάξης και στη συνέχεια θα δούµε και ένα παράδειγµα της χρήσης της. Το παράδειγµα αυτό θα αποτελέσει την αρχή ενός applet που θα εξελίξουµε καθώς θα προχωράµε στα επόµενα µαθήµατα. Η τάξη java.awt.Button Τα αντικείµενα τα οποία αποτελούν πλήκτρα εντολών στις εφαρµογές που γίνανε µε το java.awt ανήκουν στην τάξη java.awt.Button. Έτσι για να δηλώσουµε και να δηµιουργήσουµε ένα αντικείµενο της τάξης java.awt.Button γράφουµε στο πρόγραµµά µας τις ακόλουθες εντολές και δηλώσεις:

import java.awt.*; ... Button aButton = new Button();

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

import java.awt.*; ... Button anotherButton = new Button("Κάνε κλικ εδώ!");

Οι κυριότερες µέθοδοι ενός java.awt.Button είναι οι ακόλουθες:

Μέθοδος Λειτουργία της µεθόδου

addActionListener(ActionListener) Θέτει τον ακροατή συµβάντων γι' αυτό το Button. Για ακροατές συµβάντων θα µιλήσουµε στο επόµενο µάθηµα

removeActionListener(ActionListener) Αφαιρεί τον συγκεκριµένο ακροατή συµβάντων που δίνεται ως παράµετρος.

setLabel(String) Θέτει την ετικέτα που εµφανίζεται πάνω στο πλήκτρο εντολών στο String που δίνεται ως παράµετρος

String getLabel() Επιστρέφει το String που είναι η ετικέτα του πλήκτρου εντολών

setEnabled(boolean) Αν η boolean παράµετρος είναι true τότε το

- 87 -

Page 88: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

πλήκτρο ενεργοποιείται διαφορετικά απενεργοποιείται

Όπως βλέπουµε υπάρχουν και δύο µέθοδοι (οι addActionListener και removeActionListener) που προσθέτουν ένα ακροατή συµβάντων γι' αυτό το button. ∆εν θα µιλήσουµε τώρα για χειρισµό συµβάντων οπότε θα αναβάλλουµε τη συζήτηση αυτών των µεθόδων για αργότερα. Ένα παράδειγµα Θα ξεκινήσουµε σε αυτό το σηµείο ένα Applet το οποίο θα το εξελίξουµε καθώς προχωράµε στα επόµενα µαθήµατα. Σκοπός του Applet αυτού θα είναι να παίζει ένα παιχνίδι ερωτήσεων. Θα εµφανίζει στο χρήστη µια ερώτηση και κάποιες πιθανές απαντήσεις. Ο χρήστης θα µπορεί να επιλέξει µια απο αυτές τις ερωτήσεις ως σωστή και το πρόγραµµα θα κρατά το σκορ του που θα είναι πόσες σωστές απαντήσεις έδωσε αλλά θα του δείχνει και τη σωστή απάντηση στη περίπτωση λάθους έτσι ώστε να µαθαίνει παίζοντας. Ένα τέτοιο παιχνίδι απαιτεί και τη χρήση κάποιων πλήκτρων εντολών (command buttons). Τα buttons αυτά θα επιτρέπουν στο χρήστη να µετακινείτε στην επόµενη ερώτηση, στη προηγούµενη ερώτηση, αλλά και να βλέπει το πόσες σωστές απαντήσεις έδωσε, τερµατίζοντας έτσι την τρέχουσα εκτέλεση του Applet. Το Applet που θα κάνουµε θα κάνει (προς το παρόν και µέχρι την συζήτηση για τις διατάξεις (layouts) της Java), εκτεταµένη χρήση της εντολής setBounds. Η εντολή αυτή κληρονοµείται από την τάξη java.awt.Component που είναι η αφηρηµένη υπερτάξη όλων των παραθυρικών συστατικών του java.awt (εκτός αυτών που έχουν σχέση µε µενού τα οποία κληρονοµούν από την τάξη java.awt.MenuComponent) και εποµένως την έχουν όλες οι τάξεις του πακέτου αυτού. Η εντολή αυτή µας επιτρέπει να τοποθετήσουµε το αντικείµενο στο οποίο καλείται σε συγκεκριµένη θέση (x, y) και να έχει συγκεκριµένο πλάτος (width) και ύψος (height) σε pixels. Έτσι η εντολή αυτή έχει την ακόλουθη µορφή:

public void setBounds(int x, int y, int width, int height) Επίσης θα χρησιµοποιήσουµε τη µέθοδο setSize για τον καθορισµό του µεγέθους του Applet. Και αυτή η µέθοδος ανήκει στην αφηρηµένη υπερτάξη java.awt.Component και έχει την ακόλουθη µορφή και προφανή σηµασία:

public void setSize( int width, int height ) Σηµειώστε ότι αυτές οι µέθοδοι έχουν και άλλες εκδόσεις (είναι δηλαδή υπερφορτωµένες) µε παραµέτρους που είναι java.awt.Rectangle για την setBounds και java.awt.Dimension για την setSize. Εδώ όµως θα χρησιµοποιήσουµε τις µεθόδους που προαναφέραµε. Το applet, λοιπόν, στη µέθoδο init θα θέτει το µέγεθός του µε τη setSize και στη συνέχεια θα δηµιουργεί τρία buttons χρησιµοποιώντας τη µορφή του κατασκευαστή που του επιτρέπει να θέσει και τις ετικέτες που θα εµφανίζονται πάνω σ' αυτά τα πλήκτρα εντολών. Αφού καθορίσει και τη θέση τους µε τη setBounds στη συνέχεια τα προσθέτει, µε την µέθοδο add, στην οπτική περιοχή του Applet. Η µέθοδος add είναι µέθοδος της τάξης java.awt.Container και γι' αυτό κληρονοµείται από τα applets. Τέλος, επειδή όταν θα έχουµε ερωτήσεις δεν θα έχει νόηµα όταν είµαστε στη πρώτη ερώτηση να πάµε στην προηγούµενη γιατί δεν θα υπάρχει προηγούµενη ερώτηση, το applet θα απενεργοποιεί όταν ξεκινά το πλήκτρο εντολών που αντιστοιχεί στη προηγούµενη ερώτηση καλώντας τη µέθοδο setEnabled σ' αυτό το button µε όρισµα false. Προς το παρόν δεν θα θέσουµε ακροατές συµβάντων για τα πλήκτρα εντολών και έτσι το Applet µας δεν θα είναι και πολύ ενδιαφέρον. Αλλά µην ανησυχείτε, θα έρθουµε και σ' αυτό. Το πρόγραµµα

- 88 -

Page 89: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

import java.awt.*; import java.applet.*; public class QuestionApplet extends Applet //To platos (W) kai to ypsos (H) toy panel private static final int W = 300; private static final int H = 300; private Button prevButton; private Button nextButton; private Button endButton; public void init() setLayout(null); //setSize gia to applet: setSize(platos, ypsos) setSize(W, H); prevButton = new Button("Προηγούµενη"); prevButton.setBounds(10, H-60, W/3-20, 40); nextButton = new Button("Επόµενη"); nextButton.setBounds(W/3+10, H-60, W/3-20, 40); aboutButton = new Button("Σκορ"); aboutButton.setBounds(W/3*2+10, H-60, W/3-20, 40); add(prevButton); add(nextButton); add(endButton); prevButton.setEnabled(false);

Η ακόλουθη εικόνα δείχνει το αποτέλεσµα της φόρτωσης του applet στον browser:

- 89 -

Page 90: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

Συστατικά κειµένου Σ' αυτό το µάθηµα θα ασχοληθούµε µε τις τάξεις java.awt.TextArea, java.awt.TextField και java.awt.Label. Αυτές οι τάξεις µπορούν να χρησιµοποιηθούν για να εµφανιστεί κείµενο στην οθόνη. Οι TextField και TextArea µπορούν να χρησιµοποιηθούν και για εισαγωγή κειµένου από τον χρήστη. Η διαφορά τους είναι ότι ένα TextField έχει µία γραµµή κειµένου, ενώ µια TextArea έχει πολλές γραµµές. Επειδή πρέπει να εµφανίζεται µία ερώτηση στο Applet µε το παιχνίδι των ερωτήσεων που αναπτύσσουµε θα χρησιµοποιήσουµε µια Label για να εµφανιστεί η ερώτηση. Θα κάνουµε λοιπόν αυτή τη προσθήκη στο Applet µας. Η υπερτάξη java.awt.TextComponent Η τάξη java.awt.TextComponent είναι η κοινή υπερτάξη των τάξεων java.awt.TextField και java.awt.TextArea. Παρέχει κάποιες µεθόδους οι οποίες είναι κοινές για τις δύο τάξεις που είναι υποτάξεις της και αποτελούν τα παραθυρικά συστατικά κειµένου µε τα οποία ο χρήστης µπορεί να επεξεργαστεί κείµενο σε µια παραθυρική εφαρµογή java που βασίζεται στο java.awt. Σηµειώστε ότι ένα Label δεν κληρονοµεί από αυτή την τάξη αλλά από την τάξη java.awt.Component. Αυτό συµβαίνει διότι η τάξη java.awt.TextComponent έχει µια πληθώρα µεθόδων που δεν είναι εφαρµόσιµες σε ένα Label το οποίο χρησιµοποιείται για την απλή εµφάνιση κειµένου και όχι σαν µια περιοχή επεξεργασίας κειµένου. Οι κυριότερες µέθοδοι της τάξης java.awt.TextComponent, που κληρονοµούνται από τις TextArea και TextField, είναι οι ακόλουθες:

Μέθοδος ή κατασκευαστής Λειτουργία της µεθόδου ή του κατασκευαστή

String getText() Η µέθοδος αυτή επιστρέφει το String που είναι κατά τη στιγµή της κλήσης το περιεχόµενο του TextComponent

void setText(String t) Αυτή η µέθοδος θέτει το κείµενο του TextComponent στο String που περνιέται σαν παράµετρος

boolean isEditable() Επιστρέφει true αν το TextComponent είναι επεξεργάσιµο από τον χρήστη

void setEditable(boolean b) Θέτει την κατάσταση επεξεργασίας του κειµένου που

- 90 -

Page 91: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

περιέχεται στο TextComponent στην παράµετρο b

void addTextListener(TextListener l)

Προσθέτει το αντικείµενο l που υλοποιεί το interface TextListener στη λίστα των χειριστών συµβάντων αυτού του TextComponent. Το συµβάν αυτό δεν είναι άλλο από την αλλαγή του κειµένου σ' αυτό το TextComponent

Επίσης ένα TextComponent παρέχει µία πληθώρα µεθόδων που έχουν σχέση µε την επιλογή (selection) ενός τµήµατος του κειµένου που περιέχει το TextComponent. Σηµειώστε ότι η αναφορά µας εδώ αλλά και στις επόµενες τάξεις σε αντικείµενα χειρισµού συµβάντων (τα αντικείµενα αυτά στην ορολογία της Java λέγονται ακροατές συµβάντων - action listeners) δεν θα είναι αναλυτική. Για τον χειρισµό συµβάντων θα αφιερώσουµε ένα από τα επόµενα µαθήµατά µας, οπότε η αναφορές µας µέχρι τότε όπου χρειαστεί θα είναι αναγκαστικά συνοπτικές και όχι πλήρεις. Η τάξη java.awt.TextField Η τάξη java.awt.TextField είναι υποτάξη της τάξης java.awt.TextComponent και αποτελεί ένα συστατικό το οποίο είναι κατάλληλο για να εισάγει ο χρήστης µια γραµµή κειµένου. Πέρα από τις µεθόδους που είδαµε στην υπερτάξη και που κληρονοµούνται από τη τάξη αυτή οι ουσιαστικότερες "µέθοδοι" της τάξης είναι οι τέσσερις κατασκευαστές (constructors) της και επίσης δυο µέθοδοι που µας επιτρέπουν να προσθέσουµε και να αφαιρέσουµε ένα αντικείµενο για το χειρισµό συµβάντων (event handler ή αν προτιµάτε την ορολογία της Java ένα ακροατή συµβάντων (action listener)) που προκαλεί ο χρήστης σε ένα TextField. Οι δύο µέθοδοι που αναφέραµε καθώς και οι τέσσερις κατασκευαστές δίνονται στον ακόλουθο πίνακα.

Μέθοδος ή κατασκευαστής Λειτουργία της µεθόδου ή του κατασκευαστή

void addActionListener(ActionListener l)

Προσθέτει το αντικείµενο που περνιέται σαν παράµετρος και που υλοποιεί το interface ActionListener έτσι ώστε να χειρίζεται το συµβάν του πατήµατος του Enter σ' αυτό το TextField

void removeActionListener(ActionListener l) Αφαιρεί τον ActionListener που περνιέται σαν παράµετρος έτσι ώστε να µην χειρίζεται πλέον τo πάτηµα του Enter σ' αυτό το TextField

TextField() Με την χρήση αυτού του κατασκευαστή (constructor) κατασκευάζουµε ένα TextField χωρίς καµία προκαθορισµένη ιδιότητα.

TextField(String t) Με την χρήση αυτού του κατασκευαστή (constructor) κατασκευάζουµε ένα TextField το οποίο αρχικά θα περιέχει ως κείµενο το String t που δίνεται σαν παράµετρος.

TextField(int c) Με την χρήση αυτού του κατασκευαστή (constructor) κατασκευάζουµε ένα TextField το οποίο θα έχει χώρο για c χαρακτήρες.

TextField(String t, int c)

Με την χρήση αυτού του κατασκευαστή (constructor) κατασκευάζουµε ένα TextField το οποίο θα έχει αρχικά το κείµενο t και θα χωράει c χαρακτήρες.

Σαν ένα παράδειγµα ο ακόλουθος κώδικας θα δηµιουργήσει ένα TextField µε τη µορφή που δίνεται αν το πρόγραµµα τρέξει σε ένα σύστηµα Windows.

- 91 -

Page 92: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

Τµήµα κώδικα Μορφή TextField στα Windows ... TextField tf = new TextField("Hello", 30); ... Η τάξη java.awt.TextArea Η τάξη java.awt.TextArea είναι µία περιοχή κειµένου που όπως και η java.awt.TextField είναι υποτάξη της java.awt.TextComponent. Η διαφορά της από την TextField είναι ότι µπορεί να χρησιµοποιηθεί για την επεξεργασία κειµένου µε πολλές γραµµές. Πέρα λοιπόν από τις µεθόδους που κληρονοµούνται από την υπερτάξη κάποιες ακόµα ενδιαφέροντες µέθοδοι και κατασκευαστές της τάξης αυτής είναι οι ακόλουθες:

Μέθοδος ή κατασκευαστής Λειτουργία της µεθόδου ή του κατασκευαστή

TextArea() Με την χρήση αυτού του κατασκευαστή (constructor) κατασκευάζουµε µια TextArea χωρίς καµία προκαθορισµένη ιδιότητα.

TextArea(String t) Με την χρήση αυτού του κατασκευαστή (constructor) κατασκευάζουµε µια TextArea στην οποία αρχικά θα εµφανίζεται το String t που περνιέται σαν παράµετρος.

TextArea(int r, int c) Με την χρήση αυτού του κατασκευαστή (constructor) κατασκευάζουµε µια TextArea µε r γραµµές και c στήλες

TextArea(String t, int r, int c) Με την χρήση αυτού του κατασκευαστή (constructor) κατασκευάζουµε µια TextArea στην οποία θα εµφανίζεται αρχικά το t και θα έχει r γραµµές και c στήλες.

TextArea( String t, int r, int c, int scrollbars )

Αυτός ο κατασκευαστής έχει την ίδια λειτουργία µε τον προηγούµενο. Η τελευταία παράµετρος µπορεί να είναι: TextArea.SCROLLBARS_BOTH, TextArea.SCROLLBARS_VERTICAL_ONLY, TextArea.SCROLLBARS_HORIZONTAL_ONLY και TextArea.SCROLLBARS_NONE και καθορίζει το αν θα υπάρχουν δύο ράβδοι ολίσθησης, µόνο κάθετη, µόνο οριζόντια ή καµία, αντίστοιχα.

void append(String t) Αυτή η µέθοδος προσθέτει το String t στο τέλος του κειµένου που ήδη υπάρχει στην TextArea.

void insert(String t, int i) Αυτή η µέθοδος εισάγει το κείµενο t στη θέση i µέσα στην TextArea

int getRows() και void setRows(int i)

Μέθοδοι get και set για τον αριθµό των γραµµών της TextArea

int getColumns() και void setColumns(int i) Μέθοδοι get και set για τον αριθµό των στηλών της TextArea

Σαν ένα παράδειγµα ο ακόλουθος κώδικας θα δηµιουργήσει µια TextArea µε τη µορφή που δίνεται αν το πρόγραµµα τρέξει σε ένα σύστηµα Windows.

Τµήµα κώδικα Μορφή TextArea στα Windows

- 92 -

Page 93: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

... TextArea ta = new TextArea("Hello", 10, 30, TextArea.SCROLLBARS_BOTH); ...

Η τάξη java.awt.Label Η τάξη java.awt.Label είναι µία περιοχή στην οποία µπορεί να εµφανιστεί κάποιο κείµενο που όµως είναι µόνο για ανάγνωση (read-only) και δεν µπορεί ο χρήστης να το επεξεργαστεί. Τα περιεχόµενα ενός Label µπορούν µόνο να αλλάξουν µέσα από ένα πρόγραµµα µε την κλήση της µεθόδου setText(). Οι κυριότερες µέθοδοι και οι κατασκευαστές της τάξης δίνονται στον ακόλουθο πίνακα:

Μέθοδος ή κατασκευαστής Λειτουργία της µεθόδου ή του κατασκευαστή

Label() Με την χρήση αυτού του κατασκευαστή (constructor) κατασκευάζουµε ένα Labelχωρίς καµία προκαθορισµένη ιδιότητα

Label(String t) Με την χρήση αυτού του κατασκευαστή (constructor) κατασκευάζουµε ένα Label το οποίο αρχικά θα περιέχει το κείµενο t.

Label(String t, int alignment)

Η σηµασία αυτού του κατασκευαστή είναι ίδια µε του προηγούµενου µόνο που θέτουµε και την στοίχιση µε την δεύτερη παράµετρο (alignment) η οποία µπορεί να είναι µία από τις: Label.CENTER, Label.LEFT και Label.RIGHT για στοίχιση στο κέντρο, αριστερά και δεξιά, αντίστοιχα.

String getText() και void setText(String t)

Μέθοδοι get και set για να ανακτούµε και να θέτουµε αντίστοιχα τα περιεχόµενα του Label

int getAlignment() και void setAlignment( int alignment)

Μέθοδοι get και set για να ανακτούµε και να θέτουµε αντίστοιχα τη στοίχιση του Label. Αυτή µπορεί να είναι µία από τις σταθερές που αναφέρθηκαν και για τον τρίτο κατασκευαστή.

Σαν παράδειγµα χρήσης των Label θα αλλάξουµε το Applet που ξεκινήσαµε στο προηγούµενο µάθηµα έτσι ώστε να έχουµε µια περιοχή για να εµφανίζονται οι ερωτήσεις του Applet. Τα µέρη που διαφέρουν σε σχέση µε το Applet του προηγούµενου µαθήµατος δίνονται µε έντονους χαρακτήρες. import java.awt.*; import java.applet.*; public class QuestionApplet extends Applet //To πλάτος (W) και το ύψος (H) του panel private static final int W = 300; private static final int H = 300; private Button prevButton;

- 93 -

Page 94: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

private Button nextButton; private Button endButton; //Η ετικέτα στην οποία θα εµφανίζονται οι ερωτήσεις private Label qLabel; public void init() setLayout(null); //setSize gia to applet: setSize(platos, ypsos) setSize(W, H); prevButton = new Button("Προηγούµενη"); prevButton.setBounds(10, H-60, W/3-20, 40); nextButton = new Button("Επόµενη"); nextButton.setBounds(W/3+10, H-60, W/3-20, 40); endButton = new Button("Σκορ"); endButton.setBounds(W/3*2+10, H-60, W/3-20, 40); /* ∆ηµιουργούµε ένα νέο Label το τοποθετούµε µε τη setBounds και βάζουµε σε αυτό το κείµενο 1η ερώτηση. */ qLabel = new Label(); qLabel.setBounds(10, 10, W-230, 20); qLabel.setText("1η Ερώτηση;"); //Προσθήκη των αντικειµένων πάνω στο Applet µε την add add(qLabel); add(prevButton); add(nextButton); add(endButton); prevButton.setEnabled(false); Το applet στον browser θα φαίνεται ως εξής:

- 94 -

Page 95: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

∆ιαχειριστές ∆ιάταξης Μέχρι τώρα χρησιµοποιήσαµε µια απόλυτη τοποθέτηση των παραθυρικών συστατικών πάνω στο applet χρησιµοποιώντας την setLayout(null) και την setBounds. Στη Java υπάρχει και η δυνατότητα να αντιστοιχίσουµε ένα αντικείµενο διαχείρισης της διάταξης των συστατικών πάνω σε ένα applet όπως και πάνω σε οποιοδήποτε άλλο container. Τα αντικείµενα αυτά ονοµάζονται διαχειριστές διάταξης (layout managers) και αφού αντιστοιχισθούν σε ένα container από κει και πέρα όσα συστατικά τοποθετούνται πάνω στο container τοποθετούνται µε βάση τη πολιτική τοποθέτησης που εφαρµόζει ο συγκεκριµένος διαχειριστής διάταξης. Υπάρχουν διάφορες τάξεις xLayout στο πακέτο java.awt (π.χ. BorderLayout, FlowLayout, GridLayout κ.α.). Αυτές τις τάξεις και τις πολιτικές τοποθέτησης που εφαρµόζουν θα µελετήσουµε σε αυτό το µάθηµα. Οι διεπαφές LayoutManger και LayoutManager2 Όλοι οι διαχειριστές διάταξης θα πρέπει να υλοποιούν τις διεπαφές (interfaces) java.awt.LayoutManager και java.awt.LayoutManager2. Φυσιολογικά εσείς δεν θα χρειαστεί να ασχοληθείτε µε αυτές τις διεπαφές εκτός και αν αναπτύξετε τους δικούς σας διαχειριστές διάταξης. Η διεπαφή LayoutManager παρέχει µεθόδους για την προσθήκη και αφαίρεση συστατικών από το container και για τον υπολογισµό του ελάχιστου και προτιµώµενου µεγέθους του container που περιέχει τα συστατικά που διατάσσονται, µε βάση το µέγεθος αυτών των συστατικών. Η τάξη LayoutManager2 επεκτείνει (extends) την τάξη LayoutManager προσθέτοντας κάποιες µεθόδους ειδικά για περιορισµένους διαχειριστές διάταξης. Για παράδειγµα ο διαχειριστής διάταξης FlowLayout υλοποιεί την διεπαφή LayoutManager διότι η πολιτική τοποθέτησης των συστατικών που εφαρµόζει δεν είναι περιορισµένη. Αντιθέτως ο διαχειριστής διάταξης BorderLayout υλοποιεί την διεπαφή LayoutManager2 διότι είναι περιορισµένος. Όπως αναφέραµε δεν χρειάζεται να γνωρίζει κανείς τις λεπτοµέρειες αυτών των διαχειριστών διάταξης για να χρησιµοποιήσει τις τάξεις διαχείρισης διάταξης που τους υλοποιούν. Στη συνέχεια θα δούµε κάποιες από αυτές τις τάξεις διαχείρισης διάταξης και παραδείγµατα της χρήσης τους. Η µέθοδοι setLayout και getLayout της τάξης java.awt.Container

- 95 -

Page 96: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

Όπως έχουµε ήδη αναφέρει όλες οι τάξεις που είναι containers κληρονοµούν από την τάξη java.awt.Container. Εποµένως δεν είναι παράξενο που η µέθοδος καθορισµού του διαχειριστή διάταξης βρίσκεται σε αυτή τη τάξη. Αυτή η µέθοδος δεν είναι άλλη από την

public void setLayout( LayoutManager mgr )

Έτσι, για παράδειγµα, µπορούµε να καλέσουµε σε ένα Applet (που είναι Container) την µέθοδο setLayout για να καθορίσουµε τον διαχειριστή διάταξης του συγκεκριµένου αντικειµένου. Αν δε καλέσουµε καθόλου την µέθοδο setLayout τότε κάποιος διαχειριστής διάταξης θα χρησιµοποιηθεί εξ ορισµού. Έτσι για παράδειγµα για τα Panel αυτός ο διαχειριστής διάταξης είναι ο FlowLayout ενώ για τα Window, Frame και Dialog είναι ο BorderLayout. Αυτό έχει µια σηµαντική συνέπεια: Αν θέλουµε να τοποθετήσουµε µόνοι µας τα παραθυρικά συστατικά σε συγκεκριµένες θέσεις µε την setBounds θα πρέπει προηγουµένως να καλέσουµε την setLayout µε παράµετρο null διαφορετικά θα χρησιµοποιηθεί ο εξ ορισµού διαχειριστής διάταξης. Φυσικά τα συστατικά θα προστεθούν µε την χρήση των διαφόρων µεθόδων add της τάξης java.awt.Container αφού καθοριστεί ο διαχειριστής διάταξης µε την setLayout. Για να ανακτήσουµε το αντικείµενο διαχείρισης διάταξης ενός Container µπορούµε να χρησιµοποιήσουµε τη µέθοδο getLayout της τάξης java.awt.Container µε την ακόλουθη συντακτική µορφή:

public LayoutManager getLayout() Η τάξη java.awt.FlowLayout Αυτός ο διαχειριστής διάταξης είναι ο εξ ορισµού διαχειριστής διάταξης για τα Panel και εποµένως και για τα Applets που είναι, όπως ξέρουµε, Panels. Αν για ένα Panel δεν επιθυµούµε αυτόν το διαχειριστή διάταξης αλλά θέλουµε να έχουµε απόλυτη τοποθέτηση θα πρέπει να καλέσουµε την setLayout µε παράµετρο null. Η λειτουργία αυτού του διαχειριστή είναι η ακόλουθη: ∆ιατάσσει τα αντικείµενα που προστίθενται στο container µε µια σειρά από αριστερά προς τα δεξιά και από πάνω προς τα κάτω. Αν για παράδειγµα έχουµε το τµήµα του πιο κάτω κώδικα:

... setLayout(new FlowLayout()); add(new Button("Ενα")); add(new Button("∆ύο")); ...

τότε το Button µε την ετικέτα "Ενα" θα προστεθεί στην αριστερή πάνω περιοχή του container που διατάσσεται, το Button µε την ετικέτα "∆ύο" αµέσως δεξιότερα κ.ο.κ. Υπάρχει και η δυνατότητα προσδιορισµού της περιοχής τοποθέτησης χρησιµοποιώντας τις αριθµητικές σταθερές FlowLayout.LEFT (αριστερά), FlowLayout.CENTER (κέντρο), και FlowLayout.RIGHT (δεξιά). Για παράδειγµα το πιο πάνω τµήµα κώδικα θα µπορούσε να ήταν:

... setLayout(new FlowLayout()); add(new Button("Ενα"), FlowLayout.LEFT); add(new Button("∆ύο"), FlowLayout.RIGHT); ...

Ένα απλό παράδειγµα της χρήσης του FlowLayout είναι το ακόλουθο Applet το οποίο τοποθετεί τα τέσσερα κουµπιά που δηµιουργούνται και τοποθετούνται πάνω του µε τη λογική που περιγράψαµε. Ο κώδικας του Applet είναι ο ακόλουθος:

import java.awt.*; import java.applet.Applet;

- 96 -

Page 97: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

public class FlowLayoutApplet extends Applet Button b1 = new Button("Ένα"); Button b2 = new Button("∆ύο"); Button b3 = new Button("Τρία"); Button b4 = new Button("Τέσσερα"); public void init() setLayout(new FlowLayout()); setSize(150, 150); add(b1); add(b2); add(b3); add(b4);

Για να τρέξουµε το Applet θα χρειαστούµε και µια HTML σελίδα όπως η ακόλουθη:

<applet code=FlowLayoutApplet.class width=150 height=150></applet>

Αν φορτώσετε τη σελίδα αυτή θα δείτε µια εικόνα όπως η ακόλουθη:

Η τάξη java.awt.BorderLayout Αυτός ο διαχειριστής διάταξης διατάσσει τα αντικείµενα που τοποθετούνται στο container που διατάσσεται από αυτόν τοποθετώντας τα σε µια από τις ακόλουθες πέντε περιοχές: North (Βόρεια), South (Νότια), East (Ανατολικά), West (∆υτικά) και Center (στο Κέντρο). Έτσι όταν έχετε θέσει ως διαχειριστή διάταξης σε ένα container ένα αντικείµενο GridLayout θα πρέπει να καλείτε την add δίνοντας σαν πρώτη παράµετρο το αντικείµενο που θέλετε να προσθέσετε και σαν δεύτερη το που θέλετε να προστεθεί αυτό το αντικείµενο, όπως δείχνει ο ακόλουθος κώδικας:

... setLayout(new GridLayout()); add(new Button("∆υτικά"), "West"); add(new Button("Βόρεια"), "North"); add(new Button("Ανατολικά"), "East"); add(new Button("Νότια"), "South"); add(new Button("Κεντρικά"), "Center"); ...

Αν δεν δώσετε δεύτερη παράµετρος τότε το αντικείµενο τοποθετείται στο κέντρο. Ένα απλό παράδειγµα είναι το ακόλουθο Applet το οποίο τοποθετεί πέντε κουµπιά καθένα και σε µια άλλη περιοχή:

import java.awt.*;

- 97 -

Page 98: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

import java.applet.Applet; public class BorderLayoutApplet extends Applet Button b1 = new Button("∆υτικά"); Button b2 = new Button("Βόρεια"); Button b3 = new Button("Ανατολικά"); Button b4 = new Button("Νότια"); Button b5 = new Button("Κεντρικά"); public void init() setLayout(new BorderLayout()); setSize(300, 300); add(b1,"West"); add(b2,"North"); add(b3,"East"); add(b4,"South"); add(b5,"Center");

Μία HTML σελίδα γι' αυτό το Applet θα µπορούσε να ήταν η ακόλουθη:

<applet code=BorderLayoutApplet.class width=300 height=300></applet> Αν φορτώσετε αυτή τη σελίδα θα δείτε κάτι σαν την ακόλουθη εικόνα:

Η τάξη java.awt.GridLayout Αυτός ο διαχειριστής διάταξης διατάσσει τα αντικείµενα που τοποθετούνται στο container που διατάσσεται από αυτόν σε ένα πλέγµα ορθογώνιων περιοχών. Κάθε αντικείµενο εισάγεται και στη δική του ορθογώνια περιοχή. Έτσι µπορούµε να χωρίσουµε την περιοχή ενός container σε γραµµές και στήλες. Ο αριθµός των γραµµών και των στηλών καθορίζεται συνήθως µε ένα από τους κατασκευαστές (constructor) της GridLayout τάξης, ο οποίος έχει την ακόλουθη συντακτική µορφή:

public GridLayout(int rows, int columns)

- 98 -

Page 99: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

ή µπορούµε να θέσουµε τον αριθµό των γραµµών ή των στηλών αργότερα (µετά την κατασκευή του αντικειµένου διάταξης) µε την χρήση των µεθόδων setRows και setColumns. Σαν ένα παράδειγµα δίνεται το ακόλουθο Applet:

import java.awt.*; import java.applet.Applet; public class GridLayoutApplet extends Applet Button b1 = new Button("1.1"); Button b2 = new Button("1.2"); Button b3 = new Button("1.3"); Button b4 = new Button("2.1"); Button b5 = new Button("2.2"); Button b6 = new Button("2.3"); public void init() setLayout(new GridLayout(2, 3)); setSize(200, 200); add(b1); add(b2); add(b3); add(b4); add(b5); add(b6);

και µια HTML σελίδα γι' αυτό το Applet όπως η ακόλουθη:

<applet code=GridLayoutApplet.class width=300 height=300></applet> Αν φορτώσετε αυτή τη σελίδα σε ένα browser θα δείτε κάτι ανάλογο µε την εικόνα που ακολουθεί:

Η τάξεις java.awt.CardLayout και java.awt.GridBagLayout

- 99 -

Page 100: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

O διαχειριστής διάταξης CardLayout διατάσσει τα αντικείµενα που τοποθετούνται στο container που διατάσσεται από αυτόν σαν ένα σύνολο από επικαλυπτόµενες κάρτες από τις οποίες µόνο µια είναι ορατή κάθε φορά (η διάταξη αυτή θυµίζει τα χαρτιά µιας τράπουλας (deck of cards) και απ' αυτό το γεγονός προέρχεται και το όνοµα CardLayout). Αυτός ο διαχειριστής διάταξης παρέχει µεθόδους για να καθορίσουµε ποιο αντικείµενο είναι το πρώτο, ποιο είναι το τελευταίο, ποιο είναι το προηγούµενο και ποιο το επόµενο. Επίσης ένας άλλος διαχειριστής διάταξης είναι ο GridBagLayout ο οποίος είναι παραλλαγή του GridLayout και δίνει τη δυνατότητα σε κάποια συστατικά που προστίθενται στο container που διατάσσεται από αυτόν να καταλαµβάνουν περισσότερες γραµµές ή στήλες. Συνδυασµοί διαχειριστών διάταξης Όπως είδαµε και στα παραδείγµατα που δώσαµε προηγούµενα, ένας διαχειριστής διάταξης εφαρµόζεται και σε ένα Panel. Υπάρχει εποµένως η δυνατότητα να έχουµε ένα διαχειριστή διάταξης σε κάποιο Panel (έστω στο Panel p1) και ένα άλλο διαχειριστή διάταξης σε κάποιο άλλο (έστω στο Panel p2). Στη συνέχεια θα µπορούσαµε να συνδυάσουµε τα δύο αυτά Panels τοποθετώντας τα σε ένα container (Panel, Frame κλπ) το οποίο θα είχε για παράδειγµα ένα τρίτο τύπο διαχειριστή διάταξης. Όπως είναι προφανές αυτού του είδους η εµφωλευµένη εφαρµογή διαφορετικών διαχειριστών διάταξης µπορεί να δώσει αρκετά εντυπωσιακά αποτελέσµατα. Αυτήν τη τεχνική της εµφωλευµένης διάταξης θα χρησιµοποιήσουµε και στα επόµενα µαθήµατα. Θα έχουµε ένα Panel στο οποίο θα τοποθετήσουµε τις πιθανές απαντήσεις. Αυτό το Panel θα το διατάξουµε χρησιµοποιώντας ένα GridLayout. Αλλά το Panel αυτό θα το τοποθετήσουµε πάνω στο Applet το οποίο ήδη έχουµε αρχίσει να αναπτύσσουµε και το οποίο δεν έχει κανένα διαχειριστή διάταξης µια και τοποθετούµε τα παραθυρικά συστατικά πάνω του σε συγκεκριµένες θέσεις µε την setBounds.

Λίστες Επιλογών (Lists) Σ' αυτό το µάθηµα θα µιλήσουµε για λίστες επιλογών. Οι λίστες επιτρέπουν στο χρήστη ενός προγράµµατος να επιλέξει µια επιλογή από ένα δοθέν σύνολο επιλογών. Υπάρχει η δυνατότητα να έχουµε λίστες που επιτρέπουν στο χρήστη να επιλέξει πολλές επιλογές κάθε φορά ή και µόνο µια. Όπως θα δούµε οι λίστες στο πακέτο java.awt υλοποιούνται µε τη τάξη List. Θα συζητήσουµε γι' αυτή τη τάξη τους κατασκευαστές της και τις κυριότερες µεθόδους της. Στη συνέχεια θα επεκτείνουµε το παιχνίδι των Ερωτήσεων που κατασκευάζουµε έτσι ώστε να µας δίνει τη δυνατότητα να επιλέξουµε µια κατηγορία ερωτήσεων από ένα σύνολο δοθέντων κατηγοριών ερωτήσεων. Οι κατηγορίες αυτές θα εµφανίζονται σε µία λίστα (java.awt.List). Η τάξη java.awt.List Η τάξη java.awt.List χρησιµοποιείται όπως ήδη αναφέραµε για την εµφάνιση επιλογών από τις οποίες ο χρήστης µπορεί να επιλέξει µια ή και περισσότερες. Ο επόµενος πίνακας έχει τις κυριότερες µεθόδους και τους κατασκευαστές της τάξης java.awt.List.

Μέθοδος ή κατασκευαστής Λειτουργία της µεθόδου ή του κατασκευαστή

List() Με αυτό το κατασκευαστή κατασκευάζουµε µία λίστα χωρίς καµιά προκαθορισµένη ιδιότητα. Μόνο µια επιλογή µπορεί να επιλεγεί τη φορά.

List( int rows ) Με αυτό το κατασκευαστή κατασκευάζουµε µια λίστα µε τόσες ορατές γραµµές όσες προσδιορίζονται από την παράµετρο rows. Μόνο µια επιλογή µπορεί να επιλεγεί τη φορά.

List( int rows, boolean multipleMode) Με αυτό το κατασκευαστή κατασκευάζουµε µια λίστα µε τόσες ορατές γραµµές όσες προσδιορίζονται από την παράµετρο rows. Αν η

- 100 -

Page 101: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

παράµετρος multipleMode είναι false τότε µόνο µια επιλογή µπορεί να επιλεγεί τη φορά. Αν είναι true τότε επιτρέπονται οι πολλαπλές επιλογές.

void add( String t ) Προσθέτει το String t στο τέλος της λίστας.

void add( String t, int index )

Προσθέτει το String t στη θέση που προσδιορίζεται από την παράµετρο index. Σηµειώστε ότι το πρώτο index σε µια λίστα έχει index 0, το δεύτερο 1 κ.ο.κ. Αν η παράµετρος index είναι -1 ή µεγαλύτερη ή ίση µε το πλήθος των στοιχείων της λίστας η προσθήκη του νέου String γίνεται στο τέλος.

void setMultipleMode( boolean b)

Μπορείτε να αλλάξετε τη κατάσταση της λίστας ακόµα και αφού τη δηµιουργήσετε χρησιµοποιώντας την µέθοδο αυτή. Αν η παράµετρος b είναι true τότε θα επιτρέπονται οι πολλαπλές επιλογές διαφορετικά όχι.

int getSelectedIndex()

Επιστρέφει τον δείκτη (από 0 µέχρι πλήθος_επιλογών-1) της επιλογής που είναι επιλεγµένη κατά τη στιγµή της κλήσης. Αν καµιά επιλογή δεν είναι επιλεγµένη κατά τη στιγµή της κλήσης επιστρέφει -1. Επίσης -1 επιστρέφεται και αν είναι περισσότερες επιλογές επιλεγµένες στη περίπτωση που η κλήση γίνεται σε λίστα πολλαπλών επιλογών

int[] getSelectedIndexes() Επιστρέφει ένα array από ακεραίους που είναι οι δείκτες των επιλογών που είναι επιλεγµένες κατά τη στιγµή της κλήσης. Αυτή η µέθοδος είναι χρήσιµη σε λίστες πολλαπλών επιλογών.

String getSelectedItem() Έχει το ίδιο νόηµα µε την getSelectedIndex µόνο που αντί να επιστρέφει τον δείκτη της επιλεγµένης επιλογής, επιστρέφει το String της επιλεγµένης επιλογής.

String[] getSelectedItems()

Έχει το ίδιο νόηµα µε την getSelectedIndexes µόνο που αντί να επιστρέφει τους δείκτες των επιλεγµένων επιλογών µιας λίστας πολλαπλών επιλογών επιστρέφει τα Strings των επιλεγµένων επιλογών σε ένα array από Strings.

boolean isIndexSelected( int index ) Επιστρέφει true αν η παράµετρος index είναι κατά τη στιγµή της κλήσης ο δείκτης της επιλγµένης επιλογής. ∆ιαφορετικά επιστρέφει false.

void addActionListener(ActionListenerl)

Προσθέτει ένα αντικείµενο ακρόασης συµβάντων. Ο ActionListener που δίνεται σαν παράµετρος θα ενεργοποιείται όταν πατιέται το Enter στη λίστα.

void addItemListener( ItemListener l) Προσθέτει ένα αντικείµενο ακρόασης συµβάντων. Ο ItemListener που δίνεται σαν παράµετρος ενεργοποιείται όταν αλλάζουν τα επιλεγµένα String στη λίστα.

Λίστα κατηγοριών στο παιχνίδι "Ερωτήσεις" Σαν ένα παράδειγµα της χρήσης της τάξης List θα τοποθετήσουµε µία λίστα στο Applet των ερωτήσεων που κατασκευάζουµε. Σε αυτό το αντικείµενο τύπου List θα εµφανίζονται οι κατηγορίες των ερωτήσεων στις οποίες µπορεί να απαντήσει ένας παίκτης. Θα τοποθετήσουµε τη λίστα αυτή στην δεξιά µεριά του Applet οπότε θα χρειαστεί να διαπλατύνουµε λίγο το

- 101 -

Page 102: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

Applet µας. Προς το παρόν ασχολούµαστε µόνο µε το GUI του Applet µας οπότε δεν θα έχουµε πραγµατικές κατηγορίες όπως δεν έχουµε και πραγµατικές ερωτήσεις. Αυτά θα τα προσθέσουµε αργότερα. Τώρα θα αρκεστούµε στο να προσθέσουµε δύο Strings στη λίστα αυτή: τα Strings "Κατηγορία 1" και "Κατηγορία 2" απλά για να έχουµε µια οπτική παράσταση του πως θα είναι το παιχνίδι µας όταν θα τελειώσει. Ο νέος κώδικας για το Applet δίνεται στη συνέχεια. Οι αλλαγές επισηµαίνονται µε έντονους χαρακτήρες:

import java.awt.*; import java.applet.*; public class QuestionApplet extends Applet //To platos (W) kai to ypsos (H) toy panel private static final int W = 600; private static final int H = 300; private Button prevButton; private Button nextButton; private Button endButton; //H etiketa stin opoia tha emfanizontai oi erwtiseis private Label qLabel; //To list box twn katigoriwn private List cList; public void init() setLayout(null); //setSize gia to applet: setSize(platos, ypsos) setSize(W, H); prevButton = new Button("Προηγούµενη"); prevButton.setBounds(10, H-60, W/3-20, 40); nextButton = new Button("Επόµενη"); nextButton.setBounds(W/3+10, H-60, W/3-20, 40); endButton = new Button("Σκορ"); endButton.setBounds(W/3*2+10, H-60, W/3-20, 40); /* Dimiourgoume ena neo Label to topothetoyme me ti setBounds kai vazoume se ayto ti 1H erotisi */ qLabel = new Label(); qLabel.setBounds(10, 10, W-230, 20); qLabel.setText("1η Ερώτηση;"); /* Dimiourgoume mia lista me katigories erotisewn kai tin topothetoyme me tin setBounds se sygekrimeni thesi */ cList = new List(); cList.add("1η κατηγορία"); cList.add("2η κατηγορία"); cList.setBounds(W-210, 10 , 200, 90);

- 102 -

Page 103: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

//Prosthiki twn antikeimenwn pano sto Applet me tin add add(qLabel); add(prevButton); add(nextButton); add(endButton); add(cList); prevButton.setEnabled(false);

Για να τρέξει το Applet θα χρειαστεί να δηµιουργήσουµε και µια HTML σελίδα όπως η ακόλουθη:

<applet code="QuestionApplet.class" width=610 height=310> </applet> Το applet στον browser θα φαίνεται ως εξής:

Πλαίσια Ελέγχου Σ' αυτό το µάθηµα θα αχοληθούµε τα πλαίσια ελέγχου (checkboxes) και τις οµάδες πλαισίων ελέγχου (checkbox groups). Τα πλαίσια ελέγχου επιτρέπουν και αυτά σε ένα χρήστη να

- 103 -

Page 104: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

ενεργοποιήσει ή να απενεργοποιήσει µια επιλογή µαρκάροντας το τετράγωνο που βρίσκεται δίπλα της. Αν θέλουµε να επιτρέψουµε σε ένα χρήστη να επιλέξει µόνο µια επιλογή κάθε φορά (αυτό είναι καλό για αµοιβαία αποκλειόµενες επιλογές) θα πρέπει να εντάξουµε τα πλαίσια ελέγχου σε µία οµάδα (checkbox group). Γι' αυτά τα δύο παραθυρικά συστατικά το πακέτο java.awt διαθέτει τις τάξεις Checkbox και CheckboxGroup. Τις τάξεις αυτές τους κατασκευαστές και τις κυριότερες µεθόδους τους θα συζητήσουµε σ' αυτό το µάθηµα. Στη συνέχεια θα χρησιµοποιήσουµε µία οµάδα επιλογών πλαισίου ελέγχου στην οποία θα εµφανίζονται οι πιθανές απαντήσεις για το παιχνίδι των ερωτήσεων που κατασκευάζουµε. Θα χρησιµοποιήσουµε οµάδα πλαισίων ελέγχου διότι ο χρήστης θα µπορεί να επιλέξει µόνο µια απάντηση κάθε φορά. Η τάξη java.awt.Checkbox Όπως ήδη αναφέρθηκε η τάξη αυτή χρησιµοποιείται για την παρουσίαση κάποιων πλαισίων ελέγχου από τα οποία ο χρήστης µπορεί να ενεργοποήσει ή να απενεργοποιήσει κάποιες από τις επιλογές που τα συνοδεύουν. Στον ακόλουθο πίνακα δίνονται οι κατασκευαστές καθώς και οι κυριότερες από τις µεθόδους της τάξης java.awt.checkbox.

Μέθοδος ή κατασκευαστής Λειτουργία της µεθόδου ή του κατασκευαστή

Checkbox() ∆ηµιουργεί ένα πλαίσιο ελέγχου χωρίς καµία προκαθορισµένη ιδιότητα.

Checkbox( String t) ∆ηµιουργεί ένα πλαίσιο ελέγχου µε ετικέτα το String t

Checkbox( String t, boolean state) ∆ηµιουργεί ένα πλαίσιο ελέγχου µε ετικέτα το String t και θέτει την αρχική του κατάσταση σε επιλεγµένο ή όχι ανάλογα µε το αν η παράµετρος state είναι true ή false, αντίστοιχα.

Checkbox(String t, boolean state, CheckboxGroup group)

∆ηµιουργεί ένα πλαίσιο ελέγχου µε ετικέτα το String t και µε αρχική κατάσταση που προσδιορίζεται από την παράµετρο state. Η παράµετρος group είναι το CheckboxGroup στο οποίο θα ενταχθεί αυτό το πλαίσιο ελέγχου.

boolean getState() ΚΑΙ setState(boolean state)

Με αυτές τις δύο µεθόδους, getState και setState µπορούµε να βρούµε ποια είναι η κατάσταση ενός πλαισίου ελέγχου (true=επιλεγµένο και false=όχι επιλεγµένο), ή να θέσουµε αυτή τη κατάσταση µέσα από ένα πρόγραµµα, αντίστοιχα.

String getLabel() ΚΑΙ setLabel(String t) Με αυτές τις δύο µεθόδους, getLabel και setLabel µπορούµε να βρούµε ποια είναι η ετικέτα, ή να θέσουµε την ετικέτα σε ένα πλαίσιο ελέγχου, αντίστοιχα.

CheckboxGroup getCheckboxGroup() ΚΑΙ void setCheckboxGroup(CheckboxGroup g)

Με αυτές τις δύο µεθόδους, getCheckboxGroup και setCheckboxGroup µπορούµε να βρούµε ποια είναι η οµάδα ενός πλαισίου ελέγχου, ή να θέσουµε ένα πλαίσιο ελέγχου σε µια οµάδα, αντίστοιχα.Αν το αποτέλεσµα της πρώτης µεθόδου είναι null τότε αυτό σηµαίνει ότι το πλαίσιο ελέγχου δεν ανήκει σε κάποια οµάδα.

void addItemListener( ItemListener l) Με αυτή τη µέθοδο µπορούµε να προσθέσουµε σε ένα πλαίσιο ελέγχου ένα ακροατή αλλαγών στη κατάστασή του. Το αντικείµενο αυτό είναι

- 104 -

Page 105: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

ένα αντικείµενο µιας τάξης που υλοποιεί το ItemListener interface και ενεργοποιείται κάθε φορά που αλλάζει η κατάσταση του πλαισίου ελέγχου στο οποίο έχει αντιστοιχισθεί.

Σαν ένα παράδειγµα ο ακόλουθος κώδικας θα δηµιουργήσει τρια αντικείµενα Checkbox µε τη µορφή που δίνεται αν το πρόγραµµα τρέξει σε ένα σύστηµα Windows. Παρατηρείστε τη χρήση ενός GridLayout για την διάταξη των πλαισίων ελέγχου στο Panel που τα περιέχει.

Τµήµα κώδικα Μορφή των Checkboxes στα Windows

... Panel p = new Panel(); p.setLayout(new GridLayout(3,1)); p.add(new Checkbox("Eνα",true, null)); p.add(new Checkbox("∆υο")); p.add(new Checkbox("Τρια")); //Προσθήκη του Panel p σε ένα container add(p); ... Η τάξη java.awt.CheckboxGroup Η τάξη αυτή χρησιµοποιείται όταν θέλουµε να εντάξουµε ένα σύνολο πλαισίων ελέγχου σε µια οµάδα. Η βασική λειτουργία της τάξης αυτής είναι να εξασφαλίζει ότι µόνο ένα πλαίσιο ελέγχου µπορεί να είναι επιλεγµένο κάθε φορά. Τα πλαίσια ελέγχου µάλιστα όταν εντάσσονται σε µια οµάδα ελέγχου έχουν διαφορετική µορφή. Για παράδειγµα σε ένα σύστηµα Windows έχουν την µορφή radio buttons. Ο κατασκευαστής και οι κυριότερες µέθοδοι αυτής της τάξης δίνονται στον ακόλουθο πίνακα.

Μέθοδος ή κατασκευαστής Λειτουργία της µεθόδου ή του κατασκευαστή

CheckboxGroup() ∆ηµιουργεί ένα νέο CheckboxGroup το οποίο µπορούµε να χρησιµοποιήσουµε για την ένταξη σε αυτό πλαισίων ελέγχου.

Checkbox getSelectedCheckbox() ΚΑΙ void setSelectedCheckbox(Checkbox box )

Με αυτές τις δύο µεθόδους, getSelectedCheckbox και setSelectedCheckbox µπορούµε να βρούµε ποιο από τα πλαίσια ελέγχου µιας οµάδας ελέγχου είναι επιλεγµένο ή να θέσουµε ως επιλεγµένο κάποιο από αυτά µέσα από το πρόγραµµα, αντίστοιχα

Οι πιθανές απαντήσεις στο παιχνίδι των Ερωτήσεων Σαν ένα παράδειγµα της χρήσης των πλαισίων ελέγχου που είναι ενταγµένα σε µια οµάδα θα προσθέσουµε κάποιες πιθανές απαντήσεις στο παιχνίδι των ερωτήσεων. Για να το κάνουµε αυτό θα δηµιουργήσουµε µία νέα τάξη. Η τάξη αυτή θα είναι η CheckboxGroupPanel και θα είναι απλά ένα Panel (θα επεκτείνει τη τάξη Panel) στο οποίο θα προσθέσουµε κάποια πλαίσια ελέγχου τα οποία όµως θα ανήκουν σε µία οµάδα. Στη συνέχεια θα µπορέσουµε να δηµιουργήσουµε ένα αντικείµενο αυτής της τάξης το οποίο θα περιέχει τις πιθανές µας απαντήσεις και να το τοποθετήσουµε πάνω στο Applet των ερωτήσεων. Επειδή το CheckGroupPanel είναι Panel θα χρησιµοποιήσουµε την setBounds για να το τοποθετήσουµε πάνω στο Αpplet αλλά επίσης θα καθορίσουµε το δικό του Layout σε GridLayout για να εµφανίζονται τα πλαίσια ελέγχου το ένα κάτω από το άλλο. Ο κατασκευαστής θα δηµιουργεί τα πλαίσια ελέγχου από τις ετικέτες που δίνονται σαν πρώτη

- 105 -

Page 106: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

παράµετρος, θα τα προσθέτει πάνω στο Panel το ένα κάτω από το άλλο, και θα τα εντάσσει στην ίδια οµάδα. Μέσω του κατασκευαστή θα δίνεται η δυνατότητα να θέσουµε και σαν ακροατή των αλλαγών των καταστάσεων αυτών των πλαισίων ελέγχου κάποιον ItemListener που θα είναι η δεύτερη παράµετρος του κατασκευαστή. Εµείς προς το παρόν από το Appelt µας θα θέσουµε αυτή τη δεύτερη παράµετρο σε null, γιατί προς το παρόν δεν θα χειριστούµε συµβάντα από τα πλαίσια ελέγχου. Τέλος η τάξη CheckGroupPanel παρέχει τρεις ακόµα µεθόδους:

1. Μια για την επιστροφή του δείκτη της τρέχουσας επιλογής ή -1 αν δεν υπάρχει τρέχουσα επιλογή (int getSelection()).

2. Μια για την αλλαγή των ετικετών, έτσι ώστε να µπορούµε να εµφανίζουµε πολλές διαφορετικές απαντήσεις χωρίς να δηµιουργούµε νέα αντικείµενα CheckboxGroupPanel (void setLabels(String labels[])) , και

3. Μια για τον καθαρισµό όλων των επιλογών έτσι ώστε να µην υπάρχει καµία τρέχουσα επιλογή (void clearCheckboxes()).

Η τάξη CheckboxGroupPanel δίνεται στη συνέχεια:

import java.awt.*; import java.awt.event.*; public class CheckboxGroupPanel extends Panel private CheckboxGroup group = new CheckboxGroup(); public CheckboxGroupPanel(String labels[], ItemListener il) int length = labels.length; setLayout(new GridLayout(length,1)); //Πρόσθεσε τα checkboxes στο πλέγµα και στην οµάδα for(int i=0;i<length;++i) Checkbox ch = new Checkbox(labels[i], false, group); add(ch); ch.addItemListener(il); //Η µέθοδος αυτή επιστρέφει τον δείκτη του επιλεγµένου checkbox public int getSelection() Component components[] = getComponents(); for (int i=0; i<components.length; ++i) Checkbox chb = (Checkbox) components[i]; if (chb.getState()) return i; return -1; //Η µέθοδος αυτή θέτει τις ετικέτες της οµάδας σε αυτούς που δίνονται στο array public void setLabels(String labels[]) Component c[] = this.getComponents(); for (int i=0; i<c.length; i++) ((Checkbox) c[i]).setLabel(labels[i]);

- 106 -

Page 107: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

//Η µέθοδος αυτή θέτει το επιλεγµένο checkbox σε null. ∆ηλαδή δεν έχουµε κανένα //επιλεγµένο checkbox public void clearCheckboxes() group.setSelectedCheckbox(null);

Πρέπει ακόµα να µεταβάλλουµε το Applet µας έτσι ώστε να προσθέσουµε ένα CheckboxGroupPanel µε τις πιθανές απαντήσεις στις ερωτήσεις µας. Για να προσθέσουµε αυτό το CheckboxGroupPanel στο Applet µας θα πρέπει να καθορίσουµε και τη θέση του µε τη setBounds. Θα βάλουµε το CheckboxGroupPanel ανάµεσα από την ερώτηση και τα κουµπιά και στα αριστερά του πλαισίου λίστας µε τις κατηγορίες των ερωτήσεων. Προς το παρόν οι απαντήσεις µας θα είναι απλά κάποιο array από String και δεν θα αντιστοιχίσουµε κανένα χειριστή συµβάντων αλλαγών στα πλαίσια ελέγχου. Ο κώδικας για το νέο QuestionApplet δίνεται στη συνέχεια. Ως συνήθως οι αλλαγές είναι µε έντονα γράµµατα: import java.awt.*; import java.applet.*; public class QuestionApplet extends Applet //To platos (W) kai to ypsos (H) toy panel private static final int W = 600; private static final int H = 300; private Button prevButton; private Button nextButton; private Button endButton; //H etiketa stin opoia tha emfanizontai oi erwtiseis private Label qLabel; //To list box twn katigoriwn private List cList; //To CheckboxGroupPanel twn apantisewn private CheckboxGroupPanel cbp; public void init() setLayout(null); //setSize gia to applet: setSize(platos, ypsos) setSize(W, H); prevButton = new Button("Προηγούµενη"); prevButton.setBounds(10, H-60, W/3-20, 40); nextButton = new Button("Επόµενη"); nextButton.setBounds(W/3+10, H-60, W/3-20, 40); endButton = new Button("Σκορ");

- 107 -

Page 108: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

endButton.setBounds(W/3*2+10, H-60, W/3-20, 40); /* Dimiourgoume ena neo Label to topothetoyme me ti setBounds kai vazoume se ayto ti 1H erotisi */ qLabel = new Label(); qLabel.setBounds(10, 10, W-230, 20); qLabel.setText("1η Ερώτηση;"); /* Dimiourgoume mia lista me katigories erotisewn kai tin topothetoyme me tin setBounds se sygekrimeni thesi */ cList = new List(); cList.add("1η κατηγορία"); cList.add("2η κατηγορία"); cList.setBounds(W-210, 10 , 200, 90); /* Dimiourgoume to Panel me tis pithanes apantiseis kai to topothetoyme sto Applet */ String labels[] = "1η Απάντηση", "2η Απάντηση", "3η Απάντηση", "4η Απάντηση", "5η Απάντηση"; cbp = new CheckboxGroupPanel(labels, null); cbp.setBounds(10, 40, W-230, H-120); //Prosthiki twn antikeimenwn pano sto Applet me tin add add(qLabel); add(prevButton); add(nextButton); add(endButton); add(cList); add(cbp); prevButton.setEnabled(false); Για να τρέξει το Applet θα χρειαστεί να δηµιουργήσουµε και µια HTML σελίδα όπως η ακόλουθη: <applet code="QuestionApplet.class" width=610 height=310> </applet> Το applet στον browser θα φαίνεται ως εξής:

- 108 -

Page 109: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

Η τάξη Question Σ' αυτό το µάθηµα θα προσθέσουµε την τάξη Question στην εφαρµογή µας. Η τάξη Question θα αποτελέσει την τάξη των ερωτήσεών µας. ∆εν αρκεί γι' αυτό ένα String, διότι µια ερώτηση θα πρέπει να συνοδεύεται από τις πιθανές απαντήσεις της, θα πρέπει να "ξέρει" ποια από αυτές είναι η σωστή απάντηση και θα πρέπει να έχει µεθόδους για να µας επιστρέψει τις απαντήσεις της, το κείµενο της ερώτησης, τη σωστή απάντηση και, για διευκόλυνση, το αν η απάντηση που δόθηκε είναι σωστή ή όχι. Φυσικά ο σκοπός του µαθήµατος δεν είναι η απλή κατασκευή µιας τάξης. Ο σκοπός είναι να (απο-)δείξουµε ότι δεν αρκεί το GUI σε µία εφαρµογή και επίσης να δείξουµε ότι δεν µπορούµε να ενσωµατώσουµε όλη τη λειτουργικότητα του προγράµµατος στο Applet ή στην εφαρµογή που κατασκευάζουµε. Θα πρέπει να επινοούµε τάξεις όπου και όταν χρειάζεται διαµοιράζοντας έτσι την λειτουργικότητα του Applet ή της εφαρµογής µας. Η τάξη Question είναι µια τέτοια τάξη. Η τάξη Question Η τάξη Question η οποία θα βρίσκεται στο Question.java έχει τον ακόλουθο κώδικα Java:

import java.io.*; public class Question private String question; private String answers[];

- 109 -

Page 110: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

private int correctAnswer; private int selectedAnswer; public Question(String q, String[] a, int c) question = q; answers = a; correctAnswer = c; selectedAnswer = -1; public String[] getAnswers() return answers; public String getQuestion() return question; public void setSelectedAnswer(int a) selectedAnswer = a; public boolean isCorrect() return (selectedAnswer == correctAnswer); public int getSelectedAnswer() return selectedAnswer;

Η τάξη Question έχει αρκετές λειτουργίες και ενσωµατώνει δεδοµένα και µεθόδους. Αυτό είναι και το βασικότερο κριτήριο (από άποψης τεχνολογίας λογισµικού) για το αν µία οντότητα µπορεί να αποτελέσει ή όχι µια τάξη. Την τάξη αυτή θα την χρησιµοποιήσουµε στο επόµενο µάθηµα στο οποίο θα ολοκληρώσουµε το παιχνίδι των ερωτήσεων προσθέτοντας ανάδραση µε τον χειρισµό συµβάντων του Appet µας.

Ακροατές Συµβάντων Σ' αυτό το µάθηµα θα δούµε πως µπορούµε να προσθέσουµε κάποια αναδραστικότητα στα Applet µας. Θα δούµε ότι αυτό γίνεται αντιστοιχίζοντας κάποια αντικείµενα χειρισµού συµβάντων στα αντικείµενα του GUI µας. Έτσι όταν συµβαίνει κάτι σε ένα στοιχείο του GUI θα καλείται (αυτόµατα από το σύστηµα εκτέλεσης της Java) µια µέθοδος χειρισµού του συµβάντος στο ή στα αντικείµενα ακρόασης συµβάντων που έχουν αντιστοιχισθεί σ' αυτό το αντικείµενο του GUI γι' αυτό το σκοπό. Θα δούµε τι είναι αυτά τα αντικείµενα και θα συζητήσουµε για τις τάξεις που βρίσκονται πίσω από αυτά και είναι οι τάξεις του πακέτου java.awt.event. Τέλος θα συσχετίσουµε τα διάφορα αντικείµενα του Applet µας (πλήκτρα εντολών, πλαίσια ελέγχου και λίστα) µε τα κατάλληλα αντικείµενα ακρόασης συµβάντων. Βήµατα για τον χειρισµό συµβάντων

- 110 -

Page 111: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

Για να χειριστούµε τα συµβάντα µιας πηγής συµβάντων θα πρέπει να κάνουµε τα ακόλουθα: Να αποφασίσουµε για ποιο ή για ποια συµβάντα ενδιαφερόµαστε και ποια θα είναι η πηγή αυτών των συµβάντων Να αποφασίσουµε ποια είναι η διεπαφή (interface) του πακέτου java.awt.event που θα πρέπει να υλοποιηθεί από την τάξη χειρισµού συµβάντων Να αποφασίσουµε ποια θα είναι η τάξη χειρισµού συµβάντων που θα υλοποιήσει αυτή τη διεπαφή και να υλοποιήσουµε τη διεπαφή σ' αυτή τη τάξη υλοποιώντας τη µέθοδο που θα κληθεί όταν θα προκληθεί το συµβάν Να δηµιουργήσουµε ένα αντικείµενο της τάξης του βήµατος (3) και να προσθέσουµε στο αντικείµενο πηγή-συµβάντων το αντικείµενο αυτό ως ακροατή συµβάντων µε κλήση στη κατάλληλη addXXXListener. Η αντιστοίχιση αυτή θα γίνει µε µια εντολή της ακόλουθης µορφής:

αντικείµενο-πηγή.addXXXListener(αντικείµενο-ακρόασης-συµβάντων) Από εκείνο το σηµείο και µετά το αντικείµενο ακρόασης συµβάντων θα είναι ο χειριστής των συµβάντων που προκαλούνται στο αντικείµενο πηγή. Έτσι όταν προκαλείται το συγκεκριµένο συµβάν στο αντικείµενο-πηγή θα καλείται αυτοµάτως η µέθοδος χειρισµού συµβάντων στο αντικείµενο-ακρόασης-συµβάντων. Συµβάντα (events) Υπάρχουν διάφορα συµβάντα που µπορεί να ενδιαφέρεται να χειριστεί κάποια εφαρµογή ή κάποιο Applet. Για παράδειγµα σε ένα πλήκτρο ελέγχου µπορεί να ενδιαφερόµαστε για το συµβάν του πατήµατος (κλικ) του πλήκτρου. Σε ένα πλαίσιο ελέγχου µπορεί να ενδιαφερόµαστε για το αν επιλέχτηκε ή αναιρέθηκε η επιλογή του. Σε ένα Panel µπορεί να µας ενδιαφέρει ακόµα και η κίνηση του ποντικιού πάνω από το Panel. Για όλα αυτά τα συµβάντα υπάρχουν τάξεις που τα αντιπροσωπεύουν στο πακέτο java.awt.event. Οι τάξεις αυτές κληρονοµούν από την τάξη java.awt.AWTEvent που κληρονοµεί από την τάξη java.util.EventObject. Κάτι σηµαντικό που όλες οι τάξεις για συµβάντα κληρονοµούν από την τάξη java.util.EventObject είναι η µέθοδος getSource() που επιστρέφει το αντικείµενο στο οποίο προκλήθηκε το συµβάν σαν ένα java.Object. Έτσι µπορεί να έχετε τον ίδιο χειριστή να χειρίζεται συµβάντα από πολλά αντικείµενα. Ο χειριστής µπορεί να διαπιστώσει σε ποιο αντικείµενο προκλήθηκε το συµβάν απλά καλώντας τη µέθοδο getSource και στη συνέχεια συγκρίνοντας το Object που επιστρέφεται µε τα αντικείµενα στα οποία ακροάζεται για συµβάντα. Οι τάξεις που είναι τάξεις για αντικείµενα συµβάντων στο πακέτο java.awt.event χρησιµοποιούνται για τη δηµιουργία αντικειµένων τα οποία περνιούνται ως παράµετροι στις µεθόδους που χειρίζονται το κάθε συµβάν. Έτσι µια µέθοδος χειρισµού συµβάντος µπορεί να εξετάσει το αντικείµενο αυτό για να αποκτήσει πληροφορίες για το συµβάν. Φυσικά, πέρα από την πηγή που επιστρέφεται από την µέθοδο getSource() και µπορεί να κληθεί από κάθε αντικείµενο συµβάντων, υπάρχουν και άλλες εξειδικευµένες µέθοδοι και ιδιότητες ανάλογα µε τον τύπο του συµβάντος. Για παράδειγµα αν το συµβάν είναι αντικείµενο της τάξης KeyEvent (δηλαδή είναι συµβάν πληκτρολογίου) τότε µπορεί κανείς να βρεί ποιος χαρακτήρας πατήθηκε στο πληκτρολόγιο για να προκληθεί το συµβάν καλώντας τη µέθοδο getKeyChar() η οποία επιστρέφει ένα char. Οι κυριότερες τάξεις για αντικείµενα συµβάντων είναι οι ακόλουθες:

1. ActionEvent: Η τάξη αυτή χρησιµοποιείται για συµβάντα που προκαλούνται από το χρήστη όπως το πάτηµα ενός πλήκτρου εντολής (Button) ή το πάτηµα του Enter σε µία List.

2. AdjustmentEvent: Η τάξη αυτή χρησιµοποιείται για συµβάντα που προκαλούνται από το χρήστη σε αντικείµενα του GUI που είναι προσαρµόσιµα (adjustable) όπως οι ράβδοι ολίσθησης.

- 111 -

Page 112: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

3. ItemEvent: Η τάξη αυτή χρησιµοποιείται για συµβάντα που προκαλούνται από το χρήστη σε επιλέξιµα αντικείµενα όπως για παράδειγµα ένα πλαίσιο ελέγχου. Τα συµβάντα αυτά είναι η επιλογή ή η ακύρωση της επιλογής στο επιλέξιµο αντικείµενο.

4. KeyEvent: Η τάξη αυτή χρησιµοποιείται για συµβάντα που προκαλούνται από το χρήστη στο πληκτρολόγιο.

5. MouseEvent: Η τάξη αυτή χρησιµοποιείται για συµβάντα που προκαλούνται από το χρήστη µε το ποντίκι.

Τάξεις χειρισµού συµβάντων Όπως είπαµε ήδη οι τάξεις που χειρίζονται ή ακροάζονται συµβάντα θα πρέπει να υλοποιούν κάποιες διεπαφές ανάλογα µε τα συµβάντα για τα οποία ενδιαφέρονται. Έτσι για παράδειγµα αν κατασκευάζουµε µια τάξη που ενδιαφέρεται να χειριστεί συµβάντα που προκαλούνται από το πάτηµα ενός πλήκτρου εντολών θα πρέπει να υλοποιήσει την διεπαφή ActionListener. Η υλοποίηση αυτής της διεπαφής συνεπάγεται τον ορισµό της µεθόδου void actionPerformed(ActionEvent e) η οποία καλείται αυτοµάτως όταν προκαλείται το συµβάν αυτό στα αντικείµενα (πλήκτρα για παράδειγµα) στα οποία έχει αντιστοιχισθεί ο συγκεκριµένος listener. Κάποες από τις διεπαφές (interfaces) που πρέπει να υλοποιηθούν από τις τάξεις χειρισµού συµβάντων είναι οι ακόλουθες:

1. ActionListener: Υλοποιείται από τάξεις που ενδιαφέρονται για Action Events όπως το πάτηµα ενός πλήκτρου. Η µέθοδος που πρέπει να υλοποιηθεί είναι η void actionPerformed( ActionEvent e ) η οποία θα κληθεί αυτόµατα για να χειριστεί το συµβάν όταν αυτό προκληθεί.

2. AdjustmentListener: Υλοποιείται από τάξεις που ενδιαφέρονται για Adjustment Events όπως η αλλαγή θέσης µιας ράβδου ολίσθησης. Η µέθοδος που πρέπει να υλοποιηθεί είναι η void adjustmentValueChanged( AdjustmentEvent e )

3. ItemListener: Υλοποιείται από τις τάξεις που ενδιαφέρονται για Item Events όπως η αλλαγή κατάστασης σε ένα πλαίσιο ελέγχου. Η µέθοδος που πρέπει να υλοποιηθεί είναι η void itemStateChanged( ItemEvent e ).

Επίσης υπάρχουν διεπαφές που συνεπάγονται την υλοποίηση περισσότερων από µια µεθόδων όπως για παράδειµγα η διεπαφή ComponentListener. Έτσι λοιπόν ανάλογα µε το συµβάν που θα πρέπει να χειριστούµε θα πρέπει να δηµιουργήσουµε και µια τάξη που θα χειριστεί το συµβάν. Αυτή η τάξη θα πρέπει να υλοποιεί (implements) την διεπαφή που υπάρχει για συµβάντα αυτού του τύπου και εποµένως και την αντίστοιχη ή τις αντίστοιχες µεθόδους. Όταν προκαλείται το συµβάν ή τα συµβάντα καλούνται οι µέθοδοι αυτοί και εκεί θα πρέπει να υπάρχει ο κώδικας που θα χειριστεί το συµβάν. Αντιστοίχιση πηγών συµβάντων µε ακροατές συµβάντων Όταν µια τάξη υλοποιεί µια διεπαφή xxxListener από κει και πέρα µπορεί να αντιστοιχισθεί σε αντικείµενα που µπορεί να είναι πηγή γι' αυτά τα συµβάντα. Η αντιστοίχιση αυτή γίνεται µε µεθόδους της µορφής addXXXListener. Για παράδειγµα σε ένα Checkbox υπάρχει η µέθοδος void addItemListener( ItemListener l ). Αυτή η µέθοδος προσθέτει ένα αντικείµενο που υλοποιεί το interface ItemListener στο Checbox στο οποίο καλείται. Όταν τρέχει το πρόγραµµα και αλλάζει η κατάσταση αυτού του Checkbox καλείται αυτόµατα η µέθοδος itemStateChanged σ' αυτό το Checkbox. Χειρισµός συµβάντων στο παιχνίδι των ερωτήσεων Σαν παράδειγµα χειρισµού συµβάντων θα δούµε το χειρισµό συµβάντων για το παιχνίδι των ερωτήσεων που κατασκευάζουµε. Σ' αυτό το παιχνίδι πρέπει να χειριστούµε τα συµβάντα για τα τρία πλήκτρα εντολών (nextButton, prevButton και endButton) για την αλλαγή επιλογής

- 112 -

Page 113: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

στη λίστα κατηγοριών ερωτήσεων (cList) και για την αλλαγή της επιλογής στα πλαίσια ελέγχου µε τις απαντήσεις της κάθε ερώτησης. Για να χειριστούµε τα συµβάντα στα πλήκτρα εντολών θα πρέπει να υλοποιήσουµε τη διεπαφή java.awt.event.ActionListener ενώ για τις αλλαγές επιλογής στη λίστα και στα πλαίσια ελέγχου θα πρέπει να υλοποιήσουµε τη διεπαφή ItemListener. Θα βάλουµε την τάξη QuestionApplet να είναι ταυτόχρονα και ο ακροατής συµβάντων γι' αυτά τα συµβάντα. Άρα θα πρέπει να υλοποιεί τις δυο αυτές διεπαφές. Εποµένως η τάξη αυτή θα ξεκινά τώρα ως εξής:

import java.awt.*; import java.applet.*; import java.awt.event.*; public class QuestionApplet extends Applet implements ActionListener, ItemListener ...

Όπως βλέπετε τώρα εισάγουµε και το πακέτο java.awt.event για να χρησιµοποιήσουµε τις τάξεις του και βάζουµε την τάξη QuestionApplet να υλοποιήσει τις δυο διεπαφές που απαιτούνται για τον χειρισµό των συµβάντων που αναφέραµε προηγουµένως. Στη µέθοδο init θα πρέπει βεβαίως να συσχετίσουµε και τα αντικείµενα που είναι πηγές συµβάντων µε το QuestionApplet που θα είναι ο ακροατής συµβάντων. Επειδή η κλήση των µεθόδων addXXXListener θα γίνει µέσα από το QuestionApplet το QuestionApplet θα περάσει τον εαυτό του µε το this ως ακροατή συµβάντων γι' αυτά τα αντικείµενα, όπως βλέπετε και στα ακόλουθα αποσπάσµατα από το QuestionApplet.

/* Tώρα η δεύτερη παράµετρος του CheckboxGroupPanel δεν είναι null αλλά this. Αυτό σηµαίνει ότι σαν ακροατής συµβάντων των πλαισίων ελέγχου θα τεθεί από το CheckboxGroupPanel το this, δηλαδή αυτό το applet */ cbp = new CheckboxGroupPanel(labels,this); ... //Πρόσθεσε τους listeners και στα υπόλοιπα στοιχεία του GUI που είναι πηγές συµβάντων nextButton.addActionListener(this); prevButton.addActionListener(this); endButton.addActionListener(this); cList.addItemListener(this);

Επίσης θα πρέπει προφανώς να δηλώσουµε και τις µεθόδους χειρισµού των συµβάντων. Αυτές είναι η actionPerformed που θα χειριστεί τα συµβάντα στα πλήκτρα εντολών και η itemStateChanged που θα χειριστεί τα συµβάντα στα πλαίσια ελέγχου και στις λίστες. Ο κώδικας των µεθόδων αυτών µαζί και µε τον κώδικα για όλο το νέο QuestionApplet δίνεται στη συνέχεια:

import java.awt.*; import java.applet.*; import java.awt.event.*; public class QuestionApplet extends Applet implements ActionListener, ItemListener //To platos (W) kai to ypsos (H) toy panel private static final int W = 600; private static final int H = 300; private static final int MAX_QUESTIONS = 2;

- 113 -

Page 114: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

private Button prevButton; private Button nextButton; private Button endButton; //H etiketa stin opoia tha emfanizontai oi erwtiseis private Label qLabel; //To list box twn katigoriwn private List cList; //To CheckboxGroupPanel twn apantisewn private CheckboxGroupPanel cbp; //Ta dio vectors exoyn to kathena kai mia katigoria erotisewn private Question javaQuestions[] = new Question[MAX_QUESTIONS]; private Question comQuestions[] = new Question[MAX_QUESTIONS]; //To array poy einai h trexousa katigoria erwtisewn private Question currentCategory[]; //To skor int score; //H trexousa erwtisi kathe fora int currentQuestion; public void init() setLayout(null); //setSize gia to applet: setSize(platos, ypsos) setSize(W, H); prevButton = new Button("Προηγούµενη"); prevButton.setBounds(10, H-60, W/3-20, 40); nextButton = new Button("Επόµενη"); nextButton.setBounds(W/3+10, H-60, W/3-20, 40); endButton = new Button("Σκορ"); endButton.setBounds(W/3*2+10, H-60, W/3-20, 40); /* Dimiourgoume ena neo Label to topothetoyme me ti setBounds kai vazoume se ayto ti 1H erotisi */ qLabel = new Label(); qLabel.setBounds(10, 10, W-230, 20); /* Dimiourgoume mia lista me katigories erotisewn kai tin topothetoyme me tin setBounds se sygekrimeni thesi */ cList = new List(); cList.add("Ερωτήσεις για τη Java"); cList.add("Ερωτήσεις για το COM"); cList.setBounds(W-210, 10 , 200, 90); cList.select(0);

- 114 -

Page 115: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

//Dimiourgoume tis erwtiseis createQuestions(); /* Dimiourgoume to Panel me tis pithanes apantiseis kai to topothetoyme sto Applet */ String labels[] = "","","","",""; cbp = new CheckboxGroupPanel(labels,this); cbp.setBounds(10, 40, W-230, H-120); //Prosthiki twn antikeimenwn pano sto Applet me tin add add(qLabel); add(prevButton); add(nextButton); add(endButton); add(cList); add(cbp); //Prosthese toys listeners sta diafora stoixeia nextButton.addActionListener(this); prevButton.addActionListener(this); endButton.addActionListener(this); cList.addItemListener(this); //Arxikopoiise to Applet me tin katigoria poy dinetai san //parametros initialize(javaQuestions); public void itemStateChanged(ItemEvent e) Object source = e.getSource(); //O xristis epelexe mia alli katigoria if (source == cList) int selection = cList.getSelectedIndex(); if (selection == 0) initialize(javaQuestions); else if (selection == 1) initialize(comQuestions); //alliws an i pigi toy simvantos einai checkbox else if (source.getClass() == Checkbox.class) currentCategory[currentQuestion].setSelectedAnswer(cbp.getSelection()); public void actionPerformed(ActionEvent e) Object source = e.getSource(); if (source == prevButton || source == nextButton)

- 115 -

Page 116: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

if (source == prevButton) currentQuestion--; if (currentQuestion == 0) prevButton.setEnabled(false); if (currentQuestion == MAX_QUESTIONS-2) nextButton.setEnabled(true); else if (source == nextButton) currentQuestion++; if (currentQuestion == MAX_QUESTIONS-1) nextButton.setEnabled(false); if (currentQuestion == 1) prevButton.setEnabled(true); qLabel.setText(currentCategory[currentQuestion].getQuestion()); cbp.setLabels(currentCategory[currentQuestion].getAnswers()); cbp.clearCheckboxes(); else if (source == endButton) int score = 0; for (int i=0; i<MAX_QUESTIONS; i++) if (currentCategory[i].isCorrect()) score++; qLabel.setText("**** Το σκορ σου είναι: "+score+" ****. ΤΕΛΟΣ!"); //Game is over this.setEnabled(false); //Ayti i idiotiki synartisi arxikopoiei stin katigoria poy //dinetai ws parametros private void initialize(Question[] category) currentQuestion = 0; currentCategory = category; qLabel.setText(currentCategory[currentQuestion].getQuestion()); cbp.setLabels(currentCategory[currentQuestion].getAnswers()); cbp.clearCheckboxes(); prevButton.setEnabled(false); nextButton.setEnabled(true); repaint(); //H createQuestions dimiourgei ta dio array twn erwtisewn private void createQuestions() //Dimiourgise tesseris erwtiseis dyo gia kathe katigoria String ajq1[] = new String[5]; ajq1[0] = "∆ιαδικασιακή γλώσσα προγραµµατισµού"; ajq1[1] = "Συναρτησιακή γλώσσα προγραµµατισµού"; ajq1[2] = "Μάρκα Καφέ"; ajq1[3] = "Αντικειµενοστραφής γλώσσα προγραµµατισµού";

- 116 -

Page 117: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

ajq1[4] = "Ψυχική Ασθένεια"; Question jq1 = new Question("Τι είναι η Java;", ajq1, 3); String ajq2[] = new String[5]; ajq2[0] = "Προσπαθεί να µας πείσει ότι η VB είναι καλύτερη από τη Java"; ajq2[1] = "Ξέρει ότι η VB είναι καλύτερη από τη Java"; ajq2[2] = "Θέλει να κάνει τη VB Java στη θέση της Java"; ajq2[3] = "Αγαπάει τη Java"; ajq2[4] = "Θεωρεί τη Java απειλή"; Question jq2 = new Question("Ποια είναι η γνώµη της Microsoft για τη Java;", ajq2, 2); String acq1[] = new String[5]; acq1[0] = "Component Object Model"; acq1[1] = "Component Oriented Modeling"; acq1[2] = "Component Objects Marriage"; acq1[3] = "Cost Objective Method"; acq1[4] = "Common OLE Model"; Question cq1 = new Question("Τι είναι το COM;",acq1,0); String acq2[] = new String[5]; acq2[0] = "Ένα"; acq2[1] = "Κανένα"; acq2[2] = "Το πολύ δύο"; acq2[3] = "Πολλά"; acq2[4] = "Ένα φανερό και πολλά κρυφά"; Question cq2 = new Question("Πόσα COM objects µπορεί να περιέχει µια DLL;", acq2,3); javaQuestions[0] = jq1; javaQuestions[1] = jq2; comQuestions[0] = cq1; comQuestions[1] = cq2;

Για να µεταφράσετε το Applet θα χρειαστείτε και τα τρία αρχεία *.java από τα προηγούµενα µαθήµατα που είναι τα:

QuestionApplet.java CheckboxGroupPanel.java, και Question.java

Για να τρέξετε το applet θα χρειαστείτε και µια HTML σελίδα όπως η ακόλουθη:

<applet code=QuestionApplet.class width=610 height=320></applet> Το applet στον browser θα φαίνεται όπως δείχνει η εικόνα που ακολουθεί:

- 117 -

Page 118: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

- 118 -

Page 119: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

Κεφάλαιο 4: Τα νήµατα της Java (Java Threads)

Τι είναι ένα νήµα εκτέλεσης (thread); Ένα πρόγραµµα είναι κάτι στατικό. Όταν εκτελείται όµως δηµιουργείται ένα νήµα εκτέλεσης για την εκτέλεση αυτού του προγράµµατος. Το νήµα εκτέλεσης έχει το δικό του µετρητή προγράµµατος (program counter) και το δικό του περιβάλλον (context) το οποίο είναι η κατάσταση του εκτελούµενου προγράµµατος. Έτσι λοιπόν µπορείτε να φανταστείτε το νήµα εκτέλεσης του προγράµµατος σαν ένα κουβάρι που ξετυλίγεται και περνά από τις εντολές του προγράµµατος που εκτελούνται κάθε φορά. Φυσικά το κουβάρι αυτό µπορεί να περάσει από ένα σύνολο εντολών τη µια φορά και από ένα τελείως διαφορετικό σύνολο εντολών την επόµενη. Αυτό εξαρτάται από το τι θα γίνει κάθε φορά, για παράδειγµα εξαρτάται από το ποια πλήκτρα εντολών θα πατήσει ο χρήστης σε µια παραθυρική εφαρµογή ή ένα Applet. Ισχύει λοιπόν κάτι σαν το επόµενο σχήµα:

Εντολή-2

Εντολή-ν

Νήµα Εκτέλεσης

Εντολή-1

Όπως φαίνεται και από το σχήµα το Νήµα Εκτέλεσης (thread) του προγράµµατος θα διατρέξει κάποιες εντολές οι οποίες και θα εκτελεστούν. Γενικά οι εντολές που θα εκτελεστούν θα είναι ένα υποσύνολο των εντολών του προγράµµατος µια και σε ένα πρόγραµµα υπάρχουν διακλαδώσεις (branches). Για παράδειγµα σε µια εντολή if … else σε ένα πρόγραµµα θα εκτελεστεί το σύνολο εντολών µετά το if αν η συνθήκη είναι αληθής ή αυτό µετά το else αν η συνθήκη είναι ψευδής. Τα νήµατα εκτέλεσης δεν επιβαρύνουν πολύ το σύστηµα. Τα νήµατα εκτέλεσης µοιράζονται τον ίδιο χώρο διευθύνσεων µε τα άλλα νήµατα εκτέλεσης του ίδιου προγράµµατος. Μοιράζονται δηλαδή τα ίδια δεδοµένα και τις ίδιες εντολές. Γι’ αυτό µερικές φορές τα νήµατα εκτέλεσης ονοµάζονται και ελαφρές διεργασίες (lightweight processes) διότι σε αντιδιαστολή µε τα νήµατα οι διεργασίες (processes) που αποτελούν τον ιστορικό πρόγονο των νηµάτων είναι πιο βαριές µια και κάθε διεργασία έχει τον δικό της χώρο διευθύνσεων.

Το Σύστηµα στην περίπτωση που έχουµε περισσότερα νήµατα εκτέλεσης και ένα µόνο επεξεργαστή θα αποδώσει ένα χρονικό διάστηµα εκτέλεσης (κβάντο χρόνου) σε κάθε νήµα, δίνοντας έτσι την ευκαιρία σε όλα τα νήµατα να εκτελεστούν «ταυτόχρονα». Βεβαίως το ταυτόχρονα είναι µέσα σε εισαγωγικά διότι ο πραγµατικός ταυτοχρονισµός επιτυγχάνεται µόνο όταν έχουµε πολλούς επεξεργαστές, οπότε και χρησιµοποιείται ο όρος παράλληλο πρόγραµµα (parallel program) αντί του όρου ταυτόχρονο πρόγραµµα (concurrent program). Όπως προκύπτει και από την πιο πάνω συζήτηση είναι άλλο το πρόγραµµα και άλλο το νήµα εκτέλεσης του προγράµµατος. ∆εν υπάρχει λόγος λοιπόν να µην έχουµε παραπάνω από ένα νήµατα εκτέλεσης (threads) για το ίδιο πρόγραµµα ταυτόχρονα. Αυτό είναι εφικτό στη γλώσσα Java. Υπάρχει δηλαδή η δυνατότητα ταυτόχρονα µε το κύριο νήµα εκτέλεσης του προγράµµατος (αυτό που ξεκινά µε την εκτέλεση της main για παράδειγµα) να έχουµε και άλλα νήµατα εκτέλεσης. Αυτό που ίσως να διερωτάται κανείς είναι γιατί να θέλουµε να κάνουµε κάτι τέτοιο. Η απάντηση είναι διπλή:

- 119 -

Page 120: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

1. Για απλούστευση στον προγραµµατισµό πολύπλοκων εφαρµογών: Πολλές φορές είναι πιο απλό να σκεφτούµε ένα πρόγραµµα σαν ένα σύνολο από νήµατα εκτέλεσης που το καθένα ακολουθεί τον δικό του δρόµο. Για παράδειγµα αν κατασκευάζουµε ένα εξυπηρετητή (server) που µπορεί να εξυπηρετήσει πολλούς πελάτες (clients) ταυτόχρονα, τότε είναι ίσως καλύτερο να σκεφτούµε το πρόγραµµα σαν ένα πρόγραµµα εξυπηρέτησης που εκτελείται για κάθε πελάτη ταυτόχρονα. Έχουµε δηλαδή ένα πρόγραµµα και για κάθε πελάτη ξεκινάµε ένα νέο νήµα εκτέλεσης που θα εκτελέσει το πρόγραµµα εξυπηρέτησης για να εξυπηρετήσει τον πελάτη. Το κάθε διαφορετικό νήµα εκτέλεσης εξυπηρετεί και ένα διαφορετικό πελάτη. Θα µπορούσαµε να κατασκευάσουµε το ίδιο πρόγραµµα και διαφορετικά. Για παράδειγµα θα µπορούσε ο Server να διατηρεί εκείνες τις δοµές µε πληροφορίες που θα του επέτρεπαν να αφιερώνει από λίγο χρόνο σε κάθε ένα πελάτη του και να κάνει µεταγωγή περιβάλλοντος (switch) για να πάει από τον ένα στον άλλο, ας πούµε µε ένα κυκλικό τρόπο (Round Robin). Αλλά αυτό το κάνει το Λειτουργικό Σύστηµα για µας όταν έχουµε πολλά νήµατα εκτέλεσης. Γιατί λοιπόν να προγραµµατίσουµε κάτι τόσο πολύπλοκο όταν µας προσφέρεται ελεύθερα;

2. Για εξοικονόµηση χρόνου: Όπως ήδη αναφέραµε αν έχουµε ένα σύστηµα

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

Όπως προκύπτει από τα παραπάνω και κυρίως από το (1) υπάρχουν εφαρµογές που ο προγραµµατισµός µε την χρήση νηµάτων εκτέλεσης είναι αναπόφευκτος και οποιαδήποτε άλλη προσέγγιση θα ήταν χάσιµο χρόνου. Αξίζει λοιπόν να µελετήσουµε τα νήµατα εκτέλεσης. Στην εικόνα που ακολουθεί φαίνονται δύο νήµατα εκτέλεσης που εκτελούν τις εντολές του ίδιου προγράµµατος. Φυσικά τα δύο νήµατα εκτέλεσης µπορεί να εκτελούνε διαφορετικές εντολές κάθε. Για παράδειγµα θα µπορούσαµε να είχαµε ένα αντικείµενο BankDeposit το οποίο εξυπηρετεί ένα πελάτη όταν αυτός κάνει µια κατάθεση σε ένα ATM. Είναι προφανές ότι ενώ ένας πελάτης δίνει το PIN του κάποιος άλλος θα µπορούσε να δίνει το ποσό της κατάθεσης ενώ κάποιος τρίτος µπορεί να βρίσκεται στο σηµείο εισαγωγής του φακέλου µε τα χρήµατα. Προσοχή χρειάζεται ώστε να µην δοθεί η εντύπωση ότι ένα αντικείµενο από το οποίο µπορεί να ξεκινήσει ένα νέο νήµα εκτέλεσης, έχει µία και µόνη µέθοδο. Τότε δεν θα ήταν αντικείµενο αλλά συνάρτηση. Το αντικείµενο αυτό εξακολουθεί να είναι αντικείµενο µε µεθόδους και ιδιότητες. Απλά έχει µία «ξεχωριστή» µέθοδος που αποτελεί και την είσοδο του νήµατος εκτέλεσης που θα το διατρέξει. Έτσι, για παράδειγµα, το νήµα εκτέλεσης που ξεκίνησε από το αντικείµενο αυτό µπορεί να βρίσκεται στον κώδικα κάποιου άλλου αντικειµένου ενώ στον κώδικα αυτού του αντικειµένου µπορεί να εκτελείται κάποιο άλλο νήµα. Θα πρέπει δηλαδή να

Εντολ

Εντολή-1

Εντολή-2

ή-ν

Νήµα Εκτέλεσης-1 Νήµα Εκτέλεσης-2

- 120 -

Page 121: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

διαχωρίσουµε στη σκέψη µας τις έννοιες αντικείµενο και νήµα εκτέλεσης. Απλά κάποια αντικείµενα βολεύει να τα αντιµετωπίσουµε σαν αντικείµενα που διαθέτουν την δυνατότητα να εκκινήσουν νήµατα εκτέλεσης. Επίσης πρέπει να τονιστεί είναι ότι όταν ένα αντικείµενο µπορεί να προσπελαστεί από πολλά νήµατα εκτέλεσης ταυτόχρονα, χρειάζεται ειδική µέριµνα για να προστατεύσουµε το αντικείµενο από περιπτώσεις που µπορεί να πλήξουν την ορθότητα της κατάστασής του. Αυτά τα προβλήµατα είναι γνωστά από τη θεωρία των ταυτόχρονων συστηµάτων (concurrent systems) και θα εξετάσουµε εν συντοµία τη λύση αυτών των προβληµάτων στη Java.

Υλοποίηση των νηµάτων στη Java Για να ξεκινά ένα νήµα από ένα αντικείµενο στη Java, θα πρέπει η τάξη από την οποία προέρχεται να υλοποιεί την µέθοδο run(). Στην ορολογία της Java τα αντικείµενα αυτά λέγονται Runnable objects (ΟΧΙ δεν θα προσπαθήσω να µεταφράσω το Runnable!). Η µέθοδος run() του αντικειµένου θα είναι η «πύλη» από την οποία θα ξεκινά ένα νέο νήµα εκτέλεσης. Το νήµα εκτέλεσης θα ξεκινά µε την µέθοδο run() και θα τερµατίζεται όταν τερµατίζεται η µέθοδος run(). Υπάρχουν δύο τρόποι για να κάνουµε µια τάξη στη Java να υλοποιήσει την µέθοδο run():

1. Ο πρώτος τρόπος είναι να κάνουµε την τάξη υποτάξη της τάξης Thread. Αυτή η τάξη παρέχει µια κενή µέθοδο run() και λογικά θα πρέπει να την ορίσουµε και πάλι στην υποτάξη υπερβαίνοντας την κενή run() της υπερτάξης. Η τάξη Thread υλοποιεί την διασύνδεση (interface) Runnable που πρέπει να υλοποιούν όλα τα αντικείµενα από τα οποία ξεκινούνε νέα νήµατα εκτέλεσης. Η υποχρέωση που προκύπτει από την υλοποίηση του Runnable είναι ο ορισµός της µεθόδου run().

2. Ο δεύτερος τρόπος είναι η απευθείας υλοποίηση της διασύνδεσης Runnable. Σε αυτή τη περίπτωση θα πρέπει να οριστεί εξαρχής η µέθοδος run(). Αυτή η µέθοδος προτιµάται σε περιπτώσεις που η τάξη που ορίζουµε έχει ήδη µια άλλη υπερτάξη. Περισσότερο χαρακτηριστικό παράδειγµα είναι ένα Applet: Επειδή µια τάξη που είναι Applet πρέπει να επεκτείνει ήδη την τάξη java.applet.Applet η µοναδική υπερτάξη που επιτρέπει η Java – θυµηθείτε ότι η Java δεν επιτρέπει πολλαπλή κληρονοµικότητα – είναι ήδη πιασµένη. Εποµένως είναι αδύνατο να εφαρµόσουµε την πρώτη µέθοδο για να κάνουµε ένα Applet Runnable. Θα πρέπει σε αυτήν, και σε παρόµοιες περιπτώσεις, να εφαρµόσουµε την δεύτερη µέθοδο.

Μέθοδος 1: Κάνοντας την τάξη µας υποτάξη της Thread Η πρώτη µέθοδος είναι να κάνουµε µια τάξη υποτάξη της Thread. Έστω για παράδειγµα ότι θέλουµε να κάνουµε µια τάξη, την SimpleTimer, που θα παίξει το ρόλο του χρονοµέτρου: Ο κατασκευαστής της τάξης θα παίρνει δυο ορίσµατα. Το πρώτο θα είναι ένας long που θα είναι ο αριθµός των δευτερολέπτων που θα µετρήσει το χρονόµετρο και το δεύτερο ένα String που θα είναι το όνοµα του Thread. Ο κατασκευαστής θα καλέσει τον κατασκευαστή της υπερτάξης δίνοντας το όνοµα σαν παράµετρο. Αργότερα µπορούµε να ανακαλέσουµε το όνοµα του Thread αντικειµένου καλώντας την µέθοδο getName() της υπερτάξης Thread. Επίσης ο κατασκευαστής θα θέσει και την ιδιότητα time ίση µε το δοθέν πρώτο όρισµα. Ο κατασκευαστής είναι ο ακόλουθος:

public SimpleTimer(long t, String threadName)

super(threadName); time = t;

Επίσης θα πρέπει να ορίσουµε και την µέθοδο run(). H run() θα εκτελεί ένα βρόχο που θα µετρά τα δευτερόλεπτα µέχρι να τελειώσουν. Σε κάθε δευτερόλεπτο θα εµφανίζεται το µήνυµα όνοµα: <ν> όπου όνοµα θα είναι το όνοµα του Thread και <ν> το τρέχον

- 121 -

Page 122: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

δευτερόλεπτο του χρονοµέτρου. Στο τέλος, αφού τερµατιστεί ο βρόχος for θα εµφανίζεται το µήνυµα: όνοµα: End of Time. Ο κώδικας για την µέθοδο run δίνεται στη συνέχεια:

public void run()

try

for (long i=0; i<time; i++) sleep(1000); System.out.println(getName()+": "+i); System.out.println(getName()+":End of Time"); catch (InterruptedException e) e.printStackTrace();

Ο κώδικας για όλη την τάξη δίνεται στη συνέχεια. Παρατηρείστε ότι φυσικά η τάξη SimpleTimer είναι υποτάξη της τάξης Thread:

public class SimpleTimer extends Thread long time; public SimpleTimer(long t, String threadName) super(threadName); time = t; public void run() try for (long i=0; i<time; i++) sleep(1000); System.out.println(getName()+": "+i); System.out.println(getName()+":End of Time"); catch (InterruptedException e) e.printStackTrace();

Τώρα µπορούµε να χρησιµοποιήσουµε την τάξη TimerThread για να δηµιουργήσουµε αντικείµενα τύπου TimerThread, και να τα ξεκινήσουµε καλώντας την µέθοδο start() σε αυτά τα αντικείµενα. Κάθε φορά που καλείται η µέθοδος start() σε ένα αντικείµενο τύπου Thread ξεκινά η µέθοδος run() αυτού του αντικειµένου να εκτελείται. Φυσικά τα αντικείµενα της τάξης SimpleTimer είναι τύπου Thread αφού είναι αντικείµενα µιας υποτάξης της τάξης Thread.

- 122 -

Page 123: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

Η τάξη SimpleTimerTest που δίνεται στη συνέχεια απλά δηµιουργεί δυο χρονόµετρα. Το ένα θα λέγεται «Timer 1» και θα µετρήσει πέντε δεύτερα και το άλλο «Timer 2» και θα µετρήσει 2 δεύτερα. Αφού δηµιουργήσει τα δυο αντικείµενα στη συνέχεια η main καλεί την µέθοδο start() στο καθένα απ’ αυτά πράγµα που ξεκινά την εκτέλεση της run() στα δυο αντικείµενα.

class SimpleTimerTest public static void main(String[] args) SimpleTimer st1 = new SimpleTimer(5, "Timer 1"); SimpleTimer st2 = new SimpleTimer(2, "Timer 2"); System.out.println("Timers start now!"); st1.start(); st2.start();

Όταν τρέξετε το πρόγραµµα SimpleTimerTest θα δείτε κάτι σαν το ακόλουθο output:

Timers start now! Timer 1: 0 Timer 2: 0 Timer 1: 1 Timer 2: 1 Timer 2:End of Time Timer 1: 2 Timer 1: 3 Timer 1: 4 Timer 1:End of Time

Λέµε “κάτι σαν το ακόλουθο output” και όχι το ακόλουθο output, διότι το τι ακριβώς θα δείτε δεν είναι προκαθορισµένο, µια και δυο νήµατα που τρέχουν ταυτόχρονα έχουν ίση πιθανότητα να πάρουν το επόµενο κβάντο χρόνου, κάνοντας έτσι την όλη κατάσταση κάπως αβέβαιη. Μέθοδος 2: Υλοποιώντας την διασύνδεση Runnable Η δεύτερη µέθοδος µε την υλοποίηση της διασύνδεσης Runnable, όπως ήδη είπαµε, είναι ο τρόπος µε τον οποίο µπορούµε να κάνουµε µια τάξη να είναι τάξη από την οποία παράγονται ενεργά αντικείµενα και ας έχει ήδη µια άλλη υπερτάξη. Σε αυτή τη περίπτωση δεν γίνεται να χρησιµοποιήσουµε την πρώτη µέθοδο γιατί η Java δεν υποστηρίζει την λεγόµενη πολλαπλή κληρονοµικότητα (multiple inheritance), δηλαδή στη Java δεν γίνεται µια τάξη να επεκτείνει περισσότερες από µια τάξεις. Με την µέθοδο αυτή θα χρειαστεί να δηµιουργήσουµε και πάλι ένα αντικείµενο τύπου Thread και να το ξεκινήσουµε µε την µέθοδο start(), µόνο που αυτή τη φορά θα χρησιµοποιήσουµε έναν κατασκευαστή της τάξης Thread που παίρνει σαν όρισµα ένα αντικείµενο τύπου Runnable. Αργότερα όταν καλούµε την µέθοδο start() στο αντικείµενο Thread που δηµιουργήσαµε, ξεκινά η εκτέλεση της µεθόδου run() στο Runnable αντικείµενο που περάσαµε σαν παράµετρο στον κατασκευαστή του. Για να κάνουµε τα παραπάνω πιο σαφή ας δώσουµε ένα παράδειγµα αυτής της δεύτερης µεθόδου: θα δηµιουργήσουµε ένα Applet το οποίο θα χρησιµοποιεί την µέθοδο run() για να εµφανίσει ένα κινούµενο σχέδιο. Το κινούµενο σχέδιο θα αποτελείται από τέσσερα εικόνες jpeg που θα εναλλάσσονται γρήγορα δίνοντας την εντύπωση της κίνησης. Το κινούµενο σχέδιο δεν είναι άλλο από τον γνωστό Duke της Java.

- 123 -

Page 124: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

Το Applet που θα κάνουµε θα υλοποιεί την διεπαφή Runnable. Εποµένως η δήλωση του Applet θα ξεκινά ως εξής:

import java.applet.*; import java.awt.*; public class AnimationApplet extends Applet implements Runnable int count; final int lastcount=4; final long timeDiff=100; Image pictures[]; Thread runningThread; ...

− Όπως βλέπετε το πλήθος των εικόνων (lastcount) τίθεται σε 4. Αυτή είναι µια σταθερή τιµή και γι’ αυτό δηλώνεται ως final (τελική)

− Οι µεταβλητή count είναι ο µετρητής της επόµενης εικόνας που θα εµφανιστεί. − Η σταθερά timeDiff είναι η χρονική διαφορά µεταξύ της ανανέωσης των εικόνων σε

milliseconds. Για το παράδειγµα αυτό χρησιµοποιούµε την τιµή 100 milliseconds. Μπορείτε φυσικά να δοκιµάσετε και άλλες ταχύτητες.

− Ο πίνακας pictures θα περιέχει τις τέσσερις εικόνες που θέλουµε να εµφανίσουµε και − Το αντικείµενο runningThread τάξης Thread θα είναι το νήµα εκτέλεσης που θα

ξεκινήσει για την εκτέλεση της µεθόδου run() του applet. Η µέθοδος init() εκτελείται – όπως ξέρουµε – κάθε φορά που ξεκινά η εκτέλεση του applet:

public void init() count = 0; pictures = new Image[lastcount]; for (int a = 0; a < lastcount; a++) pictures[a] = getImage ( getCodeBase(),"Juggler"+a+".gif");

Αρχικά θα εµφανιστεί η πρώτη εικόνα η οποία στον πίνακα pictures θα έχει δείκτη 0, εποµένως το count αρχικοποιείται σε 0. Στη συνέχεια η init() δηµιουργεί τον πίνακα των εικόνων µε την εντολή pictures = new Image[lastcount]. Ο for βρόχος που εκτελείται µετά γεµίζει τον πίνακα pictures µε τις εικόνες που βρίσκονται στα αρχεία µε το όνοµα Juggler0.gif µέχρι Jugler3.gif. Τα αντικείµενα τύπου Image δηµιουργούνται από τα αρχεία µε την κλήση της µεθόδου getImage(). Αυτή η µέθοδος παίρνει σαν όρισµα το URL του αρχείου στο οποίο περιέχεται το Image και το όνοµά του. Όπως βλέπετε εδώ, για URL χρησιµοποιούµε το URL από το οποίο φορτώθηκε το Applet το οποίο επιστρέφει η κλήση της µεθόδου getCodeBase(). Το δεύτερο όρισµα της getImage() δεν είναι τίποτ’ άλλο παρά το όνοµα του αρχείου το οποίο δηµιουργείτε από το συστατικό «Juggler» το οποίο συνενώνεται µε τον δείκτη του αρχείου (π.χ. Juggler0 για το πρώτο αρχείο, Juggler1 για το δεύτερο κ.ο.κ.) και µε την επέκταση gif (π.χ. Juggler0.gif για το

- 124 -

Page 125: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

πρώτο αρχείο, Juggler1.gif για το δεύτερο κ.ο.κ.). Φυσικά τα αρχεία αυτά θα πρέπει να είναι διαθέσιµα στην περιοχή από την οποία φορτώθηκε το applet. Η µέθοδος start() που εκτελείται κάθε φορά που ξεκινά το Applet δηµιουργεί ένα νέο αντικείµενο Thread που έχει σαν παράµετρο το ίδιο το Applet και το ξεκινά:

public void start() runningThread = new Thread(this); runningThread.start();

Το Applet είναι ένα Runnable αντικείµενο και εποµένως η συσχέτισή του µε το Thread αντικείµενο µε το πέρασµά του σαν παράµετρο στον κατασκευαστή του Thread (εντολή: runningThread = new Thread(this)) θα έχει σαν αποτέλεσµα να εκτελεστεί η δική του µέθοδος run() όταν θα ξεκινήσουµε το Thread. Αυτό γίνεται µε την αµέσως επόµενη εντολή: runningThread.start(). Η µέθοδος run() που αρχίζει να εκτελείται όταν ξεκινά το αντικείµενο runningThread, είναι αυτή που ελέγχει την εµφάνιση των γραφικών:

public void run() while (runningThread == Thread.currentThread()) try repaint(); Thread.sleep(timeDiff); catch (InterruptedException e) e.printStackTrace();

Παρατηρείστε ότι η µέθοδος run() εκτελείται όσο το αντικείµενο runningThread είναι αυτό που διασχίζει την µέθοδο run(), είναι ίσο δηλαδή µε το τρέχον νήµα εκτέλεσης που επιστρέφεται από την κλήση της στατικής µεθόδου Thread.currentThread(). Αυτός ο έλεγχος είναι σηµαντικός, γιατί από την µέθοδο stop() που καλείται κάθε φορά που σταµατά η εκτέλεση του Applet θα κάνουµε το runningThread ίσο µε null. Αυτό θα έχει σαν αποτέλεσµα τον τερµατισµό της µεθόδου run(), αφού το αντικείµενο runningThread δεν θα είναι ίσο πια µε το Thread.CurrentThread(). Μπορείτε να χρησιµοποιείτε αυτή τη µέθοδο κάθε φορά που κάνετε ένα Applet Runnable για να σταµατάτε την µέθοδο run() κάθε φορά που σταµατά το Applet. Κατά τα άλλα η µέθοδος run() καλεί την repaint() που έχει σαν αποτέλεσµα την κλήση της paint(), που εµφανίζει – όπως θα δούµε σε λίγο – το τρέχον Image. Μετά την κλήση της repaint() η run() θα περιµένει για 100 milliseconds πράγµα που επιτυγχάνεται µε την κλήση της Thread.sleep(timeDiff). Η µέθοδος paint() είναι αυτή που τοποθετεί το τρέχον Image στην επιφάνεια σχεδίασης του Applet:

public void paint(Graphics g) g.drawImage(pictures[count++], 0, 0, this); if (count == lastcount) count = 0;

Όπως είδαµε η paint() καλείται συνεχώς από την run() κάθε 10/100 του δευτερολέπτου. Αυτό που κυρίως κάνει είναι ότι καλεί την drawImage() για την επιφάνεια γραφικών της (η παράµετρος Graphics g). Η πρώτη παράµετρος της drawImage() είναι το Image που θα

- 125 -

Page 126: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

σχεδιαστεί. Αυτό είναι το pictures[count]. To ++ στο τέλος της count αυξάνει την count έτσι ώστε την επόµενη φορά να εµφανιστεί το επόµενο Image. Οι επόµενες δυο τιµές είναι οι συντεταγµένες της πάνω αριστερής γωνίας από την οποία θα αρχίσει η εµφάνιση του Image. Παρατηρείστε τέλος την χρήση του if µετά την κλήση της drawImage(). Αν το count έχει πάρει την τιµή lastcount (στη περίπτωσή µας 4) το θέτουµε ξανά ίσο µε 0, για να εµφανιστεί και πάλι το πρώτο Image του πίνακα pictures[]. Τέλος η µέθοδος stop() απλά θέτει το runningThread ίσο µε null:

public void stop() runningThread = null;

Αυτό όπως ήδη εξηγήσαµε όταν µιλήσαµε για την run() θα έχει σαν αποτέλεσµα τον τερµατισµό της µεθόδου run(). Το όλο Applet παρατίθεται στη συνέχεια:

import java.applet.*; import java.awt.*; public class AnimationApplet extends Applet implements Runnable int count; final int lastcount = 4; final long timeDiff=100; Image pictures[]; Thread runningThread; public void init() count = 0; pictures = new Image[lastcount]; for (int a = 0; a < lastcount; a++) pictures[a] = getImage ( getCodeBase(),"Juggler"+a+".gif"); public void start() runningThread = new Thread(this); runningThread.start(); public void stop() runningThread = null; public void paint(Graphics g) g.drawImage(pictures[count++], 0, 0, this); if (count == lastcount) count = 0; public void run() while (runningThread == Thread.currentThread()) try repaint(); Thread.sleep(timeDiff);

- 126 -

Page 127: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

catch (InterruptedException e) e.printStackTrace();

Τέλος θα χρειαστούµε και µια html σελίδα στην οποία θα εκτελεστεί το Applet. Αυτή µπορεί να περιοριστεί στα εντελώς βασικά όπως στην ακόλουθη σελίδα (Animate.html) που περιέχει απλώς το tag <applet>:

<applet CODE="AnimationApplet.class" HEIGHT="120" WIDTH="120"> </applet>

Νήµατα εκτέλεσης και συνθήκες αγώνων δρόµου Όσοι έχουν ασχοληθεί µε ταυτόχρονες διεργασίες γνωρίζουν πολύ καλά ότι τα πράγµατα πολλές φορές δεν είναι τόσο απλά όσο φαίνονται αρχικά. Τα νήµατα εκτέλεσης πολλές φορές προσπελάζουν κοινά δεδοµένα και απαιτείται κάποιου είδους συγχρονισµός για να µην δηµιουργηθούν προβλήµατα. Πολλές φορές στην βιβλιογραφία για τα προβλήµατα αυτά χρησιµοποιείται ο όρος συνθήκες αγώνων δρόµου (race conditions), ακριβώς γιατί µπορούµε να φανταστούµε τα νήµατα εκτέλεσης σαν δροµείς σε µια κούρσα που συναγωνίζονται ο ένας τον άλλον. Εµείς θα πρέπει να εξασφαλίσουµε ότι αυτός ο συναγωνισµός δεν θα προκαλέσει προβλήµατα. Για να αποφευχθούν αυτά τα προβλήµατα στα οποία αναφερόµαστε η Java επιτρέπει τον συγχρονισµό των νηµάτων εκτέλεσης. Οι µηχανισµοί που µπορούν να χρησιµοποιηθούν γι’ αυτό το σκοπό είναι οι ακόλουθοι: Συγχρονισµένες µέθοδοι και τµήµατα κώδικα Τα νήµατα εκτέλεσης της Java θα µπορούσαν να προσπελάσουν ταυτόχρονα –τουλάχιστον εννοιολογικά ταυτόχρονα – κοινές µεταβλητές. Σκεφτείτε, για παράδειγµα, την ακόλουθη τάξη:

public class Point protected int _x=0; protected int _y=0; public void read() System.out.println(“X= ”+_x+“, Y=”+_y); public void write(int x, int y) _x=x; _y=y;

Φανταστείτε ότι διάφορα νήµατα καλούν τις write και read για να θέσουν ή για να τυπώσουν αντίστοιχα τις συντεταγµένες _x,_y του σηµείου. Θα πρέπει να επιτευχθεί ο αµοιβαίος αποκλεισµός (mutual exclusion) των κλήσεων αυτών. Αν αυτό δεν γίνει ενδέχεται να προκύψουν προβλήµατα. Για παράδειγµα φανταστείτε το ακόλουθο σενάριο:

− Το νήµα εκτέλεσης Α καλεί την write αρχικά µε συντεταγµένες (3,5) και εκτελεί την εντολή _x=x κάνοντας το _x ίσο µε 3. Στη συνέχεια το νήµα Α διακόπτεται από το νήµα Β πριν να θέσει την τιµή του _y σε 5.

- 127 -

Page 128: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

− Το νήµα Β, που συνεχίζει, εκτελεί και αυτό την write µε συντεταγµένες (10, 20). Έστω τώρα ότι το νήµα Β δεν διακόπτεται και θέτει επιτυχώς τις συντεταγµένες σε 10 και 20.

− Στη συνέχεια το νήµα Α που είχε διακοπεί συνεχίζει και θέτει το _y ίσο µε 5. − Τελικά το _x θα είναι 10 (από το write του νήµατος Β) και το _y θα είναι 5 (από το

write του νήµατος Α). Αυτό σηµαίνει ότι καµία κλήση δεν κατάφερε να ενηµερώσει σωστά το σηµείο και καταλήξαµε να έχουµε µια ασυνεπή κατάσταση µια και ουδέποτε υπήρξε κλήση της write µε (x,y)=(10,5).

Φανταστείτε ακόµα το ακόλουθο σενάριο:

− Το νήµα εκτέλεσης Α εκτελεί την write µε τιµές παραµέτρων (10,20) και θέτει το _x σε 10 πριν διακοπεί από το νήµα Β.

− Το νήµα Β εκτελεί την read και τυπώνει στην οθόνη την νέα τιµή του _x (αυτήν που τέθηκε από το A) αλλά την παλιά τιµή του _y, µια και το νήµα Α δεν πρόλαβε να θέσει το _y σε 20.

Γενικά αυτό είναι γνωστό σαν το πρόβληµα των αναγνωστών και των συγγραφέων (Readers/Writers Problem) και αυτό που πρέπει να γίνει είναι να αποκλειστεί η ταυτόχρονη κλήση της write µε άλλες write και read. Η Java υποστηρίζει τον αµοιβαίο αποκλεισµό µε χρήση της δεσµευµένης λέξης synchronized, που µπορεί να χρησιµοποιηθεί είτε για τον συγχρονισµό σε ολόκληρες µεθόδους είτε και για το συγχρονισµό σε συγκεκριµένα τµήµατα κώδικα (blocks). Η δήλωση µιας synchronized µεθόδου έχει ως αποτέλεσµα την αποκλειστική εκτέλεση της σε σχέση µε όλες τις άλλες synchronized µεθόδους ή synchronized τµήµατα κώδικα (στο ίδιο αντικείµενο), και συντάσσεται ως εξής:

προσδ.-πρόβασης synchronized τύπος όνοµα (λίστα-παραµέτρων) ...

Η δήλωση ενός τµήµατος κώδικα ως synchronized γίνεται µε το ακόλουθο συντακτικό και ο κώδικας θα εκτελεστεί αποκλειστικά για όλα τα άλλα synchronized τµήµατα κώδικα ή τις synchronized µεθόδους του . αντικειµένου-1

... synchronized( ) αντικείµενο-1 ... ...

Στη Java υπάρχει ακριβώς µια κλειδαριά (lock) ανά αντικείµενο. Όταν καλείται µια συγχρονισµένη µέθοδος, περιµένει έως την απόκτηση αυτής της κλειδαριάς, µετά εκτελεί τον κώδικα της και στη συνέχεια απελευθερώνει τη κλειδαριά για να µπορέσουν να συνεχίσουν άλλες συγχρονισµένες µέθοδοι που πιθανώς περίµεναν το πέρας της συγχρονισµένης µεθόδου που είχε την κλειδαριά. Το ίδιο ισχύει και για τις συγχρονισµένες οµάδες εντολών οι οποίες περιµένουν να αποκτήσουν τη κλειδαριά του αντικειµένου στο οποίο έχουν συγχρονιστεί µε την φράση synchronized. Για να συγχρονίσουµε, λοιπόν, την τάξη Point, µπορούµε να δώσουµε την ακόλουθη πρώτη λύση:

public class Point

- 128 -

Page 129: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

protected int _x=0; protected int _y=0; public synchronized void read() System.out.println(“X= ”+_x+“, Y=”+_y); public synchronized void write(int x, int y) _x=x; _y=y;

Ή ισοδύναµα µε την χρήση συγχρονισµού τµηµάτων κώδικα στο ίδιο το αντικείµενο (στο this):

public class Point protected int _x=0; protected int _y=0; public void read() synchronized(this) System.out.println(“X= ”+_x+“, Y=”+_y); public void write(int x, int y) synchronized(this)

_x=x; _y=y;

Για να δοκιµάσουµε τη λύση µας, θα δηµιουργήσουµε δύο τάξεις Read και Writer που θα επεκτείνουν την τάξη Thread και οι οποίες θα διαβάζουν και θα ενηµερώνουν αντίστοιχα ένα Point αντικείµενο. Θα φροντίσουµε έτσι ώστε αυτό το αντικείµενο να είναι το ίδιο Point αντικείµενο. Οι τάξεις αυτές είναι οι ακόλουθες:

public class Reader extends Thread int _rounds; Point _point; public Reader(int rounds, Point point) _rounds=rounds; _point=point; public void run() for (int i=0; i<_rounds; i++) _point.read(); public class Writer extends Thread int _rounds; Point _point;

- 129 -

Page 130: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

public Writer(int rounds, Point point) _rounds=rounds; _point=point; public void run() for (int i=0; i<_rounds; i++) _point.write(i,i);

Στην συνέχεια µπορούµε να κατασκευάσουµε µια τάξη Main που θα κατασκευάσει δύο αντικείµενα ένα Reader και ένα Writer και θα τα ξεκινήσει. Τα δύο νήµατα θα εκτελέσουν 1000 επαναλήψεις στο ίδιο αντικείµενο point, ενηµερώνοντάς το και εκτυπώνοντας τις συντεταγµένες του.

public class Main public static void main(String[] args) int rounds=1000; Point point=new Point(); new Reader(rounds, point).start(); new Writer(rounds, point).start();

Σε ότι αφορά τις synchronized µεθόδους σηµειώστε επίσης τα ακόλουθα: 1. Ένα νήµα εκτέλεσης που κατέχει τη κλειδαριά ενός αντικειµένου µπορεί να καλέσει µια

άλλη synchronized µέθοδο του ίδιου αντικειµένου ή να εκτελέσει τµήµατα κώδικα που έχουν συγχρονισθεί πάνω στο ίδιο αντικείµενο. Αυτό επιτρέπεται διότι στην αντίθετη περίπτωση θα µπορούσε ένα νήµα εκτέλεσης να οδηγήσει σε αδιέξοδο τον ίδιο τον εαυτό του.

2. Υπάρχει κίνδυνος αδιεξόδου (deadlock) αν µια synchronized µέθοδο Α ενός αντικειµένου καλέσει µια άλλη synchronized µέθοδο Β σε ένα άλλο αντικείµενο, η οποία µε την σειρά της καλεί τη Α. Τότε µπορεί αν οι δύο κλήσεις γίνουν «ταυτόχρονα», να υπάρξει αδιέξοδο.

Οι µέθοδοι wait, notify και notifyAll Η µέθοδος wait() που είναι µέθοδος της τάξης Object (υπερτάξης όλων των τάξεων), προκαλεί την αναµονή στο νήµα εκτέλεσης που την καλεί, µέχρις ότου ένα άλλο νήµα εκτέλεσης καλέσει την µέθοδο notify() ή την µέθοδο notifyAll() (βλ. συνέχεια). Το νήµα εκτέλεσης που καλεί την wait() θα πρέπει να έχει τη κλειδαριά του αντικειµένου. Με άλλα λόγια η wait πρέπει να καλείται µόνο µέσα από synchronized µεθόδους ή synchronized τµήµατα κώδικα. Όταν κληθεί η wait το νήµα απελευθερώνει τη κλειδαριά και περιµένει µέχρι κάποιο άλλο νήµα (που προφανώς πήρε τη κλειδαριά) να καλέσει την notify ή την notifyAll. Το νήµα εκτέλεσης τότε περιµένει µέχρις ότου ανακτήσει και πάλι την ιδιοκτησία της κλειδαριάς, οπότε και συνεχίζει την εκτέλεσή του. Η µέθοδος wait µπορεί να προκαλέσει την µη ελεγχόµενη εξαίρεση IllegalMonitorException εφόσον το αντικείµενο που καλέσει την wait δεν έχει την ιδιοκτησία της κλειδαριάς στο αντικείµενο. Επίσης µπορεί να προκαλέσει την ελεγχόµενη (πρέπει να δηλωθεί ότι προκαλείται ή να γίνει ο χειρισµός της) εξαίρεση InterruptedException εάν κάποιο άλλο νήµα εκτέλεσης διακόψει (µε την κλήση της interrupt) το εν λόγω νήµα εκτέλεσης ή διακοπεί εξαιτίας κάποιου λάθους η εκτέλεση του νήµατος. Όταν ένα νήµα αφήσει την κλειδαριά µε την wait(), κάποιο από τα υπόλοιπα νήµατα που περίµεναν για την κλειδαριά θα εισέλθει στο κρίσιµο τµήµα του (critical section). Το νήµα που εκτέλεσε την wait() θα περιµένει µέχρις ότου κάποιο άλλο νήµα εκτελέσει την notify() ή την

- 130 -

Page 131: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

notifyAll(). Η διαφορά των δύο µεθόδων είναι ότι η notifyAll() ειδοποιεί όλα τα νήµατα που έχουν εκτελέσει την wait() και βρίσκονται στη µια και µοναδική λίστα αναµονής (waiting list) του αντικειµένου, ενώ η notify() επιλέγει µε τυχαίο τρόπο ένα νήµα και του δίνει τη κλειδαριά για να συνεχίσει στο κρίσιµο τµήµα του. Στη συνέχεια θα δώσουµε µια λύση στο πρόβληµα των αναγνωστών και συγγραφέων που χρησιµοποιεί τις wait και notifyAll, έτσι ώστε να επιτρέπεται η ανάγνωση του σηµείου από πολλούς ταυτόχρονους αναγνώστες, αλλά να επιτρέπεται µόνο ένας συγγραφέας κάθε φορά. ∆ηλαδή οι συγγραφείς λειτουργούν αποκλειστικά και ως προς τους άλλους συγγραφείς και ως προς τους άλλους αναγνώστες. Οι αναγνώστες λειτουργούν αποκλειστικά µόνο ως προς τους άλλους συγγραφείς. Αυτή είναι και η επιθυµητή λύση µια και η ανάγνωση των συντεταγµένων του σηµείου από πολλούς ταυτόχρονους αναγνώστες δεν είναι προβληµατική, εφόσον βέβαια δεν υπάρχει ταυτόχρονη προσπέλαση συγγραφέα. Ακολουθεί ο κώδικας της νέας τάξης Point:

public class Point protected int _x=0; protected int _y=0; /* Αριθµός ταυτόχρονων αναγνωστών.

Πρέπει να είναι 0 για να προχωρήσει ένας συγγραφέας */ protected int _nr=0; public void read() startRead(); System.out.println("X= "+_x+", Y="+_y); endRead(); private synchronized void startRead() _nr++; private synchronized void endRead() _nr--; if (_nr==0) notifyAll(); public synchronized void write(int x, int y) while (_nr>0) try wait(); catch (InterruptedException ex) return; _x=x; _y=y; notifyAll();

Προσθέσαµε στη τάξη δύο ακόµα ιδιωτικές µεθόδους. Την µέθοδο startRead() και την µέθοδο endRead(). Αυτές καλούνται από την µέθοδο read() πριν και αφού διαβάσει τα δεδοµένα. Η µέθοδος startRead() αυξάνει την µεταβλητή _nr που είναι ο αριθµός των ενεργών αναγνωστών. Η µέθοδος endRead() µειώνει αυτή τη τιµή. Αν ο αριθµός των

- 131 -

Page 132: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

αναγνωστών είναι 0 στο τέλος της endRead(), η endRead() ειδοποιεί πιθανά «κλειδωµένους» συγγραφείς µε την κλήση της notifyAll(). Οι µέθοδοι startRead(), endRead() και write() είναι συγχρονισµένες που σηµαίνει ότι το πολύ µια από αυτές µπορεί να εκτελεστεί κάθε φορά. Εποµένως ένα νήµα ενός συγγραφέα δεν µπορεί να εκτελεστεί ενώ είναι ενεργές είτε η startRead() είτε η endRead(). Αντιθέτως η read() δεν είναι συγχρονισµένη, επιτρέποντας έτσι την ταυτόχρονη εκτέλεσή της από πολλούς αναγνώστες. Αν ένας συγγραφέας καλέσει την write() ενώ ένας αναγνώστης διαβάζει τα δεδοµένα, η τιµή του _nr θα είναι θετική και θα αναγκαστεί να περιµένει. Ένας συγγραφέας αφυπνίζεται όταν το _nr γίνει 0 (από την endRead()). Επίσης αφού προσπελάσει τα δεδοµένα, ο συγγραφέας θα πρέπει να καλέσει την notifyAll() για να αφυπνίσει και άλλους κλειδωµένους συγγραφείς (αν αυτοί υπάρχουν).

Το πρόβληµα του δείπνου των φιλοσόφων Ο καθηγητής Edsger Dijkstra (1971) διατύπωσε ένα πρόβληµα το οποίο καλύπτει σχεδόν όλα όσα µπορούν να πάνε λάθος όταν ταυτόχρονες διεργασίες προσπελάζουν κοινούς πόρους, ενώ ταυτόχρονα είναι αρκετά απλό ώστε να αναλυθεί σε µια συζήτηση στη τάξη. Το πρόβληµα είναι το ακόλουθο: “Πέντε φιλόσοφοι περνούν τις ζωές τους σκεπτόµενοι ή τρώγοντας. Κάθονται όλοι γύρω από ένα στρογγυλό τραπέζι. Το τραπέζι έχει στο κέντρο του µια άπειρη ποσότητα από ρύζι. Μπροστά από κάθε φιλόσοφο υπάρχει ένα πιάτο ενώ ανάµεσα από τα πιάτα υπάρχει ένα κινέζικο ξυλάκι. Κάθε φιλόσοφος σκέφτεται για ένα τυχαίο χρονικό διάστηµα ενώ στη συνέχεια τρώει για ένα τυχαίο χρονικό διάστηµα. Για να φάει ένας φιλόσοφος θα πρέπει να πάρει και τα δύο ξυλάκια. Αν κάποιο ξυλάκι εκείνη τη στιγµή το έχει πάρει κάποιος από τους δυο διπλανούς του φιλοσόφους, θα πρέπει να περιµένει.”

Το κύριο πρόβληµα που θίγεται εδώ είναι αυτό του αδιεξόδου (deadlock). Τι θα γίνει αν όλοι οι φιλόσοφοι προσπαθήσουν να πάρουν το αριστερό τους ξυλάκι ταυτόχρονα; Είναι προφανές ότι αφού τα ξυλάκια είναι πέντε ο κάθε φιλόσοφος θα βρεθεί µε ένα ξυλάκι στο χέρι, περιµένοντας τον εκ δεξιών διπλανό του να αφήσει το αριστερό του ξυλάκι για να φάει. Επειδή όλοι οι φιλόσοφοι θα περιµένουν το ίδιο ακριβώς πράγµα, αυτό δε θα συµβεί ποτέ και οι καηµένοι φιλόσοφοι θα πεθάνουν από τη πείνα. Μια λύση σε αυτό το πρόβληµα είναι να εξασφαλίσουµε ότι αν ένας φιλόσοφος µπορεί να πάρει και τα δύο ξυλάκια µόνο τότε θα προσπαθήσει να τα πάρει, ενώ αυτή του η ενέργεια θα είναι αδιαίρετη: αφού ξεκινήσει κανείς άλλος δεν θα προσπαθήσει να πάρει αυτά τα δυο ξυλάκια. Μια άλλη λύση είναι κάποιος από τους πέντε φιλοσόφους να είναι ανάποδος: Ενώ όλοι σηκώνουν πρώτα το δεξί τους ξυλάκι αυτός σηκώνει το αριστερό. Αυτό θα έχει ως αποτέλεσµα το σπάσιµο του κύκλου κάνοντας έτσι εφικτό το να µπορεί πάντα να φάει ένας τουλάχιστον φιλόσοφος. Αυτή την ελεύθερη από αδιέξοδα λύση δίνουµε στη συνέχεια µε την µορφή ενός Applet. Στο Applet αυτό θα έχουµε 5 φιλοσόφους (αντικείµενα της τάξης Philosopher) τα οποία θα έχουν και µια οπτική αναπαράσταση από εικόνες. Για να κάνουµε ανεξάρτητη την τάξη Philosopher από το Applet θα χρησιµοποιήσουµε το µοτίβο (design pattern) του Παρατηρητή-Παρατηρούµενου (Observer-Observable design pattern) του οποίου η υλοποίηση στη Java περιγράφεται στο πλαίσιο που ακολουθεί:

- 132 -

Page 133: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

Το µοτίβο του Παρατηρητή-Παρατηρούµενου (Observer-Observable pattern) Το µοτίβο αυτό στη Java υλοποιείται µε την διασύνδεση java.util.Observer και την τάξη java.util.Observable. 1. Ένα αντικείµενο που θέλουµε να παρατηρείται από έναν ή περισσότερους παρατηρητές

πρέπει να επεκτείνει την τάξη java.util.Observable. Αυτή η επέκταση παρέχει στο αντικείµενο µεθόδους για την προσθήκη παρατηρητών (addObserver) και για την ειδοποίηση των παρατηρητών όταν η κατάσταση του αντικειµένου µεταβληθεί (notifyObservers).

2. Ένα αντικείµενο που θέλουµε να παίξει τον ρόλο του παρατηρητή (Observer) σε ένα παρατηρούµενο αντικείµενο θα πρέπει να υλοποιεί την διασύνδεση Observer. Η διασύνδεση αυτή έχει την µέθοδο update(Observable o, Object arg) και καλείται σε ένα παρατηρητή όταν κάποιο παρατηρούµενο αντικείµενο (στους παρατηρητές του οποίου έχει προστεθεί ο παρατηρητής) καλεί την notifyObservers.

Τα παραπάνω επιτρέπουν την αποσύνδεση των παρατηρούµενων αντικειµένων από τους παρατηρητές τους, δηλαδή θα µπορούσε να προσθέσει κανείς παρατηρητές διαφόρων ειδών που ενδιαφέρονται για τις αλλαγές στην κατάσταση ενός παρατηρούµενου αντικειµένου, χωρίς να αλλάξει ούτε µια γραµµή κώδικα στο παρατηρούµενο αντικείµενο. Στην δική µας εφαρµογή το Applet θα παίξει τον ρόλο του παρατηρητή και η τάξη Philosopher τον ρόλο του παρατηρούµενου. Ισχύει δηλαδή το ακόλουθο UML διάγραµµα:

Observable

changed : boolean = false

Observable()addObserver()deleteObserver()notifyObservers()notifyObservers()deleteObservers()setChanged()clearChanged()hasChanged()countObservers()

(f rom ut il)

Observer

update()

(from util)

<<Interface>>-arr[]

PhilosopherPhilApplet

Πρέπει να είναι προφανές από το διάγραµµα ότι η αντικατάσταση του παρατηρητή (π.χ. από µια εφαρµογή κονσόλας) ή η προσθήκη και άλλων παρατηρητών, δεν επηρεάζει σε τίποτα τον κώδικα της τάξης Philosopher, µια και η ενηµέρωση των παρατηρητών γίνεται µέσω της update() που µπορούν να υλοποιήσουν µε όποιο τρόπο αυτοί επιθυµούν, αλλά δεν µπορούν να αλλάξουν την διασύνδεσή της. Ας ξεκινήσουµε από την απλούστερη τάξη, την τάξη που αντιπροσωπεύει στο σύστηµά µας το ξυλάκι, που είναι η τάξη Chopstick:

public class Chopstick private boolean taken; public Chopstick()

- 133 -

Page 134: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

taken = false; public synchronized void get() try while(taken) wait(); catch(InterruptedException e) taken = true; public synchronized void leave() taken = false; notifyAll();

Η τάξη Chopstck έχει τις µεθόδους get() µέσω της οποίας ένας φιλόσοφος παίρνει το ξυλάκι και leave() µέσω της οποίας ένας φιλόσοφος αφήνει το ξυλάκι. Αν η get() διαπιστώσει ότι το ξυλάκι είναι πιασµένο από άλλον (η µεταβλητή taken είναι true) τότε απελευθερώνει την κλειδαριά του αντικειµένου Chopstick και περιµένει (µέσω της wait()). Όταν ο φιλόσοφος που είχε το ξυλάκι φάει, αφήνει το ξυλάκι µε την leave(), η οποία κάνει την taken false, και καλεί την notifyAll() που αφυπνίζοντας πιθανώς κάποιον µπλοκαρισµένο φιλόσοφο που περίµενε να πάρει το ξυλάκι. Η τάξη Philosopher επεκτείνει την τάξη Observable και υλοποιεί την διασύνδεση Runnable µια και κάθε φιλόσοφος έχει το δικό του νήµα εκτέλεσης στο οποίο σκέφτεται και τρώει.

public class Philosopher extends Observable implements Runnable public static final int THINKING = 0; public static final int EATING = 1; public static final int HAS_LEFT = 2; public static final int HAS_RIGHT = 3; private int number; private int state; private Chopstick left_chopstick; private Chopstick right_chopstick; //ola symvainoyn se rythmo 5 sec

private final int speed = 5000; private Thread runningThread; ...

Οι δηµόσιες συµβολικές σταθερές THINKING (σκέπτεται), EATING (τρώει), HAS_LEFT (έχει το αριστερό ξυλάκι) και HAS_RIGHT (έχει το δεξί ξυλάκι) είναι ακέραιες τιµές που αντιπροσωπεύουν τις διάφορες καταστάσεις στις οποίες µπορεί να βρίσκεται ένας φιλόσοφος. Η µεταβλητή number είναι ο κωδικός αριθµός του φιλοσόφου (από 0 έως 4 µια και θα έχουµε 5 φιλοσόφους). Η µεταβλητή state θα είναι κάποια από τις σταθερές της κατάστασης που αναφέραµε. To left_chopstick είναι το ξυλάκι στα αριστερά του φιλοσόφου, ενώ το right_chopstick είναι το ξυλάκι στα δεξιά του.

- 134 -

Page 135: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

Η σταθερά speed είναι ο ρυθµός µε τον οποίο οι φιλόσοφοι τρώνε και σκέφτονται. Κάνοντας αυτή τη σταθερά πιο µικρή µπορούµε να επιταχύνουµε το Applet. Τέλος, το νήµα εκτέλεσης runningThread θα έχει το νήµα στο οποίο εκτελείται ο κάθε φιλόσοφος, και θα τίθεται σε null από τη µέθοδο stop του Applet για να σταµατάνε τα νήµατα των φιλοσόφων όταν σταµατάµε το Applet (π.χ. µε την αποχώρηση από την σελίδα που το περιέχει). Ο κατασκευαστής της τάξης Philosopher έχει την ακόλουθη µορφή:

public Philosopher(int i, Observer o, Chopstick l_c, Chopstick r_c) number = i; addObserver(o); setState(THINKING); left_chopstick = l_c; right_chopstick = r_c;

και δέχεται σαν παραµέτρους τον αριθµό του φιλοσόφου (i), τον παρατηρητή του (o), το αριστερό ξυλάκι (l_c) και το δεξί ξυλάκι (r_c). Επίσης θέτει την κατάσταση του φιλοσόφου σε THINKING µια και αρχικά ο φιλόσοφος σκέφτεται. Οι µέθοδοι getState() και setState(), χρησιµοποιούνται για την ανάγνωση και την µεταβολή της κατάστασης του αντικειµένου. Η µέθοδος setState() καλεί – αφού θέσει το state στην δοθείσα παράµετρο – την setChanged() της υπερτάξης Observable που καταγράφει ότι το αντικείµενο άλλαξε κατάσταση και την notifyObservers() επίσης της τάξης Observable που ειδοποιεί τους καταχωρηµένους παρατηρητές (στην περίπτωσή µας το Applet) ότι η κατάσταση του φιλοσόφου άλλαξε (το Applet όπως θα δούµε στην µέθοδό του update() που καλείται σαν απόρροια αυτής της κλήσης της notifyObservers() θα ζωγραφίσει στην περιοχή του τον φιλόσοφο µε την σωστή εικόνα ανάλογα µε την κατάστασή του):

public void setState(int s) state = s; setChanged(); notifyObservers(); public int getState() return state;

Η µέθοδος run() που δίνεται στη συνέχεια εκτελεί ένα βρόχο στον οποίο συνεχώς ο φιλόσοφος τρώει και σκέφτεται. Το πόσο διαρκεί το κάθε τι προσδιορίζεται µε τυχαίο τρόπο, από 0 έως speed.

public void run() while (runningThread == Thread.currentThread()) try //Σκέφτεται για λίγο Thread.sleep((long) (Math.random()*speed)); //Μετά προσπαθεί να φάει.

- 135 -

Page 136: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

//Εδώ µπορεί να εµποδιστεί getChopsticks(); //τρώει για λίγο Thread.sleep((long) (Math.random()*speed)); //τέλος αφήνει τα ξυλάκια leaveChopsticks(); catch (InterruptedException e)

Για να φάει ένας φιλόσοφος πρέπει να πάρει τα ξυλάκια καλώντας την getChopsticks() που δίνεται στη συνέχεια. Για να αποφύγουµε τα αδιέξοδα βάζουµε τον φιλόσοφο µε αριθµό 0 να παίρνει πρώτα το αριστερό του ξυλάκι και µετά το δεξί, ενώ βάζουµε όλους τους άλλους να παίρνουν πρώτα το δεξί ξυλάκι και µετά το αριστερό. Παρατηρείστε την κλήση της setState() µέσω της οποίας ενηµερώνεται τελικά και το Applet.

public void getChopsticks() //Αυτό το if είναι που λύνει το πρόβληµα του αδιεξόδου if (number == 0) //Αν είσαι ο φιλόσοφος 0 (ο πρώτος) //προσπάθησε να πάρεις το αριστερό ξυλάκι πρώτα left_chopstick.get(); setState(HAS_LEFT); //και µετά το δεξιό right_chopstick.get(); setState(HAS_RIGHT); else //αν είσαι οποιοσδήποτε άλλος //προσπάθησε να πάρεις πρώτα το δεξί ξυλάκι right_chopstick.get(); setState(HAS_RIGHT); //και µετά το αριστερό left_chopstick.get(); setState(HAS_LEFT); //αφού πάρεις και τα δυο ξυλάκια µπορείς να φας setState(EATING);

Η µέθοδος µε την οποία απελευθερώνονται τα ξυλάκια είναι η leave_chopsticks() που δίνεται στη συνέχεια:

public void leaveChopsticks() //άφησε το δεξί ξυλάκι right_chopstick.leave(); setState(HAS_LEFT); //µετά το αριστερό left_chopstick.leave(); //συνέχιση τη σκέψη

- 136 -

Page 137: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

setState(THINKING);

Τέλος θα πρέπει να υπάρχει και µια µέθοδο που θα θέτει το runningThread και που θα χρησιµοποιείται από το Applet για να ξεκινά και να σταµατά το Thread του κάθε φιλόσοφου. Η setRunningThread(), µαζί µε την ολοκληρωµένη Philosopher δίνεται στη συνέχεια:

import java.util.*; public class Philosopher extends Observable implements Runnable public static final int THINKING = 0; public static final int EATING = 1; public static final int HAS_LEFT = 2; public static final int HAS_RIGHT= 3; private int number; private int state; private Chopstick left_chopstick; private Chopstick right_chopstick; private final int speed = 5000; private Thread runningThread; public Philosopher(int i, Observer o, Chopstick l_c, Chopstick r_c) number = i; addObserver(o); setState(THINKING); left_chopstick = l_c; right_chopstick = r_c; public void setState(int s) state = s; setChanged(); notifyObservers(); public int getState() return state; public void run() while (runningThread == Thread.currentThread()) try Thread.sleep((long) (Math.random()*speed)); getChopsticks(); Thread.sleep((long) (Math.random()*speed)); leaveChopsticks(); catch (InterruptedException e)

- 137 -

Page 138: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

public void getChopsticks() if (number == 0) left_chopstick.get(); setState(HAS_LEFT); right_chopstick.get(); setState(HAS_RIGHT); else right_chopstick.get(); setState(HAS_RIGHT); left_chopstick.get(); setState(HAS_LEFT); setState(EATING); public void leaveChopsticks() right_chopstick.leave(); setState(HAS_LEFT); left_chopstick.leave(); setState(THINKING); public void setRunningThread(Thread t) runningThread = t;

Τέλος ακολουθεί η τάξη PhilApplet η οποία στην µέθοδο init, δηµιουργεί τα ξυλάκια και τους φιλοσόφους και στην συνέχεια ξεκινά τα νήµατα εκτέλεσης των φιλοσόφων στην start. Η µέθοδος update() καλείται κάθε φορά που γίνεται αλλαγή κατάστασης σε ένα φιλόσοφο σαν αποτέλεσµα της κλήσης της notifyObservers σε αυτό το φιλόσοφο (βλ. προηγούµενα). Αυτό που κάνει η update() είναι ότι αλλάζει την εικόνα του φιλοσόφου που άλλαξε κατάσταση ανάλογα µε την νέα του κατάσταση. Η update() γνωρίζει ποιος είναι αυτός ο φιλόσοφος διότι η πρώτη παράµετρός της δεν είναι άλλη παρά το παρατηρούµενο (Observable) αντικείµενο που προκάλεσε το συµβάν. Για την διευκόλυνση στην κατανόηση της τάξης αυτής έχουν συµπεριληφθεί στον κώδικα σχόλια.

import java.awt.*; import java.applet.*; import java.util.*; public class PhilApplet extends Applet implements Observer //Το array των φιλοσόφων private Philosopher ph[] = new Philosopher[5]; //Το array µε τα ξυλάκια

- 138 -

Page 139: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

private Chopstick ch[] = new Chopstick[5]; //Το array µε τις εικόνες. Κάθε φιλόσοφος θα έχει και µια //εικόνα ανάλογα µε την κατάστασή του

private Image phi[] = new Image[5]; //εικόνες που αντιστοιχούν στις διάφορες καταστάσεις των

//φιλοσόφων private Image think_image, eat_image, left_image, right_image; //Αυτά τα δύο αντικείµενα θα χρησιµοποιηθούν για double buffering Image backImage;

Graphics backGraphics; public void init() think_image = getImage(getCodeBase(), "think_phil.gif"); eat_image = getImage(getCodeBase(), "eat_phil.gif"); left_image = getImage(getCodeBase(), "left_chop_phil.gif"); right_image = getImage(getCodeBase(), "right_chop_phil.gif"); //∆ηµιουργία των chopsticks for (int i=0; i<5; i++) ch[i] = new Chopstick(); //∆ηµιουργία των φιλοσόφων και των εικόνων τους for (int i=0; i<5; i++) ph[i] = new Philosopher(i, this, ch[i], ch[(i-1==-1)?4:i-1]); phi[i] = think_image; public void start() for (int i=0; i<5; i++) //Αρχικά όλοι οι φιλόσοφοι σκέφτονται ph[i].setState(Philosopher.THINKING); //∆ηµιουργούµε ένα νήµα για κάθε φιλόσοφο

Thread a = new Thread(ph[i]); //Το θέτουµε ίσο µε το νήµα του φιλοσόφου

ph[i].setRunningThread(a); //ξεκινάµε τον φιλόσοφο a.start(); repaint(); public void update(Observable o, Object arg) Philosopher p = (Philosopher) o; int i; //βρες ποιος φιλόσοφος άλλαξε κατάσταση (το Observable) for (i=0; i<5; i++) if (p == ph[i])

- 139 -

Page 140: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

break; //και ενηµέρωσε την εικόνα του φιλοσόφου

if (i != 5)

switch (ph[i].getState()) case Philosopher.EATING: phi[i] = eat_image; break; case Philosopher.THINKING: phi[i] = think_image; break; case Philosopher.HAS_LEFT: phi[i] = left_image; break; case Philosopher.HAS_RIGHT: phi[i] = right_image; break;

repaint(); /* Η µέθοδος αυτή εµφανίζει την σωστή εικόνα των φιλοσόφων κάθε φορά. Για να δουλέψει αδιάλλειπτα χρησιµοποιεί µια τεχνική γνωστή σαν double-buffering. Με αυτή τη τεχνική σχεδιζουµε όλους τους φιλοσόφους σε ένα image (backImage) και στην συνέχεια εµφανίζουµε µε µια εντολή ολόκληρο αυτό το image στην περιοχή σχεδίασης του Applet. */

public void paint(Graphics g) int w = 85; int h = 110; if (backGraphics == null) Dimension d = getSize(); backImage = createImage(d.width, d.height); backGraphics = backImage.getGraphics(); backGraphics.setColor(g.getColor()); //Σχεδίασε την πρώτη γραµµή των φιλοσόφων backGraphics.drawImage(phi[0], 2*w, 0, null); //Σχεδίασε την δεύτερη γραµµή των φιλοσόφων backGraphics.drawImage(phi[4], 0, h, null); backGraphics.drawImage(phi[1], 4*w, h, null); //Σχεδίασε την τρίτη γραµµή των φιλοσόφων backGraphics.drawImage(phi[3], w, 2*h, null); backGraphics.drawImage(phi[2], 3*w, 2*h, null); //Ζωγράφισε ολόκληρο το backImage µε µια εντολή στην

- 140 -

Page 141: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

//περιοχή του Applet g.drawImage(backImage, 0, 0, this);

/* Η stop σταµατά του φιλοσόφους θέτοντας το running thread τους σε null. */ public void stop() for (int i=0; i<5; i++) ph[i].setRunningThread(null);

Για να δείτε το applet να τρέχει θα πρέπει στον ίδιο κατάλογο µε το Applet να βάλετε µια εικόνα για κάθε κατάσταση του φιλοσόφου µε τα ονόµατα που µπορείτε να δείτε στην init. Οι εικόνες αυτές θα τοποθετούνται ανάλογα µε την κατάσταση του φιλοσόφου στο array phi που είναι το array των εικόνων για κάθε φιλόσοφο. Όπως µπορείτε να δείτε από την ακόλουθη εικόνα το Applet εµφανίζει συνεχώς αυτές τις εικόνες κάθε φορά που αλλάζει η κατάσταση ενός φιλοσόφου και καλείται η update η οποία µε την σειρά της καλεί την repaint(), που καλεί την paint(). Το applet στον browser θα φαίνεται όπως δείχνει η ακόλουθη εικόνα:

- 141 -

Page 142: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

Κεφάλαιο 5: Οι βοηθητικές τάξεις του πακέτου java.util Το πακέτο java.util περιέχει ορισµένες βοηθητικές τάξεις που χρειάζονται συχνά σε εφαρµογές Java. Κάποιες από αυτές θα δούµε σε αυτό το κεφάλαιο.

Η τάξη java.util.Vector Η τάξη Vector είναι µία τάξη container που επιτρέπει την συγκράτηση αντικειµένων (Object) στις θέσεις της: άρα χρησιµοποιούνται οι τάξεις περιτύλιξης, π.χ. Integer, αντί των βασικών τύπων , π.χ. int. Ένα vector δεν έχει σταθερό µέγεθος και αυξάνεται όταν αυτό χρειάζεται και παρέχει εύκολη διαχείριση, µέσω των πολλών µεθόδων της. Χρησιµοποιούµε την τάξη Vector για να αποφύγουµε προβλήµατα µε την χρήση των πινάκων. Τα κυριότερα από αυτά τα προβλήµατα είναι:

− Έχουν σταθερό µέγεθος. Αν χρειαστεί να αυξηθεί το µέγεθος πρέπει να γίνει µε ένα δεύτερο µεγαλύτερο πίνακα.

− Έχουν δύσκολη διαχείριση, π.χ. είναι δύσκολο να βάλουµε ένα στοιχείο στη µέση ενός πίνακα. Αυτό θα συνεπάγονταν την µετακίνηση των στοιχείων στις επόµενες θέσεις του πίνακα από το διεγραµµένο στοιχείο στην προηγούµενη θέση από αυτήν που έχουν.

− Βασική χρήση της τάξης Vector Η βασική χρήση της τάξης Vector σε ένα πρόγραµµα συνεπάγεται τα ακόλουθα βήατα:

− Εισάγουµε τη τάξη java.util.Vector στο πρόγραµµά µας, π.χ.: import java.util.Vector; − ∆ηµιουργούµε ένα αντικείµενο Vector, π.χ.: Vector v = new Vector(); − Εισάγουµε αντικείµενα στο Vector, π.χ.: v.addElement(o); − Ανακαλούµε αντικείµενα από το Vector, π.χ.: Object o = v.elementAt(i), όπου το i

είναι κάποια θέση του Vector Βασικοί constructors της τάξης Vector Κάποιοι από τους βασικούς constructors της τάξης Vector είναι οι ακόλουθοι:

− Vector(): Default Vector µε µέγεθος 10 − Vector(int initialCapacity): Vector µε το δοθέν αρχικό µέγεθος − Vector(int initialCapacity, int increment): Vector µε το δοθέν αρχικό µέγεθος που

αυξάνεται όσο προσδιορίζεται από την 2η παράµετρο, όταν υπερχειλίζει. Βασικές µέθοδοι της τάξης Vector Κάποιες από τις βασικότερες µεθόδους της τάξης Vector είναι οι εξής:

− void add (Object obj) ή void addElement(Object obj): Προσθέτει το αντικείµενο obj στην τελευταία θέση του Vector

− void insertElementAt(Object obj, int index): Εισάγει το αντικείµενο obj στην θέση index του Vector. Σηµειώστε πως η πρώτη θέση είναι η θέση 0, η δεύτερη η 1 κοκ.

− Object firstElement(): επιστρέφει το αντικείµενο στην αρχή του Vector χωρίς να το αφαιρέσει από το Vector

− Object elementAt(int index): επιστρέφει το αντικείµενο στην θέση index στο Vector χωρίς να το αφαιρέσει από το Vector

− Enumeration elements(): επιστρέφει ένα αντικείµενο java.util.Enumeration µε τα αντικείµενα του Vector. Για Enumerations βλ. επόµενα.

- 142 -

Page 143: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

− Object remove(int index): αφαιρεί το αντικείµενο από την θέση index του Vector και το επιστρέφει

− boolean remove(Object obj): αφαιρεί την πρώτη εµφάνιση του αντικειµένου obj από το Vector και το επιστρέφει. Η σύγκριση γίνεται µε την µέθοδο equals.

Παράδειγµα χρήσης της τάξης Vector Ένα απλό παράδειγµα της τάξης Vector είναι το ακόλουθο, στο οποίο εισάγουµε κάποιες τιµές µε την add και την insertElementAt (θυµηθείτε πως η insertElementAt εισάγει σε συγκεκριµένη θέση). Εµφανίζουµε το Vector µε γνώµονα το µέγεθος του Vector που επιστρέφει η µέθοδος size(). Τέλος αφαιρούµε µε την remove κάποιες τιµές και εµφανίζουµε και πάλι το Vector.

import java.util.Vector; public class VectorApp1

public static void main(String args[]) Vector v = new Vector(); v.add ("one"); v.add ("two"); v.add ("three"); v.insertElementAt("zero",0); v.insertElementAt("oops",3); v.insertElementAt("four",5); System.out.println("Size: "+v.size()); for(int i=0;i<v.size();++i) System.out.print(v.elementAt(i)+" "); System.out.println(); v.removeElement("oops"); System.out.println("Size: "+v.size()); for(int i=0;i<v.size();++i) System.out.print(v.elementAt(i)+" "); System.out.println();

Το αποτέλεσµα της εκτέλεσης του προγράµµατος θα είναι αυτό που δείχνει η ακόλουθη εικόνα:

Η διασύνδεση java.util.Enumeration Μία τάξη που υλοποιεί την διασύνδεση java.util.Enumeration θα υλοποιεί κάποιες µεθόδους που επιτρέπουν την διαδοχική ανάκληση κάποιων στοιχείων Έτσι για ένα Vector η εκτύπωση των στοιχείων του µπορεί να γίνει ως εξής:

- 143 -

Page 144: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

for (Enumeration e = v.elements() ; e.hasMoreElements() ;) System.out.println(e.nextElement()); Μέθοδοι της διασύνδεσης Enumeration Οι µέθοδοι µιας Enumeration είναι οι εξής:

− boolean hasMoreElements(): Επιστρέφει true αν υπάρχουν και άλλα στοιχεία στην απαρίθµηση και false διαφορετικά

− Object nextElement(): Επιστρέφει το επόµενο αντικείµενο της απαρίθµησης Παράδειγµα της διασύνδεσης Enumeration Σαν παράδειγµα χρήσης της διασύνδεσης Enumeration θα δούµε το προηγούµενο παράδειγµα µε το Vector, µόνο που τώρα για την διάσχιση του Vector θα χρησιµοποιήσουµε το Enumeration αντικείµενο που επιστρέφει η µέθοδος elements() της τάξης Vector:

import java.util.Vector; import java.util.Enumeration; public class VectorApp1

public static void main(String args[]) Vector v = new Vector(); v.add("one"); v.add("two"); v.add("three"); v.insertElementAt("zero",0); v.insertElementAt("oops",3); v.insertElementAt("four",5); System.out.println("Size: "+v.size()); Enumeration e = v.elements(); while (e.hasMoreElements()) String s = (String) e.nextElement(); System.out.println(s); System.out.println(); v.remove("oops"); System.out.println("Size: "+v.size()); e = v.elements(); while (e.hasMoreElements()) String s = (String) e.nextElement(); System.out.println(s); System.out.println();

Το αποτέλεσµα της εκτέλεσης του πιο πάνω προγράµµατος φαίνεται στην ακόλουθη εικόνα:

- 144 -

Page 145: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

Ένα πιο σύνθετο παράδειγµα Ένας ιδιοκτήτης αυτοκινήτου µπορεί να έχει πολλά αυτοκίνητα και ένα αυτοκίνητο µπορεί να ανήκει σε έναν ιδιοκτήτη. Πρέπει να υπάρχει δυνατότητα πλοήγησης και από τις δύο κατευθύνσεις, δηλαδή έχοντας ένα αντικείµενο της τάξης Car θα πρέπει να µπορούµε να πούµε σε ποιόν ανήκει, να βρούµε δηλαδή τον αντίστοιχο CarOwner και έχοντας ένα αντικείµενο CarOwner, τον ιδιοκτήτη, θα πρέπει να µπορούµε να βρούµε τα αντικείµενα Car που αντιστοιχούν σε αυτόν. Η πιο πάνω περιγραφή απεικονίζεται στο ακόλουθο UML διάγραµµα:

Οι δύο τάξεις του διαγράµµατος είναι οι εξής:

public class Car private CarOwner _carOwner;

private String _model; /* Constructor που παίρνει σαν παράµετρο το µοντέλο του αυτοκινήτου και τον ιδιοκτήτη του. Συσχετίζει το νέο αυτοκίνητο που δηµιουργείται µε τον ιδιοκτήτη του καλώντας την friendCarAdd(this) στον ιδιοκτήτη */ public Car(String model, CarOwner carOwner) _model = model; _carOwner = carOwner; _carOwner.friendCarAdd(this); ;

//Επιστρέφει τον ιδιοκτήτη του αυτοκινήτου public CarOwner getCarOwner()

return _carOwner; ;

- 145 -

Page 146: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

//Επιστρέφει το µοντέλο του αυτοκινήτου

public String getModel() return _model; //Αλλάζει το µοντέλο του αυτοκινήτου public void setModel(String model)

_model = model;

και η τάξη CarOwner όπου γίνεται χρήση των τάξεων java.util.Vector και java.util.Enumeration, µια και τα αυτοκίνητα ενός ιδιοκτήτη αποθηκεύονται σε ένα Vector (το _cars) σε αυτή τη τάξη: import java.util.Vector; import java.util.Enumeration; public class CarOwner private Vector _cars = new Vector();

private String _name; public CarOwner(String name)

_name=name; void friendCarAdd(Car newCar)

_cars.add(newCar);

public void setCars(String[] models)

for (int i = 0; i < models.length; i++) new Car(models[i], this);

public Enumeration getCars()

return _cars.elements();

public boolean removeCar(Car c)

return _cars.removeElement(c); public String getName()

return _name;

Τέλος θα κάνουµε µία τάξη Main µε µία συνάρτηση main που θα δηµιουργεί δύο ιδιοκτήτες και τα αυτοκίνητά τους και στη συνέχεια θα εµφανίζει τα αυτοκίνητα του κάθε ιδιοκτήτη µε την χρήση της Απαρίθµησης (Enumeration) που επιστρέφει η µέθοδος getCars() της τάξης CarOwner:

- 146 -

Page 147: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

import java.util.Enumeration; public class Main

public static void main(String[] args)

CarOwner george = new CarOwner("George"); CarOwner jim = new CarOwner("Jim"); Car citroen = new Car("citroen zx", george); Car bmw = new Car("bmw 316", george); Car opel = new Car("opel vectra",jim); System.out.println("ciroen owner:"+citroen.getCarOwner().getName()); System.out.println("bmw owner:" +bmw.getCarOwner().getName()); System.out.println("opel owner:"+opel.getCarOwner().getName()); System.out.println("George's cars:"); for (Enumeration e = george.getCars(); e.hasMoreElements();)

System.out.println( ((Car)e.nextElement()).getModel());

System.out.println("Jim's cars:"); for (Enumeration e = jim.getCars(); e.hasMoreElements();)

System.out.println( ((Car) e.nextElement()).getModel());

//end of main

//end of Class Το αποτέλεσµα της εκτέλεσης της main φαίνεται στην εικόνα που ακολουθεί:

Η τάξη java.util.Stack Με την τάξη java.util.Stack µπορούµε να δηµιουργήσουµε στοίβες αντικειµένων. Μία στοίβα παρέχει την δυνατότητα εισαγωγής αντικειµένων και εξαγωγής αντικειµένων. Η διαφορά από το Vector είναι πως οι εισαγωγές αντικειµένων γίνονται στην αρχή και οι εξαγωγές αντικειµένων γίνονται επίσης από την αρχή. ∆ηλαδή, τα αντικείµενα που εξάγονται είναι αυτά που εισήχθησαν τελευταία στη στοίβα. Γι’ αυτό και η στοίβα αναφέρεται συχνά και ως δοµή LIFO (Last In First Out – Το τελευταίο που µπήκε θα είναι το πρώτο που θα βγει). Constructors και βασικές λειτουργίες της τάξης java.util.Stack Constructors:

Stack(): ∆ηµιουργεί µια άδεια στοίβα Βασικές λειτουργίες:

- 147 -

Page 148: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

− public boolean empty(): Επιστρέφει true αν η στοίβα είναι κενή και false διαφορετικά − public Object push(Object item): Εισάγει ένα αντικείµενο στη στοίβα − public Object pop(): Εξάγει το αντικείµενο που βρίσκεται στην κορυφή της

στοίβας.Προκαλεί την εξαίρεση EmptyStackException αν η στοίβα είναι κενή. − public Object peek(): Επιστρέφει το αντικείµενο στην κορυφή της στοίβας. Προκαλεί

την εξαίρεση EmptyStackException αν η στοίβα είναι κενή. Η διαφορά µε την pop() είναι πως το αντικείµενο δεν εξάγεται από τη στοίβα αλλά παραµένει στην κορυφή της στοίβας.

− public int search(Object o): Επιστρέφει την απόσταση από την κορυφή του αντικειµένου o ή –1 αν αυτό δεν υπάρχει.

Παράδειγµα χρήσης µιας Stack Να γίνει πρόγραµµα που χρησιµοποιεί την Stack για να υπολογίσει την τιµή µιας RPN (Reverse Polish Notation) αριθµητικής έκφρασης. Υποθέστε ότι η έκφραση έχει µόνο + ή – και φυσικά αριθµούς. Επίσης η έκφραση θα πρέπει να δοθεί ως παράµετρος της main. Σε µία RPN αριθµητική έκφραση οι τελεστές προηγούνται των ορισµάτων που αφορούν. Έτσι αν για παράδειγµα δοθεί η έκφραση «3 6 + 4 + 5 8 1 - - +» στο πρόγραµµα, τότε το αποτέλεσµα θα βγει ως το άθροισµα των 3 και 6 (ο τελεστής µετά τα 3 και 6 είναι το +) σύν το 4 κλπ. Το τελικό αποτέλεσµα αυτής της έκφρασης είναι 11. Το πρόγραµµα RPNCalc (Reverse Polish Notation Calculator) ακολουθεί:

import java.util.*; class RPNCalc Stack stack; String[] args; int value = 0; RPNCalc(String[] a ) stack = new Stack(); args = a; public double process() int length = args.length; for( int i = 0; i < length; i++ ) String s = args[ i ]; if( s.compareTo( "+" ) == 0 ) System.out.println( "operation: add" ); int a = 0; int b = 0; try a = (( Integer )stack.pop()).intValue(); catch( EmptyStackException se ) try b = (( Integer )stack.pop()).intValue(); value = a + b; catch( EmptyStackException se )

- 148 -

Page 149: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

value += a; stack.push( new Integer( value )); System.out.println( "Partial sum: " + value ); else if( s.compareTo( "-" ) == 0 ) System.out.println( "operation: subtract" ); int a = 0; int b = 0; try a = (( Integer )stack.pop()).intValue(); catch( EmptyStackException se ) try b = (( Integer )stack.pop()).intValue(); value = b - a; catch( EmptyStackException se ) value -= a; stack.push( new Integer( value )); System.out.println( "Partial sum: " + value );

else try Integer x = new Integer( s ); System.out.println( "operand: " + x ); stack.push( x ); catch( NumberFormatException e ) return value; public static void main( String[] args ) RPNCalc rpn = new RPNCalc(args); double value = rpn.process(); System.out.println( "Output: " + value );

Αν τρέξουµε το πιο πάνω πρόγραµµα δίνοντας σαν παράµετρο στην main το String

3 6 + 4 + 5 8 1 - - + (προσέξτε τα κενά µεταξύ των ορισµάτων), τότε θα πάρουµε το ακόλουθο αποτέλεσµα:

- 149 -

Page 150: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

Η τάξη java.util.Hashtable Ένα λεξικό είναι µια συλλογή αντικειµένων που συσχετίζονται µε κάποια κλειδιά. Κάθε είσοδος σε ένα λεξικό είναι ένα ζεύγος (key, object). Εισάγουµε αντικείµενα µε τα κλειδιά τους και στη συνέχεια τα αναζητούµε µε βάση το κλειδί. Η τάξη java.util.Dictionary είναι abstract. Η υποτάξη java.util.Hashtable είναι µια συµπαγής (concrete) τάξη που επεκτείνει την τάξη java.util.Dictionary. Κατασκευαστές και βασικές µέθοδοι της τάξης java.util.Hashtable Constructor

public Hashtable(): Κατασκευάζει ένα κενό Hashtable Κυριότερες µέθοδοι:

− public Object put(Object key, Object value): Εισάγει το αντικείµενο value µε κλειδί key. Επιστρέφει την προηγούµενη τιµή του key στο Hashtable, ή null αν το key εισάγεται για πρώτη φορά.

− public Object remove(Object key): Αφαιρεί το κλειδί και την συσχετιζόµενη τιµή του από το Hashtable. Η µέθοδος δεν κάνει τίποτε αν το κλειδί που δίνεται δεν υπάρχει στο Hashtable. Επιστρέφει την τιµή του key που αφαιρείται, ή null αν το κλειδί δεν είχε κάποια συσχετιζόµενη τιµή.

− public Object get(Object key): Επιστρέφει την τιµή µε την οποία το key έχει αντιστοιχηθεί σε αυτό το Hashtable. Επιστρέφει null αν το κλειδί δεν συσχετίζεται µε καµία τιµή στο Hashtable.

Παράδειγµα χρήσης της τάξης Hashtable Θα δούµε ένα πρόγραµµα το οποίο θα εισάγει σε ένα Hashtable τα στοιχεία αυτοκινήτων (VehicleRecords). Θα παρέχει δυνατότητες:

− Αναζήτησης µε βάση τον αριθµό κυκλοφορίας − ∆ιαγραφής µε βάση τον αριθµό κυκλοφορίας − Το κλειδί κάθε εγγραφής στο Hashtable θα είναι ο αριθµός κυκλοφορίας

- 150 -

Page 151: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

− Θα έχει γραφική διασύνδεση (GUI) Η γραφική διασύνδεση της εφαρµογής φαίνεται στην εικόνα που ακολουθεί και θα την δηµιουργήσουµε µε τις τάξεις του πακέτου java.awt. Το πακέτο αυτό περιέχει την τάξη Frame η οποία θα είναι και το βασικό παράθυρο της εφαρµογής µας. Τα υπόλοιπα είναι γνωστά από το κεφάλαιο των applets:

Για να αναπαραστήσουµε τα αντικείµενα µε τις πληροφορίες των οχηµάτων θα χρησιµοποιήσουµε την τάξη Vehicle που ακολουθεί. Στην τάξη αυτή έχουµε τα δεδοµένα ενός αυτοκινήτου που µας ενδιαφέρουν (αρ. κυκλοφορίας, µάρκα, µοντέλο, έτος, όνοµα, επώνυµο) καθώς και get µεθόδους που επιστρέφουν αυτά τα δεδοµένα γιατί είναι ιδιωτικά. Επίσης έχουµε έναν constructor στον οποίο περνάµε τα δεδοµένα αυτά κάθε φορά που κατασκευάζουµε ένα νέο όχηµα: public class Vehicle private String licence; private String make; private String model; private String year; private String fName; private String lName;

model = mdl;

public Vehicle(String l, String mk, String mdl, String yr, String fn, String ln) licence = l; make = mk;

year = yr; fName = fn; lName = ln;

public String getFName() return fName;

public String getLicence() return licence;

- 151 -

Page 152: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

public String getMake()

public String getLName() return lName;

return make; public String getModel() return model; public String getYear() return year; Για το παράθυρο της εφαρµογής όπως είπαµε θα δηλώσουµε µία τάξη VehicleWindow, η οποία θα επεκτείνει την τάξη java.awt.Frame (για να είναι παράθυρο) και θα χειρίζεται τα συµβάντα των πλήκτρων που θα πατάει ο χρήστης, άρα θα υλοποιεί την διασύνδεση java.awt.event.ActionListener. Ο χειρισµός των συµβάντων θα γίνεται στην µέθοδο actionPerformed(). Για κάθε συµβάν (εκτός του κλεισίµατος της εφαρµογής) θα δηλώσουµε µία ιδιωτική µέθοδο που θα το χειρίζεται. Για παράδειγµα για το συµβάν της προσθήκης ενός νέου αυτοκινήτου, θα δηλώσουµε την µέθοδο addVehicle. Η τάξη VehicleWindow ακολουθεί: import java.awt.*; import java.awt.event.*; import java.util.Hashtable; public class VehicleWindow extends Frame implements ActionListener private Panel dataEntryPanel; private Panel buttonPanel; private Hashtable register;

private Button btnClean;

private Button btnDelete;

private TextField licenceField; private TextField makeField; private TextField modelField; private TextField yearField; private TextField fNameField; private TextField lNameField;

private Button btnAdd;

private Button btnSearch; private Button btnClose;

public VehicleWindow() super("Καταχώρηση οχηµάτων"); //δηµιουργία του Hashtable των αυτοκινήτων

register = new Hashtable(); //δηµιουργία της γραφικής διασύνδεσης setLayout(new BorderLayout());

- 152 -

Page 153: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

dataEntryPanel.add(new Label("Μάρκα")); dataEntryPanel.add(makeField = new TextField());

dataEntryPanel = new Panel(); dataEntryPanel.setLayout(new GridLayout(6, 2)); dataEntryPanel.add(new Label("Αριθµός κυκλ. #")); dataEntryPanel.add(licenceField = new TextField());

dataEntryPanel.add(new Label("Μοντέλο")); dataEntryPanel.add(modelField = new TextField()); dataEntryPanel.add(new Label("Έτος")); dataEntryPanel.add(yearField = new TextField()); dataEntryPanel.add(new Label("Όνοµα")); dataEntryPanel.add(fNameField = new TextField()); dataEntryPanel.add(new Label("Επώνυµο")); dataEntryPanel.add(lNameField = new TextField());

btnSearch = new Button("Αναζήτηση");

buttonPanel = new Panel(); buttonPanel.setLayout(new GridLayout(1, 5)); btnClean = new Button("Καθαρισµός"); buttonPanel.add(btnClean); btnAdd = new Button("Προσθήκη"); buttonPanel.add(btnAdd); btnDelete = new Button("∆ιαγραφή"); buttonPanel.add(btnDelete);

buttonPanel.add(btnSearch); btnClose = new Button("Κλείσιµο"); buttonPanel.add(btnClose); btnClean.addActionListener(this); btnAdd.addActionListener(this); btnDelete.addActionListener(this); btnSearch.addActionListener(this); btnClose.addActionListener(this); add("Center", dataEntryPanel); add("South", buttonPanel); setSize(400, 250);

//µέθοδος χειρισµού συµβάντων για τα κουµπιά της εφαρµογής. //παρατηρείστε ότι η µέθοδος καλεί τις ιδιωτικές µεθόδους που ακολουθούν και οι οποίες //κάνουν τις απαραίτητες ενέργειες public void actionPerformed(ActionEvent evt) //το Button από το οποίο προκλήθηκε το συµβάν είναι το b

Button b = (Button) evt.getSource(); //ποιο είναι το b από τα buttons που έχουµε στην εφαρµογή;

if (b == btnClose) //αν είναι το btnClose απλά τερµάτισε την εφαρµογή System.exit(0);

- 153 -

Page 154: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

else if (b == btnClean) //αν είναι το btnClean κάλεσε την clearFields για να καθαρίσει τα πεδία clearFields(); else if (b == btnAdd) //αν είναι το btnAdd κάλεσε την addVehicle για να προσθέσει το νέο //όχηµα στο Hashtable register addVehicle(); else if (b == btnSearch) //αν είναι το btnSearch κάλεσε την searchVehicle για να κάνει την //αναζήτηση του οχήµατος searchVehicle(); else if (b == btnDelete)

//το όχηµα

private void clearFields()

private void searchVehicle()

//αν είναι το btnDelete κάλεσε την deleteVehicle για να διαγράψει

deleteVehicle();

//Ιδιωτική µέθοδος που καθαρίζει τα πεδία του παραθύρου

licenceField.setText(""); makeField.setText(""); modelField.setText(""); yearField.setText(""); fNameField.setText(""); lNameField.setText(""); //ιδιωτική µέθοδος που βρίσκει τον αριθµό κυκλοφορίας από το πεδίο licenseField και //καλεί την remove στο Hashtable register για να κάνει την διαγραφή. Αν η διαγραφή //είναι επιτυχής, καλεί την clearFields για να καθαρίσει τα πεδία του παραθύρου private void deleteVehicle() String licenceKey = licenceField.getText(); if (register.remove(licenceKey) != null) clearFields(); //ιδιωτική µέθοδος που αναζητά το όχηµα µε αριθµ. κυκλ. αυτόν που δόθηκε στο πεδίο //licenseField. Η αναζήτηση γίνεται µε την µέθοδο get του Hashtable register. Αν βρεθεί //το όχηµα παρουσιάζονται τα στοιχεία του, αν όχι βγαίνει το µήνυµα “Key not found” //στην κονσόλα (όχι στο παράθυρο)

String licenceKey = licenceField.getText(); Object object = register.get(licenceKey); if (object != null) Vehicle record = (Vehicle) object; makeField.setText(record.getMake()); modelField.setText(record.getModel()); yearField.setText(record.getYear()); fNameField.setText(record.getFName()); lNameField.setText(record.getLName()); else System.out.println("Key not found");

- 154 -

Page 155: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

//ιδιωτική µέθοδος που δηµιουργεί ένα νέο Vehicle µε τα στοιχεία που δόθηκαν στα πεδία //και εισάγει το νέο όχηµα στο Hashtable register µε την κλήση της µεθόδου put. Για να //γίνει η εισαγωγή απαιτείται ο αρ. κυκλοφορίας. Αν δεν έχει δοθεί εµφανίζεται µήνυµα //λάθους στην κονσόλα private void addVehicle() String licenceKey = licenceField.getText();

new Vehicle(

//Η βασική συνάρτηση της εφαρµογής δηµιουργεί ένα νέο παράθυρο (µε την new) και το

Στο παράδειγµα που ακολουθεί θα κατασκευάσουµε µια ουρά (Queue) χρησιµοποιώντας ένα java.util.Vector. Ουσιαστικά η ενσωµάτωση του Vector µέσα στη νέα τάξη επιβάλλει τη χρήση του µέσω των µεθόδων της, και µέσω αυτής της ελεγχόµενης πρόσβασης υλοποιείται η ουρά. ∆ηλ. στην τάξη Queue θα δώσουµε το δικαίωµα εισαγωγής µόνο στην αρχή και εξαγωγής µόνο από το τέλος υλοποιώντας έτσι την ουρά που είναι µια δοµή FIFO (First In First Out).

if (licenceKey != null) Vehicle vehicle =

licenceKey, makeField.getText(), modelField.getText(), yearField.getText(), fNameField.getText(), lNameField.getText()); register.put(licenceKey, vehicle); clearFields(); else System.out.println("Need a key in order to add");

//εµφανίζει µε την setVisible(true). public static void main(String args[]) VehicleWindow manager = new VehicleWindow(); manager.setVisible(true);

Κατασκευή ουράς µε την χρήση java.util.Vector Μπορούµε να χρησιµοποιήσουµε δοµές που ήδη παρέχονται για την κατασκευή άλλων δοµών

Η τάξη Queue Η διασύνδεση της τάξης Queue θα είναι αυτήν που φαίνεται στην ακόλουθη εικόνα:

- 155 -

Page 156: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

Όπως φαίνεται και από την εικόνα, η τάξη µας θα έχει:

1. Ένα ιδιωτικό µέλος, το myVector, που θα είναι το Vector στο οποίο θα τοποθετούνται τα αντικείµενα της ουράς. Επειδή το µέλος είναι ιδιωτικό δεν θα παρέχεται πρόσβαση απευθείας στο myVector, αλλά η πρόσβαση θα γίνεται µέσω των δηµοσίων µεθόδων της τάξης Queue.

2. Έναν κατασκευαστή της τάξης Queue που θα δηµιουργεί το Vector myVector 3. Τις ακόλουθες δηµόσιες µεθόδους:

a. append(Object): θα προσθέτει το Object που δίνεται ως παράµετρος στο τέλος της ουράς

b. clear(): θα αφαιρεί όλα τα αντικείµενα από την ουρά c. size(): θα επιστρέφει το µέγεθος της ουράς d. get(): θα επιστρέφει το τελευταίο στοιχείο της ουράς αφαιρώντας το από την

ουρά e. elements(): θα επιστρέφει µία Enumeration µε τα αντικείµενα της ουράς

Η τάξη Queue είναι η εξής:

import java.util.*; public class Queue private Vector myVector; public Queue() myVector = new Vector(); public Object append( Object obj ) myVector.add ( obj ); return obj;

myVector.removeElementAt( 0 );

public void clear() myVector.removeAllElements(); public int size() return myVector.size(); public Object get() Object object = null; try object = myVector.firstElement();

- 156 -

Page 157: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

catch (ArrayIndexOutOfBoundsException e) return object;

Queue queue = new Queue();

public Enumeration elements() return myVector.elements();

Για να δοκιµάσουµε την τάξη Queue θα κάνουµε µία άλλη τάξη µε το όνοµα QueueTester και τον κώδικα που ακολουθεί: import java.util.Enumeration; public class QueueTester public static void main(String args[])

for (int i = 0; i < args.length; ++i) queue.append(args[i]); System.out.println("At first the queue contains:"); for (Enumeration e = queue.elements(); e.hasMoreElements();) System.out.println((String) (e.nextElement())); System.out.println("Removing first element from queue:"); System.out.println((String) (queue.get())); for (Enumeration e = queue.elements(); e.hasMoreElements();)

At first the queue contains:

System.out.println("Now the queue contains:");

System.out.println((String) (e.nextElement()));

System.out.println("Adding string 'end'"); queue.append("end"); System.out.println("Now the queue contains:"); for (Enumeration e = queue.elements(); e.hasMoreElements();) System.out.println((String) (e.nextElement())); Το πρόγραµµα αυτό προσθέτει στην ουρά τα Strings που δίνονται ως παράµετροι στην main και στην συνέχεια εµφανίζει την ουρά. Στην συνέχεια αφαιρεί το πρώτο στοιχείο της ουράς και εµφανίζει και πάλι την ουρά. Τέλος προσθέτει το String “end” στην ουρά και εµφανίζει και πάλι την ουρά. Παρατηρείστε ότι η append προσθέτει τα αντικείµενα στην αρχή της ουράς, ενώ η get τα αφαιρεί από το τέλος, έτσι έχουµε την FIFO συµπεριφορά που επιθυµούµε. Αν τρέξετε το πρόγραµµα θα δείτε το ακόλουθο αποτέλεσµα (εφόσον δώσετε παραµέτρους στην main τα Strings George, John και Mary):

George John Mary Removing first element from queue:

- 157 -

Page 158: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας

George Now the queue contains: John Mary Adding string 'end' Now the queue contains: John Mary End

- 158 -

Page 159: Σημειώσεις - Java - Κακαρόντζας

Αντικειµενοστραφής Προγραµµατισµός ΙΙ (Java) – Κακαρόντζας Γιώργος – Τµήµα Τεχνολογίας Πληροφορικής ΤΕΙ Λάρισας