JAVA ΕΝΟΤΗΤΑ 31 – OBJECT CLASS AND ITS METHODS (PART 2)

 


ΕΙΣΑΓΩΓΗ

Στο σημερινό δωρεάν μάθημα Java θα συνεχίσουμε την ανάλυση μας στις υπόλοιπες μεθόδους της Object class. Πιο συγκεκριμένα θα μιλήσουμε για την equals( ) και την hashCode( ). Όπως ήδη έχουμε αναφέρει από την προηγούμενη ενότητα, οι υπόλοιπες μέθοδοι όπως η wait( ) και η notify( ) αναφέρονται σε threads και θα τις αναλύσουμε σε μελλοντική ενότητα.

hashCode( ) METHOD

Ας ξεκινήσουμε πρώτα από την hashCode( ). Αλλά πριν μιλήσουμε για την ίδια την μέθοδο, ας καταλάβουμε τι είναι το hash code γενικότερα. Ένα hash code είναι ένας ακέραιος αριθμός που είναι το αποτέλεσμα εφαρμογής ενός αλγόριθμου σε κάποια δεδομένα. Με άλλα λόγια, έχουμε κάποια δεδομένα, εφαρμόζουμε έναν αλγόριθμο σε αυτά και λαμβάνουμε πίσω έναν ακέραιο αριθμό. Ο υπολογισμός του hash code είναι one-way process. Αυτό σημαίνει ότι θα είναι αρκετά δύσκολο να πάρουμε πίσω την αρχική πληροφορία αν και γνωρίζουμε το hash code. Άλλωστε η λογική του hash code δεν σχεδιάστηκε έτσι ώστε να είναι two-way.

Για ποιο λόγο να μας είναι χρήσιμο ένα hash code? Υπάρχουν είδος πινάκων όπως HashTables και HashMaps που αποθηκεύουν αντικείμενα χρησιμοποιώντας το hash code. Πριν τα αντικείμενα αποθηκευτούν, το hash code για κάθε αντικείμενο υπολογίζεται και αποθηκεύεται ξεχωριστά. Όταν θέλουμε να κάνουμε εύρεση κάποιων αντικειμένων που έχουν ήδη αποθηκευτεί με αυτό τον τρόπο, χρησιμοποιείται το hash code για να βρεθεί η θέση τους μέσα στους πίνακες και κατά συνέπεια να μας επιστραφεί το αποτέλεσμα. Αυτή η διαδικασία κάνει πολύ γρήγορη την εύρεση των δεδομένων. Φυσικά αν δεν χρησιμοποιήσετε ποτέ πίνακες που να χρησιμοποιούν hash code, τότε δεν υπάρχει λόγος να ασχοληθείτε και πολύ με αυτό το θέμα.

Υπάρχει όμως και ένας ακόμα λόγος που χρειαζόμαστε το hash code και κυρίως να κάνουμε override την hashCode( ) μέθοδο και να γράψουμε τον δικό μας κώδικα – κάθε φορά που κάνουμε override την equals( ) (για την οποία θα δείξουμε ένα παράδειγμα πολύ σύντομα) πρέπει να κάνουμε override και την hashCode( ).

equals( ) METHOD

Ωραία, αλλά πως συνδέεται το hash code με τα αντικείμενα που δημιουργούμε από τις κλάσεις μας? Και γιατί πρέπει να κάνουμε override την hashCode( ) όποτε χρησιμοποιούμε την equals( )? Η Object κλάση μας δίνει την hashCode( ) μέθοδο η οποία μας επιστρέφει έναν int αριθμό ο οποίος είναι το hash code του αντικειμένου. Η προκαθορισμένη από την Java συμπεριφορά της hashCode( ) μεθόδου, χωρίς να την κάνουμε override, είναι να υπολογίσει το hash code του αντικειμένου μετατρέποντας την διεύθυνση της μνήμης, στην οποία βρίσκεται το αντικείμενο, σε ακέραιο. Η equals( ) μέθοδο συγκρίνει αν δύο references δείχνουν στο ίδιο αντικείμενο ή σε δύο διαφορετικά αντικείμενα. Αν τα δύο object references είναι ίσα, δηλαδή η σύγκριση μεταξύ τους μας δίνει true, τότε και τα αντικείμενα στα οποία αναφέρονται είναι ίσα. Στην ουσία μιλάμε για το ίδιο αντικείμενο. Όταν χρησιμοποιούμε την equals( ) στην μορφή που μας δίνεται από την Java, δεν χρειάζεται να κάνουμε override την hashCode( ). Όμως πολλές φορές, μας ενδιαφέρει να βρούμε αν δύο αντικείμενα είναι ίσα με βάση κάποια συγκεκριμένα στοιχεία τους. Για παράδειγμα, δύο αντικείμενα είδος Employee θα είναι ίσα αν έχουν τον ίδιο αριθμό εργαζόμενου ή το ίδιο ΑΦΜ. Τότε στην ουσία μιλάμε για τον ίδιο εργαζόμενο. Αυτή η σύγκριση για να γίνει θα πρέπει να κάνουμε override την equals( ). Ταυτόχρονα όμως θα πρέπει να κάνουμε override και την hashCode( ) γιατί αν τα δύο αντικείμενα είναι ίσα, τότε θα πρέπει να επιστρέφουν το ίδιο hashCode.

