JAVA ΕΝΟΤΗΤΑ 32 – CLONING OBJECTS

 


ΕΙΣΑΓΩΓΗ

Στο σημερινό δωρεάν μάθημα Java, θα δούμε τους τρόπους με τους οποίους μπορούμε να κάνουμε αντίγραφα αντικειμένων (clones), τι σημαίνει shallow cloning και τι deep cloning, και γιατί πρέπει να προτιμάμε το copy constructor αντί της clone( ) μεθόδου.

clone( ) METHOD

Η Java δεν μας προσφέρει κάποιο αυτόματο μηχανισμό για να δημιουργήσει πιστό αντίγραφο ενός αντικειμένου. Μην ξεχνάτε ότι όταν αναθέτουμε το reference variable σε ένα άλλο reference variable δεν έχουμε δύο αντικείμενα αλλά δύο references που δείχνουν στο ίδιο αντικείμενο. Όταν μιλάμε για cloning εννοούμε να αντιγράψουμε τα περιεχόμενα ενός αντικειμένου στοιχείο προς στοιχείο σε ένα καινούργιο αντικείμενο. Για να πραγματοποιήσουμε μια τέτοια πράξη, θα πρέπει να γράψουμε κώδικα για την μέθοδο clone( ). Ας δούμε όμως πιο συγκεκριμένα τα βήματα που πρέπει να υλοποιήσουμε.

Για αρχή θα πρέπει να κάνουμε implements το interface Cloneable στην κλάση μας. Δεν έχουμε μιλήσει ακόμα για interfaces αλλά θεωρείστε τα σαν ένα είδος κλάσης από τα οποία κληρονομούμε μεθόδους. Για να μπορέσει η κλάση μας να κληρονομήσει τις μεθόδους ενός interface χρησιμοποιήσουμε τη λέξη κλειδί implements αντί για extends όπως συνηθίζουμε να κάνουμε στις κλάσεις. Θα μιλήσουμε για το πώς κληρονομούμε από interfaces, και τις διαφορές τους από τις κλάσεις σε μελλοντικό δωρεάν μάθημα Java. Για τώρα μας είναι αρκετό να γνωρίζουμε ότι το interface, όταν κληρονομούμε από αυτό, μας προσφέρει κάποιες μεθόδους για τις οποίες θα πρέπει εμείς να γράψουμε κώδικα, δηλαδή να τις κάνουμε override. Αρχικά ο κώδικας του Employee αλλάζει ως εξής:

Employee.java

package com.example;

public class Employee implements Cloneable {
    public String name;
    public int AFM;
    public String department;
    public int employeeId;

    public void mailCheck() {
        System.out.println("Mailing a check to " + this.name);
    }

    public String toString() {
        return "new Employee. Welcome to our Department";
    }
}

Το δεύτερο βήμα είναι να κάνουμε override την μέθοδο clone( ) που κληρονομούμε από το interface Cloneable. Επειδή όμως το return type της clone( ) είναι Object θα πρέπει να κάνουμε casting το αντικείμενο μας στην κλάση μέσα στην οποία υλοποιείται η clone( ) - δηλαδή την Employee. Επίσης, θα πρέπει να προστατέψουμε τον κώδικα μας από τυχόν λάθη τοποθετώντας τον μέσα σε ένα try-catch. Το πώς χρησιμοποιούμε το try-catch θα το δούμε και αυτό σε μελλοντικό δωρεάν μάθημα Java. Για τώρα απλά θεωρείστε αναγκαίο ότι ο κώδικας πρέπει να προστατευτεί γιατί υπάρχει περίπτωση να μας παρουσιάσει CloneNotSupportedException. Ο κώδικας της Employee τώρα είναι ο εξής:

Employee.java

package com.example;

public class Employee implements Cloneable {
    public String name;
    public int AFM;
    public String department;
    public int employeeId;

    public void mailCheck() {
        System.out.println("Mailing a check to " + this.name);
    }

    public String toString() {
        return "new Employee. Welcome to our Department";
    }

    public Object clone() {
        Employee copy = null;
        try {
            copy = (Employee) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return copy;
    }
}


Τώρα ας γράψουμε το κώδικα της EmployeeDemo μέσα από την οποία θα επιβεβαιώσουμε την cloning διαδικασία. Για αρχή δημιουργούμε ένα αντικείμενο είδος Employee και αφού αναθέσουμε ένα όνομα στην instance variable name, πραγματοποιούμε cloning στο αντικείμενο. Το αποτέλεσμα είναι ότι και τα δύο αντικείμενα έχουν την ίδια τιμή για την μεταβλητή name. Μετά αλλάζουμε το όνομα σε ένα από τα αντικείμενα. Επιβεβαιώνουμε ότι ενώ η τιμή του ενός αντικειμένου αλλάζει, το δεύτερο αντικείμενο δεν επηρεάζεται.

EmployeeDemo.java

package com.example;

public class EmployeeDemo {
    public static void main(String[] args) {
        Employee e1 = new Employee();
        e1.name = "Michail";
        Employee e1Clone = (Employee) e1.clone();

        System.out.println("Original:" + e1.name);
        System.out.println("Clone:" + e1Clone.name);

        e1.name = "George";

        System.out.println("Original:" + e1.name);
        System.out.println("Clone:" + e1Clone.name);
    }
}


Output

Original:Michail

Clone:Michail

Original:George

Clone:Michail

 

Shallow Cloning

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

Η κλάση Employee στο προηγούμενο πρόγραμμα περιέχει instance μεταβλητές είδος int και String. Τώρα ας θεωρήσουμε ότι έχουμε μια ακόμα κλάση με το όνομα Manager.

Manager.java

package com.example;

public class Manager {

