nadpisanie metody `hashCode()`

0

Jestem nadal prawie kompletnie zielony, tzn. rozpocząłem naukę Javy dosłownie kilka tygodni temu.

Mam problem odnośnie nadpisania metody hashCode().

Dla porównania szybkości przetwarzania różnych kolekcji chcę stworzyć kilka milionów obiektów klasy Book, które będą miały tylko dwa pola: String author i String title.

Pola te będą wyglądały następująco:
Author-0, Title-0
Author-1, Title-1
Author-2, Title-2
Author-3, Title-3
itd.

Pełny kod:

import java.util.*;
import java.lang.*;
import java.io.*;

class Book {

    private String title;
    private String author;

    public Book(String title, String author) {
        this.title = title;
        this.author = author;
    }

    public String getTitle() {
        return this.title;
    }

    public String getAuthor() {
        return this.author;
    }

    @Override
    public boolean equals(Object o){
        final Book b = (Book) o;
        return (title.equals(b.getTitle())) && (author.equals(b.getAuthor()));
    }

/* TO JEST ŹLE  

    @Override
    public int hashCode(){
        Random randomGenerator = new Random();
        return randomGenerator.nextInt(50);
    }
*/

    @Override
    public String toString(){
        return title + ", " + author;
    }

}

class Application {

    public static void main (String[] args) throws java.lang.Exception {

        LinkedList<Book> bookList = new LinkedList<Book>();
        HashMap<String, Book> bookMap = new HashMap<String, Book>();

        String author = null;
        String title = null;
        String key = null;

        for(int i = 0; i<1000000; i++) {

            author = "Author-" + Integer.toString(i);
            title = "Title-" + Integer.toString(i);
            key = "Key-" + Integer.toString(i);

            bookList.add(new Book(author, title));
            bookMap.put(key, new Book(author, title));

        }

        System.out.println("Liczba elementów w kolekcji LinkedList: " + bookList.size());
        System.out.println();

        long beginLL = System.nanoTime();
        bookList.add(new Book("New last author", "New last title"));
        long endLL = System.nanoTime();
        System.out.println("Dodanie elementu na końcu kolekcji LinkedList zajęło " + (endLL - beginLL) + " nanosekund");

        long beginLL1 = System.nanoTime();
        bookList.add(0, new Book("New first author", "New first title"));
        long endLL1 = System.nanoTime();
        System.out.println("Dodanie elementu na początku kolekcji LinkedList zajęło " + (endLL1 - beginLL1) + " nanosekund");

        System.out.println();
        System.out.println("Aktualny pierwszy element w kolekcji LinkedList to: " + bookList.get(0));
        System.out.println("Aktualny ostatni element w kolekcji LinkedList to: " + bookList.get(bookList.size()-1));
        System.out.println();

        long beginLL2 = System.nanoTime();
        bookList.remove(bookList.size()-1);
        long endLL2 = System.nanoTime();
        System.out.println("Usunięcie elementu z końca kolekcji LinkedList zajęło " + (endLL2 - beginLL2) + " nanosekund");

        long beginLL3 = System.nanoTime();
        bookList.remove(0);
        long endLL3 = System.nanoTime();
        System.out.println("Usunięcie elementu z początku kolekcji LinkedList zajęło " + (endLL3 - beginLL3) + " nanosekund");

        System.out.println();
        System.out.println("Aktualny pierwszy element w kolekcji LinkedList to: " + bookList.get(0));
        System.out.println("Aktualny ostatni element w kolekcji LinkedList to: " + bookList.get(bookList.size()-1));
        System.out.println();

    }

}

I pytanie - jak nadpisać metodę hashCode(), żeby mi te kilka milionów obiektów podzieliła na w miarę równoliczne podzbiory.

Nie potrafię sobie wyobrazić takiego kodu. Tzn. chodzi mi po głowie, żeby w hashCode() usunąć z tych Stringów wszystkie powtarzające się elementy (Author- i Title-), pozostałe części zamienić z powrotem na liczby i jakoś to podzielić.

Proszę o podpowiedź.

Przy czym po lekturze wpisu @jarekr000000 hashCode() - explain the magic zastanawiam się, czy w tym moim konkretnym przypadku metoda hashCode() w ogóle będzie miała sens (bo wszystkie te wpisy są przecież prawie identyczne, praktycznie wyglądają tak, jak jakakolwiek lista kolejnych elementów typu int). Ale nadpisać tę metodę muszę tak czy siak.

1

Te pisanie samemu funkcji hashującej to tak dla wartości edukacyjnych? Jeśli nie to popatrz na to - Objects.hash

0

Taki hint poza tematem. Mierzenie czasu dodania jednego elementu do kolekcji nie ma żadnego sensu. W uproszczeniu - jeśli twój pomiar nie trwa kilka sekund to nie ma nawet o jakimkolwiek sensie tych pomiarów mówić.

0

Bo mniej więcej tak zrozumiałem ideę metody hashCode, gdy nadpisuję ją do wykorzystania w kolekcji HashMap. Więc pewnie źle to rozumiem.

CountZero napisał(a):

A po co chcesz by te zbiory były równoliczne? Im mniej jest kolizji w funkcji haszującej tym lepiej.

