Java/๐Ÿ“• Effective Java

[Effective Java] ๋ชจ๋“  ๊ฐ์ฒด์˜ ๊ณตํ†ต ๋ฉ”์„œ๋“œ ไธŠ

soogoori 2024. 9. 21. 21:35

10. equals๋Š” ์ผ๋ฐ˜ ๊ทœ์•ฝ์„ ์ง€์ผœ ์žฌ์ •์˜

equals๋ฅผ ์žฌ์ •์˜ํ•˜๋Š” ๊ฒฝ์šฐ๋Š” ...
๐Ÿ‘‰ ๋‘ ๊ฐ์ฒด๊ฐ€ ๋ฌผ๋ฆฌ์ ์œผ๋กœ ๊ฐ™์€์ง€(๊ฐ์ฒด ์‹๋ณ„์„ฑ)๊ฐ€ ์•„๋‹ˆ๋ผ ๋…ผ๋ฆฌ์  ๋™์น˜์„ฑ์„ ํ™•์ธํ•ด์•ผ ํ•˜๋Š”๋ฐ, ์ƒ์œ„ ํด๋ž˜์Šค์˜ equals๊ฐ€ ๋…ผ๋ฆฌ์  ๋™์น˜์„ฑ์„ ๋น„๊ตํ•˜๋„๋ก ์žฌ์ •์˜๋˜์ง€ ์•Š์„ ๋•Œ 

 

โœณ๏ธ ์ผ๋ฐ˜ ๊ทœ์•ฝ 

  • ๋Œ€์นญ์„ฑ : x.equals(y)๊ฐ€ true์ด๋ฉด, y.equals(x)๋„ true
  • ์ถ”์ด์„ฑ : x.equals(y)๊ฐ€ true์ด๊ณ , y.equals(z)๋„ true์ด๋ฉด, x.equals(z)๋„ true
  • ์ผ๊ด€์„ฑ : x.equals(y)๋ฅผ ๋ฐ˜๋ณตํ•ด์„œ ํ˜ธ์ถœํ•˜๋ฉด ํ•ญ์ƒ true ๋˜๋Š” false ๋ฐ˜ํ™˜

โœณ๏ธ equals ๋ฉ”์„œ๋“œ ๊ตฌํ˜„ ๋ฐฉ๋ฒ•

  • == ์—ฐ์‚ฐ์ž ์‚ฌ์šฉํ•ด ์ž…๋ ฅ์ด ์ž๊ธฐ ์ž์‹ ์˜ ์ฐธ์กฐ์ธ์ง€ ํ™•์ธ ๐Ÿ‘‰ ์ž๊ธฐ ์ž์‹ ์ด๋ฉด true ๋ฐ˜ํ™˜
  • instanceof ์—ฐ์‚ฐ์ž๋กœ ์ž…๋ ฅ์ด ์˜ฌ๋ฐ”๋ฅธ ํƒ€์ž…์ธ์ง€ ํ™•์ธ
  • ์ž…๋ ฅ์„ ์˜ฌ๋ฐ”๋ฅธ ํƒ€์ž…์œผ๋กœ ํ˜•๋ณ€ํ™˜
  • ์ž…๋ ฅ ๊ฐ์ฒด์™€ ์ž๊ธฐ ์ž์‹ ์˜ ๋Œ€์‘๋˜๋Š” ํ•ต์‹ฌ ํ•„๋“œ๋“ค์ด ๋ชจ๋‘ ์ผ์น˜ํ•˜๋Š”์ง€ ํ•˜๋‚˜์”ฉ ๊ฒ€์‚ฌ
  • Object ์™ธ์˜ ํƒ€์ž…์„ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ๋ฐ›๋Š” equals ๋ฉ”์„œ๋“œ ์„ ์–ธ X ๐Ÿ‘‰@Override ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•ด ์‹ค์ˆ˜ ์˜ˆ๋ฐฉ
public class PhoneNumber {
    private final short areaCode, prefix, lineNum;

