πŸ§…Java equals() λ©”μ†Œλ“œλ₯Ό 까보자

무심코·2023λ…„ 3μ›” 29일
2

μ—¬λŠλ•Œμ™€ 같이 ν‰ν™”λ‘­κ²Œ κ°œλ°œν•˜λ˜ 쀑 κ°‘μžκΈ° equals() 에 λŒ€ν•œ ꢁ금증이 μƒκ²ΌμŠ΅λ‹ˆλ‹€.

== μ—°μ‚°μžλŠ” λΉ„κ΅ν•˜κ³ μž ν•˜λŠ” λ‘κ°œμ˜ λŒ€μƒμ˜ μ£Όμ†Œκ°’μ„ λΉ„κ΅ν•˜λŠ”λ° λ°˜ν•΄ String클래슀의 equals λ©”μ†Œλ“œλŠ” λΉ„κ΅ν•˜κ³ μž ν•˜λŠ” λ‘κ°œμ˜ λŒ€μƒμ˜ κ°’ 자체λ₯Ό λΉ„κ΅ν•œλ‹€.

μœ„μ™€ 같은 μ„€λͺ…은 읡히 μ•Œκ³  μžˆλŠ” μ„€λͺ…일 κ²ƒμž…λ‹ˆλ‹€. Primitive Type이 아닐 경우 == 둜 두 λŒ€μƒμ„ λΉ„κ΅ν•˜κ²Œ 되면 κ°’ κ·Έ 자체(λŒ€μ²΄λ‘œ μ‚¬λžŒλ“€μ΄ λΉ„κ΅ν•˜κΈ° μ›ν•˜λŠ”)λ₯Ό λΉ„κ΅ν•˜μ§€ μ•Šκ³  두 λŒ€μƒμ˜ μ£Όμ†Œκ°’μ„ λΉ„κ΅ν•œλ‹€λŠ” 것이죠. κ·Έλž˜μ„œ 으레 Stringμ΄λ‚˜ Wrapper 클래슀λ₯Ό μ΄μš©μ‹œ == 을 λŒ€μ‹ ν•΄μ„œ equals() λ©”μ†Œλ“œλ₯Ό μ‚¬μš©ν•˜μ—¬ λΉ„κ΅ν•©λ‹ˆλ‹€.

κ·Έλ ‡λ‹€λ©΄ equals() λ©”μ†Œλ“œλŠ” μ–΄λ–€ λ°©μ‹μœΌλ‘œ λ™μž‘ν•˜λŠ” κ²ƒμΌκΉŒμš”?

equals() λ©”μ†Œλ“œλ₯Ό 까보기 전에 λ¨Όμ € equals() κ°€ μ–΄λ””μ„œ λΆ€ν„° μ™”λŠ”μ§€λΆ€ν„° μ‚΄νŽ΄λ³΄κ³  κ°€λ΄…μ‹œλ‹€.

Object Classμ—μ„œ equals()

λͺ¨λ“  클래슀의 근원(root)이 λ˜λŠ” λ§ˆλ” 클래슀인 'Object'μ—λŠ” 총 11개의 λ©”μ†Œλ“œκ°€ μ‘΄μž¬ν•˜λŠ”λ°(waitκ°€ μ˜€λ²„λ‘œλ”© 된 것을 ν•˜λ‚˜λ‘œ 보면 9개) κ·Έ 쀑 ν•˜λ‚˜κ°€ 이 equals() λ©”μ†Œλ“œ μž…λ‹ˆλ‹€.

Objectμ—μ„œ equals() λŠ” μœ„μ™€ 같이 λ”± ν•œ μ€„μ˜ μ½”λ“œλ‘œ μ •μ˜λ˜μ–΄ μžˆλŠ”λ°..

	return (this == obj);

κ·Έλƒ₯ μ•„μ£Ό μ‹¬ν”Œν•˜κ²Œ μ°Έμ‘°κ°’(객체의 μ£Όμ†Œκ°’)이 같은지, λ‹€μ‹œ λ§ν•΄μ„œ 동일 κ°μ²΄μΈμ§€λ§Œμ„ ν™•μΈν•˜λŠ” λ©”μ†Œλ“œμž…λ‹ˆλ‹€.

