ΕΙΣΑΓΩΓΗ
Στο σημερινό δωρεάν μάθημα 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
EmployeeDemo.java
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
Ο ορισμός της 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
Ας αλλάξουμε και τον κώδικα της
EmployeeDemo και να δούμε τι αποτέλεσμα παίρνουμε από την σύγκριση δύο αντικειμένων
για διαφορετικές αλλά και για ίδιες τιμές του AFM.
EmployeeDemo.java
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
0 Comments
Η γνώμη σας είναι σημαντική.