User Tools

Site Tools


java:objects

Δημιουργία Αντικειμένων

Μέχρι τώρα αναφέραμε στην "Εισαγωγή στον Αντικειμενοστραφή Προγραμματισμό" ότι η κλάση είναι το βασικό “σχέδιο” μέσα από το οποίο δημιουργούνται επιμέρους αντικείμενα που φέρουν τα χαρακτηριστικά της κλάσης. Επίσης, δείξαμε πως ορίζουμε μία κλάση μέσα από παραδείγματα, αλλά δεν δείξαμε πως δημιουργούμε αντικείμενα από τις κλάσεις που ορίσαμε.

Η δημιουργία αντικειμένων γίνεται με χρήση του τελεστή new. Για παράδειγμα, για να δημιουργήσουμε ένα αντικείμενο της κλάσης Point αρκεί να γράψουμε

  /* δημιουργεί ένα αντικείμενο τύπου Point 
   * με συντεταγμένες 3,5 και το αναθέτει στη
   * μεταβλητή p που είναι τύπου Point.
   */  
  Point p;               // (a)
  p = new Point(3,5);    // (b)

Μπορείτε να σκεφτείτε τη μεταβλητή p ως ένα δείκτη σε αντικείμενα τύπου Point. Αρχικά ο δείκτης είναι μη αρχικοποιημένος δείχνοντας στην τιμή null. Στην επόμενη γραμμή καλείται ο κατασκευαστής ο οποίος δημιουργεί ένα αντικείμενο τύπου Point με συντεταγμένες 3,5 και αναθέτει το αντικείμενο αυτό στη μεταβλητή p. Τα παρακάτω σχήματα απεικονίζουν εποπτικά την διαδικασία.

Ένα πιο εκτεταμένο παράδειγμα

Παρακάτω δίνεται η κλάση CreateObjectDemo που δημιουργεί συγκεκριμένα αντικείμενα του τύπου Point και Rectangle και εκτυπώνει τα αποτελέσματα στην κονσόλα. Οι κλάσεις Point και Rectangle δίνονται επίσης παρακάτω:

Point.java
class Point {
  int x;   // x coordinate
  int y;   // y coordinate
 
  public Point(int xPos, int yPos) {
    x = xPos;
    y = yPos;
  }
 
  int getX() {
    return x;
  }
 
  void setX(int xPos) {
    x = xPos;
  }
 
  int getY() {
    return y;
  }
 
  void setY(int yPos) {
    y = yPos;
  }
}
Rectangle.java
class Rectangle {
 
  int width, height;
  Point origin;
 
  public Rectangle(int initWidth, int initHeight, Point initOrigin) {
    width = initWidth;
    height = initHeight;
    origin = initOrigin;
  }
 
  public Rectangle(int initWidth, int initHeight, int originX, int originY) {
    width = initWidth;
    height = initHeight;
    origin = new Point(originX,originY);
  }
 
  void setWidth(int newWidth ) {
    width = newWidth;
  }
 
  int getWidth() {
    return width;
  }
 
  void setHeight(int newHeight ) {
    height = newHeight;
  }
 
  int getHeight() {
    return height;
  }
 
  void setOrigin(Point newOrigin) {
    origin = newOrigin;
  }
 
  Point getOrigin() {
    return origin;
  }
 
  int getArea() {
       return width * height;
  }
 
  // Move rectangle origin by dx,dy
  void moveOrigin(int dx, int dy) {
    origin.setX( origin.getX() + dx );
    origin.setY( origin.getY() + dy );
  }
}
CreateObjectDemo.java
public class CreateObjectDemo {
 
