[Java] πŸ‘΄πŸ» Object Class - toString(), equals(), hashCode()

Sangho HanΒ·2025λ…„ 6μ›” 19일
2

β˜•οΈΒ Java

λͺ©λ‘ 보기
18/20
post-thumbnail

πŸ“Œ Object Class

Object ν΄λž˜μŠ€λž€?

μžλ°”μ—μ„œ 클래슀λ₯Ό λ§Œλ“€ λ•Œ extendsλ₯Ό λͺ…μ‹œν•˜μ§€ μ•ŠμœΌλ©΄ μžλ™μœΌλ‘œ Objectλ₯Ό 상속 λ°›κ²Œ λœλ‹€.

Object ν΄λž˜μŠ€λŠ” java.lang νŒ¨ν‚€μ§€μ— 속해 있으며, 이 νŒ¨ν‚€μ§€λŠ” μžλ™ import λŒ€μƒμ΄κΈ° λ•Œλ¬Έμ— λ³„λ„λ‘œ importλ₯Ό λͺ…μ‹œν•˜μ§€ μ•Šμ•„λ„ λœλ‹€.

λ•Œλ¬Έμ— java.lang.Object λŠ” λͺ¨λ“  μžλ°” 클래슀의 쑰상(μ΅œμƒμœ„ 클래슀)이라고 ν•  수 μžˆλ‹€.

class Person {
	...
}
class Person extends Object {
	...
}

μœ„ 두 μ½”λ“œλŠ” λ™μΌν•˜λ‹€.
그렇기에 μš°λ¦¬κ°€ Javaμ—μ„œ 클래슀λ₯Ό λ§Œλ“€ λ•Œ, λ”°λ‘œ Object 클래슀λ₯Ό extends ν•˜μ§€ μ•Šλ”λΌλ„ toString(), equals() 같은 λ©”μ„œλ“œλ₯Ό μ΄μš©ν•  수 μžˆλŠ” 것이닀.

μ™œ μ‘΄μž¬ν• κΉŒ?

1. 일관성

λͺ¨λ“  객체가 κ³΅ν†΅μ μœΌλ‘œ μ‚¬μš©ν•  수 μžˆλŠ” κΈ°λŠ₯(toString, equals λ©”μ„œλ“œ λ“±)을 μ œκ³΅ν•œλ‹€.

2. λ‹€ν˜•μ„± 기반

Object νƒ€μž… ν•˜λ‚˜λ‘œ λͺ¨λ“  객체λ₯Ό 담을 수 μžˆλ‹€.

Object obj = "hello";  // λ¬Έμžμ—΄
obj = new ArrayList<>(); // 리슀트

μ£Όμš” λ©”μ„œλ“œλ“€

λ©”μ„œλ“œμ„€λͺ…
toString()객체 정보λ₯Ό λ¬Έμžμ—΄λ‘œ λ°˜ν™˜ν•œλ‹€. (κΈ°λ³Έ κ΅¬ν˜„μ€ ν΄λž˜μŠ€μ΄λ¦„@ν•΄μ‹œμ½”λ“œ)
equals(Object obj)두 객체의 논리적 동등성을 λΉ„κ΅ν•œλ‹€. (기본은 μ°Έμ‘° 비ꡐ, ν•„μš”μ‹œ μ˜€λ²„λΌμ΄λ”©)
hashCode()객체의 ν•΄μ‹œ 값을 λ°˜ν™˜ν•œλ‹€. HashMap, HashSet λ“±μ—μ„œ μ‚¬μš©λœλ‹€.
getClass()λŸ°νƒ€μž„ μ‹œ μ‹€μ œ 클래슀 정보λ₯Ό λ°˜ν™˜ν•œλ‹€. λ¦¬ν”Œλ ‰μ…˜ 등에 μ‚¬μš©λœλ‹€.
clone()객체λ₯Ό λ³΅μ œν•œλ‹€. 단, Cloneable μΈν„°νŽ˜μ΄μŠ€ κ΅¬ν˜„μ΄ ν•„μš”ν•˜λ‹€.
finalize()객체가 GC λŒ€μƒμ΄ 되기 μ „ λ§ˆμ§€λ§‰ 정리 μž‘μ—…μ„ μ •μ˜ν•  수 μžˆλ‹€. (ν˜„μž¬λŠ” Deprecated)

이번 κΈ€μ—μ„œλŠ” 이 쀑
toString(), equals(), hashCode() λ©”μ„œλ“œμ— λŒ€ν•΄ μžμ„Ένžˆ 닀루어보렀고 ν•œλ‹€.


πŸ“Œ toString()

Object Class 내에 μ •μ˜λœ toString()

    /**
     * Returns a string representation of the object.
     * @apiNote
     * In general, the
     * {@code toString} method returns a string that
     * "textually represents" this object. The result should
     * be a concise but informative representation that is easy for a
     * person to read.
     * It is recommended that all subclasses override this method.
     * The string output is not necessarily stable over time or across
     * JVM invocations.
     * @implSpec
     * The {@code toString} method for class {@code Object}
     * returns a string consisting of the name of the class of which the
     * object is an instance, the at-sign character `{@code @}', and
     * the unsigned hexadecimal representation of the hash code of the
     * object. In other words, this method returns a string equal to the
     * value of:
     * <blockquote>
     * <pre>
     * getClass().getName() + '@' + Integer.toHexString(hashCode())
     * </pre></blockquote>
     *
     * @return  a string representation of the object.
     */
    public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

toString() λ©”μ„œλ“œλŠ” 객체λ₯Ό λ¬Έμžμ—΄λ‘œ ν‘œν˜„ν•œ 값을 λ°˜ν™˜ν•œλ‹€.
μ΄λŠ” 객체의 정보λ₯Ό μ‚¬λžŒμ΄ 읽을 수 μžˆλŠ” ν˜•νƒœλ‘œ λ³€ν™˜ν•œ λ¬Έμžμ—΄μ΄λΌκ³  ν•  수 μžˆλ‹€.

  • @return a string representation of the object.
return getClass().getName() + "@" + Integer.toHexString(hashCode());

클래슀 이름 + @ + 16μ§„μˆ˜ ν•΄μ‹œμ½”λ“œ ν˜•νƒœ 둜 λ°˜ν™˜ν•œλ‹€.

예제

Person 클래슀

