Skip to content

Latest commit

Β 

History

History
442 lines (313 loc) Β· 19.7 KB

File metadata and controls

442 lines (313 loc) Β· 19.7 KB

item10 : equalsλŠ” 일반 κ·œμ•½μ„ μ§€μΌœ μž¬μ •μ˜ν•˜λΌ

equals λ©”μ„œλ“œλŠ” μž¬μ •μ˜ν•˜κΈ° μ‰¬μ›Œ λ³΄μ΄μ§€λ§Œ 곳곳에 함정이 도사리고 μžˆλ‹€. κ°€μž₯ μ‰¬μš΄ 길은 μ•„μ˜ˆ μž¬μ •μ˜ν•˜μ§€ μ•ŠλŠ” 것이닀.

1. equalsλ₯Ό μž¬μ •μ˜ν•˜μ§€ μ•Šμ•„λ„ λ˜λŠ” 경우

값이 같은 μΈμŠ€ν„΄μŠ€κ°€ λ‘˜ 이상 λ§Œλ“€μ–΄μ§€μ§€ μ•ŠλŠ” μΈμŠ€ν„΄μŠ€ ν†΅μ œ 클래슀라면 equalsλ₯Ό μž¬μ •μ˜ν•˜μ§€ μ•Šμ•„λ„ λ©λ‹ˆλ‹€. 이런 κ²½μš°μ—λŠ” 객체의 식별성(Identity)κ³Ό 논리적 λ™μΉ˜μ„±(Equality)이 λ™μΌν•˜κ²Œ μ·¨κΈ‰λ˜κΈ° λ•Œλ¬Έμž…λ‹ˆλ‹€.

1) 각각의 객체(μΈμŠ€ν„΄μŠ€)κ°€ 본질적으둜 κ³ μœ ν•  λ•Œ

값을 ν‘œν˜„ν•˜λŠ” 게 μ•„λ‹ˆλΌ λ™μž‘ν•˜λŠ” 개체λ₯Ό ν‘œν˜„ν•˜λŠ” ν΄λž˜μŠ€κ°€ μ—¬κΈ° ν•΄λ‹Ήν•œλ‹€. λŒ€ν‘œμ μΈ 예둜 Thread, Controller, Service 등이 이 쑰건에 λΆ€ν•©ν•œλ‹€.

public class MyService {
    // μ„œλΉ„μŠ€ 둜직 κ΅¬ν˜„
    public void execute() {
        // μ‹€ν–‰ μ½”λ“œ
    }
}

// μ‚¬μš© μ˜ˆμ‹œ
MyService service1 = new MyService();
MyService service2 = new MyService();

// μ„œλ‘œ λ‹€λ₯Έ μ„œλΉ„μŠ€ μΈμŠ€ν„΄μŠ€λŠ” 본질적으둜 κ³ μœ ν•©λ‹ˆλ‹€.
System.out.println(service1.equals(service2)); // false

2) μΈμŠ€ν„΄μŠ€μ˜ 논리적 λ™μΉ˜μ„±(logical equality)1을 검사할 일이 없을 λ•Œ

Pattern의 μΈμŠ€ν„΄μŠ€κ°€ 같은 μ •κ·œ ν‘œν˜„μ‹μ„ λ‚˜νƒ€λ‚΄λŠ”μ§€ κ²€μ‚¬ν•˜κ±°λ‚˜ Random 클래슀의 equals λ©”μ„œλ“œκ°€ 큰 의미λ₯Ό 가지지 λͺ»ν•˜λŠ” κ²ƒμ²˜λŸΌ ν΄λΌμ΄μ–ΈνŠΈκ°€ 이 방식이 ν•„μš” μ—†λ‹€κ³  νŒλ‹¨λ˜λ©΄ equalsλ₯Ό μž¬μ •μ˜ ν•˜μ§€ μ•Šμ•„λ„ λœλ‹€.

Pattern pattern1 = Pattern.compile("[a-z]*");
Pattern pattern2 = Pattern.compile("[a-z]*");

// Pattern ν΄λž˜μŠ€λŠ” equalsλ₯Ό μž¬μ •μ˜ν•˜μ§€ μ•Šμ•˜μœΌλ―€λ‘œ μ°Έμ‘° 비ꡐλ₯Ό ν•©λ‹ˆλ‹€.
System.out.println(pattern1.equals(pattern2)); // false

// κ·ΈλŸ¬λ‚˜ λ™μΌν•œ μ •κ·œ ν‘œν˜„μ‹μ„ λ‚˜νƒ€λƒ…λ‹ˆλ‹€.
String testStr = "abc";
System.out.println(pattern1.matcher(testStr).matches()); // true
System.out.println(pattern2.matcher(testStr).matches()); // true

3) μƒμœ„ ν΄λž˜μŠ€μ—μ„œ μž¬μ •μ˜ν•œ equalsκ°€ ν•˜μœ„ ν΄λž˜μŠ€μ—λ„ λ“€μ–΄λ§žμ„ λ•Œ