  public static void main(String[] args) {
 
    // Declare variables
    Point origin1, origin2;
    Rectangle rect1, rect2;
 
    // Create the objects
    origin1 = new Point(23, 94);
    origin2 = new Point(15, -33);
    rect1 = new Rectangle(100, 200, origin1);
    rect2 = new Rectangle(50, 100, origin2);
 
    // print origin of rect1 and rect2
    System.out.println("[rect1]  origin.x: " + rect1.getOrigin().getX() + ", origin.y: " + rect1.getOrigin().getY());
    System.out.println("[rect2]  origin.x: " + rect2.getOrigin().getX() + ", origin.y: " + rect2.getOrigin().getY());
 
    // set rect2 origin
    rect2.setOrigin(origin1);
    // display rect2 origin
    System.out.println("[rect2]  origin.x: " + rect2.getOrigin().getX() + ", origin.y: " + rect2.getOrigin().getX());
 
    // move rect2 origin and display its new position
    rect2.moveOrigin(40, -20);
 
    // print origin of rect1 and rect2
    System.out.println("[rect1]  origin.x: " + rect1.getOrigin().getX() + ", origin.y: " + rect1.getOrigin().getY());
    System.out.println("[rect2]  origin.x: " + rect2.getOrigin().getX() + ", origin.y: " + rect2.getOrigin().getY());
 
    // assign originOne value to originTwo
    origin2 = origin1;
  }
}

Δημιουργήστε ένα project με τα παραπάνω τρία αρχεία java στο IDE της επιλογής σας και εκτελέστε τον κώδικα. Τρέξτε τον κώδικα βήμα-βήμα βλέποντας πως αλλάζουν οι τιμές των μεταβλητών.

Επεξήγηση του παραπάνω κώδικα

Στο παραπάνω πρόγραμμα ορίζονται στη μέθοδο main τα εξής

    // Declare variables
    Point origin1, origin2;
    Rectangle rect1, rect2;
    // Create objects
    origin1 = new Point(23, 94);
    origin2 = new Point(15, -33);
    rect1 = new Rectangle(100, 200, origin1);
    rect2 = new Rectangle(50, 100, origin2);

Οι πρώτες δύο γραμμές ορίζουν τις μεταβλητές origin1, origin2,rect1 και rect2. Οι μεταβλητές δυνητικά δείχνουν σε τύπους δεδομένων Point και Rectangle. Προς το παρόν όμως το περιεχόμενο των μεταβλητών αυτών είναι απροσδιόριστο (στην πραγματικότητα ο compiler αρχικοποιεί τις μεταβλητές αυτές στην τιμή null).

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

Η παρακάτω εικόνα δείχνει την ύπαρξη των τεσσάρων μη αρχικοποιημένων δεικτών. Όλοι αρχικά δείχνουν στην τιμή null.

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

Η παρακάτω εικόνα δείχνει τις μεταβλητές origin1, origin2, rect1, rect2 μετά την αρχικοποίηση τους από τους αντίστοιχους κατασκευαστές.

Στη συνέχεια ακολουθούν οι εξής γραμμές κώδικα:

    // set rect2 origin
    rect2.setOrigin(origin1);
    // display rect2 origin
    System.out.println("[rect2]  origin.x: " + rect2.getOrigin().getX() + ", origin.y: " + rect2.getOrigin().getX());
 
    // move rect2 origin and display its new position
    rect2.moveOrigin(40, -20);
 
    // print origin of rect1 and rect2
    System.out.println("[rect1]  origin.x: " + rect1.getOrigin().getX() + ", origin.y: " + rect1.getOrigin().getY());
    System.out.println("[rect2]  origin.x: " + rect2.getOrigin().getX() + ", origin.y: " + rect2.getOrigin().getY());

Στις γραμμές αυτές συμβαίνουν τα εξής:

  1. Το αντικείμενο rect2 επιλέγει ως πεδίο origin to origin1.
  2. Στη συνέχεια, μέσω του rect2 μεταβάλλονται οι συντεταγμένες του αντικειμένου origin1.
  3. Εκτυπώνονται οι αλλαγές για το rect1 στην κονσόλα.
  4. Εκτυπώνονται οι αλλαγές για το rect2 στην κονσόλα.