static class Person {
    String name;
    int age;

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
public static void main(String[] args) {
    Person person = new Person("Sangho", 26);
    System.out.println(person.toString());
}

κ²°κ³Ό

{νŒ¨ν‚€μ§€λͺ…}.test$Person@251a69d7

λ©”μ„œλ“œμ— μ •μ˜λœ λ‚΄μš©λŒ€λ‘œ 잘 좜λ ₯λ˜λŠ” 것을 λ³Ό 수 μžˆλ‹€.
ν•˜μ§€λ§Œ 보톡 좜λ ₯을 ν•  λ•Œ, toString() 을 λͺ…μ‹œν•΄μ„œ μ‚¬μš©ν•˜λŠ” κ²½μš°λŠ” λ“œλ¬Όλ‹€.

public static void main(String[] args) {
    Person person = new Person("Sangho", 26);
    System.out.println(person);
}

κ²°κ³Ό

{νŒ¨ν‚€μ§€λͺ…}.test$Person@251a69d7

μœ„μ²˜λŸΌ toString() λ©”μ„œλ“œλ₯Ό 뢙이지 μ•Šλ”λΌλ„ λ™μΌν•œ κ²°κ³Όλ₯Ό 얻을 수 μžˆλ‹€.

κ·Έ μ΄μœ κ°€ λ¬΄μ—‡μΌκΉŒ?

System.out.println()

System.out.println() λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•˜λ©΄, μ•ˆμ „ν•˜κ²Œ 좜λ ₯을 ν•˜κΈ° μœ„ν•΄μ„œ λͺ¨λ“  νƒ€μž…μ„ λ¨Όμ € String.valueOf()둜 κ°μ‹Έκ²Œ λœλ‹€.

System.out.println(person); // λ‚΄λΆ€μ μœΌλ‘œ ↓ μ΄λ ‡κ²Œ λ™μž‘ν•¨
PrintStream.println(String.valueOf(person));

String.valueOf()

public static String valueOf(Object obj) {
    return (obj == null) ? "null" : obj.toString();
}

그리고 String.valueOf() λ©”μ„œλ“œλŠ” μœ„μ™€ 같이 λ™μž‘ν•˜κ²Œ λœλ‹€.

즉, null 인지 λ¨Όμ € μ²΄ν¬ν•˜κ³ , null이 μ•„λ‹ˆλ©΄ toString() 을 ν˜ΈμΆœν•΄μ„œ κ·Έ κ²°κ³Όλ₯Ό 좜λ ₯ν•΄μ€€λ‹€.

이 λ•Œλ¬Έμ— System.out.println() 둜 좜λ ₯을 ν•  λ•Œμ—λŠ” μžλ™μ μœΌλ‘œ toString() λ©”μ„œλ“œκΉŒμ§€ 호좜이 λ˜λŠ” 것이닀.

μ™œ μ΄λ ‡κ²Œ μ„€κ³„ν–ˆμ„κΉŒ?

그럼 String.valueOf()λ₯Ό ν˜ΈμΆœν•˜μ§€ μ•Šκ³ , λ°”λ‘œ toString()을 ν˜ΈμΆœν•΄λ„ λ˜μ§€ μ•ŠλŠλƒ ν•˜λŠ” ꢁ금증이 생길 수 μžˆλ‹€.

λ§Œμ•½ null 값에 λŒ€ν•΄μ„œ, null.toString() 을 λ°”λ‘œ ν˜ΈμΆœν•˜λ©΄ NPE(NullPointerException) κ°€ λ°œμƒν•˜κ²Œ λœλ‹€.
그렇기에 λ¨Όμ € valueOf()둜 κ°μ‹Έμ„œ μ•ˆμ „ν•˜κ²Œ null도 μ²˜λ¦¬ν•  수 있게 λ§Œλ“  것이닀.

String, int νƒ€μž… 좜λ ₯

public static void main(String[] args) {
    Person person = new Person("Sangho", 26);
    
    System.out.println(person.name);
    System.out.println(person.age);
}

κ·Έλ ‡λ‹€λ©΄ λ§Œμ•½ μœ„μ²˜λŸΌ 객체 μžμ²΄κ°€ μ•„λ‹Œ, 객체 μ•ˆμ—μ„œ 각각 νƒ€μž…μ΄ μ‘΄μž¬ν•˜λŠ” ν•„λ“œ 값을 좜λ ₯ν•  λ•ŒλŠ” μ–΄λ–»κ²Œ λ™μž‘ν• κΉŒ?

κ²°κ³Ό

Sangho
26

λͺ¨λ‘κ°€ μ•Œλ‹€μ‹œν”Ό κ·ΈλŒ€λ‘œ 값이 좜λ ₯λ˜μ–΄μ„œ λ‚˜μ˜¨λ‹€.
κ·Έ μ΄μœ λŠ” String.valueOf() λ©”μ„œλ“œκ°€ μ˜€λ²„λ‘œλ”©λ˜μ–΄ 있기 λ•Œλ¬Έμ΄λ‹€.

String.valueOf() λ©”μ„œλ“œ μ˜€λ²„λ‘œλ”©

νƒ€μž…λ©”μ„œλ“œ μ‹œκ·Έλ‹ˆμ²˜μ„€λͺ…
booleanpublic static String valueOf(boolean b)"true" λ˜λŠ” "false"둜 λ°˜ν™˜
charpublic static String valueOf(char c)문자 ν•˜λ‚˜λ₯Ό λ¬Έμžμ—΄λ‘œ λ³€ν™˜
intpublic static String valueOf(int i)Integer.toString(i) 호좜
longpublic static String valueOf(long l)Long.toString(l) 호좜
floatpublic static String valueOf(float f)Float.toString(f) 호좜
doublepublic static String valueOf(double d)Double.toString(d) 호좜
char[]public static String valueOf(char[] data)문자 λ°°μ—΄ 전체λ₯Ό λ¬Έμžμ—΄λ‘œ λ³€ν™˜ (new String(data))
char[], int, intpublic static String valueOf(char[] data, int offset, int count)문자 λ°°μ—΄μ˜ 일뢀λ₯Ό λ¬Έμžμ—΄λ‘œ λ³€ν™˜
Stringpublic static String valueOf(String s)null이면 "null", μ•„λ‹ˆλ©΄ κ·ΈλŒ€λ‘œ λ°˜ν™˜
Objectpublic static String valueOf(Object obj)null이면 "null", μ•„λ‹ˆλ©΄ obj.toString() 호좜

μ΄λŠ” String 클래슀 λ‚΄λΆ€μ—μ„œ μ‚΄νŽ΄λ³Ό 수 μžˆλ‹€.

int, long, float, double 같은 μ›μ‹œ νƒ€μž…μ€ System.out.println() λ“±μ—μ„œ λ¬Έμžμ—΄λ‘œ 좜λ ₯될 λ•Œ, λ‚΄λΆ€μ μœΌλ‘œ ν•΄λ‹Ή 래퍼 클래슀(Integer, Long, Float, Double)의 toString() λ©”μ„œλ“œλ₯Ό μ‚¬μš©ν•˜μ—¬ λ¬Έμžμ—΄λ‘œ λ³€ν™˜λœλ‹€.

μ—¬κΈ°μ„œ Integer, String 만 μ‚΄νŽ΄λ³΄λ„λ‘ ν•˜κ² λ‹€.

Integer 클래슀 λ‚΄μ˜ toString()

/**
 * Returns a {@code String} object representing this {@code Integer}'s value.
 * The value is converted to signed decimal representation and returned as a string,
 * exactly as if the integer value were given as an argument to the {@link java.lang.Integer#toString(int)} method.
 *
 * @return a string representation of the value of this object in base 10.
 */
public String toString() {
    return toString(value);
}

Integer 클래슀 λ˜ν•œ Objectλ₯Ό 상속받고 있으며, toString() λ©”μ„œλ“œλ₯Ό μ˜€λ²„λΌμ΄λ“œν•˜μ—¬ λ‚΄λΆ€ μ •μˆ˜ 값을 μ‚¬λžŒμ΄ 읽을 수 μžˆλŠ” λ¬Έμžμ—΄λ‘œ λ°˜ν™˜ν•œλ‹€.

μœ„ λ©”μ„œλ“œκ°€ μ˜€λ²„λΌμ΄λ“œ 된(@Override μƒλž΅) toString()이며, μ΄λŠ” 내뢀적인 static λ©”μ„œλ“œ toString() 을 ν•œ 번 더 ν˜ΈμΆœν•˜κ²Œ λœλ‹€.

내뢀적인 static λ©”μ„œλ“œ (radix 쑴재 X)

/**
 * Returns a {@code String} object representing the specified integer.
 * The argument is converted to signed decimal representation and returned as a string,
 * exactly as if the argument and radix 10 were given as arguments to the {@link #toString(int, int)} method.
 *
 * @param i an integer to be converted.
 * @return a string representation of the argument in base 10.
 */
@IntrinsicCandidate
public static String toString(int i) {
    int size = stringSize(i);
    if (COMPACT_STRINGS) {
        byte[] buf = new byte[size];
        getChars(i, size, buf);
        return new String(buf, LATIN1);
    } else {
        byte[] buf = new byte[size * 2];
        StringUTF16.getChars(i, size, buf);
        return new String(buf, UTF16);
    }
}

μ΄λŠ” μ •μˆ˜ 값을 10μ§„μˆ˜ λ¬Έμžμ—΄λ‘œ λΉ λ₯΄κ²Œ λ³€ν™˜ν•΄ μ£ΌλŠ” λ©”μ„œλ“œμ΄λ‹€.
Integer ν΄λž˜μŠ€κ°€ Object.toString()을 μ˜€λ²„λΌμ΄λ”©ν•  λ•Œ 이 λ©”μ„œλ“œλ₯Ό λ‚΄λΆ€μ μœΌλ‘œ ν˜ΈμΆœν•œλ‹€.

COMPACT_STRINGS μ΅œμ ν™” λΆ„κΈ°κΉŒμ§€ ν¬ν•¨λœ κ³ μ„±λŠ₯ 버전이라고 λ³Ό 수 μžˆλ‹€.

@IntrinsicCandidate

ν•΄λ‹Ή μ–΄λ…Έν…Œμ΄μ…˜μ€ JVM HotSpot이 이 λ©”μ„œλ“œλ₯Ό λ„€μ΄ν‹°λΈŒ μ½”λ“œλ‘œ 인라인 μ΅œμ ν™”ν•  수 μžˆλ‹€λŠ” 것을 μ˜λ―Έν•œλ‹€.

즉, int β†’ String λ³€ν™˜μ΄ 자주 쓰이기 λ•Œλ¬Έμ—, JIT μ»΄νŒŒμΌλŸ¬κ°€ μ•„μ£Ό λΉ λ₯΄κ²Œ μ²˜λ¦¬ν•  수 μžˆλ„λ‘ νŠΉλ³„ λŒ€μš°λ₯Ό ν•΄ μ£ΌλŠ” 것이닀.

JIT μ»΄νŒŒμΌλŸ¬μ— λŒ€ν•œ κ°„λž΅ν•œ 정리 κΈ€

내뢀적인 static λ©”μ„œλ“œ (radix 쑴재 O)

/**
 * Returns a string representation of the first argument in the radix specified by the second argument.
 * If the radix is smaller than {@code Character.MIN_RADIX} or larger than {@code Character.MAX_RADIX}, then radix 10 is used.
 *
 * @param i an integer to be converted to a string.
 * @param radix the radix to use in the string representation.
 * @return a string representation of the argument in the specified radix.
 */
public static String toString(int i, int radix) {
    if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX)
        radix = 10;
    if (radix == 10) {
        return toString(i);
    }
    if (COMPACT_STRINGS) {
        byte[] buf = new byte[33];
        boolean negative = (i < 0);
        int charPos = 32;
        if (!negative) {
            i = -i;
        }
        while (i <= -radix) {
            buf[charPos--] = (byte)digits[-(i % radix)];
            i = i / radix;
        }
        buf[charPos] = (byte)digits[-i];
        if (negative) {
            buf[--charPos] = '-';
        }
        return StringLatin1.newString(buf, charPos, (33 - charPos));
    }
    return toStringUTF16(i, radix);
}