μ–΄ 그러면 처음 equals() λ₯Ό == λŒ€μ‹  μ‚¬μš©ν•˜λŠ” μ΄μœ κ°€ μ—†μžλ‚˜? 라고 생각이 되죠.
λ§žμŠ΅λ‹ˆλ‹€. Object ν΄λž˜μŠ€μ—μ„œ equals() λŠ” == μ—°μ‚°μžμ™€ μ „ν˜€ λ‹€λ₯Ό λ°”κ°€ μ—†μŠ΅λ‹ˆλ‹€(μ½”λ“œκ°€ κ°™μ•„μ„œμš”). κ·Έλž˜μ„œ μ‹€μ§ˆμ μΈ 값을 λΉ„κ΅ν•΄μ€˜μ•Ό ν•˜λŠ” λ‹€λ₯Έ ν΄λž˜μŠ€λ“€μ€ equals() λ₯Ό μ˜€λ²„λΌμ΄λ”©, 즉 μž¬μ •μ˜ν•˜μ—¬ μ‚¬μš©ν•˜κ²Œ λ©λ‹ˆλ‹€.

equals() in Wrapper Class

그럼 이제 Objectλ₯Ό 상속받은 λ‹€λ₯Έ ν΄λž˜μŠ€λ“€μ—μ„œ equals() λ₯Ό μ–΄λ–»κ²Œ μ˜€λ²„λΌμ΄λ”© ν–ˆλŠ”μ§€ μ½”λ“œλ₯Ό κΉŒλ³΄κ² μŠ΅λ‹ˆλ‹€.

λ¨Όμ € λŒ€ν‘œμ μΈ Wrapper Class인 Integerλ₯Ό λ“€μ—¬λ‹€ 보면 λ‹€μŒκ³Ό 같은 equals() λ₯Ό λ§Œλ‚  수 μžˆμŠ΅λ‹ˆλ‹€.

	/**
     * Compares this object to the specified object.  The result is
     * {@code true} if and only if the argument is not
     * {@code null} and is an {@code Integer} object that
     * contains the same {@code int} value as this object.
     *
     * @param   obj   the object to compare with.
     * @return  {@code true} if the objects are the same;
     *          {@code false} otherwise.
     */
    public boolean equals(Object obj) {
        if (obj instanceof Integer) {
            return value == ((Integer)obj).intValue();
        }
        return false;
    }

λ‹€μŒ μ½”λ“œλ₯Ό 보면 Object의 equals() μ™€λŠ” 쑰금 λ‹€λ₯Έ 것을 확인할 수 μžˆμŠ΅λ‹ˆλ‹€. 둜직의 흐름은 λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

if (obj instanceof Integer)

> λ¨Όμ € 처음 if 문으둜 λΉ„κ΅ν•˜κ³ μž ν•˜λŠ” 객체가 Integer ν΄λž˜μŠ€μΈμ§€(μ•„λ‹˜ 상속받은 ν΄λž˜μŠ€μΈμ§€) ν™•μΈν•©λ‹ˆλ‹€.

value == ((Integer)obj).intValue()