Παρατηρούμε ότι οι συντεταγμένες του πεδίου origin άλλαξαν για το αντικείμενο rect2 και παράλληλα για το αντικείμενο rect1, διότι τα πεδία origin των δύο αντικειμένων δείχνουν στο ίδιο αντικείμενο.

Τα παρακάτω δύο σχήματα αποτυπώνουν α) την αλλαγή του πεδίου origin του rect2, ώστε να δείχνει στο αντικείμενο rect1 και β) την αλλαγή των περιεχομένων του origin1 μέσω του αντικειμένου rect2.

Παρατηρείστε ότι πλέον μόνο η μεταβλητή origin2 δείχνει στο αντικείμενο τύπου Point με συντεταγμένες 15, -33.

(a) (b)

Τέλος το πρόγραμμα τελειώνει με την γραμμή κώδικα:

    // assign origin1 value to origin2
    origin2 = origin1;

Μετά την γραμμή αυτή η μεταβλητή origin2 δείχνει στο αντικείμενο που δείχνει και η μεταβλητή origin1. Πλέον δεν υπάρχει καμία μεταβλητή ή αναφορά που να δείχνει στο αντικείμενο τύπου Point με συντεταγμένες 15, -33. Το αντικείμενο αυτό θα διαγραφεί αυτόματα από την λειτουργία Garbage Collection του JVM.

Επεξήγηση της χρήσης του τελεστή new

Προκειμένου να δημιουργηθούν νέα αντικείμενα χρησιμοποιείται ο τελεστής new. O τελεστής new χρησιμοποιείται συνήθως με τον κατασκευαστή μίας κλάσης προκειμένου να κάνει τα εξής:

  1. Δέσμευση της απαραίτητης μνήμης και δημιουργία του αντικειμένου. Η αρχικά ορισμένη μεταβλητή δείχνει πλέον στην περιοχή μνήμης που έχει δεσμευτεί.
  2. Αρχικοποίηση των εσωτερικών μεταβλητών (πεδίων) του αντικειμένου με κλήση του κατάλληλου κατασκευαστή της κλάσης. Εάν δεν έχει οριστεί κατασκευαστής τότε ο τελεστής new καλείται με χρήση του default κατασκευαστή (default constructor) που δεν έχει ορίσματα. Για παράδειγμα, για την ενδεικτική κλάση MyObject στην οποία δεν έχει οριστεί κανένας κατασκευατής
 MyObject obj = new MyObject();

Κατά την χρήση primitive τύπων δεδομένων σε ένα πρόγραμμα (int, float, double) δεν απαιτείται η χρήση του τελεστή new. Ο λόγος που συμβαίνει αυτό είναι ότι είναι γνωστό εκ των προτέρων το εύρος μνήμης που απαιτούν. Οι primitive τύποι δεδομένων αποθηκεύονται πάντοτε στο stack της μεθόδου μέσα στην οποία δηλώνονται.

Αντίθετα, για τα αντικείμενα των κλάσεων ο compiler δεν μπορεί να γνωρίζει εκ των προτέρων το μέγεθος τους, καθώς με την σειρά τους αυτά μπορεί να περιέχουν άλλα αντικείμενα, τα οποία με την σειρά τους μπορεί να περιέχουν άλλα αντικείμενα κ.ο.κ. Για τον λόγο αυτό, για τα αντικείμενα αυτά δεσμεύεται πάντοτε η απαραίτητη μνήμη δυναμικά, στην περιοχή δυναμικής δέσμευσης μνήμης που παραδοσιακά ονομάζεται heap.

Διαφορετικοί κατασκευαστές σε μία κλάση

Μία κλάση μπορεί να έχει περισσότερους από ένα κατασκευαστές. Κάθε κατασκευαστής ορίζει ένα διαφορετικό τρόπο αρχικοποίησης των εσωτερικών μεταβλητών των αντικειμένων που δημιουργούνται από την κλάση. Το ποιoς κατασκευαστής θα κληθεί εξαρτάται από τον τύπο, τη σειρά και τον αριθμό των ορισμάτων σε αναλογία με την υπερφόρτωση μεθόδων. Ας υποθέσουμε την παρακάτω μέθοδο main, στην οποία καλούνται δύο διαφορετικοί κατασκευαστές για δύο διαφορετικά αντικείμενα της ίδιας κλάσης Rectangle. Παρατηρήστε ότι η μεταβλητή rect1 αρχικοποιείται από την πρώτο κατασκευαστή της κλάσης και η μεταβλητή rect2 από τον δεύτερο κατασκευαστή.