ν•˜μœ„ ν΄λž˜μŠ€μ—μ„œλ„ μ‚¬μš©ν•˜κΈ°μ— μ ν•©ν•œ equlas라면 μž¬μ •μ˜ν•  ν•„μš” 없이 상속받아 μ‚¬μš©ν•˜λ©΄ λœλ‹€

  • Set κ΅¬ν˜„μ²΄λŠ” Abstractset이 κ΅¬ν˜„ν•œ equalsλ₯Ό 상속받아 μ“°κ³ , List κ΅¬ν˜„μ²΄λ“€μ€ AbstractListλ‘œλΆ€ν„°, Map κ΅¬ν˜„μ²΄λ“€μ€ AbstractMapμœΌλ‘œλΆ€ν„° 상속받아 κ·ΈλŒ€λ‘œ μ“΄λ‹€.

4) ν΄λž˜μŠ€κ°€ private λ˜λŠ” package-private(default)이고 equals λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•  일이 없을 λ•Œ

equalsκ°€ 호좜될 일이 μ—†μ–΄ μ‹€μˆ˜λ‘œλΌλ„ ν˜ΈμΆœλ˜λŠ” κ±Έ 막고 μ‹Άλ‹€λ©΄ λ‹€μŒκ³Ό 같이 κ΅¬ν˜„ν•˜λ©΄ λœλ‹€.

class PackagePrivateClass {
    // 클래슀 λ‚΄μš©

    @Override
    public boolean equals(Object o) {
        throw new AssertionError("equals λ©”μ„œλ“œλŠ” 호좜될 수 μ—†μŠ΅λ‹ˆλ‹€.");
    }
}

// μ‚¬μš© μ˜ˆμ‹œ
PackagePrivateClass obj1 = new PackagePrivateClass();
PackagePrivateClass obj2 = new PackagePrivateClass();

// equals λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•˜λ©΄ AssertionErrorκ°€ λ°œμƒν•©λ‹ˆλ‹€.
boolean isEqual = obj1.equals(obj2); // AssertionError λ°œμƒ
  • AssertionError μ‚¬μš© 이유: equals λ©”μ„œλ“œκ°€ ν˜ΈμΆœλ˜μ§€ μ•Šλ„λ‘ 보μž₯ν•˜λ©°, λ§Œμ•½ 호좜되면 ν”„λ‘œκ·Έλž˜λ¨Έμ—κ²Œ 즉각적인 ν”Όλ“œλ°±μ„ μ œκ³΅ν•œλ‹€.
  • μ£Όμ˜μ‚¬ν•­: AssertionErrorλŠ” 일반적으둜 μ˜ˆμƒμΉ˜ λͺ»ν•œ μƒν™©μ—μ„œ μ‚¬μš©λ˜λ©°, λŸ°νƒ€μž„ ν™˜κ²½μ—μ„œ 였λ₯˜λ₯Ό λ‚˜νƒ€λ‚Έλ‹€. λ”°λΌμ„œ 이 방식을 μ‚¬μš©ν•  λ•ŒλŠ” ν•΄λ‹Ή 클래슀의 μ‚¬μš© λ²”μœ„μ™€ λͺ©μ μ„ λͺ…ν™•νžˆ ν•΄μ•Ό ν•œν•œλ‹€.

2. equalsλ₯Ό μž¬μ •μ˜ν•΄μ•Ό ν•˜λŠ” 경우

객체의 식별성이 μ•„λ‹Œ 논리적 λ™μΉ˜μ„±μ„ 확인해야 ν•˜λŠ”λ°, μƒμœ„ 클래슀의 equalsκ°€ 논리적 λ™μΉ˜μ„±μ„ λΉ„κ΅ν•˜λ„λ‘ μž¬μ •μ˜λ˜μ§€ μ•Šμ•˜μ„ λ•Œ ν•΄μ•Όν•œλ‹€.

주둜 Stringμ΄λ‚˜ Integer와 같은 κ°’ ν΄λž˜μŠ€κ°€ 이에 ν•΄λ‹Ήν•œλ‹€.

κ°’ 클래슀의 equalsλ₯Ό μž¬μ •μ˜ ν•  λ•Œ 논리적 λ™μΉ˜μ„±μ„ ν™•μΈν•˜λ„λ‘ μž¬μ •μ˜ν•΄λ‘λ©΄, 값을 λΉ„κ΅ν•˜λŠ” 것 λΏλ§Œμ•„λ‹ˆλΌ Map의 ν‚€λ‚˜ Set의 μ›μ†Œλ‘œ μ‚¬μš©ν•  수 있게 λœλ‹€.

// 값을 비ꡐ
@Override
public boolean equals(Object obj) {
    if (obj instanceof Integer) {
        return value == ((Integer)obj).intValue();
    }
    return false;
}