    public String position;

    public Manager(String position) {
        this.position = position;
    }

    public String getPosition() {
        return this.position;
    }

    public void setPosition(String position) {
        this.position = position;
    }

}

Τώρα δηλώνουμε στην Employee ένα instance variable είδος Manager. Ο κώδικας του Employee αλλάζει ως εξής:

Employee.java

package com.example;

public class Employee implements Cloneable {
    public String name;
    public int AFM;
    public String department;
    public int employeeId;

    Manager manager = new Manager("Director");

    public Employee(String position) {
        this.manager.setPosition(position);
    }

    public void setPosition(String position) {
        this.manager.setPosition(position);
    }

    public String getPosition() {
        return this.manager.getPosition();
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAFM() {
        return this.AFM;
    }

    public void setAFM(int AFM) {
        this.AFM = AFM;
    }

    public String getDepartment() {
        return this.department;
    }

    public void setDepartment(String department) {
        this.department = department;
    }

    public int getEmployeeId() {
        return this.employeeId;
    }

    public void setEmployeeId(int employeeId) {
        this.employeeId = employeeId;
    }

    public void mailCheck() {
        System.out.println("Mailing a check to " + this.name);
    }

    public String toString() {
        return "new Employee. Welcome to our Department";
    }

    public Object clone() {
        Employee copy = null;
        try {
            copy = (Employee) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return copy;
    }
}


Τώρα αν εκτελέσουμε την clone( ) θα δημιουργήσουμε ένα πραγματικό κλώνο της Employee αλλά όχι της Manager. Μόνο το reference αντιγράφεται. Όταν λοιπόν δεν αντιγράφονται όλα τα αντικείμενα στοιχείο προς στοιχείο αλλά μερικά μόνο αυτό ονομάζεται shallow cloning.

EmployeeDemo.java

package com.example;

public class EmployeeDemo {
    public static void main(String[] args) {
        Employee e1 = new Employee("Director");
        Employee e1Clone = (Employee) e1.clone();

        System.out.println("Original:" + e1.getPosition());
        System.out.println("Clone:" + e1Clone.getPosition());

        e1.setPosition("Global");

        System.out.println("Original:" + e1.getPosition());
        System.out.println("Clone:" + e1Clone.getPosition());
    }
}


Output

Original:Director

Clone:Director

Original:Global

Clone:Global

Το αποτέλεσμα νομίζω μιλάει από μόνο του. Δεν έγινε deep cloning στο manager αλλά αντιγράφτηκε μόνο το reference. Για αυτό και κάθε αλλαγή στo manager reference επηρεάζει και τα δύο αντικείμενα. Σε αυτή τη περίπτωση θα πρέπει να κάνουμε cloning σε όλα τα reference instance variables για να λειτουργήσει σωστά το πρόγραμμα μας. Αλλά δεν θα προχωρήσουμε σε αυτή τη λύση γιατί όπως ήδη αναφέραμε είναι αναξιόπιστη και χρονοβόρα αφού πρέπει να γράψουμε πολύ κώδικα, να κάνουμε αρκετά casting και να κάνουμε implements το interface Cloneable.

Copy Constructor

Για να γλυτώσουμε όλα τα παραπάνω έξτρα βήματα αλλά και ταυτόχρονα για να δημιουργήσουμε ένα πραγματικά deep cloning αντικείμενο χρησιμοποιούμε την τεχνική του copy constructor.

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

Employee.java

package com.example;

public class Employee implements Cloneable {
    public String name;
    public int AFM;
    public String department;
    public int employeeId;

    public Employee(String name) {
        this.name = name;
    }

    public Employee(Employee employee) {
        this.name = employee.name;
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAFM() {
        return this.AFM;
    }

    public void setAFM(int AFM) {
        this.AFM = AFM;
    }

    public String getDepartment() {
        return this.department;
    }

    public void setDepartment(String department) {
        this.department = department;
    }

    public int getEmployeeId() {
        return this.employeeId;
    }

    public void setEmployeeId(int employeeId) {
        this.employeeId = employeeId;
    }

    public void mailCheck() {
        System.out.println("Mailing a check to " + this.name);
    }

    public String toString() {
        return "new Employee. Welcome to our Department";
    }

}

EmployeeDemo.java

package com.example;

public class EmployeeDemo {
    public static void main(String[] args) {
        Employee e1 = new Employee("Michail");
        Employee e2 = new Employee(e1);

        System.out.println("Original:" + e1.name);
        System.out.println("Clone:" + e2.name);

        e1.name = "George";

        System.out.println("Original:" + e1.name);
        System.out.println("Clone:" + e2.name);
    }
}


Output

Original:Michail

Clone:Michail

Original:George

Clone:Michail

Μην ξεχάσετε να κάνετε ένα μικρό donation έτσι ώστε αυτό το blog να μεγαλώσει ακόμα πιο πολύ και να έχει περισσότερες δυνατότητες στην online παράδοση δωρεάν μαθημάτων.

full-width

Post a Comment

0 Comments