ITEM 11: Overriding hashCode

equals๋ฅผ ์žฌ์ •์˜ํ•œ ํด๋ž˜์Šค๋Š” hashCode๋„ ์žฌ์ •์˜ํ•ด์•ผ ํ•œ๋‹ค. ๋งŒ์•ฝ hashCode๋ฅผ ์žฌ์ •์˜ํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด, ์ผ๋ฐ˜ ๊ทœ์•ฝ์„ ์–ด๊ธฐ๊ฒŒ ๋˜์–ด HashSet์ด๋‚˜ HashMap๊ณผ ๊ฐ™์€ ์ปฌ๋ ‰์…˜์˜ ์›์†Œ๋กœ ์‚ฌ์šฉ์‹œ ๋ฌธ์ œ๋ฅผ ์ผ์œผํ‚ฌ ์ˆ˜ ์žˆ๋‹ค.

  • equals ๋น„๊ต์— ์‚ฌ์šฉ๋˜๋Š” ์ •๋ณด๊ฐ€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜๋‹ค๋ฉด, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์‹คํ–‰๋˜๋Š” ๋™์•ˆ ๊ทธ ๊ฐ์ฒด์˜ hashCode ๋ฉ”์„œ๋“œ๋Š” ๋ช‡๋ฒˆ์„ ํ˜ธ์ถœํ•ด๋„ ์ผ๊ด€๋˜๊ฒŒ ํ•ญ์ƒ ๊ฐ™์€ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•ด์•ผํ•œ๋‹ค.

  • equals(Object)๊ฐ€ ๋‘ ๊ฐ์ฒด๋ฅผ ๊ฐ™๋‹ค๊ณ  ํŒ๋‹จํ–ˆ๋‹ค๋ฉด, ๋‘ ๊ฐ์ฒด์˜ hashCode๋Š” ๋˜‘๊ฐ™์€ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•ด์•ผํ•œ๋‹ค.

  • equals(Object)๊ฐ€ ๋‘ ๊ฐ์ฒด๋ฅผ ๋‹ค๋ฅด๋‹ค๊ณ  ํŒ๋‹จํ–ˆ๋”๋ผ๋„, ๋‘ ๊ฐ์ฒด์˜ hashCode๊ฐ€ ์„œ๋กœ ๋‹ค๋ฅธ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•  ํ•„์š”๋Š” ์—†์œผ๋ฉฐ, ํ•˜์ง€๋งŒ ๋‹ค๋ฅธ ๊ฐ์ฒด์— ๋Œ€ํ•ด์„œ๋Š” ๋‹ค๋ฅธ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•ด์‹œํ…Œ์ด๋ธ”์˜ ์„ฑ๋Šฅ์ด ์ข‹์•„์ง„๋‹ค. ๋งŒ์•ฝ ๋ชจ๋“  ๊ฐ์ฒด์˜ hashCode๊ฐ€ ๋™์ผํ•œ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค๋ฉด, ํ‰๊ท  ์ˆ˜ํ–‰์‹œ๊ฐ„์ด O(1)์—์„œ O(n)์œผ๋กœ ๋Š๋ ค์ ธ, ๊ฐ์ฒด๊ฐ€ ๋งŽ์•„์ง€๋ฉด ์“ธ ์ˆ˜ ์—†๊ฒŒ ๋œ๋‹ค.

๋…ผ๋ฆฌ์ ์œผ๋กœ ๊ฐ™์€ ๊ฐ์ฒด๋Š” ๊ฐ™์€ ํ•ด์‰ฌ์ฝ”๋“œ๋ฅผ ๋ฐ˜ํ™˜ํ•ด์•ผํ•˜๋ฉฐ, hashCode ์žฌ์ •์˜๋ฅผ ์ž˜๋ชปํ–ˆ์„ ๋•Œ ๊ฐ€์žฅ ํฌ๊ฒŒ ๋ฌธ์ œ๊ฐ€ ๋˜๋Š” ๋ถ€๋ถ„์ด๋‹ค.

public final class PhoneNumber {
    private final short areaCode, prefix, lineNum;

    public PhoneNumber(short areaCode, short prefix, short lineNum){
        this.areaCode = rangeCheck(areaCode, 999, "์ง€์—ญ์ฝ”๋“œ");
        this.prefix = rangeCheck(prefix, 999,"ํ”„๋ฆฌํ”ฝ์Šค");
        this.lineNum = rangeCheck(lineNum, 9999, "๊ฐ€์ž…์ž๋ฒˆํ˜ธ");
    }
    private static short rangeCheck(int val, int max, String arg){
        if(val < 0 || val > max){
            throw new IllegalArgumentException(arg+" : "+val);
        }
        return  (short) val;
    }

