java:jfc_intf_sort

Ορίζοντας την σειρά καταχώρησης και ανάκτησης των δεδομένων

Κατά κανόνα όταν θέλουμε να συγκρίνουμε επιμέρους αντικείμενα προκειμένου να ορίσουμε την σειρά αποθήκευσης και ανάκτησης τους σε μία δομή δεδομένων αυτό θα πρέπει να ακολουθεί κάποιους κανόνες. Για παράδειγμα, εάν συγκρίνουμε δύο String, αυτά θα συγκριθούν με βάση συγκεκριμένα κριτήρια λεξικού. Αντίστοιχα, αν συγκρίνουμε δύο ημερομηνίες αυτές θα συγκριθούν με βάση τις ημερομηνίες που αντιπροσωπεύουν. Γενικότερα στην java ορίζεται το interface Comparable το οποίο ορίζει τα κριτήρια σύγκρισης μεταξύ δύο αντικειμένων του ιδίου τύπου. Οι κλάσεις της standard βιβλιοθήκης String, Date, Integer κ.α. υλοποιούν το συγκεκριμένο interface ορίζοντας τους κανόνες σύγκρισης. Παρακάτω δίνεται μία σειρά από δημοφιλείς κλάσεις της java και τα κριτήρια υλοποίησης του συγκεκριμένου interface για κάθε μία από αυτές.

Υλοποιώντας το interface Comparable για δικούς μας τύπους δεδομένων

Παρακάτω δίνεται η υλοποίηση του interface Comparable για την κλάση Name.

Name.java
import java.util.*;
 
public class Name implements Comparable<Name> {
    private final String firstName, lastName;
 
    public Name(String firstName, String lastName) {
        if (firstName == null || lastName == null)
            throw new NullPointerException();
        this.firstName = firstName;
        this.lastName = lastName;
    }
 
    public String firstName() { return firstName; }
    public String lastName()  { return lastName;  }
 
    public boolean equals(Object o) {
        if (!(o instanceof Name))
            return false;
        Name n = (Name) o;
        return n.firstName.equals(firstName) && n.lastName.equals(lastName);
    }
 
    public int hashCode() {
        return 31*firstName.hashCode() + lastName.hashCode();
    }
 
    public String toString() {
	return firstName + " " + lastName;
    }
 
    public int compareTo(Name n) {
        int lastCmp = lastName.compareTo(n.lastName);
        return (lastCmp != 0 ? lastCmp : firstName.compareTo(n.firstName));
    }
}

Τα βασικά χαρακτηριστικά της παραπάνω κλάσης είναι τα εξής:

  • Έλεγχος στον κατασκευαστή εάν περνάνε ή όχι null ορίσματα. Αυτό μας προστατεύει από το να έρθουμε αντιμέτωποι με NullPointerException στην συνέχεια.
  • Η μέθοδος equals() επαναπροσδιορίζεται, έτσι ώστε να συγκρίνονται τα πεδία της κλάσης Name μεταξύ τους.
  • Η μέθοδος hashCode() επαναπροσδιορίζεται. Ο λόγος που πρέπει να επαναπροσδιορίσουμε την μέθοδο αυτή είναι ότι εξ' ορισμού δύο ίδια αντικείμενα θα πρέπει να έχουν και ίδια hashCodes. Εφόσον άλλαξε η μέθοδος equal() θα αλλάξει και η μέθοδος hashCode() ώστε να επιστρέφει το ίδιο hash για αντικείμενα που η equals επιστρέφει ισότητα.
  • Υλοποιείται η μέθοδος int comparTo(T t).

Εξ' ορισμού (εάν δεν την επαναπροσδιορίσετε) η μέθοδος equals, συγκρίνει δύο αντικείμενα με βάση την διεύθυνση του κάθε αντικειμένου. Στην πραγματικότητα επιστρέφει ισότητα μόνο αν τα references των δύο αντικειμένων είναι ίδια, δηλ. και οι δύο μεταβλητές δείχνουν στο ίδιο αντικείμενο. Στην πλειοψηφία των περιπτώσεων ο συγκεκριμένος τρόπος σύκρισης δεν μας δίνει στην πράξη ικανοποιητικό αποτελέσμα, καθώς αγνοεί αντικείμενα με τα ίδια περιεχόμενα.

Σε αναλογία η μέθοδος hashCode επιστρέφει την δεκαεξαδική τιμή που δείχνει η μεταβλητή του αντικειμένου στην μνήμη. Με δεδομένο ότι για δύο ίδια αντικείμενα τα hashCodes τους θα πρέπει να είναι κοινά, η default υλοποίηση ταιριάζει με την default υλοποίηση της μεθόδου equals. Αν όμως αλλάξει η υλοποίηση της equals σε μία κλάση θα πρέπει να αλλάξει και η hashCode() ώστε δύο ίδια αντικείμενα κατά την μέθοδο equals να έχουν και ίδια hashCodes.

NameSort.java
import java.util.*;
 
public class NameSort {
    public static void main(String[] args) {
        Name nameArray[] = {
            new Name("John", "Smith"),
            new Name("Karl", "Ng"),
            new Name("Jeff", "Smith"),
            new Name("Tom", "Rich")
        };
 
        List<Name> names = Arrays.asList(nameArray);
        Collections.sort(names);
        System.out.println(names);
    }
}

Συγκρίνοντας με χρήσης ενός Comparator object

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

NameComparatorSort.java
import java.util.*;
public class NameComparatorSort {
    static final Comparator<Name> NameOrdering = 
                                        new Comparator<Name>() {
            public int compare(Name n1, Name n2) {
                int cmp = n1.firstName().compareTo(n2.firstName() );
                return (cmp != 0 ? cmp : n1.lastName().compareTo(n2.lastName() ));
            }
    };
 
    public static void main(String[] args) {
        Name nameArray[] = {
            new Name("John", "Smith"),
            new Name("Karl", "Ng"),
            new Name("Jeff", "Smith"),
            new Name("Tom", "Rich")
        };
        Collections.sort(Arrays.asList(nameArray), NameOrdering);
        for(Name n: nameArray)
        System.out.println(n);
    }
}

Ο συγκεκριμένος Comparator μας δίνει την δυνατότητα να αλλάξουμε την μέθοδο με την οποία συγκρίνονται τα αντικείμενα του συγκεκριμένου τύπου δεδομένων δίνοντας προτεραιότητα στο μικρό όνομα αντί για το επίθετο.

java/jfc_intf_sort.txt · Last modified: 2017/05/12 12:18 by gthanos