2μ§„μˆ˜, 8μ§„μˆ˜, 16μ§„μˆ˜ λ“± μ›ν•˜λŠ” μ§„μˆ˜λ‘œ λ³€ν™˜ν•  λ•Œ μ“°λŠ” μ˜€λ²„λ‘œλ”© λ©”μ„œλ“œμ΄λ‹€.

λ§Œμ•½ radix == 10이라면 μœ„μ˜ toString(int)을 ν˜ΈμΆœν•΄μ„œ μ²˜λ¦¬ν•œλ‹€.
μ•„λ‹ˆλ©΄ digits[] λ°°μ—΄λ‘œ 직접 문자 λ³€ν™˜ν•˜κ³  StringLatin1 λ˜λŠ” toStringUTF16λ₯Ό μ‚¬μš©ν•œλ‹€.

String 클래슀 λ‚΄μ˜ toString()

/**
 * This object (which is already a string!) is itself returned.
 *
 * @return the string itself.
 */
public String toString() {
    return this;
}

String ν΄λž˜μŠ€λŠ” κ·Έ μžμ²΄κ°€ λ¬Έμžμ—΄μ΄κΈ° λ•Œλ¬Έμ—, λ³€ν™˜ κ³Όμ • 없이 κ·ΈλŒ€λ‘œ μžμ‹ μ„ λ°˜ν™˜ν•˜κ²Œ λœλ‹€. return this

이 λ˜ν•œ Object.toString() λ©”μ„œλ“œλ₯Ό μ˜€λ²„λΌμ΄λ“œ ν•œ 것이닀.


πŸ“Œ equals()

equals()λž€?

μ–΄λ– ν•œ 두 객체의 값이 같은지, 즉 λ™λ“±ν•œμ§€ μ—¬λΆ€λ₯Ό μ•Œλ €μ£ΌλŠ” λ©”μ„œλ“œμ΄λ‹€.

λ™μΌν•˜λ‹€ vs λ™λ“±ν•˜λ‹€

λ‘˜μ€ 맀우 μœ μ‚¬ν•˜μ§€λ§Œ 약간은 λ‹€λ₯Έ 점이 μ‘΄μž¬ν•œλ‹€.

μš°μ„  λ™μΌν•˜λ‹€λŠ” 것은 두 객체가 μ™„μ „νžˆ κ°™μŒμ„ μ˜λ―Έν•œλ‹€. Java둜 λ”°μ§€μžλ©΄, 객체의 μ°Έμ‘° μ£Όμ†Œκ°€ λ™μΌν•œ 객체라고 ν•  수 μžˆκ² λ‹€.

반면 λ™λ“±ν•˜λ‹€λŠ” 것은 λ…Όλ¦¬μ μœΌλ‘œ 같은 값을 κ°€μ§„λ‹€λŠ” μ˜λ―Έμ΄λ‹€. 즉 객체 μžμ²΄κ°€ μ•„λ‹ˆλΌ, 객체의 값이 같은지λ₯Ό νŒλ‹¨ν•˜κ²Œ λœλ‹€.

λ•Œλ¬Έμ— λ™μΌν•œμ§€ μ—¬λΆ€λŠ” 더블 이퀄 == 을, λ™λ“±ν•œμ§€ μ—¬λΆ€λŠ” equals()둜 νŒλ‹¨ν•˜κ²Œ λœλ‹€.

equals()λ₯Ό μ˜€λ²„λΌμ΄λ“œ ν•΄μ•Ό ν•˜λŠ” 이유

κ·Έλ ‡λ‹€λ©΄ equals() λ©”μ„œλ“œλŠ” μ™œ μ˜€λ²„λΌμ΄λ“œ ν•΄μ„œ μ‚¬μš©ν•˜λΌλŠ” κ²ƒμΌκΉŒ? μ΄λŠ” Object ν΄λž˜μŠ€μ— μžˆλŠ” 기본적인 λ™μž‘μ„ 보면 이해할 수 μžˆλ‹€.

public boolean equals(Object obj) {
	return (this == obj);
}

equals() λ‚΄λΆ€μ—μ„œλ„ == 둜 비ꡐλ₯Ό ν•œ 후에 λ°˜ν™˜ν•˜λŠ” 것을 λ³Ό 수 μžˆλ‹€. λ•Œλ¬Έμ— μ˜€λ²„λΌμ΄λ“œλ₯Ό ν•˜μ§€ μ•ŠλŠ”λ‹€λ©΄, κ·Έλƒ₯ == λ₯Ό μ‚¬μš©ν•˜λŠ” 것과 μ „ν˜€ 닀름이 μ—†λ‹€.

