1. 동일성 vs 동등성
1) 동일성 (Identity)
- 개념: 두 객체/변수가 메모리상에서 완전히 같은 인스턴스(동일한 주소/참조값)를 가리키고 있는지 비교. 즉, 물리적으로 같은 객체인지를 판단한다.
- 비교 방법: == 사용 (참조값 비교)
- 특징:
- 참조값이 같으면 두 객체는 실제 동일한 객체이므로 내부의 값(Value)도 당연히 같다.
- equals() 메서드를 구현할 때 성능 최적화를 위해 내부 최우선 조건문으로 if (this == anObject) 형태를 자주 사용한다.
Person p1 = new Person("홍길동");
Person p2 = p1; // p2는 p1과 동일한 메모리 주소를 참조함
System.out.println(p1 == p2); // true (동일함)
2) 동등성 (Equality)
- 개념: 두 객체의 참조값이 다르더라도(서로 다른 메모리에 있더라도), 내용(값)이 논리적으로 같은지 비교한다.
- 비교 방법: equals() 메서드 오버라이딩
- Object 클래스의 기본 equals()는 ==와 동일 (오버라이딩 필요)
- 특징:
- 클래스를 개발자가 정의한 기준에 따라 두 객체의 값이 같은지 판단한다.
- 대표적으로 String 같은 모든 래퍼 클래스들은 equals()를 오버라이딩하여 래퍼로 감싼 진짜 값(value)을 비교하도록 구현되어 있다.(문자열 구성 내용이 동일한지 등)
String str1 = new String("Hello");
String str2 = new String("Hello");
System.out.println(str1 == str2); // false (메모리 주소가 다르므로 동일하지 않음)
System.out.println(str1.equals(str2)); // true (내부 문자열 값이 같으므로 동등함)
더보기
💡 Object 클래스 추가 설명
1) Object 상속 (String 클래스 선언부)
- 자바의 모든 클래스는 direct 또는 indirect(간접) 방식으로 최상위 클래스인 Object를 자동으로 상속받는다.
- 상속 체인: 명시적으로 다른 클래스를 상속받더라도(자바는 단일 상속만 지원), 결국 최상위까지 올라가면 상속받게 됨
- 명시적으로 다른 클래스를 상속받지 않으면, 컴파일러가 바이트코드를 만들 때 extends Object를 직접 주입해 준다.
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
// extends Object 가 생략되어 있음
}
2) Object의 equals() 구현 내용
- equals()는 자바의 모든 객체가 물려받는 메소드
public boolean equals(Object obj) {
return (this == obj); // 주소값(==)만 비교
}
💡 기본 자료형은 클래스/객체가 아니기 때문에 메소드 자체를 가지지 못 한다.
2. String, Integer로 알아보는 동일성과 동등성
1) String로 설명하기
- str1 == str2 (true):
- 리터럴("")로 생성한 문자열은 Heap 영역 내의 문자열 리터럴 재사용 풀(String Constant Pool)에 저장된다.
- 자바는 메모리 절약을 위해 기존에 존재하는 "Hello"의 주소를 재사용하므로, str1과 str2는 동일한 주소를 가리킴.
- str1 == str3 (false):
- new String()을 사용하면 리터럴 풀과 별개로 새로운 메모리 공간(Heap)에 객체를 생성한다.
- 따라서 주소값이 달라 동일하지 않는다.
- str1.equals(str3) (true):
- String 클래스는 equals()를 재정의하여 문자열의 내용만 비교하도록 구현되어 있음.
- 주소는 달라도 들어있는 문자열이 "Hello"로 같으므로 동등하다.
String str1 = "Hello";
String str2 = "Hello";
String str3 = new String("Hello");
// 1. 동일성 비교 (==)
System.out.println(str1 == str2); // true
System.out.println(str1 == str3); // false
// 2. 동등성 비교 (equals)
System.out.println(str1.equals(str3)); // true
더보기
ℹ️ 실제 equals 내부 구현 코드 (java.lang.String.java)
1) String.equals() 동작 원리 (단순 일치 여부)
- 타입: byte[] (바이트 배열)
- 특징:
- Compact Strings 기능이 도입되면서 변경됨. (Java 9 ~)
- 기존 2바이트 공간을 쓰던 문자열 중 영문/숫자처럼 1바이트만으로 충분한 문자열은 Latin-1(1바이트)로 다루고, 한글/한자 같은 문자는 UTF-16(2바이트)으로 저장한다.
- 이를 구분하기 위해 인코딩 플래그 변수인 coder(LATIN1 또는 UTF16) 필드가 함께 추가됨.
- @Stable 어노테이션은 JVM(JIT 컴파일러) 수준에서 배열 요소가 변경되지 않음을 보장받아 정적 최적화를 수행하는 역할.
- 동작 원리:
- 전달받은 Object가 Integer 타입인지 확인(instanceof). — 패턴 매칭
- Integer가 맞다면 다운캐스팅하여 객체가 감싸고 있는 기본형 필드 value(int)의 값을 == 연산자로 직접 비교.
- 다른 타입이거나 null이면 바로 false를 반환한다.
/** The value is used for character storage. */
@Stable
private final byte[] value;
/** The identifier of the encoding used to encode the bytes in {@code value}. */
private final byte coder;
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String aString = (String)anObject;
if (coder() == aString.coder()) {
return isLatin1() ? StringLatin1.equals(value, aString.value)
: StringUTF16.equals(value, aString.value);
}
}
return false;
}
ℹ️ Java 9 이전 버전은 모든 문자를 기본적으로 UTF-16 단위인 2바이트(char)로 저장. (낭비)
ℹ️ if(super.equals(anObject)) 도 가능하지만 메소드 호출 과정이 한 번 더 추가되어 성능상 손해(오버헤드)
2) (참고) 비슷하게 값을 비교하는 String.compareTo() 동작 원리 (정수값 반환)
- 문자 단위 순차 비교 (for 루프): 두 문자열의 첫 번째 글자부터 순서대로 한 글자씩 비교.
- 달라지는 시점의 문자로 판단 (return char1 - char2):
- 비교 도중 처음으로 달라지는 문자가 등장하면, 두 문자의 코드값(ASCII / Unicode) 차이인 this_char - other_char 결과를 즉시 리턴한다.
- 예: "apple" vs "banana" ➔ 'a' - 'b' 연산 결과인 음수(-1) 반환
- 길이 차이로 판단 (return len1 - len2):
- 한쪽 문자열이 다른 쪽의 접두어(Prefix)인 경우(예: "app" vs "apple"), 짧은 길이(lim) 동안은 문자가 전부 같다.
- 이때는 더 이상 비교할 문자가 없으므로 두 문자열의 길이 차이(len1 - len2)를 반환다.
- 예: "app"(길이 3) vs "apple"(길이 5) ➔ 3 - 5 연산 결과인 음수(-2) 반환
public int compareTo(String anotherString) {
byte v1[] = value;
byte v2[] = anotherString.value;
// 두 문자열의 인코딩(Latin1 vs UTF16)이 같은 경우
if (coder() == anotherString.coder()) {
return isLatin1() ? StringLatin1.compareTo(v1, v2)
: StringUTF16.compareTo(v1, v2);
}
// 두 문자열의 인코딩이 서로 다른 경우 (Latin1 vs UTF16)
return isLatin1() ? StringLatin1.compareToUTF16(v1, v2)
: StringUTF16.compareToLatin1(v1, v2);
}
2) Integer로 설명하기
- 왜 a == b (100 비교) 와 c == d (200 비교) 결과값이 다를까?:
- Integer a = 100;을 실행하면 캐시되어 있는 기존 100 객체의 주소를 공유하므로 a == b는 true가 된다.
- 반면 200은 캐싱 범위를 벗어나므로 매번 새로운 Integer 객체를 생성하여 주소가 달라지므로 c == d는 false가 된다.
- Integer Caching (캐싱 메커니즘):
- 자바의 Integer 클래스는 자주 사용하는 -128 ~ 127 범위의 정수 값을 내부적으로 미리 캐싱(Integer Cache)해 둠.
- ⚠️ 때문에 new 키워드 대신 일반적인 오토박싱(Auto-boxing) 형태로 생성할 때는 주의가 필요하다.
Integer num1 = new Integer(100); // (참고: Java 9 이후 deprecated)
Integer num2 = new Integer(100);
// 1. new 키워드로 생성 시
System.out.println(num1 == num2); // false (주소 다름)
System.out.println(num1.equals(num2)); // true (값 동일)
// 2. 오토박싱 형태로 생성 시
Integer a = 100;
Integer b = 100;
Integer c = 200;
Integer d = 200;
System.out.println(a == b); // true (?!?)
System.out.println(c == d); // false
🚨 결론: Integer 같은 래퍼의 값을 비교할 때는 주소값이 달라지는 상황을 예방하기 위해 반드시 == 대신 equals()를 사용해야 한다.
더보기
ℹ️ 실제 equals() 내부 구현 코드
1) Integer.equals() 동작 원리 (단순 일치 여부)
- 전달받은 Object가 Integer 타입인지 확인(instanceof). — 패턴 매칭
- Integer가 맞다면 다운캐스팅하여 객체가 감싸고 있는 기본형 필드 value(int)의 값을 == 연산자로 직접 비교.
- 다른 타입이거나 null이면 바로 false를 반환한다.
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
2) (참고) Integer.compareTo() 동작 원리 (값 비교 후 정수값 반환)
- this.value와 상대방의 anotherInteger.value를 가져온다.
- 삼항 연산자를 통해 두 int 값의 크기를 판단한다:
- x < y 이면 -1
- x == y 이면 0
- x > y 이면 1
- 양수/0/음수라는 정수(int) 값을 리턴하여 어느 쪽이 더 큰지 위치/순서를 알려줌
public int compareTo(Integer anotherInteger) {
return compare(this.value, anotherInteger.value);
}
// Integer.compare() 정적 메서드
public static int compare(int x, int y) {
return (x < y) ? -1 : ((x == y) ? 0 : 1);
}
'Language & Tool > Java' 카테고리의 다른 글
| ☕JVM 메모리 동작과 객체지향의 이해 (1) | 2026.06.14 |
|---|---|
| ☕Java의 기본 개념과 컴파일 메커니즘 (0) | 2026.06.14 |
| ☕예외 처리(Exception Handling) (0) | 2026.06.11 |
| ☕Java 제네릭(Generic) 이해하기 (0) | 2026.06.11 |
| ☕Java 제어문 총정리 (+ 레이블) (0) | 2026.06.08 |