public class CreateRectangleObjects {
  public static void main(String []args) {
    Point origin1 = new Point(10,5);
    rect1 = new Rectangle(100, 200, origin1);
    rect2 = new Rectangle(50, 100, -40, 80);
  }
}

Χρήση των πεδίων και των μεθόδων ενός αντικειμένου

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

Χρήση των πεδίων ενός αντικειμένου

Τα πεδία ενός αντικειμένου είναι προσβάσιμα με χρήση του ονόματος του αντικειμένου, μία τελεία '.' και το όνομα του πεδίου. Για παράδειγμα,

   // fields are width, height
   Rectangle rect = new Rectangle(10,20, -5, 22);
   System.out.println("Rectangle dimensions are " + rect.width + ", " + rect.height);

Χρήση των μεθόδων ενός αντικειμένου

Σε αναλογία με τα πεδία οι μέθοδοι ενός αντικειμένου είναι προσβάσιμες μέσω του ονόματος του αντικειμένου, μία τελεία '.' και το όνομα της μεθόδου. Για παράδειγμα,

   Rectangle rect = new Rectangle(10,20, -5, 22);
   System.out.println("Rectangle dimensions are " + rect.getWidth() + ", " + rect.getHeight() );

Garbage Collection

Οι γλώσσες προγραμματισμού που μέχρι τώρα έχετε γνωρίσει (βλέπε C) αναθέτουν την ευθύνη δέσμευσης μνήμης στον προγραμματιστή μέσω των συναρτήσεων malloc() και free(). Σε αντιδιαστολή, η JAVA αφήνει τον προγραμματιστεί να ορίσει όσα αντικείμενα επιθυμεί και δεσμεύει την μνήμη για αυτά μέσω του τελεστή new.

Πώς όμως αποδεσμεύεται η μνήμη που δεσμεύτηκε προηγούμενα από το πρόγραμμα μας, αλλά δεν την χρειαζόμαστε πλέον; Περιοδικά το JVM κοιτάει εάν υπάρχει δεσμευμένη μνήμη για αντικείμενα στα οποία δεν υφίστανται πλέον αναφορές/references που δείχνουν σε αυτά. Σε αυτές τις περιπτώσεις, ελευθερώνεται η μνήμη που έχει δεσμευτεί για τα αντικείμενα αυτής της κατηγορίας. Παράλληλα η αποδέσμευση της μνήμης και η διαγραφή των αντικειμένων πιθανόν συνεπάγεται ότι και άλλα αντικείμενα δεν διαθέτουν πια αναφορές προς αυτά κ.ο.κ. Η διαδικασία συνεχίζεται μέχρι να αποδεσμευτεί όλη η δυναμικά δεσμευμένη μνήμη στην οποία το πρόγραμμα έχει χάσει κάθε δυνατότητα πρόσβασης.

Ο μηχανισμός Garbage Collection απαντάται σε αρκετές γλώσσες υψηλού επιπέδου, απελευθερώνοντας τον προγραμματιστή από την ευθύνη αποδέσμευσης της μνήμης που δεσμεύτηκε προηγούμενα. Η ευθύνη της δεύσμευσης/αποδέσμευσης μνήμης δεν επαφίεται στον προγραμματιστή, πράγμα που κάνει λιγότερο επίπονο τον προγραμματισμό.

Περισσότερα για την λειτουργία του Garbage Collector και τις εναλλακτικές υλοποιήσεις μπορείτε να βρείτε εδώ.

java/objects.txt · Last modified: 2022/02/23 15:25 by gthanos