Melaksanakan hashCode
Tulis fungsi cincangan yang betul
Melaksanakan hashCode ialah pelajaran Java Academy percuma di CoddyKit. Ini ialah pelajaran 3 daripada 4. Anda boleh membaca keseluruhan pelajaran di bawah secara percuma — kemudian berlatih secara praktikal dalam pelayar menggunakan penyunting kod terbina dalam dan tutor kecerdasan buatan 24/7. Pelajaran ini merupakan sebahagian daripada laluan pembelajaran Java Academy, dan kemajuan anda disegerakkan merentas web serta aplikasi CoddyKit. Kursus Java Academy merangkumi sejumlah 4 pelajaran.
Matlamat hashCode yang Baik
hashCode() yang baik sepatutnya:
- Memulangkan nilai yang sama untuk objek yang sama (mengikut kontrak).
- Menyebarkan objek yang tidak sama kepada banyak nilai yang berbeza.
- Murah untuk dikira.
hashCode yang lemah dan sentiasa memulangkan pemalar masih mematuhi kontrak, tetapi menjadikan peta seperti senarai terpaut yang perlahan.
public class Main {
public static void main(String[] args) {
// Legal but terrible: every object collides
System.out.println("constant hashCode is legal but kills performance");
}
}Objects.hash untuk Kes Biasa
Pendekatan betul yang paling mudah ialah Objects.hash(field1, field2, ...).
Ia mengendalikan nilai null dan menggabungkan medan menggunakan algoritma piawai. Gunakan medan yang sama seperti yang Anda bandingkan dalam equals.
import java.util.Objects;
public class Main {
static class User {
final String name; final int age;
User(String name, int age) { this.name = name; this.age = age; }
@Override public int hashCode() { return Objects.hash(name, age); }
}
public static void main(String[] args) {
User a = new User("Ada", 36);
User b = new User("Ada", 36);
System.out.println(a.hashCode() == b.hashCode());
}
}Pendarab Klasik 31
Untuk cincangan yang ditulis dengan tangan, corak piawai mendarab hasil semasa dengan 31 dan menambah hash bagi setiap medan.
31 ialah nombor perdana ganjil, dan 31 * x sama dengan (x << 5) - x, jadi JVM boleh mengoptimumkannya.
public class Main {
static class User {
final String name; final int age;
User(String name, int age) { this.name = name; this.age = age; }
@Override public int hashCode() {
int result = 17;
result = 31 * result + (name == null ? 0 : name.hashCode());
result = 31 * result + age;
return result;
}
}
public static void main(String[] args) {
System.out.println(new User("Ada", 36).hashCode());
}
}Mencincang Jenis Primitif
Setiap jenis primitif mempunyai cara cincangan yang disyorkan:
int: gunakan nilainya sendiri.long:(int)(value ^ (value >>> 32)).boolean: 1 atau 0.double:Double.hashCode(value).
public class Main {
public static void main(String[] args) {
long id = 4_000_000_000L;
int longHash = (int) (id ^ (id >>> 32));
System.out.println("long hash: " + longHash);
System.out.println("double hash: " + Double.hashCode(3.14));
System.out.println("bool hash: " + Boolean.hashCode(true));
}
}Mencincang Tatasusunan
Jangan panggil hashCode() secara terus pada tatasusunan; ia menggunakan identiti, bukan kandungan.
Gunakan Arrays.hashCode(arr) untuk tatasusunan rata, atau Arrays.deepHashCode(arr) untuk tatasusunan bersarang.
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int[] a = {1, 2, 3};
int[] b = {1, 2, 3};
System.out.println("identity equal: " + (a.hashCode() == b.hashCode()));
System.out.println("content equal: " + (Arrays.hashCode(a) == Arrays.hashCode(b)));
}
}Selaraskan equals dan hashCode
Medan yang digunakan dalam hashCode() mestilah sebahagian daripada medan yang digunakan dalam equals() (sebaik-baiknya tepat sama).
Jika equals membandingkan lebih banyak medan daripada hashCode, dua objek yang sama masih berkongsi hash. Hal itu dibenarkan. Tetapi jika hashCode menggunakan medan yang diabaikan oleh equals, Anda melanggar kontrak.
import java.util.Objects;
public class Main {
static class Coord {
final int x, y;
Coord(int x, int y) { this.x = x; this.y = y; }
@Override public boolean equals(Object o) {
return o instanceof Coord c && c.x == x && c.y == y;
}
@Override public int hashCode() { return Objects.hash(x, y); }
}
public static void main(String[] args) {
Coord a = new Coord(3, 4), b = new Coord(3, 4);
System.out.println(a.equals(b) && a.hashCode() == b.hashCode());
}
}Menyimpan Hash dalam Cache
Untuk objek tidak berubah yang pencincangannya mahal, Anda boleh menyimpan hasilnya dalam cache di dalam suatu medan.
String melakukan perkara ini secara dalaman. Lakukan hanya apabila objek itu benar-benar tidak berubah supaya nilai cache tidak pernah menjadi lapuk.
import java.util.Objects;
public class Main {
static final class Key {
final String a, b;
private int hash; // 0 until computed
Key(String a, String b) { this.a = a; this.b = b; }
@Override public int hashCode() {
int h = hash;
if (h == 0) { h = Objects.hash(a, b); hash = h; }
return h;
}
}
public static void main(String[] args) {
Key k = new Key("x", "y");
System.out.println(k.hashCode());
System.out.println(k.hashCode());
}
}Taburan Itu Penting
hashCode yang mempunyai taburan baik menyebarkan kunci secara sekata merentas baket. Mari kita kira bilangan kod cincangan berbeza bagi sekumpulan objek.
Semakin banyak nilai yang berbeza, semakin sedikit perlanggaran dan semakin pantas peta.
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
public class Main {
record Pair(int a, int b) {}
public static void main(String[] args) {
Set<Integer> hashes = new HashSet<>();
for (int i = 0; i < 100; i++) {
hashes.add(Objects.hash(i, i * 7));
}
System.out.println("distinct hashes: " + hashes.size());
}
}Contoh Taburan yang Buruk
Menjumlahkan medan tanpa pendaraban menghasilkan perlanggaran: (1,2) dan (2,1) kedua-duanya dicincang kepada 3.
Pendarab 31 memecahkan kesimetrian ini kerana susunan kini penting.
public class Main {
static int badHash(int a, int b) { return a + b; }
static int goodHash(int a, int b) { return 31 * a + b; }
public static void main(String[] args) {
System.out.println("bad (1,2): " + badHash(1, 2) + ", (2,1): " + badHash(2, 1));
System.out.println("good (1,2): " + goodHash(1, 2) + ", (2,1): " + goodHash(2, 1));
}
}Utamakan Rekod untuk Jenis Nilai
Untuk pembawa data semata-mata, record menjana hashCode yang betul dan mempunyai taburan baik secara automatik.
Tulis hashCode dengan tangan hanya apabila Anda memerlukan semantik tersuai atau tidak boleh menggunakan rekod.
public class Main {
record Money(long cents, String currency) {}
public static void main(String[] args) {
Money a = new Money(1099, "USD");
Money b = new Money(1099, "USD");
System.out.println(a.equals(b));
System.out.println(a.hashCode() == b.hashCode());
}
}Menggabungkan Semuanya
Kelas nilai yang lengkap: medan tidak berubah, equals dan hashCode daripada medan yang sama, serta toString yang kemas.
import java.util.Objects;
public class Main {
static final class Version {
final int major, minor, patch;
Version(int major, int minor, int patch) {
this.major = major; this.minor = minor; this.patch = patch;
}
@Override public boolean equals(Object o) {
return o instanceof Version v && v.major == major && v.minor == minor && v.patch == patch;
}
@Override public int hashCode() { return Objects.hash(major, minor, patch); }
@Override public String toString() { return major + "." + minor + "." + patch; }
}
public static void main(String[] args) {
Version v = new Version(2, 1, 0);
System.out.println(v + " hash=" + v.hashCode());
}
}Semakan Pantas
Uji kemahiran hashCode Anda.
Rumusan
Anda telah mempelajari cara melaksanakan hashCode dengan betul:
- Gunakan
Objects.hash(...)untuk kes biasa. - Gunakan corak pendarab 31 untuk pencincangan yang ditulis dengan tangan.
- Cincang tatasusunan dengan
Arrays.hashCode, bukan lalai. - Selaraskan medan hashCode dengan equals dan utamakan rekod.
Seterusnya, lihat cara Java 8+ menukar baket yang terlebih muatan kepada pokok.
import java.util.Objects;
public class Main {
public static void main(String[] args) {
System.out.println("hashCode recap done: " + Objects.hash("done"));
}
}Pelajari Java dengan tutor kecerdasan buatan — percuma
Tulis dan jalankan kod sebenar dalam pelayar anda, dapatkan bantuan segera daripada tutor kecerdasan buatan yang tersedia 24/7, dan sambung semula dari tempat anda berhenti di web atau dalam aplikasi.
- Kursus
- 104
- Pelajaran
- 374
Soalan Lazim
Adakah pelajaran “Melaksanakan hashCode” percuma?
Ya — teks penuh “Melaksanakan hashCode” boleh dibaca secara percuma di web ini. Untuk berlatih secara interaktif menggunakan penyunting kod terbina dalam dan tutor kecerdasan buatan 24/7, serta membuka kunci baki kursus Java Academy, tingkat taraf kepada CoddyKit PRO. Kursus Java Academy merangkumi sejumlah 4 pelajaran.
Apakah yang akan saya pelajari dalam “Melaksanakan hashCode”?
Tulis fungsi cincangan yang betul Anda berlatih Java Academy menggunakan kod praktikal yang dijalankan terus dalam pelayar, manakala tutor kecerdasan buatan 24/7 menjawab soalan anda semasa anda mengikuti pelajaran.
Adakah saya memerlukan pengalaman untuk memulakan Java Academy?
Tiada pengalaman terdahulu diperlukan. Pembelajaran Java Academy di CoddyKit disusun untuk pelajar daripada peringkat pemula hingga lanjutan, jadi anda boleh bermula di sini atau dari awal dan belajar mengikut kadar anda sendiri. Ini ialah pelajaran 3 daripada 4.
Berapa lamakah pelajaran “Melaksanakan hashCode” diambil?
Kebanyakan pelajaran CoddyKit mengambil masa kira-kira 5–10 minit. Setiap pelajaran ringkas dan interaktif, jadi anda boleh membuat kemajuan secara berterusan dan menyambung tepat dari tempat anda berhenti di web atau aplikasi.
Bolehkah saya menulis dan menjalankan kod dalam pelajaran Java Academy ini?
Ya. Setiap pelajaran Java Academy menyertakan penyunting kod terbina dalam, jadi anda boleh menulis dan menjalankan kod sebenar terus dalam pelayar serta menerima maklum balas kecerdasan buatan serta-merta — tanpa memerlukan persediaan setempat.
Semua pelajaran dalam kursus ini
- Cara HashMap Berfungsi
- Kontrak equals/hashCode
- Melaksanakan hashCode
- Pohon dan Prestasi