κ°’ ν΄λž˜μŠ€μ—¬λ„ equals μž¬μ •μ˜κ°€ ν•„μš” μ—†λŠ” 경우

  • 값이 같은 μΈμŠ€ν„΄μŠ€κ°€ λ‘˜ 이상 λ§Œλ“€μ–΄μ§€μ§€ μ•ŠλŠ” μΈμŠ€ν„΄μŠ€ ν†΅μ œ 클래슀라면 equalsλ₯Ό μž¬μ •μ˜ν•˜μ§€ μ•Šμ•„λ„ λœλ‹€.
  • λŒ€ν‘œμ μΈ 예둜 Enum νƒ€μž…μ΄ 있으며, μ΄λŸ¬ν•œ ν΄λž˜μŠ€μ—μ„œλŠ” equals λ©”μ„œλ“œλ₯Ό μž¬μ •μ˜ν•˜μ§€ μ•Šμ•„λ„ λœλ‹€.
  • λ…Όλ¦¬μ μœΌλ‘œ 같은 μΈμŠ€ν„΄μŠ€κ°€ 2개 이상 λ§Œλ“€μ–΄μ§€μ§€ μ•ŠμœΌλ‹ˆ 논리적 λ™μΉ˜μ„±κ³Ό 객체 식별성이 λ˜‘κ°™μ€ 의미라고 λ³Ό 수 μžˆλ‹€.

λΆ€κ°€ μ„€λͺ… Enum

λŒ€ν‘œμ μΈ 예: Enum

  • Enum νƒ€μž…μ€ μžλ°”μ—μ„œ μΈμŠ€ν„΄μŠ€ ν†΅μ œ 클래슀2의 λŒ€ν‘œμ μΈ 예
  • 각 μ—΄κ±° μƒμˆ˜λŠ” μœ μΌν•œ μΈμŠ€ν„΄μŠ€μ΄λ©°, λ™μΌν•œ μ—΄κ±° μƒμˆ˜μ— λŒ€ν•΄ μ—¬λŸ¬ μΈμŠ€ν„΄μŠ€κ°€ μƒμ„±λ˜μ§€ μ•ŠλŠ”λ‹€.
  • λ”°λΌμ„œ Enum νƒ€μž…μ—μ„œλŠ” equals λ©”μ„œλ“œλ₯Ό μž¬μ •μ˜ν•˜μ§€ μ•Šμ•„λ„ λœλ‹€.
  • 객체의 식별성과 논리적 λ™μΉ˜μ„±μ΄ λ™μΌν•˜λ―€λ‘œ, == μ—°μ‚°μžλ‘œ 비ꡐ해도 μΆ©λΆ„λ‹€.

μ˜ˆμ‹œ μ½”λ“œ:

public enum Color {
    RED, GREEN, BLUE;
}

// μ‚¬μš© μ˜ˆμ‹œ
Color color1 = Color.RED;
Color color2 = Color.RED;

// λ™μΌν•œ μΈμŠ€ν„΄μŠ€μ΄λ―€λ‘œ μ°Έμ‘° 비ꡐ가 κ°€λŠ₯
System.out.println(color1 == color2);     // true
System.out.println(color1.equals(color2)); // true

// λ‹€λ₯Έ μΈμŠ€ν„΄μŠ€μ™€ 비ꡐ
Color color3 = Color.GREEN;
System.out.println(color1 == color3);     // false
System.out.println(color1.equals(color3)); // false
  • Color.REDλŠ” μœ μΌν•œ μΈμŠ€ν„΄μŠ€μ΄λ―€λ‘œ color1κ³Ό color2λŠ” λ™μΌν•œ 객체λ₯Ό μ°Έμ‘°ν•œλ‹€.
  • λ”°λΌμ„œ == μ—°μ‚°μžμ™€ equals λ©”μ„œλ“œ λͺ¨λ‘ trueλ₯Ό λ°˜ν™˜ν•œν•œλ‹€.
  • equals λ©”μ„œλ“œλ₯Ό μž¬μ •μ˜ν•˜μ§€ μ•Šμ•˜μ§€λ§Œ, Object 클래슀의 κΈ°λ³Έ κ΅¬ν˜„μœΌλ‘œλ„ 논리적 λ™μΉ˜μ„±μ„ μ˜¬λ°”λ₯΄κ²Œ νŒλ‹¨ν•  수 μžˆλ‹€.

3. equals λ©”μ„œλ“œ μž¬μ •μ˜ μ‹œ μ§€μΌœμ•Ό ν•  일반 κ·œμ•½.

  • λ°˜μ‚¬μ„±(reflexive): x.equals(x)λŠ” 항상 trueμ—¬μ•Ό ν•œλ‹€.
  • λŒ€μΉ­μ„±(symmetric): x.equals(y)κ°€ true이면 y.equals(x)도 trueμ—¬μ•Ό ν•œλ‹€.
  • 좔이성(transitive): x.equals(y)이고 y.equals(z)이면 x.equals(z)도 trueμ—¬μ•Ό ν•œλ‹€.
  • 일관성(consistent): 객체의 μƒνƒœκ°€ λ³€κ²½λ˜μ§€ μ•Šμ•˜λ‹€λ©΄ μ—¬λŸ¬ 번 ν˜ΈμΆœν•΄λ„ 항상 같은 κ²°κ³Όλ₯Ό λ°˜ν™˜ν•΄μ•Ό ν•œλ‹€.
  • null-μ•„λ‹˜(non-nullity): x.equals(null)은 항상 falseμ—¬μ•Ό λ‹€.

4. μ–‘μ§ˆμ˜ equals λ©”μ„œλ“œ κ΅¬ν˜„ 방법

  1. ==μ—°μ‚°μžλ₯Ό μ‚¬μš©ν•΄ μž…λ ₯이 자기 μžμ‹ μ˜ μ°Έμ‘°3인지 ν™•μΈν•œλ‹€.