μ™œ μ΄λ ‡κ²Œ λ§Œλ“€μ—ˆμ„κΉŒ?

μ—¬κΈ°μ„œ ν•œ κ°€μ§€ 의문이 λ“ λ‹€.
λ‚΄λΆ€μ μœΌλ‘œ == 연산을 μ§„ν–‰ν•œ λ’€ λ°˜ν™˜ν•  거라면, λ©”μ„œλ“œλ₯Ό λ§Œλ“€μ§€ μ•Šκ³  λ°”λ‘œ μ—°μ‚°ν•˜λ©΄ μ•ˆ λ˜λŠ” κ²ƒμ΄μ—ˆμ„κΉŒ?

μ—¬κΈ°μ—λŠ” μ•„λž˜μ˜ λͺ‡ κ°€μ§€ μ΄μœ λ“€μ΄ μ‘΄μž¬ν•œλ‹€.

곡톡 μΈν„°νŽ˜μ΄μŠ€ 제곡

μžλ°”μ—μ„œ λͺ¨λ“  ν΄λž˜μŠ€λŠ” Objectλ₯Ό 상속 λ°›κ²Œ λ˜κΈ°μ—, ObjectλŠ” λͺ¨λ“  κ°μ²΄μ—μ„œ κ³΅ν†΅μ μœΌλ‘œ ν•„μš”ν•œ κΈ°λŠ₯을 μ •μ˜ν•΄μ•Ό ν–ˆλ‹€.

객체 κ°„μ˜ 동등성 λΉ„κ΅λŠ” 거의 λͺ¨λ“  ν”„λ‘œκ·Έλž¨μ—μ„œ ν•„μš”ν•  수 있기 λ•Œλ¬Έμ—, 이λ₯Ό μœ„ν•œ equals() λ©”μ„œλ“œλ₯Ό 기본으둜 μ œκ³΅ν•œ 것이닀.

μ™œ κΈ°λ³Έ κ΅¬ν˜„μ€ == 인가?

ObjectλŠ” λͺ¨λ“  클래슀의 μ΅œμƒμœ„ 클래슀이기 λ•Œλ¬Έμ—, κ·Έ 객체가 무엇을 μ˜λ―Έν•˜λŠ”μ§€, λ‚΄λΆ€ 값이 무엇을 λ‚˜νƒ€λ‚΄λŠ”μ§€ μ•Œ 수 μ—†λ‹€.

κ·Έλ ‡κΈ° λ•Œλ¬Έμ— Object μž…μž₯μ—μ„œλŠ” λ©”λͺ¨λ¦¬ 상 같은 객체인지(동일성)만 λΉ„κ΅ν•˜λŠ” 것이 κ°€μž₯ 보편적이고 μ•ˆμ „ν•œ 기본값이 λœλ‹€.

즉, λ‚΄λΆ€ κ°’ 기반 비ꡐλ₯Ό 기본으둜 μ œκ³΅ν•˜κΈ°μ—λŠ” Object μˆ˜μ€€μ—μ„œλŠ” λ„ˆλ¬΄ μΌλ°˜μ μ΄μ–΄μ„œ 적용이 λΆˆκ°€λŠ₯ν–ˆλ˜ 것이닀.

μ˜€λ²„λΌμ΄λ“œλ₯Ό 염두에 λ‘” 섀계

equals()λŠ” μ²˜μŒλΆ€ν„° ν•„μš”ν•  λ•Œ 각 ν΄λž˜μŠ€μ—μ„œ μ˜€λ²„λΌμ΄λ“œν•˜λ„λ‘ μ„€κ³„λœ λ©”μ„œλ“œμ΄λ‹€.

즉, κ°œλ°œμžκ°€ 클래슀의 μ˜λ―Έμ— 맞게 논리적 동등성을 μ •μ˜ν•˜λ„λ‘ μ˜λ„λœ 것이닀.

이 점듀은 μžλ°”μ˜ 곡식 API λ¬Έμ„œ 및 λ„μ„œμ—μ„œ μ‚΄νŽ΄λ³Ό 수 μžˆλ‹€.

레퍼런슀

1. Java SE 곡식 API λ¬Έμ„œ (Object 클래슀)

The equals method implements an equivalence relation on non-null object references.
It is recommended that all subclasses override this method.
  • equals()λŠ” λͺ¨λ“  κ°μ²΄μ—μ„œ 동등성을 ν‘œν˜„ν•  수 μžˆλ„λ‘ 제곡된 λ©”μ„œλ“œλ‹€.
  • λͺ¨λ“  μ„œλΈŒ ν΄λž˜μŠ€λŠ” ν•„μš”ν•˜λ©΄ 이 λ©”μ„œλ“œλ₯Ό μž¬μ •μ˜ν•˜λΌ(recommended).

2. Effective Java (Joshua Bloch)

Always override equals when logical equality is important.
  • 논리적 동등성이 μ€‘μš”ν•œ ν΄λž˜μŠ€μ—μ„œλŠ” equalsλ₯Ό λ°˜λ“œμ‹œ μ˜€λ²„λΌμ΄λ“œν•˜λΌ.

String Classμ—μ„œμ˜ equals()

μœ„μ—μ„œ μ‚΄νŽ΄ λ³Έ 바에 λ”°λ₯΄λ©΄, equals() λ©”μ„œλ“œλŠ” == κ³Ό λ™μΌν•œ κΈ°λŠ₯을 ν•˜κ²Œ λœλ‹€.

ν•˜μ§€λ§Œ λ– μ˜¬λ € 보면 μš°λ¦¬λ“€μ€ 두 String νƒ€μž… λ¬Έμžμ—΄μ„ 비ꡐ할 λ•Œ μžμ—°μŠ€λŸ½κ²Œ equals()λ₯Ό μ‚¬μš©ν•œλ‹€.

μ˜€λ²„λΌμ΄λ“œ ν•œ 기얡은 μ—†λŠ”λ°, μ–΄λ–»κ²Œ 된 일일까?

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    return (anObject instanceof String aString)
            && (!COMPACT_STRINGS || this.coder == aString.coder)
            && StringLatin1.equals(value, aString.value);
}

κ·Έ μ΄μœ λŠ”, String ν΄λž˜μŠ€μ—μ„œ 이미 equals()λ₯Ό μ˜€λ²„λΌμ΄λ“œ ν•΄λ‘μ—ˆκΈ° λ•Œλ¬Έμ΄λ‹€. μ΄λŠ” Integer와 같은 λŒ€λΆ€λΆ„μ˜ 래퍼 ν΄λž˜μŠ€μ—μ„œ μ μš©λ˜λŠ” λ‚΄μš©μΈλ°, μ—¬κΈ°μ„œλŠ” String만 μ•Œμ•„λ³΄λ €κ³  ν•œλ‹€.

