java:objects

This is an old revision of the document!


A PCRE internal error occured. This might be caused by a faulty plugin

====== Δημιουργία Αντικειμένων ====== Μέχρι τώρα αναφέραμε στην [[ oop:introduction | "Εισαγωγή στον Αντικειμενοστραφή Προγραμματισμό" ]] ότι η κλάση είναι το βασικό σχέδιο μέσα από το οποίο δημιουργούνται επιμέρους αντικείμενα που φέρουν τα χαρακτηριστικά της κλάσης. Επίσης, δείξαμε πως ορίζουμε μία κλάση μέσα από παραδείγματα, αλλά δεν δείξαμε πως δημιουργούμε αντικείμενα από τις κλάσεις που ορίσαμε. Παρακάτω δίνεται η κλάση **CreateObjectDemo** που δημιουργεί συγκεκριμένα αντικείμενα του τύπου **Point** και **Rectangle** και εκτυπώνει τα αποτελέσματα στην κονσόλα. Οι κλάσεις **Point** και **Rectangle** δίνονται επίσης παρακάτω: <code java Point.java> public class Point { private int x; private int y; public Point(int xPos, int yPos) { x = xPos; y = yPos; } public int getX() { return x; } public void setX(int xPos) { x = xPos; } public int getY() { return y; } public void setY(int yPos) { y = yPos; } } </code> <code java Rectangle.java> public class Rectangle { // fields private int width; private int height; private Point origin; // constructors public Rectangle(int initWidth, int initHeight, Point initOrigin) { width = initWidth; height = initHeight; origin = initOrigin; } public Rectangle(int initWidth, int initHeight) { width = initWidth; height = initHeight; origin = null; } // methods public void setWidth(int newWidth ) { width = newWidth; } public int getWidth() { return width; } public void setHeight(int newHeight ) { height = newHeight; } public int getHeight() { return height; } public void setOrigin(Point newOrigin) { origin = newOrigin; } public Point getOrigin() { return origin; } public int getArea() { return width * height; } // Move rectangle origin by dx,dy public void move(int dx, int dy) { origin.setX( origin.getX() + dx ); origin.setY( origin.getY() + dy ); } } </code> <code java CreateObjectDemo.java> public class CreateObjectDemo { public static void main(String[] args) { // Declare and create a point object and two rectangle objects. Point originOne = new Point(23, 94); Rectangle rectOne = new Rectangle(100, 200, originOne); Rectangle rectTwo = new Rectangle(50, 100); // display rectOne's width, height, and area System.out.println("Width of rectOne: " + rectOne.getWidth() ); System.out.println("Height of rectOne: " + rectOne.getHeight() ); System.out.println("Area of rectOne: " + rectOne.getArea()); // set rectTwo's position rectTwo.setOrigin(originOne); // display rectTwo's position System.out.println("X Position of rectTwo: " + rectTwo.getOrigin().getX()); System.out.println("Y Position of rectTwo: " + rectTwo.getOrigin().getY()); // move rectTwo and display its new position rectTwo.move(40, -20); System.out.println("X Position of rectTwo: " + rectTwo.getOrigin().getX()); System.out.println("Y Position of rectTwo: " + rectTwo.getOrigin().getY()); } } </code> Για να μεταγλωτίσουμε τα παραπάνω πρόγραμμα αρκεί να γράψουμε <code> javac Point.java // μεταγλώττιση της κλάσης Point javac Rectange.java // μεταγλώττιση της κλάσης Rectangle javac CreateObjectDemo.java // μεταγλώττιση της κλάσης CreateObjectDemo </code> και για να το τρέξουμε γράφουμε <code> java CreateObjectDemo </code> Το παραπάνω πρόγραμμα τυπώνει τα εξής στην κονσόλα. <code> Width of rectOne: 100 Height of rectOne: 200 Area of rectOne: 20000 X Position of rectTwo: 23 Y Position of rectTwo: 94 X Position of rectTwo: 63 Y Position of rectTwo: 74 </code> Από τα παραπάνω αξίζει να προσέξουμε τα εξής. - Εκτελούμε την κλάση η οποία περιέχει την μέθοδο **main** η οποία εκκινεί την εκτέλεση του προγράμματος <code>public static void main(String[] args) { .... } </code> - Η κλάση Rectangle έχει δύο κατασκευαστές οι οποίοι καλούνται εναλλάξ στη συνάρτηση **main** προκειμένου να αρχικοποιήσουν τα αντικείμενα (**objects**) ''rectOne'' και ''rectTwo''. - Κάθε κατασκευαστής φροντίζει να αρχικοποιεί όλα τα πεδία, ακόμη και αυτά για τα οποία δεν υπάρχει αντίστοιχη παράμετρος. - H συνάρτηση ''System.out.println()'' εκτυπώνει ένα αλφαριθμητικό (string) ακολουθούμενο από χαρακτήρα αλλαγής γραμμής. ===== Δήλωση Reference μεταβλητών και δημιουργία αντικειμένων ====== Στο παραπάνω πρόγραμμα ορίζονται στη συνάρτηση ''main'' τα εξής <code java> Point originOne = new Point(23, 94); Rectangle rectOne = new Rectangle(100, 200, originOne); Rectangle rectTwo = new Rectangle(50, 100); </code> Τα παραπάνω μπορούν να γραφούν αναλυτικότερα ως εξής. <code java> Point originOne; Rectangle rectOne, rectTwo; originOne = new Point(23, 94); rectOne = new Rectangle(100, 200, originOne); rectTwo = new Rectangle(50, 100); </code> Οι πρώτες δύο γραμμές ορίζουν τις μεταλητές ''originOne'', ''rectOne'', ''rectTwo''. Οι μεταβλητές δυνητικά δείχνουν σε τύπους δεδομένων Point, Rectangle, Rectangle. Προς το παρόν όμως το περιεχόμενο των μεταβλητών αυτών είναι απροσδιόριστο (στην πραγματικότητα ο compiler αρχικοποεί τις μεταβλητές αυτές στην τιμή **null**). //__Σε αναλογία με την γλώσσα C__, φανταστείτε τις μεταβλητές αυτές ως pointers που δεν είναι αρχικοποιημένοι σε κάποια υφιστάμενη διεύθυνση μνήμης. Η τιμή τους είναι απροσδιόριστη και η προσπάθεια να γράψουμε στη διεύθυνση μνήμης όπου δείχνουν θα προκαλέσει Segmentation Fault.// Η παρακάτω εικόνα δείχνει την ύπαρξη ενός μη αρχικοποιημένου δείκτη. {{ :java:reference-variables-01.png |}} Προκειμένου να αρχικοποιηθούν οι μεταβλητές αυτές απαιτούνται δύο βήματα: - Η δέσμευση της απαραίτητης μνήμης, ώστε να μπορούμε να αποθηκεύσουμε τα δεδομένα που προδιαγράφονται από τον συγκεκριμένο τύπο (κλάση). - Η αρχικοποίηση των επιμέρους μεταβλητών των αντικειμένων που ορίζονται απο την εκάστοτε κλάση. Η παρακάτω εικόνα δείχνει τον δείκτη μετά την δέσμευση της απαιτούμενης μνήμης και την αρχικοποίηση των δεδομένων (μεταβλητών) του αντικειμένου ''originOne''. {{ :java:reference-variables-02.png ?400 |}} Ο παρακάτω κώδικας <code java>rectOne = new Rectangle(100, 200, originOne);</code> δηλώνει την δέσμευση μνήμης για ένα αντικείμενο τύπου ''Rectangle'' όπου το σημείο της αρχής του είναι το σημείο ''originOne''. Επίσης ορίζονται οι τιμές των **width** και **height** σε 100 και 200 αντίστοιχα. O παραπάνω ορισμός σημαίνει ένα αρχικοποιημένο αντικείμενο μπορεί να έχει πολλούς δείκτες προς αυτό, όπως δείχνει το παρακάτω σχήμα. {{ :java:reference-variables-03.png ?500 | }} ===== Ο τελεστής new ===== Προκειμένου να δημιουργηθούν νέα αντικείμενα χρησιμοποιείται ο τελεστής **new**. O τελεστής **new** χρησιμοποιείται συνήθως με τον κατασκευαστή μίας κλάσης προκειμένου να κάνει τα εξής: - Δέσμευση της απαραίτητης μνήμης και δημιουργία του αντικειμένου. Η αρχικά ορισμένη μεταβλητή δείχνει πλέον στην περιοχή μνήμης που έχει δεσμευτεί. - Αρχικοποίηση των εσωτερικών μεταβλητών (πεδίων) του αντικειμένου με κλήση του κατάλληλου κατασκευαστή της κλάσης. Εάν δεν έχει οριστεί κατασκευαστής τότε ο τελεστής new καλείται με χρήση του default κατασκευαστή (default constructor) που δεν έχει ορίσματα (π.χ. ''MyObject obj = new MyObject();'', όπου για την κλάση ''MyObject'' δεν έχει οριστεί κανένας κατασκευατής). <WRAP tip 80% center round> Κατά την χρήση **primitive** τύπων δεδομένων (int, float, double) δεν απαιτείται η χρήση του τελεστή **new** διότι η απαραίτητη μνήμη δεσμεύεται στο stack της μεθόδου που εκτελείται. </WRAP> ===== Πολλαπλοί κατασκευαστές σε μία κλάση ===== Μία κλάση μπορεί να έχει πολλούς διαφορετικούς κατασκευαστές. Κάθε κατασκευαστής ορίζει μία διαφορετική αρχικοποίηση των εσωτερικών μεταβλητών των αντικειμένων που δημιουργούνται με βάση το "σχέδιο" της κλάσης. Το ποιός κατασκευαστής θα κληθεί εξαρτάται από τον τύπο, τη σειρά και τον αριθμό των ορισμάτων σε αναλογία με την υπερφόρτωση μεθόδων. Έτσι στη συνάρτηση **main** του παραδείγματος μας καλούνται δύο διαφορετικοί κατασκευαστές για δύο διαφορετικά αντικείμενα της ίδιας κλάσης. <code java> rectOne = new Rectangle(100, 200, originOne); rectTwo = new Rectangle(50, 100); </code> ===== Χρήση Αντικειμένων ===== Όταν φτιάξετε ένα αντικείμενο είναι σίγουρο ότι θα θέλετε να το χρησιμοποιήσετε προκειμένου να κάνετε μία εργασία όπως να γράψετε κάτι στα δεδομένα του, να διαβάσετε από αυτά ή να χρησιμοποιήσετε κάποια από τις μεθόδους του. ==== Χρήση των πεδίων ενός αντικειμένου ==== Τα πεδία ενός αντικειμένου είναι προσβάσιμα με χρήση του ονόματος του αντικειμένου, μία τελεία '.' και το όνομα του πεδίου. Για παράδειγμα, <code java> // fields are width, height Rectangle rect = new Rectangle(10,20); System.out.println("Rectangle dimensions are " + rect.width + ", " + rect.height); </code> <WRAP important 80% center round> Απαραίτητη προϋπόθεση για να δουλέψει ο παραπάνω κώδικας είναι τα πεδία width και height να είναι προσβάσιμα, δηλαδή να ΜΗΝ έχουν προσδιοριστή πρόσβασης τύπου **private**. </WRAP> <WRAP tip 80% center round> Όπως προείπαμε μία καλή προγραμματιστική πρακτική είναι η απόκρυψη των πεδίων κάθε κλάσης και η δήλωση συναρτήσεων για την πρόσβαση στα δεδομένα της. Σε αυτή την περίπτωση η απευθείας πρόσβαση στα πεδία των αντικειμένων είναι μη επιτρεπτή (ο compiler δεν μεταγλωττίζει το πρόγραμμα). Η πρόσβαση σε μεταβλητές που έχουν τον προσδιοριστή **private** μπορεί να γίνει μόνο μέσω βοηθητικών συναρτήσεων (set/get) που έχουν τον προσδιοριστή **public**. </WRAP> ==== Χρήση των μεθόδων ενός αντικειμένου ==== Σε αναλογία με τα πεδία οι μέθοδοι ενός αντικειμένου είναι προσβάσιμες μέσω του ονόματος του αντικειμένου, μία τελεία '.' και το όνομα του πεδίου. Για παράδειγμα, <code java> // fields are width, height Rectangle rect = new Rectangle(10,20); System.out.println("Rectangle dimensions are " + rect.getWidth() + ", " + rect.getHeight() ); </code> <WRAP important 80% center round> Ισχύουν και για τις μεθόδους όσα αναφέρονται για τους προσδιοριστές τύπου **public**, **private** των πεδίων. </WRAP> ===== Garbage Collection ===== Η γλώσσα **C** που μέχρι τώρα έχετε γνωρίσει αναθέτει την ευθύνη δέσμευσης μνήμης στον προγραμματιστή μέσω των συναρτήσεων **malloc()** και **free()**. Σε αντιδιαστολή, η **JAVA** αφήνει τον προγραμματιστεί να ορίσει όσα αντικείμενα επιθυμεί και δεσμεύει την μνήμη για αυτά μέσω του τελεστή **new**. Πώς όμως αποδεσμεύεται η μνήμη που δεσμεύτηκε προηγούμενα από το πρόγραμμα μας, αλλά δεν την χρειαζόμαστε πλέον; Αν η μνήμη δεν αποδεσμεύεται και το πρόγραμμα μας τρέξει για αρκετή ώρα δεσμεύοντας διαρκώς μνήμη, τότε είναι σίγουρο ότι θα το τερματίσει το λειτουργικό σύστημα. Η απάντηση, όσον αφορά την αποδέσμευση μνήμης στη Java γίνεται μέσω του μηχανισμού **Garbage Collection** και επεξηγείται εν συντομία παρακάτω. Περιοδικά το **JVM** κοιτάει εάν υπάρχει δεσμευμένη μνήμη για αντικείμενα στα οποία δεν υφίστανται πλέον μεταβλητές που δείχνουν σε αυτά, δηλαδή δεν υφίστανται αναφορές (//references//) προς αυτά. Σε αυτές τις περιπτώσεις, ελευθερώνεται η μνήμη που έχει δεσμευτεί για τα αντικείμενα αυτή της κατηγορίας. Παράλληλα η αποδέσμευση της μνήμης και η διαγραφή των αντικειμένων πιθανόν συνεπάγεται ότι και άλλα αντικείμενα δεν διαθέτουν πια αναφορές προς αυτά κ.ο.κ. Η διαδικασία συνεχίζεται μέχρι να αποδεσμευτεί όλη η δυναμικά δεσμευμένη μνήμη. Ο μηχανισμός **Garbage Collection** απαντάται σε αρκετές γλώσσες υψηλού επιπέδου, απελευθερώνοντας τον προγραμματιστή από την ευθύνη αποδέσμευσης της μνήμης που δεσμεύτηκε προηγούμενα. Η ευθύνη της αποδέσμευσης μνήμης επαφίεται στο Garbage Collector, πράγμα που κάνει λιγότερο επίπονο τον προγραμματισμό. Φυσικά η δυνατότητα του **Garbage Collection** συνεπάγεται ότι ένα παράλληλο νήμα, τρέχει περιοδικά και ελευθερώνει την μνήμη, πράγμα που πιθανόν να εισάγει καθυστερήσεις σε συγκεκριμένες εφαρμογές. Σε προγράμματα που απαιτούν πολύ υψηλές επιδόσεις ή έχουν αυστηρούς περιορισμούς ως προς τον χρόνο εκτέλεσης τους, η παράλληλη εκτέλεση του Garbage Collector μπορεί να εισάγει μη αποδεκτή καθυστέρηση. Περισσότερα για την λειτουργία του Garbage Collector και τις εναλλακτικές υλοποιήσεις μπορείτε να βρείτε [[http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html|εδώ]]. |Προηγούμενο: [[:java:class_constructors | Κατασκευαστές της κλάσης ]] | [[:toc | Περιεχόμενα ]] | Επόμενο: [[:java:this_operator | O τελεστής this ]]|

java/objects.1488382311.txt.gz · Last modified: 2017/03/01 15:31 by gthanos