@Override
public boolean equals(Object obj) {
    if (this == obj) {
        return true; // 같은 κ°μ²΄μ΄λ―€λ‘œ true λ°˜ν™˜(λΆˆν•„μš”ν•œ 계산 ν”Όν•  수 있음)
    }
    // λ‚˜λ¨Έμ§€ 비ꡐ 둜직...
}
  1. instanceof μ—°μ‚°μžλ‘œ μž…λ ₯이 μ˜¬λ°”λ₯Έ νƒ€μž…μΈμ§€ ν™•μΈν•œλ‹€.
  • μž…λ ₯ 객체(obj)κ°€ μ˜¬λ°”λ₯Έ νƒ€μž…μΈμ§€ 확인.
  • 객체의 클래슀 νƒ€μž…μ΄ λ‹€λ₯΄λ‹€λ©΄, 두 κ°μ²΄λŠ” λ…Όλ¦¬μ μœΌλ‘œ 동등할 수 μ—†μœΌλ―€λ‘œ falseλ₯Ό λ°˜ν™˜
  • μ΄λ•Œ λΉ„κ΅ν•˜λŠ” νƒ€μž…μ€ 일반적으둜 ν•΄λ‹Ή 클래슀 λ˜λŠ” νŠΉμ • μΈν„°νŽ˜μ΄μŠ€μ΄λ‹€.
if (!(obj instanceof MyClass)) {
    return false; // νƒ€μž…μ΄ λ‹€λ₯΄λ―€λ‘œ false λ°˜ν™˜
}
if (!(obj instanceof MyInterface)) {
    return false; // μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•˜μ§€ μ•ŠμœΌλ©΄ false λ°˜ν™˜
}
  1. μž…λ ₯을 μ˜¬λ°”λ₯Έ νƒ€μž…μœΌλ‘œ ν˜•λ³€ν™˜ν•œλ‹€.
  • 2λ‹¨κ³„μ—μ„œ νƒ€μž… 검사λ₯Ό ν†΅κ³Όν–ˆλ‹€λ©΄, 이제 μž…λ ₯ 객체λ₯Ό ν•΄λ‹Ή νƒ€μž…μœΌλ‘œ ν˜•λ³€ν™˜(casting)ν•  수 μžˆλ‹€.
  • ν˜•λ³€ν™˜μ„ 톡해 μž…λ ₯ 객체의 ν•„λ“œλ‚˜ λ©”μ„œλ“œμ— μ ‘κ·Όν•  수 μžˆλ‹€.

μ½”λ“œ μ˜ˆμ‹œ:

  • 클래슀둜 ν˜•λ³€ν™˜ν•˜λŠ” 경우:

    MyClass other = (MyClass) obj;
  • μΈν„°νŽ˜μ΄μŠ€λ‘œ ν˜•λ³€ν™˜ν•˜λŠ” 경우:

    MyInterface other = (MyInterface) obj;
  1. 핡심 ν•„λ“œλ“€μ„ ν•˜λ‚˜μ”© λΉ„κ΅ν•˜μ—¬ 논리적 동등성을 νŒλ‹¨
    • ν•„λ“œμ˜ νƒ€μž…μ— 따라 μ μ ˆν•œ 비ꡐ 방법을 μ‚¬μš©
  2. λͺ¨λ“  ν•„λ“œκ°€ μΌμΉ˜ν•˜λ©΄ trueλ₯Ό λ°˜ν™˜ν•˜κ³ , ν•˜λ‚˜λΌλ„ λ‹€λ₯΄λ©΄ falseλ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€.

equalsλŠ” jvm내에 μžˆλŠ” 객체만 가지고 νŒλ‹¨ν•΄μ•Ό ν•˜λŠ”λ° λ„€νŠΈμ›Œν¬λ₯Ό νƒ€κ²Œ 되면.. 맀번 λ‹¬λΌμ§€κ²Œ λœλ‹€? ip에 따라 λ‹¬λΌμ§€κ²Œ λœλ‹€κ³  ν•œλ‹€..

{% embed url="https://stackoverflow.com/questions/3771081/proper-way-to-check-for-url-equality" %}

5. equals κ΅¬ν˜„ μ‹œ μ£Όμ˜μ‚¬ν•­

1) κΈ°λ³Ένƒ€μž… 별 비ꡐ 방법

  • float, doble을 μ œμ™Έν•œ κΈ°λ³Έ νƒ€μž… : == μ—°μ‚°μžλ‘œ λΉ„κ΅ν•˜κΈ°
  • float와 double νƒ€μž…: Float.compare(float, float)와 Double.compare(double, double) 정적 λ©”μ„œλ“œλ₯Ό μ‚¬μš©ν•œλ‹€.
    • 이유: NaN, -0.0f λ“± νŠΉμˆ˜ν•œ λΆ€λ™μ†Œμˆ˜μ  값을 μ •ν™•ν•˜κ²Œ μ²˜λ¦¬ν•˜κΈ° μœ„ν•΄μ„œ
    • 주의: Float.equals(Object)와 Double.equals(Object) λ©”μ„œλ“œλŠ” μ˜€ν† λ°•μ‹±μ„ μœ λ°œν•˜μ—¬ μ„±λŠ₯에 영ν–₯을 쀄 수 μžˆμœΌλ―€λ‘œ μ‚¬μš©μ„ 지양