λ™μž‘μ€ μ•„λž˜μ™€ κ°™λ‹€.

  1. this == anObject
  • λ¨Όμ € 동일성(μ°Έμ‘°κ°’)을 λΉ„κ΅ν•œλ‹€.
  • μ°Έμ‘° 값이 κ°™λ‹€λ©΄ μ™„μ „νžˆ λ™μΌν•œ κ°μ²΄μ΄λ―€λ‘œ trueλ₯Ό λ°”λ‘œ λ°˜ν™˜ν•œλ‹€.
  1. instanceof String
  • anObjectκ°€ String μΈμŠ€ν„΄μŠ€μΈμ§€ ν™•μΈν•œλ‹€.
  • String 클래슀 λ‚΄λΆ€ λ©”μ„œλ“œμ΄λ―€λ‘œ, λ§Œμ•½ μ•„λ‹ˆλΌλ©΄ falseλ₯Ό λ°˜ν™˜ν•œλ‹€.
  1. coder 비ꡐ (Compact String μ΅œμ ν™”)
  • COMPACT_STRINGS κ°€ ν™œμ„±ν™”λœ 경우 문자 인코딩 coder 이 같은지 ν™•μΈν•œλ‹€.
  • λ‚΄λΆ€μ—μ„œ Latin1인지 UTF-16인지 κ΅¬λΆ„ν•˜κΈ° μœ„ν•œ μ΅œμ ν™” 단계이닀.
  1. StringLatin1.equals() 호좜
  • λ§ˆμ§€λ§‰μœΌλ‘œ λ‚΄λΆ€ value λ°°μ—΄(문자 데이터)을 λΉ„κ΅ν•˜μ—¬ λ‚΄μš©μ΄ 같은지λ₯Ό νŒλ‹¨ν•œλ‹€.

StringLatin1.equals()

@IntrinsicCandidate
public static boolean equals(byte[] value, byte[] other) {
    if (value.length == other.length) {
        for (int i = 0; i < value.length; i++) {
            if (value[i] != other[i]) {
                return false;
            }
        }
        return true;
    }
    return false;
}

μ΅œμ’…μ μœΌλ‘œ ν˜ΈμΆœλ˜λŠ” λ©”μ„œλ“œλŠ” μ•„λž˜μ™€ 같이 λ™μž‘ν•œλ‹€.

  1. λ°°μ—΄ 길이λ₯Ό λ¨Όμ € λΉ„κ΅ν•˜λ©°, 길이가 λ‹€λ₯΄λ©΄ λ°”λ‘œ falseλ₯Ό λ°˜ν™˜ν•œλ‹€.
  2. 길이가 κ°™μœΌλ©΄ 각 byteλ₯Ό 순차적으둜 λΉ„κ΅ν•œλ‹€.
  3. ν•˜λ‚˜λΌλ„ λ‹€λ₯΄λ©΄ false, λͺ¨λ‘ κ°™μœΌλ©΄ trueλ₯Ό λ°˜ν™˜ν•œλ‹€.

결과적으둜 String ν΄λž˜μŠ€λŠ” 이미 equals()λ₯Ό 적절히 μ˜€λ²„λΌμ΄λ“œν•˜κ³  μ΅œμ ν™” κΈ°λŠ₯κΉŒμ§€ μ œκ³΅ν•˜λ―€λ‘œ, μš°λ¦¬λŠ” νŽΈλ¦¬ν•˜κ²Œ 호좜만 ν•˜μ—¬ λ¬Έμžμ—΄ λ‚΄μš©μ„ 비ꡐ할 수 μžˆλŠ” 것이닀.

πŸ§‘πŸ»β€πŸ’» μœ„μ—μ„œ μ–ΈκΈ‰λœ coder, COMPACT_STRINGS, Latin1, UTF-16 κΉŒμ§€ 이 κΈ€μ—μ„œ 닀루기엔 λ„ˆλ¬΄ λ°©λŒ€ν•΄μ§ˆ 것 κ°™μ•„ λ”°λ‘œ λΉΌλ €κ³  ν•œλ‹€.

μ°Έκ³ ν•˜λ©΄ 쒋을 κΈ€


πŸ“Œ hashCode()

hashCode() λ©”μ„œλ“œλŠ” 객체λ₯Ό ν•΄μ‹œ 기반 자료ꡬ쑰(HashMap, HashSet λ“±)에 μ €μž₯ν•˜κ±°λ‚˜ 검색할 λ•Œ μ‚¬μš©λ˜λŠ” ν•΄μ‹œ 값을 λ°˜ν™˜ν•˜λŠ” λ©”μ„œλ“œμ΄λ‹€.

ν•΄μ‹œ κ°’μ΄λž€?

ν•΄μ‹œ κ°’ μ΄λž€ μΌμ •ν•œ κ·œμΉ™μ— 따라 계산해 얻은 κ³ μ • 크기의 숫자(보톡 μ •μˆ˜ intν˜•)λ₯Ό μ˜λ―Έν•œλ‹€.

이 값은 λ°μ΄ν„°μ˜ λ‚΄μš©μ„ μ§§κ³  κ³ μœ ν•œ 숫자둜 μš”μ•½ν•œ 것이라고 λ³Ό 수 μžˆλ‹€. λ™μΌν•œ λ°μ΄ν„°λŠ” λ™μΌν•œ ν•΄μ‹œ 값을 κ°€μ§€λ©°, λ‹€λ₯Έ λ°μ΄ν„°λŠ” λ˜λ„λ‘μ΄λ©΄ λ‹€λ₯Έ ν•΄μ‹œ 값을 가지도둝 μ„€κ³„λœλ‹€.

μ—¬κΈ°μ„œ 'λ˜λ„λ‘'이라고 ν‘œν˜„ν•œ μ΄μœ λŠ”, λΆˆκ°€ν”Όν•˜κ²Œ ν•΄μ‹œ 값이 같은 κ²½μš°κ°€ λ°œμƒν•  수 있기 λ•Œλ¬Έμ΄λ‹€.

μ™œ ν•„μš”ν•œκ°€?

μ΄λŸ¬ν•œ ν•΄μ‹œ 값은 μ™œ λ§Œλ“€κ³ , μ™œ μ“°λŠ” κ²ƒμΌκΉŒ? μ΄μœ λŠ” μ•„λž˜μ™€ κ°™λ‹€.

  1. ν•΄μ‹œ 기반 자료ꡬ쑰(예: HashMap, HashSet)λŠ” ν•΄μ‹œ 값을 ν™œμš©ν•΄μ„œ λ°μ΄ν„°μ˜ μœ„μΉ˜λ₯Ό λΉ λ₯΄κ²Œ 찾을 수 μžˆλ‹€.
  2. 데이터가 크더라도 짧은 ν•΄μ‹œ κ°’λ§Œ λΉ„κ΅ν•˜λ©΄ λ˜λ―€λ‘œ μ„±λŠ₯이 더 μ’‹λ‹€.

즉, hashCode() μ—μ„œ λ°˜ν™˜ν•˜λŠ” 값은 객체의 λ‚΄μš©μ„ 기반으둜 κ³„μ‚°λœ μ •μˆ˜μ΄λ©°, ν•΄μ‹œ ν…Œμ΄λΈ”μ—μ„œ λΉ λ₯΄κ²Œ 데이터λ₯Ό μ°ΎκΈ° μœ„ν•œ μ£Όμ†Œ 역할을 ν•˜κ²Œ λœλ‹€.

Object 클래슀의 hashCode()

그럼 μ΄λŸ¬ν•œ ν•΄μ‹œ 값은 μ–΄λ– ν•œ 과정을 ν†΅ν•΄μ„œ λ§Œλ“€μ–΄μ§€λŠ” 걸까? μ§€κΈˆλΆ€ν„°λŠ” 이에 λŒ€ν•΄ νŒŒμ•…ν•΄λ³΄κ³ μž ν•œλ‹€.

@IntrinsicCandidate
public native int hashCode();

Object ν΄λž˜μŠ€μ—μ„œ μ •μ˜λ˜μ–΄ μžˆλŠ” hashCode() λ©”μ„œλ“œλŠ” μœ„μ™€ κ°™λ‹€.

native λ©”μ„œλ“œ

μƒμ†Œν•œ native λΌλŠ” 뢀뢄이 보일 것이닀.