Widziałem już ten wpis w dokumentacji, nie wiem tylko, czy w moim przypadku będzie mieć sens (i prawidłowo działać).
U mnie wyglądałoby to jakoś tak:

@Override public int hashCode() {
     return Book.hash(author, title);
 }

I te pisanie samemu funkcji hashującej to tak dla wartości edukacyjnych? Jeśli nie to popatrz na to - Objects.hash

0

Takie mam zadanie na kursie :)

jarekr000000 napisał(a):

Taki hint poza tematem. Mierzenie czasu dodania jednego elementu do kolekcji nie ma żadnego sensu. W uprosszczenu - jeśli twój pomiat nie trwa kilka sekund to nie ma nawet o jakimkolwiek sensie tych pomiarów mówić.

1
rdx777 napisał(a):

Takie mam zadanie na kursie :)

No to robisz to zadanie źle. Wskazówka: dodaj milion razy i zmierz ile to trwało. 6 razy. Wyciągnij średnią. Albo użyj JMH.

A teraz najciekawsze:
W twoim kodzie NIGDZIE nie jest hashCode (czy equals) używany.
Równie dobrze możesz wpisać sobie:

public int hashCode(){
        throw new UnsupportedOperationException("nie bede robił hashCode jak nikt go nie wywołuje");
}
0

@rdx777:

Czemu by miało nie działać? Chodzi ci o to, że to nie będzie wystarczająco efektywne w twoim wypadku? Pokaż może te zadanie bo jestem ciekaw.

1

W klasie Book Twój hashCode to po prostu suma "haszkodów" każdego pola, więc zsumuj wywołania metody hasCode dla obu stringów.

0
cs napisał(a):

W klasie Book Twój hashCode to po prostu suma "haszkodów" każdego pola, więc zsumuj wywołania metody hasCode dla obu stringów.

Dzięki, tak zrobię. Choć żeby zrozumieć kwestię całego kontraktu equals()' - hashCode()` pewnie będę potrzebować trochę czasu.

0
CountZero napisał(a):

@rdx777:

Czemu by miało nie działać? Chodzi ci o to, że to nie będzie wystarczająco efektywne w twoim wypadku? Pokaż może te zadanie bo jestem ciekaw.

To treść zadania, Przy czym po konsultacji (bo nadpisałem metodę hashCode() zgodnie z sugestią @jarekr000000) kluczem w HashMapie ma być obiekt klasy Book, a wartością - dowolny String.

Zadanie składa się z dwóch części. Jedna dotyczy LinkedList, a druga HashMap. Oba programy zapisz jako jeden projekt.

Część 1

Stwórz klasę reprezentującą książkę o nazwie Book. Klasa powinna mieć dwa pola: author oraz title. Pamiętaj o implementacji metod hashCode() oraz equals(Object o). Będziemy jej używali jako obiektów kolekcji LinkedList w tej części zadania, oraz jako obiektów kolekcji HashMap w drugiej części zadania.

Stwórz program, który zmierzy czas operacji wyszukiwania i usunięcia obiektu na początku (z użyciem metody remove(Object o)), wyszukiwania i usunięcia obiektu na końcu (z użyciem metody remove(Object o)), wstawiania na początku oraz wstawiania na końcu listy LinkedList liczącej kilka milionów obiektów.

Część 2

Stwórz program, który zmierzy czas operacji wyszukiwania po kluczu, a także czas dodawania i usuwania obiektu z mapy HashMap liczącej kilka milionów obiektów. Postaraj się, aby kluczem w mapie nie była wartość liczbowa.

1

W skrócie kontrakt equals wymaga tylko, aby dwa identyczne obiekty miały identyczny hashCode. Z tej równości nie wynika, że dwa różne obiekty musza mieć różne hashCode. Więc hashCode służy tylko do jednoznacznego testowania nierówności obiektów, a jeśli dwa obiekty mają identyczny skrót, wtedy trzeba przetestować równość każdej pary pól.
Twój equals powinien najpierw testować, czy oba obiekty mają różne skróty. Jeśli mają różne skróty to znaczy, że na pewno są różne. Jeśli mają równe skróty to znaczy, że mogą być równe i po stwierdzeniu równości wszystkich par pól możesz stwierdzić, że są równe.
Przykład:
Metoda hashCode tworzy skrót równy długości łańcucha. Obiekty "ALA" i "OLA" mają identyczne skróty, więc po sprawdzeniu każdej kolejnej pary znaków możesz stwierdzić, że nie są równe. Kolejna para "ALA" i "ADAM", mają różne skróty, więc na pewno nie są równe.

0

Dziękuję za wszystkie odpowiedzi :)

0
rdx777 napisał(a):

Bo mniej więcej tak zrozumiałem ideę metody hashCode, gdy nadpisuję ją do wykorzystania w kolekcji HashMap. Więc pewnie źle to rozumiem.

Przychodzi kiedyś pora na dobre "drugie" książki o Javie (tzn nie dla początkujących, żadne Hello World), Bruca Eckela "Thinking in Java" (nie jakie wydanie jest na rynku), Joshua Bloch kilka książek. Są inne, ale nie miałem w łapach

1 użytkowników online, w tym zalogowanych: 0, gości: 1