    @Override
    public boolean equals(Object o){
        if(o == this)
            return true;
        if(!(o instanceof PhoneNumber)){
            return false;
        }

        PhoneNumber pn = (PhoneNumber) o;
        return pn.lineNum == lineNum && pn.prefix == prefix && pn.areaCode == areaCode;
    }

}
Map<PhoneNumber, String> map = new HashMap<>();
map.put(new PhoneNumber((short) 707,(short) 867,(short) 5309), "์ œ๋‹ˆ");
System.out.println(map.get(new PhoneNumber((short) 707,(short) 867,(short) 5309))); // null

์—ฌ๊ธฐ์„œ PhoneNumber ํด๋ž˜์Šค๋Š” hashCode๋ฅผ ์žฌ์ •์˜ํ•˜์ง€ ์•Š์•„, ๋…ผ๋ฆฌ์  ๋™์น˜์ธ ๋‘ ๊ฐ์ฒด๊ฐ€ ์„œ๋กœ ๋‹ค๋ฅธ ํ•ด์‹œ์ฝ”๋“œ๋ฅผ ๋ฐ˜ํ™˜ํ•˜์—ฌ ๋‘ ๋ฒˆ์งธ ๊ทœ์•ฝ์„ ์ง€ํ‚ค์ง€ ๋ชปํ•˜๊ฒŒ๋œ๋‹ค.

    @Override
    public int hashCode(){
        // int ๋ณ€์ˆ˜ result ์ดˆ๊ธฐํ™” ํ›„ ํ•ด๋‹น ํ•„๋“œ์˜ ํ•ด์‹œ์ฝ”๋“œ ๊ณ„์‚ฐ
        // ๊ธฐ๋ณธํƒ€์ž… ํ•„๋“œ๋ผ๋ฉด Type.hashCode() ์—ฌ๊ธฐ์„œ๋Š” Short.hashCode();
        int result = Short.hashCode(areaCode);
        // ์•ž์—์„œ ๊ณ„์‚ฐํ•œ ํ•ด์‹œ์ฝ”๋“œ๋กœ result ๊ฐฑ์‹ 
        // ์—ฌ๊ธฐ์„œ 31์€ ํ™€์ˆ˜์ด๋ฉด์„œ ์†Œ์ˆ˜์ด๊ธฐ ๋•Œ๋ฌธ์— ์‚ฌ์šฉ
        // ์ง์ˆ˜๋Š” 2๋ฅผ ๊ณฑํ•˜๋ฉด ์‹œํ”„ํŠธ ์—ฐ์‚ฐ๊ณผ ๊ฐ™์€ ๊ฒฐ๊ณผ๋ฅผ ๋ฐœ์ƒ์‹œ์ผœ ์˜ค๋ฒ„ํ”Œ๋กœ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์ •๋ณด๋ฅผ ์žƒ๊ฒŒ ๋จ.
        result = 31 * result + Short.hashCode(prefix);
        result = 31 * result + Short.hashCode(lineNum);
        // result ๋ฐ˜ํ™˜
        return result;
    }

์œ„์˜ hashCode๋Š” ์ข‹์€ hashCode๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ ์š”๋ น์ด๋‹ค. ์—ฌ๊ธฐ์„œ๋Š” ๊ธฐ๋ณธ ํƒ€์ž… ํ•„๋“œ์ด๊ธฐ ๋•Œ๋ฌธ์— Type.hashCode(f)๋ฅผ ์ˆ˜ํ–‰ํ–ˆ์ง€๋งŒ, ๋งŒ์•ฝ ์ฐธ์กฐ ํƒ€์ž… ํ•„๋“œ์ด๋ฉด์„œ, ์ด ํด๋ž˜์„œ์˜ equals ๋ฉ”์„œ๋“œ๊ฐ€ ํ•ด๋‹นํ•„๋“œ์˜ equals๋ฅผ ์žฌ๊ท€์ ์œผ๋กœ ํ˜ธ์ถœํ•ด ๋น„๊ตํ•œ๋‹ค๋ฉด, ํ•ด๋‹น ํ•„๋“œ์˜ hashCode๋ฅผ ์žฌ๊ท€์ ์œผ๋กœ ํ˜ธ์ถœํ•˜๋ฉด๋œ๋‹ค. ๋งŒ์•ฝ ํ•„๋“œ์˜ ๊ฐ’์ด null์ธ ๊ฒฝ์šฐ 0์„ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.