native λ©”μ„œλ“œλŠ” μžλ°” μ½”λ“œλ‘œ κ΅¬ν˜„λ˜μ–΄ μžˆμ§€ μ•Šκ³ , JVM λ‚΄λΆ€(λ„€μ΄ν‹°λΈŒ μ½”λ“œ) 에 κ΅¬ν˜„λ˜μ–΄ μžˆλ‹€. JNI(Java Native Interface) λ₯Ό 톡해 JVM 레벨, μ‹œμŠ€ν…œ λ ˆλ²¨μ—μ„œ μž‘λ™ν•˜κ²Œ λ˜λŠ” 것이닀.

κΈ°λ³Έμ μœΌλ‘œλŠ” 객체의 λ©”λͺ¨λ¦¬ μ£Όμ†Œλ‚˜ κ·Έ μ£Όμ†Œλ₯Ό 기반으둜 κ³„μ‚°ν•œ 수치λ₯Ό ν•΄μ‹œ μ½”λ“œλ‘œ λ°˜ν™˜ν•˜κ²Œ λ˜λŠ”λ°, JVM κ΅¬ν˜„λ§ˆλ‹€ 방식이 λ‹€λ₯Ό μˆ˜κ°€ μžˆλ‹€.

String 클래슀의 hashCode()

String ν΄λž˜μŠ€μ—μ„œ hashCode() λ©”μ„œλ“œκ°€ μ–΄λ–»κ²Œ μ˜€λ²„λΌμ΄λ“œ λ˜μ–΄ μžˆλŠ”μ§€ μ•Œμ•„λ³΄λ„λ‘ ν•˜μž.

/**
 * Returns a hash code for this string. The hash code for a
 * {@code String} object is computed as
 * <blockquote><pre>
 * s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
 * </pre></blockquote>
 * using {@code int} arithmetic, where {@code s[i]} is the
 * <i>i</i>th character of the string, {@code n} is the length of
 * the string, and {@code ^} indicates exponentiation.
 * (The hash value of the empty string is zero.)
 *
 * @return a hash code value for this object.
 */
public int hashCode() {
    // The hash or hashIsZero fields are subject to a benign data race,
    // making it crucial to ensure that any observable result of the
    // calculation in this method stays correct under any possible read of
    // these fields. Necessary restrictions to allow this to be correct
    // without explicit memory fences or similar concurrency primitives is
    // that we can ever only write to one of these two fields for a given
    // String instance, and that the computation is idempotent and derived
    // from immutable state
    int h = hash;
    if (h == 0 && !hashIsZero) {
        h = isLatin1() ? StringLatin1.hashCode(value)
                       : StringUTF16.hashCode(value);
        if (h == 0) {
            hashIsZero = true;
        } else {
            hash = h;
        }
    }
    return h;
}

λ™μž‘ 과정은 μ•„λž˜μ™€ κ°™λ‹€.

int h = hash;

hashλŠ” String μΈμŠ€ν„΄μŠ€ λ‚΄λΆ€μ˜ μΊμ‹±λœ ν•΄μ‹œ μ½”λ“œ κ°’μœΌλ‘œ, μ΅œμ΄ˆμ—λŠ” 0으둜 μ΄ˆκΈ°ν™”λ˜μ–΄ μžˆλ‹€.