2) μ°Έμ‘° νƒ€μž… ν•„λ“œ 비ꡐ

  • μ°Έμ‘° νƒ€μž… ν•„λ“œ: ν•΄λ‹Ή 객체의 equals λ©”μ„œλ“œλ₯Ό μ‚¬μš©ν•˜μ—¬ 비ꡐ
  • null ν—ˆμš© ν•„λ“œ: 비ꡐ μ‹œ null 체크λ₯Ό μˆ˜ν–‰ν•˜μ—¬ NullPointerException을 방지

3) λ°°μ—΄ ν•„λ“œ 비ꡐ

  • λ°°μ—΄ ν•„λ“œ: λ°°μ—΄μ˜ 각 μš”μ†Œλ₯Ό μ•žμ„œ μ–ΈκΈ‰ν•œ λ°©μ‹λŒ€λ‘œ λΉ„κ΅ν•œλ‹€.
  • λͺ¨λ“  μš”μ†Œκ°€ 핡심 ν•„λ“œλΌλ©΄ Arrays.equals λ©”μ„œλ“œλ₯Ό ν™œμš©ν•  수 μžˆλ‹€.

4) null 정상 κ°’μœΌλ‘œ μ·¨κΈ‰ν•˜λŠ” μ°Έμ‘° νƒ€μž… ν•„λ“œμΌ 경우

Object.equals(Object, Object)둜 비ꡐ해 NPE λ°œμƒμ„ λ°©μ§€ν•œλ‹€.

5) ν•„λ“œμ˜ ν‘œμ€€ν˜•μ„ μ €μž₯

λΉ„κ΅ν•˜κΈ° λ³΅μž‘ν•œ ν•„λ“œλŠ” ν•„λ“œμ˜ ν‘œμ€€ν˜•μ„ μ €μž₯ν•œ ν›„ λΉ„κ΅ν•œλ‹€. λΆˆλ³€ ν΄λž˜μŠ€μ— μ œκ²©μ΄λ‹€.

6) λΉ„μš©μ΄ μ‹Ό ν•„λ“œλ₯Ό λ¨Όμ € λΉ„κ΅ν•˜λΌ(ν•„λ“œ 비ꡐ μˆœμ„œ)

  • 비ꡐ κ°€λŠ₯성이 높은 ν•„λ“œ λ˜λŠ” 비ꡐ λΉ„μš©μ΄ 적은 ν•„λ“œλΆ€ν„° λΉ„κ΅ν•˜μ—¬ μ„±λŠ₯을 μ΅œμ ν™”ν•œλ‹€.
  • 객체의 논리적 μƒνƒœμ™€ κ΄€λ ¨ μ—†λŠ” ν•„λ“œ(예: λ™κΈ°ν™”μš© 락 ν•„λ“œ)λŠ” λΉ„κ΅ν•˜μ§€ μ•ŠλŠ”λ‹€.
  • 핡심 ν•„λ“œλ‘œλΆ€ν„° 계산 κ°€λŠ₯ν•œ νŒŒμƒ ν•„λ“œλŠ” 일반적으둜 비ꡐ할 ν•„μš”κ°€ μ—†μ§€λ§Œ, κ²½μš°μ— 따라 비ꡐ가 효율적일 수 μžˆλ‹€.

7) equalsλ₯Ό μž¬μ •μ˜ν•  λ•ŒλŠ” hashCode도 λ°˜λ“œμ‹œ μž¬μ •μ˜ν•˜μž

public final 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 argName) {
        if (val < 0 || val > max)
            throw new IllegalArgumentException(argName + ": " + val);
        return (short) val;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) // μ°Έμ‘° 비ꡐ
            return true;
        if (!(o instanceof PhoneNumber)) // νƒ€μž… 체크
            return false;
        PhoneNumber pn = (PhoneNumber) o;
        // 핡심 ν•„λ“œ 비ꡐ
        return pn.lineNum == lineNum && pn.prefix == prefix && pn.areaCode == areaCode;
    }

    @Override
    public int hashCode() {
        return Objects.hash(areaCode, prefix, lineNum);
    }

    // 기타 λ©”μ„œλ“œ μƒλž΅
}

μŠ€ν„°λ””μ—μ„œ μ•Œμ•„κ°€λŠ” 것것

false인 이유

public class Test {
    private String name;

    public Test(String name) {
        this.name = name;
    }

    @Override
        public boolean equals(Object obj) {
            if (obj instanceof Test) {
                return this.name.equals(((Test) obj).name);
            }
            return false;
        }

    public static void main(String[] args) throws Exception {
        Set<Test> set = new HashSet<>();
        Test test = new Test("java");
        set.add(test);
        Test test2 = new Test("java");
        System.out.println(set.contains(test2)); //false
    }
}