    public PhoneNumber(int areaCode, int prefix, int 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)) { // instanceof๋กœ ํƒ€์ž… ํ™•์ธ 
        	return false;
        }

        PhoneNumber pn = (PhoneNumber) o; // ํ˜•๋ณ€ํ™˜
        // ํ•ต์‹ฌ ํ•„๋“œ ์ผ์น˜ํ•˜๋Š”์ง€ ํ™•์ธ
        return pn.lineNum == lineNum && pn.prefix == prefix
                && pn.areaCode == areaCode;
    }
}
โœจ ๊ผญ ํ•„์š”ํ•œ ๊ฒฝ์šฐ๊ฐ€ ์•„๋‹ˆ๋ฉด equals ์žฌ์ •์˜ X
โœจ AutoValue ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์‚ฌ์šฉํ•ด ํด๋ž˜์Šค์— ์–ด๋…ธํ…Œ์ด์…˜ ํ•˜๋‚˜๋งŒ ์ถ”๊ฐ€ํ•˜์—ฌ equals ์ž‘์„ฑํ•˜๊ณ  ํ…Œ์ŠคํŠธํ•˜๋Š” ์ผ ↓

 

 

11. equals๋ฅผ ์žฌ์ •์˜ํ•  ๋•Œ, ๋ฐ˜๋“œ์‹œ hashCode๋„ ํ•จ๊ป˜ ์žฌ์ •์˜

HashMap, HashSet๊ณผ ๊ฐ™์€ ์ปฌ๋ ‰์…˜์—์„œ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋™์ž‘ํ•˜๋„๋ก ํ•˜๊ธฐ ์œ„ํ•จ

 

โœจ HashCode๋ž€ ? 

๐Ÿ‘‰ ๊ฐ์ฒด๋ฅผ ์‹๋ณ„ํ•˜๋Š” ํ•˜๋‚˜์˜ ์ •์ˆ˜๊ฐ’์œผ๋กœ, Object์˜ hashCode() ๋ฉ”์„œ๋“œ๋Š” ๊ฐ์ฒด์˜ ๋ฉ”๋ชจ๋ฆฌ ์ฃผ์†Œ๋ฅผ ์ด์šฉํ•ด์„œ ํ•ด์‹œ ์ฝ”๋“œ๋ฅผ ๋งŒ๋“ค์–ด ๋ฐ˜ํ™˜ํ•˜๋ฏ€๋กœ ๊ฐ์ฒด๋งˆ๋‹ค ๊ณ ์œ ์˜ ๊ฐ’์„ ๊ฐ€์ง€๊ณ  ์žˆ์Œ 

 

โœณ๏ธ Object์—์„œ์˜ HashCode ๊ด€๋ จ ๊ทœ์•ฝ

  • equals ๋น„๊ต์— ์‚ฌ์šฉ๋˜๋Š” ์ •๋ณด๊ฐ€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜๋‹ค๋ฉด, ๊ทธ ๊ฐ์ฒด์˜ hashCode ๋ฉ”์„œ๋“œ๋Š” ์ผ๊ด€๋˜๊ฒŒ ํ•ญ์ƒ ๊ฐ™์€ ๊ฐ’ ๋ฐ˜ํ™˜
  • equals(Object)๊ฐ€ ๋‘ ๊ฐ์ฒด๋ฅผ ๊ฐ™๋‹ค๊ณ  ํŒ๋‹จํ–ˆ๋‹ค๋ฉด, ๋‘ ๊ฐ์ฒด์˜ hashCode๋Š” ๋˜‘๊ฐ™์€ ๊ฐ’ ๋ฐ˜ํ™˜
  • equals(Object)๊ฐ€ ๋‘ ๊ฐ์ฒด๋ฅผ ๋‹ค๋ฅด๋‹ค๋กœ ํŒ๋‹จํ–ˆ๋‹ค๋ฉด, ๋‘ ๊ฐ์ฒด์˜ hashCode๊ฐ€ ์„œ๋กœ ๋‹ค๋ฅธ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•  ํ•„์š” X ๐Ÿ‘‰ ๋‹จ, ๋‹ค๋ฅธ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•ด์‹œํ…Œ์ด๋ธ” ์„ฑ๋Šฅ์ด ์ข‹์•„์ง 

 

โœณ๏ธ ์ข‹์€ hashCode ์ž‘์„ฑํ•˜๊ธฐ

// ์ „ํ˜•์ ์ธ ๋ฉ”์„œ๋“œ
@Override
public int hashCode() {
    int result = Integer.hashCode(areaCode);
    result = 31 * result + Integer.hashCode(prefix);
    result = 31 * result + Integer.hashCode(lineNum);
    return result;
}
// ํ•œ ์ค„์งœ๋ฆฌ ๋ฉ”์„œ๋“œ -> ์„ฑ๋Šฅ ๐Ÿ‘Ž
@Override
public int hashCode() {
    return Objects.hash(lineNum,prefix,areaCode);
}