if (h == 0 && !hashIsZero) {

hash 값이 0이고, hashIsZero ν”Œλž˜κ·Έλ„ false일 λ•Œλ§Œ μƒˆλ‘œ ν•΄μ‹œ μ½”λ“œλ₯Ό κ³„μ‚°ν•œλ‹€.

μ™œλƒν•˜λ©΄ 빈 λ¬Έμžμ—΄μ˜ ν•΄μ‹œ 값은 0이 λ˜λŠ”λ°, 이λ₯Ό κ΅¬λΆ„ν•˜κΈ° μœ„ν•΄ hashIsZero ν”Œλž˜κ·Έλ₯Ό λ”°λ‘œ λ‘” 것이닀.

h = isLatin1() ? StringLatin1.hashCode(value)
               : StringUTF16.hashCode(value);

isLatin1()으둜 λ‚΄λΆ€ 인코딩 방식을 ν™•μΈν•˜κ²Œ λœλ‹€.

  1. Latin1이면 StringLatin1.hashCode() 호좜
public static int hashCode(byte[] value) {
    int h = 0;
    for (byte v : value) {
        h = 31 * h + (v & 0xff);
    }
    return h;
}

1λ°”μ΄νŠΈμ”© 읽으며 (v & 0xff)둜 λΆ€ν˜Έ μ—†λŠ” κ°’μœΌλ‘œ λ³€ν™˜ ν›„ 31 * h + v λ°©μ‹μœΌλ‘œ ν•΄μ‹œ 값을 κ³„μ‚°ν•œλ‹€.

  1. UTF-16이면 StringUTF16.hashCode() 호좜
public static int hashCode(byte[] value) {
    int h = 0;
    int length = value.length >> 1; // 2λ°”μ΄νŠΈμ”© λ¬Άμ–΄μ„œ char λ‹¨μœ„λ‘œ 처리
    for (int i = 0; i < length; i++) {
        h = 31 * h + getChar(value, i); // UTF-16μ—μ„œ char 값을 κ°€μ Έμ˜΄
    }
    return h;
}

2λ°”μ΄νŠΈ(char) λ‹¨μœ„λ‘œ 읽으며 getChar()λ₯Ό 톡해 char 값을 μΆ”μΆœν•˜κ³  λ™μΌν•˜κ²Œ 31 * h + char λ°©μ‹μœΌλ‘œ ν•΄μ‹œ 값을 κ³„μ‚°ν•œλ‹€.

if (h == 0) {
    hashIsZero = true;
} else {
    hash = h;
}

κ³„μ‚°λœ ν•΄μ‹œ 값이 0이면, 빈 λ¬Έμžμ—΄μ΄κ±°λ‚˜ μ‹€μ œλ‘œ 값이 0인 κ²½μš°μ΄λ―€λ‘œ hashIsZero = true 둜 ν”Œλž˜κ·Έλ₯Ό μ„€μ •ν•œλ‹€.

κ·Έλ ‡μ§€ μ•Šλ‹€λ©΄, hash에 μΊμ‹±ν•œλ‹€.

return h;

μ΅œμ’…μ μœΌλ‘œ 계산 or μΊμ‹±λœ ν•΄μ‹œ 값을 λ°˜ν™˜ν•œλ‹€.


πŸ“Œ equals() & hashCode(), μ™œ 같이 μž¬μ •μ˜ν•΄μ•Ό ν•˜λŠ”κ°€?

equals() λ©”μ„œλ“œλ₯Ό μž¬μ •μ˜ν•œλ‹€λ©΄, hashCode() λ©”μ„œλ“œλ„ ν•¨κ»˜ μž¬μ •μ˜ν•΄μ•Ό ν•œλ‹€λŠ” 말을 λ“€μ–΄λ³΄μ•˜μ„ 것이닀.

μ§€κΈˆλΆ€ν„°λŠ” κ·Έ μ΄μœ μ— λŒ€ν•΄μ„œ μ˜ˆμ‹œμ™€ ν•¨κ»˜ μ•Œμ•„λ³΄κ³ μž ν•œλ‹€.

equals() μž¬μ •μ˜

Person 클래슀

import java.util.Objects;

public class Person {

    int age;
    String name;

    public Person(int age, String name) {
        this.age = age;
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age && Objects.equals(name, person.name);
    }
}

equals() λ©”μ„œλ“œλ₯Ό μž¬μ •μ˜ν•œ Person ν΄λž˜μŠ€μ΄λ‹€.
age와 name이 λͺ¨λ‘ 같은 κ²½μš°μ—λ§Œ λ™λ“±ν•˜λ‹€κ³  νŒλ‹¨λœλ‹€.

πŸ’‘ Objects.equals() λ©”μ„œλ“œκ°€ 속해 μžˆλŠ” java.util.Objects ν΄λž˜μŠ€λŠ”, Java 7λΆ€ν„° μΆ”κ°€λœ μœ ν‹Έλ¦¬ν‹° ν΄λž˜μŠ€μ΄λ‹€.
null 처리λ₯Ό μ•ˆμ „ν•˜κ³  κ°„κ²°ν•˜κ²Œ ν•˜κ±°λ‚˜, 객체 κ΄€λ ¨ 곡톡 μž‘μ—…μ„ λ„μ™€μ£ΌλŠ” 정적 λ©”μ„œλ“œλ“€μ„ μ œκ³΅ν•œλ‹€.

ν…ŒμŠ€νŠΈ

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class test {

    public static void main(String[] args) {
        Person person1 = new Person(21, "sangho");
        Person person2 = new Person(21, "sangho");

        // == 비ꡐ
        System.out.println(person1 == person2);  // false
        // equals 비ꡐ
        System.out.println(person1.equals(person2));  // true

        // List에 μΆ”κ°€ ν›„ 크기 좜λ ₯
        List<Person> list = new ArrayList<>();
        list.add(person1);
        list.add(person2);
        System.out.println("List size : " + list.size());  // 2

        // HashSet에 μΆ”κ°€ ν›„ 크기 좜λ ₯
        Set<Person> set = new HashSet<>();
        set.add(person1);
        set.add(person2);
        System.out.println("HashSet size : " + set.size());  // 2 (hashCode() μ˜€λ²„λΌμ΄λ“œ μ•ˆ ν–ˆμœΌλ―€λ‘œ 2)
    }
}
  1. age, name 이 같은 두 객체λ₯Ό μƒμ„±ν•œλ‹€.
  1. == 둜 λΉ„κ΅ν•œλ‹€.
  • 객체의 μ°Έμ‘° μ£Όμ†Œλ‘œ λΉ„κ΅ν•˜κ²Œ λ˜λ―€λ‘œ, λ‹€λ₯Έ 객체이기에 falseκ°€ 좜λ ₯λœλ‹€.
  1. equals() 둜 λΉ„κ΅ν•œλ‹€.
  • Person ν΄λž˜μŠ€μ—μ„œ equals() λ©”μ„œλ“œλ₯Ό μž¬μ •μ˜ν•˜μ˜€μœΌλ―€λ‘œ, trueκ°€ 좜λ ₯λœλ‹€.
  1. 두 객체λ₯Ό ArrayList 에 λ„£κ³  μ‚¬μ΄μ¦ˆλ₯Ό 좜λ ₯ν•œλ‹€.
  • νŠΉμ •ν•œ 쑰건 없이 μ‚½μž…ν•˜λ―€λ‘œ, μ‚¬μ΄μ¦ˆλŠ” 2κ°€ 좜λ ₯λœλ‹€.
  1. 두 객체λ₯Ό HashSet 에 λ„£κ³  μ‚¬μ΄μ¦ˆλ₯Ό 좜λ ₯ν•œλ‹€.
  • HashSet은 λ™λ“±ν•œ κ°μ²΄λ‚˜ 값에 λŒ€ν•΄μ„œ 쀑볡을 ν—ˆμš©ν•˜μ§€ μ•ŠλŠ”λ‹€.
  • λ•Œλ¬Έμ— κ²°κ³ΌλŠ” 1이 λ‚˜μ˜¬ κ²ƒμœΌλ‘œ μ˜ˆμƒν–ˆμœΌλ‚˜, μ‚¬μ΄μ¦ˆκ°€ 2κ°€ 좜λ ₯λ˜μ—ˆλ‹€.

κ·Έ μ΄μœ κ°€ λ¬΄μ—‡μΌκΉŒ?
μ΄λŠ” λ‘˜μ˜ ν•΄μ‹œ μ½”λ“œκ°€ λ‹€λ₯΄κΈ° λ•Œλ¬Έμ΄λ‹€.

hashCode(), equals() λ™μž‘ μˆœμ„œ

ν•΄μ‹œ 값을 μ‚¬μš©ν•˜λŠ” μ»¬λ ‰μ…˜λ“€(HashMap, HashSet ..) 은 객체가 λ…Όλ¦¬μ μœΌλ‘œ λ™λ“±ν•œμ§€λ₯Ό νŒλ³„ν•  λ•Œ μ•„λž˜μ™€ 같은 과정을 κ±°μΉœλ‹€.

  1. μš°μ„  두 객체의 ν•΄μ‹œ μ½”λ“œλ₯Ό λΉ„κ΅ν•œλ‹€.
  • μ—¬κΈ°μ„œ λ‹€λ₯΄λ‹€λ©΄ λ°”λ‘œ λ‹€λ₯Έ 객체둜 νŒλ³„ν•œλ‹€.
  1. λ‹€μŒμœΌλ‘œ equals() λ©”μ„œλ“œμ˜ 리턴 κ°’μœΌλ‘œ μ΅œμ’… νŒλ³„ν•œλ‹€.

즉, equals()λŠ” μž¬μ •μ˜ ν•˜μ˜€κΈ°μ— true둜 λ‚˜μ˜€κ² μ§€λ§Œ, 이미 κ·Έ 이전에 두 객체의 ν•΄μ‹œ μ½”λ“œκ°€ λ‹€λ₯΄κΈ°μ— λ™λ“±ν•˜μ§€ μ•Šλ‹€κ³  νŒλ³„λœ 것이닀.

hashCode() μž¬μ •μ˜

그럼 이제 hashCode()도 μž¬μ •μ˜λ₯Ό ν•œ ν›„ κ²°κ³Όκ°€ μ–΄λ–»κ²Œ λ‚˜μ˜€λŠ”μ§€ μ‚΄νŽ΄λ³΄μž.

Person 클래슀

import java.util.Objects;

public class Person {

    int age;
    String name;

    public Person(int age, String name) {
        this.age = age;
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age && Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(age, name);
    }
}

hashCode()λ₯Ό μž¬μ •μ˜ν•œ λͺ¨μŠ΅μ΄λ©°, Objects.hash() λ©”μ„œλ“œλ₯Ό μ‚¬μš©ν•˜μ˜€λ‹€.

Objects.hash() λ©”μ„œλ“œ

public static int hash(Object... values) {
	return Arrays.hashCode(values);
}

Objects 클래슀의 hash() λ©”μ„œλ“œλŠ” μ΄λ ‡κ²Œ μ—¬λŸ¬ νŒŒλΌλ―Έν„°λ₯Ό 받을 수 μžˆλ„λ‘ κ΅¬ν˜„λ˜μ–΄ μžˆλ‹€.

κ°€λ³€ 인자λ₯Ό λ°›μ•„ 배열을 λ§Œλ“€κ³  μ²˜λ¦¬ν•˜κΈ° λ•Œλ¬Έμ—, λ‹€μ†Œ μ˜€λ²„ν—€λ“œ(λΆˆν•„μš”ν•œ λ°°μ—΄ 생성 λΉ„μš©)κ°€ μžˆμ„ μˆ˜λŠ” μžˆλ‹€κ³  ν•œλ‹€.

그리고 이λ₯Ό Arrays.hashCode() λ©”μ„œλ“œλ‘œ λ„˜κΈ΄λ‹€.

Arrays.hashCode() λ©”μ„œλ“œ

public static int hashCode(Object a[]) {
	if (a == null)
    	return 0;

	int result = 1;

	for (Object element : a)
		result = 31 * result + (element == null ? 0 : element.hashCode());

	return result;
}

λ“€μ–΄μ˜¨ νŒŒλΌλ―Έν„°λ“€μ— λŒ€ν•΄μ„œ ν•΄μ‹œ 연산을 μˆ˜ν–‰ν•œ ν›„, ν•΄μ‹œ μ½”λ“œλ₯Ό λ°˜ν™˜ν•œλ‹€.

ν…ŒμŠ€νŠΈ

package equals;

import java.util.HashSet;
import java.util.Set;

public class test {

    public static void main(String[] args) {
        Person person1 = new Person(21, "sangho");
        Person person2 = new Person(21, "sangho");

        // hashCode 좜λ ₯
        System.out.println("person1 hashCode : " + person1.hashCode()); // -909652454
        System.out.println("person2 hashCode : " + person2.hashCode()); // -909652454

        // HashSet에 μΆ”κ°€ ν›„ 크기 좜λ ₯
        Set<Person> set = new HashSet<>();
        set.add(person1);
        set.add(person2);
        System.out.println("HashSet size : " + set.size());
    }
}

ν…ŒμŠ€νŠΈ κ²°κ³Ό, 두 객체의 ν•΄μ‹œ μ½”λ“œκ°€ λ™μΌν•˜κ³  그둜 인해 HashSet에도 1개의 객체만 μ •μƒμ μœΌλ‘œ λ“€μ–΄κ°€ μžˆλŠ” λͺ¨μŠ΅μ„ λ³Ό 수 μžˆλ‹€.

정리

  1. HashSet, HashMapκ³Ό 같은 ν•΄μ‹œ 기반 μžλ£Œκ΅¬μ‘°λŠ” λ‚΄λΆ€μ μœΌλ‘œ hashCode()둜 λ¨Όμ € 후보λ₯Ό 쒁히고 equals()둜 μ΅œμ’… 비ꡐλ₯Ό ν•œλ‹€.
  1. equals()만 μž¬μ •μ˜ν•˜κ³  hashCode()λ₯Ό μž¬μ •μ˜ν•˜μ§€ μ•ŠμœΌλ©΄, equals()κ°€ true라도 μ„œλ‘œ λ‹€λ₯Έ 버킷에 μ €μž₯λœλ‹€.

-> 이둜 인해 λ…Όλ¦¬μ μœΌλ‘œ 같은 객체가 쀑볡 μ‚½μž…λ˜λŠ” λ¬Έμ œκ°€ λ°œμƒν•˜κ²Œ λœλ‹€.

  1. Java의 κ·œμ•½μ€ equals()κ°€ trueλ©΄ hashCode()도 κ°™μ•„μ•Ό ν•œλ‹€κ³  λͺ…μ‹œν•˜κ³  μžˆλ‹€.

이 κ·œμ•½μ„ μ–΄κΈ°λ©΄ ν•΄μ‹œ 기반 μžλ£Œκ΅¬μ‘°μ—μ„œ 예기치 λͺ»ν•œ λ™μž‘(쀑볡, 검색 μ‹€νŒ¨ λ“±)이 λ°œμƒν•  수 μžˆλ‹€.

identityHashCode() λ©”μ„œλ“œ

hashCode()λ₯Ό μž¬μ •μ˜ν•˜κ²Œ 되면, 객체의 식별 값이 μ•„λ‹Œ -> 클래슀의 논리적 동등성 기쀀에 맞좰 ν•΄μ‹œ μ½”λ“œλ₯Ό λ°˜ν™˜ν•˜κ²Œ λœλ‹€.

ν•˜μ§€λ§Œ λ•Œλ‘œλŠ” JVM λ‚΄λΆ€, 디버깅, λ©”λͺ¨λ¦¬ 뢄석 λ“±μ˜ 이유둜 객체 자체의 식별 κ°’(ν•΄μ‹œ μ½”λ“œ)이 ν•„μš”ν•  λ•Œκ°€ μžˆλ‹€.

이런 κ²½μš°μ—μ„œλŠ” Java의 identityHashCode() λ©”μ„œλ“œλ₯Ό μ΄μš©ν•  수 μžˆλ‹€.

identityHashCode()

/**
 * Returns the same hash code for the given object as
 * would be returned by the default method hashCode(),
 * whether or not the given object's class overrides
 * hashCode().
 * The hash code for the null reference is zero.
 *
 * @param x object for which the hashCode is to be calculated
 * @return  the hashCode
 * @since   1.1
 * @see Object#hashCode
 * @see java.util.Objects#hashCode(Object)
 */
@IntrinsicCandidate
public static native int identityHashCode(Object x);

μ΄λŠ” Object 클래슀의 hashCode() λ©”μ„œλ“œμ™€ 둜직이 λ™μΌν•˜λ‹€. λ˜ν•œ Objects 클래슀의 hashCode()와도 μœ μ‚¬ν•œλ°, λ•Œλ¬Έμ— 주석에 이에 λŒ€ν•œ λ‚΄μš©μ΄ κΈ°μž¬λ˜μ–΄ μžˆλ‹€.

ν…ŒμŠ€νŠΈ

public class test {

    public static void main(String[] args) {
        Person person1 = new Person(21, "sangho");
        Person person2 = new Person(21, "sangho");

        // μž¬μ •μ˜λœ hashCode 좜λ ₯
        System.out.println("person1 hashCode : " + person1.hashCode()); // -909652454
        System.out.println("person2 hashCode : " + person2.hashCode()); // -909652454

        // identityHashCode 좜λ ₯ (μ£Όμ†Œ 기반 ν•΄μ‹œ κ°’)
        System.out.println("person1 identityHashCode : " + System.identityHashCode(person1)); // 1159190947
        System.out.println("person2 identityHashCode : " + System.identityHashCode(person2)); // 925858445
    }
}

ν…ŒμŠ€νŠΈ κ²°κ³Ό, identityHashCode()둜 좜λ ₯ν•œ 두 객체의 ν•΄μ‹œ μ½”λ“œλŠ” λ‹€λ₯Έ 것을 λ³Ό 수 μžˆλ‹€.

μ΄λŠ” μ˜€λ²„λΌμ΄λ”©κ³Ό λ¬΄κ΄€ν•˜κ²Œ, 객체 자체의 ν•΄μ‹œ μ½”λ“œμ΄κΈ° λ•Œλ¬Έμ— λͺ¨λ“  객체듀에 λŒ€ν•΄μ„œ 항상 λ‹€λ₯Έ ν•΄μ‹œ μ½”λ“œλ₯Ό λ°˜ν™˜ν•  것을 보μž₯ν•œλ‹€.

πŸ§‘πŸ»β€πŸ’» ν•˜μ§€λ§Œ κ³Όμ—° λͺ¨λ“  객체듀은 μ„œλ‘œ λ‹€λ₯Έ ν•΄μ‹œ μ½”λ“œλ₯Ό κ°€μ§ˆκΉŒ? λ‹€μŒ 글은 이에 λŒ€ν•΄ μ•Œμ•„λ³΄κ³ μž ν•œλ‹€.


μ°Έκ³ ν•œ λΈ”λ‘œκ·Έ 1
μ°Έκ³ ν•œ λΈ”λ‘œκ·Έ 2
μ°Έκ³ ν•œ λΈ”λ‘œκ·Έ 3

profile
μ•ˆλ…•ν•˜μ„Έμš”. λΉ„μ¦ˆλ‹ˆμŠ€λ₯Ό μ΄ν•΄ν•˜λŠ” λ°±μ—”λ“œ 개발자, ν•œμƒν˜Έμž…λ‹ˆλ‹€.

0개의 λŒ“κΈ€