μœ„ μ½”λ“œμ—μ„œ Set<Test>에 λ™μΌν•œ name 값을 가진 객체(test와 test2)λ₯Ό μΆ”κ°€ν•˜κ³ , κ·Έ ν›„ set.contains(test2)κ°€ false둜 좜λ ₯λ˜λŠ” μ΄μœ λŠ” HashSet이 λ‚΄λΆ€μ μœΌλ‘œ equals와 hashCode λ©”μ„œλ“œλ₯Ό λͺ¨λ‘ μ‚¬μš©ν•΄ 객체의 동일성을 λΉ„κ΅ν•˜κΈ° λ•Œλ¬Έμ΄λ‹€.

이유

HashSet은 λ‚΄λΆ€μ μœΌλ‘œ ν•΄μ‹œ 기반 μžλ£Œκ΅¬μ‘°μ΄λ‹€. HashSet이 객체λ₯Ό μ €μž₯ν•˜κ±°λ‚˜ μ‘°νšŒν•  λ•ŒλŠ” equals뿐만 μ•„λ‹ˆλΌ hashCode λ©”μ„œλ“œλ„ μ‚¬μš©ν•œλ‹€.

  • equals: 두 객체가 λ™μΌν•œμ§€ λΉ„κ΅ν•˜λŠ” λ©”μ„œμ΄λ‹€. ν˜„μž¬ equals λ©”μ„œλ“œλ₯Ό μ˜€λ²„λΌμ΄λ“œν•˜μ—¬ name κ°’λ§Œμ„ κΈ°μ€€μœΌλ‘œ λΉ„κ΅ν•˜λ„λ‘ κ΅¬ν˜„λ˜μ–΄ μžˆλ‹€. λ”°λΌμ„œ test와 test2λŠ” equals λ©”μ„œλ“œ μƒμœΌλ‘œλŠ” λ™μΌν•œ 객체둜 νŒλ‹¨
  • hashCode: 객체λ₯Ό ν•΄μ‹œ ν…Œμ΄λΈ”μ—μ„œ λΉ λ₯΄κ²Œ μ°ΎκΈ° μœ„ν•΄ μ‚¬μš©λ˜λŠ” μ •μˆ˜ 값이닀. HashSetμ΄λ‚˜ HashMapκ³Ό 같은 ν•΄μ‹œ 기반 μžλ£Œκ΅¬μ‘°λŠ” 객체λ₯Ό 비ꡐ할 λ•Œ λ¨Όμ € hashCodeλ₯Ό λΉ„κ΅ν•˜κ³ , hashCodeκ°€ λ™μΌν•œ κ²½μš°μ—λ§Œ equals λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•˜μ—¬ μ΅œμ’…μ μœΌλ‘œ 동일성을 νŒλ‹¨ν•œλ‹€.

λ¬Έμ œλŠ” ν˜„μž¬ Test ν΄λž˜μŠ€μ—μ„œ hashCode λ©”μ„œλ“œκ°€ μ˜€λ²„λΌμ΄λ“œλ˜μ–΄ μžˆμ§€ μ•ŠκΈ° λ•Œλ¬Έμ΄λ‹€. 기본적으둜 Object 클래슀의 hashCodeλŠ” 객체의 λ©”λͺ¨λ¦¬ μ£Όμ†Œλ₯Ό 기반으둜 ν•΄μ‹œ 값을 μƒμ„±ν•˜λ―€λ‘œ, test와 test2λŠ” μ„œλ‘œ λ‹€λ₯Έ ν•΄μ‹œ μ½”λ“œλ₯Ό 가진닀.

λ”°λΌμ„œ, HashSet은 test와 test2λ₯Ό μ„œλ‘œ λ‹€λ₯Έ 객체둜 μΈμ‹ν•˜κ²Œ 되며, set.contains(test2)λŠ” falseλ₯Ό λ°˜ν™˜ν•˜κ²Œ λœλ‹€.

ν•΄κ²° 방법

hashCode λ©”μ„œλ“œλ₯Ό equals와 μΌκ΄€λ˜κ²Œ μ˜€λ²„λΌμ΄λ“œν•΄μ•Ό ν•œλ‹€. name ν•„λ“œλ₯Ό κΈ°μ€€μœΌλ‘œ hashCodeλ₯Ό μƒμ„±ν•˜λ©΄ test와 test2κ°€ 같은 ν•΄μ‹œ μ½”λ“œλ₯Ό κ°€μ§€κ²Œ λ˜μ–΄ HashSet이 동일 객체둜 μΈμ‹ν•˜κ²Œ λœλ‹€.

μˆ˜μ •λœ μ½”λ“œλŠ” λ‹€μŒκ³Ό κ°™λ‹€:

public class Test {
    private String name;

    public Test(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Test) {
            return this.name.equals(((Test) obj).name);
        }
        return false;
    }

    @Override
    public int hashCode() {
        return name.hashCode(); // name ν•„λ“œλ₯Ό κΈ°μ€€μœΌλ‘œ hashCode 생성
    }

    public static void main(String[] args) throws Exception {
        Set<Test> set = new HashSet<>();
        Test test = new Test("java");
        set.add(test);
        Test test2 = new Test("java");
        System.out.println(set.contains(test2)); // true
    }
}