๐Ÿ‘† ์ž…๋ ฅ ์ธ์ˆ˜๋ฅผ ๋‹ด๊ธฐ ์œ„ํ•œ ๋ฐฐ์—ด์ด ๋งŒ๋“ค์–ด์ง€๊ณ , ์ž…๋ ฅ ์ค‘ ๊ธฐ๋ณธ ํƒ€์ž…์ด ์žˆ๋‹ค๋ฉด ๋ฐ•์‹ฑ๊ณผ ์–ธ๋ฐ•์‹ฑ์„ ๊ฑฐ์ณ์•ผํ•˜๋ฏ€๋กœ ์†๋„ ๋Š๋ฆผ 

// ํ•ด์‹œ์ฝ”๋“œ๋ฅผ ์ง€์—ฐ ์ดˆ๊ธฐํ™”ํ•˜๋Š” ๋ฉ”์„œ๋“œ - ์Šค๋ ˆ๋“œ ์•ˆ์ •์„ฑ๊นŒ์ง€ ๊ณ ๋ คํ•ด์•ผ ํ•จ
private int hashCode; //์ž๋™์œผ๋กœ 0์œผ๋กœ ์ดˆ๊ธฐํ™”

@Override
public int hashCode() {
    int result = hashCode; 
    if(result == 0) {
        int result = Integer.hashCode(areaCode);
        result = 31 * result + Integer.hashCode(areaCode);
        result = 31 * result + Integer.hashCode(areaCode);
        hashCode = result;
    }
    return result;
}

 

โœจ ์ง€์—ฐ ์ดˆ๊ธฐํ™”๋ž€ ? 

๐Ÿ‘‰ ํ•„์š”ํ•  ๋•Œ๊นŒ์ง€ ๊ฐ์ฒด๋‚˜ ๊ฐ’์˜ ์ดˆ๊ธฐํ™”๋ฅผ ๋ฏธ๋ฃธ์œผ๋กœ์จ ์„ฑ๋Šฅ ์ตœ์ ํ™” ๋ฐ ์ž์› ์ ˆ์•ฝ 
// ์ง€์—ฐ ์ดˆ๊ธฐํ™” X ๊ฐ์ฒด ์ƒ์„ฑ
public class DataLoader {
    private HeavyResource resource = new HeavyResource(); // ๊ฐ์ฒด ์ƒ์„ฑ ์‹œ ์ฆ‰์‹œ ์ดˆ๊ธฐํ™”

    public void loadData() {
        // ๋ฌด๊ฑฐ์šด ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉ
        resource.doSomething();
    }
}
// ์ง€์—ฐ ์ดˆ๊ธฐํ™” ์ ์šฉ
public class DataLoader {
    private HeavyResource resource; // ์•„์ง ์ดˆ๊ธฐํ™”ํ•˜์ง€ ์•Š์Œ

    public void loadData() {
        if (resource == null) { // ์ฒ˜์Œ์œผ๋กœ ์‚ฌ์šฉ๋  ๋•Œ๋งŒ ์ดˆ๊ธฐํ™”
            resource = new HeavyResource();
        }
        resource.doSomething();
    }
}

 

 

12. toString ํ•ญ์ƒ ์žฌ์ •์˜ ํ•„์š” 

๊ฐ„๊ฒฐํ•˜๋ฉด์„œ ์‚ฌ๋žŒ์ด ์ฝ๊ธฐ ์‰ฌ์šด ํ˜•ํƒœ์˜ ์œ ์ตํ•œ ์ •๋ณด ๋ฐ˜ํ™˜
๐Ÿ‘‰ ๋ชจ๋“  ํ•˜์œ„ ํด๋ž˜์Šค์—์„œ ์žฌ์ •์˜ ! (์ƒ์œ„ ํด๋ž˜์Šค์—์„œ ์ด๋ฏธ ์žฌ์ •์˜ํ•œ ๊ฒฝ์šฐ๋Š” ์˜ˆ์™ธ)

 

  • ์ปฌ๋ ‰์…˜์ฒ˜๋Ÿผ ์ธ์Šคํ„ด์Šค๋ฅผ ํฌํ•จํ•˜๋Š” ๊ฐ์ฒด์—์„œ ์œ ์šฉํ•˜๊ฒŒ ์‚ฌ์šฉ๋จ
  • ๊ฐ์ฒด๊ฐ€ ๊ฐ€์ง„ ์ฃผ์š” ์ •๋ณด ๋ชจ๋‘๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Œ (๊ฑฐ๋Œ€ํ•˜๊ฑฐ๋‚˜ ๋ฌธ์ž์—ด๋กœ ํ‘œํ˜„ํ•˜๊ธฐ ์ ํ•ฉํ•˜์ง€ ์•Š๋‹ค๋ฉด ์š”์•ฝ์ •๋ณด ๋‹ด๊ธฐ)
  • ํ•˜์œ„ ํด๋ž˜์Šค๋“ค์ด ๊ณต์œ ํ•ด์•ผํ•  ๋ฌธ์ž์—ด ํ‘œํ˜„์ด ์žˆ๋Š” ์ถ”์ƒ ํด๋ž˜์Šค๋ผ๋ฉด ์žฌ์ •์˜ ํ•„์š” !
  • ๋ฐ˜ํ™˜๊ฐ’์˜ ํฌ๋งท์„ ๋ฌธ์„œํ™”ํ• ์ง€ ๊ฒฐ์ • (์ „ํ™”๋ฒˆํ˜ธ๋‚˜ ํ–‰๋ ฌ ๊ฐ’์€ ๊ฐ’)

 