> λΉ„κ΅ν•˜κ³ μž ν•˜λŠ” 객체가 Integer 클래슀라면 객체의 valueκ°€ 같은지 λΉ„κ΅ν•©λ‹ˆλ‹€. μ—¬κΈ°μ„œ valueλž€ Integer 객체λ₯Ό 생성할 λ•Œ μƒμ„±μžμ— λ„£λŠ” μˆ«μžκ°’(λ¬Έμžμ—΄λ„ κ°€λŠ₯ν•˜μ§€λ§Œ 결과적으둜 λ‚΄λΆ€μ—μ„œ λͺ¨λ‘ μˆ«μžκ°’μœΌλ‘œ λ³€κ²½λ©λ‹ˆλ‹€. 의미λ₯Ό κ°–λŠ” κ°’ κ·Έ 자체라고 μƒκ°ν•˜λ©΄ λ©λ‹ˆλ‹€)을 μ˜λ―Έν•©λ‹ˆλ‹€. 즉, μ£Όμ†Œκ°’μ΄ μ•„λ‹Œ 객체가 가지고 μžˆλŠ” 숫자λ₯Ό λΉ„κ΅ν•©λ‹ˆλ‹€.

		// Integer Class에 μ •μ˜λœ value와 intValue()
		...
		private final int value;
        
       	@Deprecated(since="9", forRemoval = true)
    	public Integer(int value) {
        	this.value = value;
    	}
        
        @Deprecated(since="9", forRemoval = true)
    	public Integer(String s) throws NumberFormatException {
        	this.value = parseInt(s, 10);
    	}
        
        @IntrinsicCandidate
   	 	public int intValue() {
       		return value;
    	}
        ...

equals() in String Class

κ·Έλ ‡λ‹€λ©΄ String ν΄λž˜μŠ€μ—μ„œλŠ” μ–΄λ–¨κΉŒμš”?

String class의 equals() μ½”λ“œλ₯Ό ν™•μΈν•΄λ΄…μ‹œλ‹€.

	/**
     * Compares this string to the specified object.  The result is {@code
     * true} if and only if the argument is not {@code null} and is a {@code
     * String} object that represents the same sequence of characters as this
     * object.
     *
     * <p>For finer-grained String comparison, refer to
     * {@link java.text.Collator}.
     *
     * @param  anObject
     *         The object to compare this {@code String} against
     *
     * @return  {@code true} if the given object represents a {@code String}
     *          equivalent to this string, {@code false} otherwise
     *
     * @see  #compareTo(String)
     * @see  #equalsIgnoreCase(String)
     */
    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 Classμ—μ„œ μ •μ˜λœ equals() λ©”μ†Œλ“œμž…λ‹ˆλ‹€. Integer보닀 쑰금 둜직이 λ§Žμ•„μ§„ 것을 확인할 수 μžˆλŠ”λ° ν•œλ²ˆ 흐름을 νŒŒμ•…ν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

if (this == anObject)

> λ¨Όμ € 비ꡐ λŒ€μƒμΈ 객체와 비ꡐ할 String 객체λ₯Ό == μ—°μ‚°μžλ‘œ λΉ„κ΅ν•˜μ—¬ κ°™λ‹€λ©΄ trueλ₯Ό λ¦¬ν„΄ν•©λ‹ˆλ‹€.

μ–΄ 근데.. μž μ‹œλ§Œ 이거 Objectλž‘ 같은 μ—°μ‚°μ΄μžλ‚˜? 라고 생각할 수 μžˆμŠ΅λ‹ˆλ‹€.
λ„€ λ§žμŠ΅λ‹ˆλ‹€. Stringμ—μ„œλŠ” λ¨Όμ € μ•„μ˜ˆ 같은 객체인지λ₯Ό ν™•μΈν•˜μ—¬ κ°™λ‹€λ©΄ true둜 λ¦¬ν„΄ν•΄μ€λ‹ˆλ‹€. 객체 μ£Όμ†Œκ°’(객체 자체)이 κ°™λ‹€λ©΄ λ‹Ήμ—°νžˆ 내뢀에 λ“€μ–΄μžˆλŠ” 객체 κ°’ κ·Έ μžμ²΄λ„ κ°™κ² μ£ .

κ·Έλ ‡λ‹€λ©΄ μ™œ Integerμ—μ„œλŠ” 객체 비ꡐλ₯Ό λ¨Όμ € μ•ˆν•΄μ£Όκ³  Stringμ—μ„œλŠ” 객체 비ꡐλ₯Ό λ¨Όμ € ν•˜λŠ”κ±ΈκΉŒ? λΌλŠ” 의문이 λ“€ 수 μžˆμŠ΅λ‹ˆλ‹€.(μ œκ°€ κ·Έλž¬μŠ΅λ‹ˆλ‹€)