μ„€λͺ…:

  • hashCode μ˜€λ²„λΌμ΄λ“œ: hashCode λ©”μ„œλ“œλ₯Ό name ν•„λ“œλ₯Ό κΈ°μ€€μœΌλ‘œ μ˜€λ²„λΌμ΄λ“œν•˜μ—¬, name이 λ™μΌν•œ κ°μ²΄λŠ” 같은 ν•΄μ‹œ μ½”λ“œλ₯Ό 가지도둝 ν–ˆμŠ΅λ‹ˆλ‹€.
  • 이제 test와 test2λŠ” 같은 ν•΄μ‹œ μ½”λ“œλ₯Ό 가지며, HashSet이 이 두 객체λ₯Ό λ™μΌν•œ 객체둜 μΈμ‹ν•˜κ²Œ λ©λ‹ˆλ‹€.

이제 set.contains(test2)λŠ” trueλ₯Ό λ°˜ν™˜ν•˜κ²Œ λ©λ‹ˆλ‹€.

✨ 정리

κΌ­ ν•„μš”ν•œ κ²½μš°κ°€ μ•„λ‹ˆλ©΄ equalsλ₯Ό μž¬μ •μ˜ν•˜μ§€ 말자. λ§Žμ€ κ²½μš°μ— Object의 equalsκ°€ μ›ν•˜λŠ” 비ꡐλ₯Ό μ •ν™•νžˆ μˆ˜ν–‰ν•΄μ€€λ‹€. μž¬μ •μ˜ν•΄μ•Ό ν•  λ•ŒλŠ” κ·Έ 클래슀의 핡심 ν•„λ“œ λͺ¨λ‘λ₯Ό 빠짐없이, λ‹€μ„― 가지 κ·œμ•½μ„ ν™•μ‹€νžˆ μ§€μΌœκ°€λ©° 비ꡐ해야 ν•œλ‹€.