โœด๏ธ ํฌ๋งท ๋ช…์‹œํ•œ ๊ฒฝ์šฐ

/**
* ์ด ์ „ํ™”๋ฒˆํ˜ธ์˜ ๋ฌธ์ž์—ด ํ‘œํ˜„์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
* ์ด ๋ฌธ์ž์—ด์€ "XXX-YYY-ZZZZ" ํ˜•ํƒœ์˜ 12๊ธ€์ž๋กœ ๊ตฌ์„ฑ๋œ๋‹ค.
* XXX๋Š” ์ง€์—ญ์ฝ”๋“œ, YYY๋Š” ํ”„๋ฆฌํ”ฝ์Šค, ZZZZ๋Š” ๊ฐ€์ž…์ž ๋ฒˆํ˜ธ๋‹ค.
* ๊ฐ๊ฐ์˜ ๋Œ€๋ฌธ์ž๋Š” 10์ง„์ˆ˜ ์ˆซ์ž ํ•˜๋‚˜๋ฅผ ๋‚˜ํƒ€๋‚ธ๋‹ค.
* 
* ์ „ํ™”๋ฒˆํ˜ธ์˜ ๊ฐ ๋ถ€๋ถ„์˜ ๊ฐ’์ด ๋„ˆ๋ฌด ์ž‘์•„์„œ ์ž๋ฆฟ์ˆ˜๋ฅผ ์ฑ„์šธ ์ˆ˜ ์—†๋‹ค๋ฉด,
* ์•ž์—์„œ๋ถ€ํ„ฐ 0์œผ๋กœ ์ฑ„์›Œ๋‚˜๊ฐ„๋‹ค. ์˜ˆ์ปจ๋ฐ ๊ฐ€์ž…์ž ๋ฒˆํ˜ธ๊ฐ€ 123์ด๋ผ๋ฉด
* ์ „ํ™”๋ฒˆํ˜ธ์˜ ๋งˆ์ง€๋ง‰ ๋„ค ๋ฌธ์ž๋Š” "0123"์ด ๋œ๋‹ค.
*/
@Override 
public String toString() {
    return String.format("%03d-%03d-%04d", areaCode, prefix, lineNum);
}

 

โœด๏ธ ํฌ๋งท ๋ช…์‹œํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ

/**
* ์ด ์•ฝ๋ฌผ์— ๊ด€ํ•œ ๋Œ€๋žต์ ์ธ ์„ค๋ช…์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
* ๋‹ค์Œ์€ ์ด ์„ค๋ช…์˜ ์ผ๋ฐ˜์ ์ธ ํ˜•ํƒœ์ด๋‚˜,
* ์ƒ์„ธ ํ˜•์‹์€ ์ •ํ•ด์ง€์ง€ ์•Š์•˜์œผ๋ฉฐ ํ–ฅํ›„ ๋ณ€๊ฒฝ๋  ์ˆ˜ ์žˆ๋‹ค.
* 
* "[์•ฝ๋ฌผ #9: ์œ ํ˜•-์‚ฌ๋ž‘, ๋ƒ„์ƒˆ=ํ…Œ๋Ÿฌ๋นˆ์œ , ๊ฒ‰๋ชจ์Šต=๋จน๋ฌผ]"
*/
@Override
public String toString() { ... }