μ΄λŠ” 개인적인 μƒκ°μœΌλ‘œ String 객체의 μ‹€μ§ˆμ μΈ 비ꡐ에 μ†Œμš”λ˜λŠ” μ½”μŠ€νŠΈκ°€ Integer에 λΉ„ν•΄ μ»€μ„œ, μ• μ΄ˆμ— 같은 객체라면 μ½”μŠ€νŠΈκ°€ 큰 λ‘œμ§μ„ μ‹€ν–‰ν•˜μ§€ μ•Šλ„λ‘ ν•˜κΈ° μœ„ν•¨μ΄μ§€ μ•Šμ„κΉŒλΌκ³  μƒκ°ν•©λ‹ˆλ‹€.
Integer.equals() λŠ” ν•œ μ€„μ˜ λΉ„κ΅λ‘œ O(1)의 μ‹œκ°„λ³΅μž‘λ„λ₯Ό κ°€μ§€λŠ” 데에 λΉ„ν•΄ String.equals() λŠ” 이후 둜직인 StringLatin1.equals() μ—μ„œ O(N)의 μ‹œκ°„λ³΅μž‘λ„λ₯Ό 가지기에, String의 경우 이미 동일 κ°μ²΄μž„μ„ 확인 ν›„ λ‘œμ§μ„ μ’…λ£Œν•œλ‹€λ©΄ 더 κΈ΄ μ‹œκ°„λ³΅μž‘λ„λ₯Ό κ°–λŠ” 것을 방지할 수 μžˆμ„ κ²ƒμž…λ‹ˆλ‹€. λ˜ν•œ Integer의 경우 이미 ν•œ μ€„μ˜ μ½”λ“œλ‘œ 객체 자체 값을 λΉ„κ΅ν•˜κ³  μžˆκΈ°μ—, λ§Œμ•½ 쑰건문으둜 λΆ„κΈ°ν•˜μ—¬ 객체 μ£Όμ†Œκ°’μ„ μΆ”κ°€λ‘œ 비ꡐ할 경우 λ¬΄μ˜λ―Έν•˜κ²Œ μ½”λ“œ μˆ˜κ°€ λ”μš± λŠ˜μ–΄λ‚˜λŠ” κ²°κ³Όλ₯Ό μ΄ˆλž˜ν•  수 μžˆμ„ κ²ƒμž…λ‹ˆλ‹€.

결둠적으둜 String.equals() λŠ” Integer.equals() 와 λ‹€λ₯΄κ²Œ 처음 객체의 동일 μ—¬λΆ€λ₯Ό μ κ²€ν•˜κ³  λ‘œμ§μ„ μ‹œμž‘ν•©λ‹ˆλ‹€.

(anObject instanceof String aString) && (!COMPACT_STRINGS || this.coder == aString.coder) && StringLatin1.equals(value, aString.value)

> κ·Έ λ‹€μŒ 3개의 쑰건을 λΉ„κ΅ν•˜λŠ”λ°μš”.

(anObject instanceof String aString)

> 처음으둜 Integer λ•Œμ™€ λ§ˆμ°¬κ°€μ§€λ‘œ λΉ„κ΅ν•˜κ³ μž ν•˜λŠ” 객체가 String ν΄λž˜μŠ€μΈμ§€(μ•„λ‹˜ 상속받은 ν΄λž˜μŠ€μΈμ§€) ν™•μΈν•˜κ³ 

(!COMPACT_STRINGS || this.coder == aString.coder)