Θα ξαναγυρίσουμε στην θεωρία του hashCode( ) και της equals( ) όταν τα κάνουμε override για να ικανοποιήσουμε την λογική του προγράμματος μας. Για τώρα, ας δούμε πως συμπεριφέρεται η equals( ) έτσι όπως την ορίζει η Java.

Το documentation της Java αναφέρει ότι η equals( ) συγκρίνει δύο αντικείμενα για ισότητα (Object (Java SE 17 & JDK 17) (oracle.com).




Employee.java

package com.example;

class Employee {
    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";
    }
}


EmployeeDemo.java

package com.example;

public class EmployeeDemo {
    public static void main(String[] args) {
        Employee e1 = new Employee();
        Employee e2 = new Employee();
        System.out.println("Is e1 equals to e2? " + e1.equals(e2));
        e2 = e1;
        System.out.println("Is e1 equals to e2? " + e1.equals(e2));
    }
}

Output

Is e1 equals to e2? false

Is e1 equals to e2? True

 

Την πρώτη φορά συγκρίνουμε δύο object references που αναφέρονται σε δύο διαφορετικά αντικείμενα αφού το κάθε ένα είναι αποθηκευμένο σε διαφορετική τοποθεσία στην μνήμη. Λογικό είναι λοιπόν το αποτέλεσμα να είναι false. Μετά αναθέτουμε την τιμή του e1 στο e2. Στην ουσία, κάνουμε το e2 reference να δείχνει στο αντικείμενο που δείχνει και το e1, δηλαδή στην ίδια διεύθυνση της μνήμης. Οπότε έχουμε δύο references που δείχνουν στο ίδιο αντικείμενο για αυτό και το αποτέλεσμα της σύγκρισης είναι true.

Όπως ήδη έχουμε αναφέρει, αυτή η προκαθορισμένη από την Java ενέργεια ενώ είναι χρήσιμη δεν είναι συγκεκριμένη. Θα επιθυμούσαμε στο συγκεκριμένο παράδειγμα να γνωρίζουμε αν δύο αντικείμενα είδος Employee είναι ίσα (δηλαδή αναφερόμαστε στο ίδιο φυσικό άτομο) αν το ΑΦΜ τους είναι ίδιο. Για να το πραγματοποιήσουμε αυτό, πρέπει να κάνουμε override την equals( ) αλλά και την hashCode( ).

Ξεκινώντας από την equals( ), θα πρέπει να αλλάξουμε τον κώδικα της Employee ως εξής:

Employee.java

package com.example;

public class Employee {
    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 boolean equals(Object x) {
        if (x == null)
            return false;

        Employee other = (Employee) x;
        if (this.AFM == other.AFM) {
            return true;
        } else {
            return false;
        }
    }
}