Footnotes

  1. 논리적 λ™μΉ˜μ„±μ€ 두 객체의 μ‹€μ œ λ©”λͺ¨λ¦¬ μ£Όμ†Œ(μ°Έμ‘°)κ°€ λ™μΌν•œμ§€ μ—¬λΆ€κ°€ μ•„λ‹Œ, 객체가 가진 μ†μ„±μ΄λ‚˜ μƒνƒœκ°€ λ™μΌν•œμ§€λ₯Ό λΉ„κ΅ν•˜λŠ” 것을 μ˜λ―Έν•©λ‹ˆλ‹€. 즉, μ„œλ‘œ λ‹€λ₯Έ μΈμŠ€ν„΄μŠ€μ΄μ§€λ§Œ λ‚΄λΆ€ λ°μ΄ν„°λ‚˜ μƒνƒœκ°€ λ™μΌν•˜λ‹€λ©΄ λ…Όλ¦¬μ μœΌλ‘œ λ™λ“±ν•˜λ‹€κ³  νŒλ‹¨ν•©λ‹ˆλ‹€.

    μžλ°”μ—μ„œ equals λ©”μ„œλ“œλ₯Ό μ˜€λ²„λΌμ΄λ“œν•˜μ—¬ 객체의 논리적 λ™μΉ˜μ„±μ„ μ •μ˜ν•  수 μžˆμŠ΅λ‹ˆλ‹€. 기본적으둜 Object 클래슀의 equals λ©”μ„œλ“œλŠ” μ°Έμ‘° 비ꡐ(물리적 λ™μΉ˜μ„±)λ₯Ό μˆ˜ν–‰ν•˜μ—¬ 두 객체가 λ™μΌν•œ λ©”λͺ¨λ¦¬ μ£Όμ†Œλ₯Ό κ°€μ§€λŠ”μ§€ ν™•μΈν•©λ‹ˆλ‹€. κ·ΈλŸ¬λ‚˜ 이λ₯Ό μ˜€λ²„λΌμ΄λ“œν•˜μ—¬ 객체의 속성 값을 기반으둜 λΉ„κ΅ν•˜λ„λ‘ κ΅¬ν˜„ν•˜λ©΄ 논리적 λ™μΉ˜μ„±μ„ νŒλ‹¨ν•  수 μžˆμŠ΅λ‹ˆλ‹€. ↩

  2. μΈμŠ€ν„΄μŠ€ ν†΅μ œ ν΄λž˜μŠ€λž€?

    • μΈμŠ€ν„΄μŠ€ ν†΅μ œ 클래슀(Instance-Controlled Class)λŠ” λ™μΌν•œ 값을 κ°–λŠ” 객체가 단 ν•˜λ‚˜λ§Œ μ‘΄μž¬ν•˜λ„λ‘ μ„€κ³„λœ ν΄λž˜μŠ€μž…λ‹ˆλ‹€.
    • μ΄λŸ¬ν•œ ν΄λž˜μŠ€λŠ” μƒμ„±μžλ₯Ό private으둜 μ œν•œν•˜κ³ , 객체 생성을 ν†΅μ œν•˜μ—¬ λ™μΌν•œ 값에 λŒ€ν•΄ ν•˜λ‚˜μ˜ μΈμŠ€ν„΄μŠ€λ§Œμ„ μ œκ³΅ν•©λ‹ˆλ‹€.
    • 이둜 인해 객체의 식별성(== μ—°μ‚°μž μ‚¬μš©)κ³Ό 논리적 λ™μΉ˜μ„±(equals λ©”μ„œλ“œ μ‚¬μš©)이 λ™μΌν•΄μ§‘λ‹ˆλ‹€.

    μΈμŠ€ν„΄μŠ€ ν†΅μ œ 클래슀의 κ΅¬ν˜„ 방법

    • μƒμ„±μžλ₯Ό private으둜 μ„ μ–Έν•˜μ—¬ μ™ΈλΆ€μ—μ„œ 객체 생성을 λ§‰μŠ΅λ‹ˆλ‹€.
    • 정적 νŒ©ν† λ¦¬ λ©”μ„œλ“œλ₯Ό μ œκ³΅ν•˜μ—¬ 객체 생성을 ν†΅μ œν•©λ‹ˆλ‹€.
    • λ™μΌν•œ 값을 κ°€μ§€λŠ” μš”μ²­μ— λŒ€ν•΄ μΊμ‹±λœ μΈμŠ€ν„΄μŠ€λ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€.

    μ˜ˆμ‹œ μ½”λ“œ:

    javaμ½”λ“œ 볡사public class Currency {
        private static final Map<String, Currency> instances = new HashMap<>();
    
        private final String code;
    
        // μƒμ„±μžλ₯Ό private으둜 μ„ μ–Έ
        private Currency(String code) {
            this.code = code;
        }
    
        // 정적 νŒ©ν† λ¦¬ λ©”μ„œλ“œλ‘œ 객체 생성 ν†΅μ œ
        public static Currency getInstance(String code) {
            synchronized (instances) {
                // 이미 μ‘΄μž¬ν•˜λ©΄ μΊμ‹±λœ μΈμŠ€ν„΄μŠ€ λ°˜ν™˜
                if (instances.containsKey(code)) {
                    return instances.get(code);
                }
                // μ‘΄μž¬ν•˜μ§€ μ•ŠμœΌλ©΄ μƒˆ μΈμŠ€ν„΄μŠ€ 생성 ν›„ 캐싱
                Currency currency = new Currency(code);
                instances.put(code, currency);
                return currency;
            }
        }
    
        public String getCode() {
            return code;
        }
    
        // equalsλ₯Ό μž¬μ •μ˜ν•˜μ§€ μ•ŠμŒ
    }
    
    // μ‚¬μš© μ˜ˆμ‹œ
    Currency usd1 = Currency.getInstance("USD");
    Currency usd2 = Currency.getInstance("USD");
    
    System.out.println(usd1 == usd2);     // true
    System.out.println(usd1.equals(usd2)); // true
    
    • Currency ν΄λž˜μŠ€λŠ” νŠΉμ • 톡화 μ½”λ“œλ₯Ό 가진 μΈμŠ€ν„΄μŠ€κ°€ ν•˜λ‚˜λ§Œ μƒμ„±λ˜λ„λ‘ ν•©λ‹ˆλ‹€.
    • usd1κ³Ό usd2λŠ” λͺ¨λ‘ "USD" μ½”λ“œλ₯Ό 가지며, λ™μΌν•œ μΈμŠ€ν„΄μŠ€λ₯Ό μ°Έμ‘°ν•©λ‹ˆλ‹€.
    • λ”°λΌμ„œ == μ—°μ‚°μžμ™€ equals λ©”μ„œλ“œ λͺ¨λ‘ trueλ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€.
    • equals λ©”μ„œλ“œλ₯Ό μž¬μ •μ˜ν•˜μ§€ μ•Šμ•„λ„ 객체의 동등성을 μ˜¬λ°”λ₯΄κ²Œ νŒλ‹¨ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

    객체 식별성과 논리적 λ™μΉ˜μ„±μ˜ 일치

    • 객체 식별성(Identity): 객체의 λ©”λͺ¨λ¦¬ μ£Όμ†Œλ₯Ό λΉ„κ΅ν•˜μ—¬ λ™μΌν•œ 객체인지 νŒλ‹¨ν•©λ‹ˆλ‹€. == μ—°μ‚°μžλ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€.
    • 논리적 λ™μΉ˜μ„±(Equality): 객체의 μ†μ„±μ΄λ‚˜ μƒνƒœλ₯Ό λΉ„κ΅ν•˜μ—¬ λ…Όλ¦¬μ μœΌλ‘œ λ™λ“±ν•œμ§€λ₯Ό νŒλ‹¨ν•©λ‹ˆλ‹€. equals λ©”μ„œλ“œλ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€.
    • μΈμŠ€ν„΄μŠ€ ν†΅μ œ ν΄λž˜μŠ€μ—μ„œλŠ” λ™μΌν•œ 값을 κ°€μ§€λŠ” 객체가 ν•˜λ‚˜λ§Œ μ‘΄μž¬ν•˜λ―€λ‘œ, 식별성과 λ™μΉ˜μ„±μ΄ μΌμΉ˜ν•©λ‹ˆλ‹€.
    ↩
  3. μ°Έμ‘° 비ꡐ(Reference Comparison)λ₯Ό 톡해 두 객체가 λ™μΌν•œ μΈμŠ€ν„΄μŠ€μΈμ§€ ν™•μΈν•©λ‹ˆλ‹€. ↩