> String이 COMPACT_STRINGS 이 μ•„λ‹ˆκ±°λ‚˜ 비ꡐ λŒ€μƒ String 객체와 비ꡐ 객체의 coder κ°€ 같은지 λΉ„κ΅ν•©λ‹ˆλ‹€.
μ—¬κΈ°μ„œ COMPACT_STRINGS 은 charν˜• λ¬Έμžκ°€ 2byteλ₯Ό μ‚¬μš©ν•˜λŠ”λ°μ— λΉ„ν•΄ μ‹€μ§ˆμ μΈ LATIN-1 문자 ν‘œν˜„μ€ 1byte만 μ‚¬μš©λ˜κΈ°μ— λ°œμƒν•˜λŠ” λ©”λͺ¨λ¦¬ λ‚­λΉ„λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ JDK6 λΆ€ν„° λ“±μž₯ν•œ μ΅œμ ν™” 방식인데 μ—¬κΈ°μ„œλŠ” ν•΄λ‹Ή 방식이 쓰이지 μ•Šμ•˜λŠ”μ§€λ₯Ό ν™•μΈν•©λ‹ˆλ‹€.

coder λŠ” κ°’μ˜ λ°”μ΄νŠΈλ₯Ό μΈμ½”λ”©ν•˜λŠ” 데 μ‚¬μš©λ˜λŠ” μΈμ½”λ”©μ˜ μ‹λ³„μžλ‘œ 두 객체의 coder λ₯Ό λΉ„κ΅ν•˜λŠ” ν–‰μœ„λŠ” 인코딩 μ‹λ³„μžκ°€ 같은지λ₯Ό ν™•μΈν•˜λŠ” λΆ€λΆ„μž…λ‹ˆλ‹€.(coder 의 경우 LATIN1,UTF16을 μ§€μ›ν•©λ‹ˆλ‹€)

즉, COMPACT_STRINGS 이 μ‚¬μš©λ˜μ—ˆμœΌλ©΄ 두 객체의 인코딩 μ‹λ³„μžκ°€ 달라도 같은 κ°’μœΌλ‘œ μΈμ‹ν•˜κ³  COMPACT_STRINGS 이 μ‚¬μš©λ˜μ§€ μ•Šμ•˜μœΌλ©΄ 두 객체의 인코딩 μ‹λ³„μžκ°€ 같아야지 같은 κ°’μœΌλ‘œ μΈμ‹ν•˜λŠ” 것을 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.

StringLatin1.equals(value, aString.value)

> λ§ˆμ§€λ§‰μœΌλ‘œ μœ„μ—μ„œμ˜ 쑰건듀을 λͺ¨λ‘ κ²€μ¦ν•˜κ³  온 객체의 value(byte[] μžλ£Œν˜•)λ₯Ό 같은지 ν™•μΈν•©λ‹ˆλ‹€. 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;
    }

μœ„ λ‘œμ§μ„ 따라가 보면 λ¨Όμ € value.length == other.length으둜 두 value의 길이가 같은지 확인 ν›„ for loop 을 톡해 ν•˜λ‚˜ν•˜λ‚˜μ˜ value κ°€ 같은 값을 가지고 μžˆλŠ”μ§€ ν™•μΈν•©λ‹ˆλ‹€. μ΄λ•Œ λͺ¨λ“  값이 κ°™λ‹€λ©΄ true λ₯Ό 좜λ ₯ν•˜κ²Œ λ©λ‹ˆλ‹€.

즉 String.equals() μ—μ„œλ„ κ°’ κ·Έ 자체λ₯Ό 비ꡐ할 수 μžˆλŠ” 둜직으둜 equals λ©”μ†Œλ“œλ₯Ό μ˜€λ²„λΌμ΄λ”© ν•œ 것을 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.

κ²°λ‘ 

μœ„μ™€ 같이 equals λ©”μ†Œλ“œλŠ” 각각의 클래슀의 μ„±μ§ˆμ˜ 맞게 μ˜€λ²„λΌμ΄λ”© λ˜μ–΄ μ‚¬μš©λ˜κ³  μžˆλ‹€λŠ” 사싀을 확인할 수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€.

참고자료

Compact Strings in Java 9 _ baeldung

profile
μ§€λ‚˜μΉ˜μ§€ μ•ŠκΈ° μœ„ν•˜μ—¬

0개의 λŒ“κΈ€