ํ•„๋“œ๊ฐ€ ๋ฐฐ์—ด์ด๋ผ๋ฉด, ํ•ต์‹ฌ ์›์†Œ ๊ฐ๊ฐ์„ ๋ณ„๋„์˜ ํ•„๋“œ์ฒ˜๋Ÿผ ๋‹ค๋ฃจ๊ณ , ๋ฐฐ์—ด์— ํ•ต์‹ฌ ์›์†Œ๊ฐ€ ์—†๋‹ค๋ฉด ๋‹จ์ˆœํžˆ ์ƒ์ˆ˜(0)๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. ๋งŒ์•ฝ ๋ชจ๋“  ์›์†Œ๊ฐ€ ํ•ต์‹ฌ ์›์†Œ๋ผ๋ฉด Arrays.hashCode๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.

Map<PhoneNumber, String> map = new HashMap<>();
map.put(new PhoneNumber((short) 707,(short) 867,(short) 5309), "์ œ๋‹ˆ");
System.out.println(map.get(new PhoneNumber((short) 707,(short) 867,(short) 5309))); // "์ œ๋‹ˆ"

์œ„์™€ ๊ฐ™์ด hashCode๋ฅผ ์ž‘์„ฑํ›„ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ˆ˜ํ–‰ํ•˜๋ฉด ๋™์น˜์ธ ์ธ์Šคํ„ด์Šค์— ๋Œ€ํ•ด ๋˜‘๊ฐ™์€ ํ•ด์‹œ์ฝ”๋“œ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค. ํŒŒ์ƒํ•„๋“œ(๋‹ค๋ฅธ ํ•„๋“œ๋กœ๋ถ€ํ„ฐ ๊ณ„์‚ฐํ•ด ๋‚ผ ์ˆ˜ ์žˆ๋Š” ํ•„๋“œ)๋Š” ๋ชจ๋‘ ๋ฌด์‹œํ•ด๋„ ๋˜๋ฉฐ, equals ๋น„๊ต์— ์‚ฌ์šฉ๋˜์ง€ ์•Š์€ ํ•„๋“œ๋Š” ๋ฐ˜๋“œ์‹œ ์ œ์™ธํ•ด์•ผ ํ•œ๋‹ค.

๋งŒ์•ฝ ํ•ด์‹œ ์ถฉ๋Œ์ด ๋”์šฑ ์ ์€ ๋ฐฉ๋ฒ•์„ ๊ผญ ์จ์•ผํ•œ๋‹ค๋ฉด, ๊ตฌ์•„๋ฐ”์˜ com.google.common.hash.Hashing์„ ์ฐธ๊ณ ํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

ํด๋ž˜์Šค๊ฐ€ ๋ถˆ๋ณ€์ด๊ณ  ํ•ด์‹œ์ฝ”๋“œ๋ฅผ ๊ณ„์‚ฐํ•˜๋Š” ๋น„์šฉ์ด ํฌ๋‹ค๋ฉด, ๋งค๋ฒˆ ์ƒˆ๋กœ ๊ณ„์‚ฐํ•˜๊ธฐ ๋ณด๋‹ค๋Š” ์บ์‹ฑํ•˜๋Š” ๋ฐฉ์‹์„ ๊ณ ๋ คํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

private int hashCode;

@Override
public int hashCode() {
    int result = hashCode;

    if(result == 0) {
        result = Short.hashCode(areaCode);
        result = 31 * result + Short.hashCode(prefix);
        result = 31 * result + Short.hashCode(lineNum);
        hashCode = result;
    }
    return result;
}

ํ•ด์‹œ์˜ ํ‚ค๋กœ ์‚ฌ์šฉ๋˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ๋ผ๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด hashCode๊ฐ€ ์ฒ˜์Œ ํ˜ธ์ถœ๋ ๋•Œ ๊ณ„์‚ฐํ•˜๋Š” ์ง€์—ฐ ์ดˆ๊ธฐํ™” ์ „๋žต๋„ ์žˆ๋‹ค. ์ด๋•Œ ์Šค๋ ˆ๋“œ ์•ˆ์ •์„ฑ๊นŒ์ง€ ๊ณ ๋ คํ•ด์•ผํ•˜๋ฉฐ, ์„ฑ๋Šฅ์„ ๋†’์ด๊ณ ์ž ํ•ด์‹œ์ฝ”๋“œ ๊ณ„์‚ฐ์‹œ ํ•ต์‹ฌ ํ•„๋“œ๋ฅผ ์ƒ๋žตํ•ด์„œ๋Š” ์•ˆ๋œ๋‹ค.

hashCode๊ฐ€ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฐ’์˜ ์ƒ์„ฑ ๊ทœ์น™์„ API ์‚ฌ์šฉ์ž์—๊ฒŒ ์ž์„ธํžˆ ๊ณตํ‘œํ•˜์ง€ ์•Š์•„์•ผ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ด ๊ฐ’์— ์˜์ง€ํ•˜์ง€ ์•Š๊ฒŒ๋˜๊ณ , ์ถ”ํ›„์— ๊ณ„์‚ฐ ๋ฐฉ์‹์„ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ๋‹ค.

Last updated