Ο ορισμός της equals( ), όπως μας δείχνει και το documentation, ορίζει ότι δέχεται ένα αντικείμενο είδος Object. Λογικό είναι αφού η Java δεν μπορεί να γνωρίζει το όνομα της κλάσης που θέλουμε εμείς να δημιουργήσουμε οπότε όλα τα αντικείμενα της τα έχει ορίσει σαν Object. Κρατάμε το signature της μεθόδου το ίδιο και μετά γράφουμε το δικό μας κώδικα μέσα στην μέθοδο. Αρχικά κάνουμε έναν έλεγχο για να δούμε αν πραγματικά μας έχει δοθεί ένα αντικείμενο μέσα στην παρένθεση για να το συγκρίνουμε. Αν δεν υπάρχει, τότε θα επιστρέψουμε την τιμή false. Εφόσον λοιπόν υπάρχει ένα αντικείμενο, η εκτέλεση προχωράει στο επόμενο μέρος του κώδικα. Το αντικείμενο που θα περάσουμε εμείς θα είναι είδος Employee, αλλά λόγω του ορισμού της equals( ) και του πολυμορφισμού περνάει μέσα στην μέθοδο σαν είδος Object. Θα χρειαστεί λοιπόν να κάνουμε casting στο reference x και να του δώσουμε μορφή είδος Employee. Τώρα μπορούμε να συγκρίνουμε το ΑΦΜ του δικού μας αντικειμένου (this.AFM) με το ΑΦΜ του αντικειμένου που περνάμε στην παρένθεση προς σύγκριση (other).

Αλλά χρειάζεται όμως να κάνουμε override και την hashCode( ). Ο ορισμός της hashCode( ) είναι ο εξής (Object (Java SE 17 & JDK 17) (oracle.com):



Για να κάνουμε override την hashCode( ) θα πρέπει να ακολουθήσουμε τον κανόνα που λέει ότι αν e1.equals(e2) είναι true τότε e1.hashCode( ) θα πρέπει να μας επιστρέφει την ίδια τιμή με την e2.hashCode( ). Ο πιο απλός τρόπος για να ικανοποιήσουμε αυτό τον κανόνα είναι να χρησιμοποιήσουμε στον κώδικα της hashCode( ), όταν την κάνουμε override, την ίδια μεταβλητή ή μεταβλητές που χρησιμοποιήσαμε στην equals( ). Στο κώδικα της equals( ) συγκρίναμε το AFM οπότε θα χρησιμοποιήσουμε την ίδια μεταβλητή και στο κώδικα της hashCode( ). Οπότε ο κώδικας της Employee αλλάζει ως εξής:

Employee.java

package com.example;

public class Employee {
    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 boolean equals(Object x) {
        if (x == null)
            return false;

        Employee other = (Employee) x;
        if (this.AFM == other.AFM) {
            return true;
        } else {
            return false;
        }
    }

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


Ας αλλάξουμε και τον κώδικα της EmployeeDemo και να δούμε τι αποτέλεσμα παίρνουμε από την σύγκριση δύο αντικειμένων για διαφορετικές αλλά και για ίδιες τιμές του AFM.

EmployeeDemo.java

package com.example;

public class EmployeeDemo {
    public static void main(String[] args) {
        Employee e1 = new Employee();
        e1.AFM = 1234567;
        Employee e2 = new Employee();
        e2.AFM = 7654321;
        System.out.println("Is e1 equals to e2? " + e1.equals(e2));
        e2.AFM = 1234567;
        System.out.println("Is e1 equals to e2? " + e1.equals(e2));
    }
}

Output

Is e1 equals to e2? false

Is e1 equals to e2? True

 

Υπάρχουν μερικές λανθασμένες απόψεις για το hash code ενός αντικειμένου στην Java. Πολλοί junior προγραμματιστές πιστεύουν ότι το hash code είναι μοναδικό για κάθε αντικείμενο και είναι πάντα θετικός αριθμός. Αυτή η άποψη όμως δεν είναι σωστή. Το hash code δεν χαρακτηρίζει την μοναδικότητα του αντικειμένου. Δύο διαφορετικά αντικείμενα μπορεί να έχουν τα ίδια hash codes. Επίσης, το hash code μπορεί να πάρει είτε θετική ή μηδέν ή αρνητική τιμή. Δεν χρειάζεται να πάμε βαθύτερα στην θεωρία των hash codes. Απλά μην ξεχνάτε ότι χρησιμοποιούνται αποκλειστικά για την γρήγορη ανάκτηση δεδομένων από ένα Collection που χρησιμοποιεί hash codes. Οπότε αν δεν θα χρησιμοποιήσετε τα αντικείμενα σαν κλειδιά σε ένα Collection που χρησιμοποιεί hash codes ή αν ποτέ δεν κάνετε override την equals( ) τότε δεν υπάρχει κανένας απολύτως λόγος να κάνετε override την hashCode().

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

full-width

Post a Comment

0 Comments