1. 程式人生 > >Java 在 Map 中使用複雜資料型別作為 Key

Java 在 Map 中使用複雜資料型別作為 Key

HashMap是一種用雜湊值來儲存和查詢鍵值對(key-value pair,也稱作entry)的一種資料結構。

為了正確使用HashMap,選擇恰當的Key是非常重要的。Key在HashMap裡是不可重複的。

1、什麼是可變物件

可變物件是指建立後自身狀態能改變的物件。換句話說,可變物件是該物件在建立後它的雜湊值可能被改變。

在下面的程式碼中,物件MutableKey的鍵在建立時變數 i=10 j=20,雜湊值是1291。

然後我們改變例項的變數值,該物件的鍵 i 和 j 從10和20分別改變成30和40。現在Key的雜湊值已經變成1931。

顯然,這個物件的鍵在建立後發生了改變。所以類MutableKey是可變的。

讓我們看看下面的示例程式碼:

注意:呼叫hashCode()時,equals()方法也同時被執行。


public class MutableKey {
    private int i;
    private int j;
 
    public MutableKey(int i, int j) {
        this.i = i;
        this.j = j;
    }
 
    public final int getI() {
        return i;
    }
 
    public final void setI(int i) {
        this.i = i;
    }
 
    public final int getJ() {
        return j;
    }
 
    public final void setJ(int j) {
        this.j = j;
    }
 
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + i;
        result = prime * result + j;
        return result;
    }
 
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (!(obj instanceof MutableKey)) {
            return false;
        }
        MutableKey other = (MutableKey) obj;
        if (i != other.i) {
            return false;
        }
        if (j != other.j) {
            return false;
        }
        return true;
    }
}

public class MutableDemo {
 
    public static void main(String[] args) {
 
        // Object created
        MutableKey key = new MutableKey(10, 20);
        System.out.println("Hash code: " + key.hashCode());
 
        // Object State is changed after object creation.
        key.setI(30);
        key.setJ(40);
        System.out.println("Hash code: " + key.hashCode());
    }
}

2、HashMap如何儲存鍵值對

HashMap用Key的雜湊值來儲存和查詢鍵值對。

當插入一個Entry時,HashMap會計算Entry Key的雜湊值。Map會根據這個雜湊值把Entry插入到相應的位置。

查詢時,HashMap通過計算Key的雜湊值到特定位置查詢這個Entry。

3、在HashMap中使用可變物件作為Key帶來的問題

如果HashMap Key的雜湊值在儲存鍵值對後發生改變,Map可能再也查詢不到這個Entry了。

如果Key物件是可變的,那麼Key的雜湊值就可能改變。在HashMap中可變物件作為Key會造成資料丟失。

下面的例子將會向你展示HashMap中有可變物件作為Key帶來的問題

import java.util.HashMap;
import java.util.Map;
 
public class MutableDemo1 {
 
    public static void main(String[] args) {
 
        // HashMap
        Map<MutableKey, String> map = new HashMap<>();
 
        // Object created
        MutableKey key = new MutableKey(10, 20);
 
        // Insert entry.
        map.put(key, "Robin");
 
        // This line will print 'Robin'
        System.out.println(map.get(key));
 
        // Object State is changed after object creation.
        // i.e. Object hash code will be changed.
        key.setI(30);
 
        // This line will print null as Map would be unable to retrieve the
        // entry.
        System.out.println(map.get(key));
    }
}
Robinnull

4、如何解決

在HashMap中使用不可變物件。在HashMap中,使用String、Integer等不可變型別用作Key是非常明智的。

我們也能定義屬於自己的不可變類。

如果可變物件在HashMap中被用作鍵,那就要小心在改變物件狀態的時候,不要改變它的雜湊值了。

在下面的Employee示例類中,雜湊值是用例項變數id來計算的。一旦Employee的物件被建立,id的值就能再改變。只有name可以改變,但name不能用來計算雜湊值。所以,一旦Employee物件被建立,它的雜湊值不會改變。所以Employee在HashMap中用作Key是安全的。

import java.util.HashMap;
import java.util.Map;
 
public class MutableSafeKeyDemo {
 
    public static void main(String[] args) {
        Employee emp = new Employee(2);
        emp.setName("Robin");
 
        // Put object in HashMap.
        Map<Employee, String> map = new HashMap<>();
        map.put(emp, "Showbasky");
 
        System.out.println(map.get(emp));
 
        // Change Employee name. Change in 'name' has no effect
        // on hash code.
        emp.setName("Lily");
        System.out.println(map.get(emp));
    }
}
 
class Employee {
    // It is specified while object creation.
    // Cannot be changed once object is created. No setter for this field.
    private int id;
    private String name;
 
    public Employee(final int id) {
        this.id = id;
    }
 
    public final String getName() {
        return name;
    }
 
    public final void setName(final String name) {
        this.name = name;
    }
 
    public int getId() {
        return id;
    }
 
    // Hash code depends only on 'id' which cannot be
    // changed once object is created. So hash code will not change
    // on object's state change
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + id;
        return result;
    }
 
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Employee other = (Employee) obj;
        if (id != other.id)
            return false;
        return true;
    }
}
12ShowbaskyShowbasky


總結:眾所周知,map的key以不變的型別,比如String,Integer,作為key是最明智的,當然如果要以可變物件作為key的話,那就必須要重寫hashcode和equals方法來達到這個目的,除此之外,別無他法。