JavaEasy#53
Java에서 == 와 equals()의 차이점은 무엇인가요?
#Java#equals#기초#String
힌트
참조값 비교와 내용 비교의 차이를 생각해보세요.
정답 및 해설
Java에서 == 와 equals()의 차이점은 무엇인가요?
Java에서 == 연산자와 equals() 메서드는 모두 "같음"을 비교하지만, 비교하는 대상이 근본적으로 다릅니다. 이 차이를 정확히 이해하지 못하면 버그가 발생하기 쉬운 대표적인 함정 중 하나입니다.
== 연산자
==는 참조(주소)를 비교하는 연산자입니다.
- 원시 타입(primitive type): 값 자체를 비교합니다.
- 참조 타입(reference type): 두 변수가 힙(Heap) 메모리의 같은 객체를 가리키는지를 비교합니다.
// 원시 타입 — 값 비교
int a = 10;
int b = 10;
System.out.println(a == b); // true (값이 같으므로)
// 참조 타입 — 주소(참조) 비교
String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2); // false (서로 다른 객체)
equals() 메서드
equals()는 객체의 논리적 동등성(내용)을 비교합니다.
Object 클래스의 기본 구현은 ==와 동일하게 참조를 비교하지만, String, Integer 등 많은 클래스에서 내용 비교로 오버라이드되어 있습니다.
String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2); // false — 다른 객체
System.out.println(s1.equals(s2)); // true — 내용이 같음
String Pool과 리터럴 비교
Java는 문자열 리터럴을 String Pool에 캐싱하므로 같은 리터럴은 동일한 객체를 재사용합니다.
String a = "hello"; // String Pool에 저장
String b = "hello"; // Pool에서 동일 객체 반환
System.out.println(a == b); // true — 같은 Pool 객체
System.out.println(a.equals(b)); // true — 내용도 같음
String c = new String("hello"); // 강제로 새 객체 생성 (Heap)
System.out.println(a == c); // false — 다른 객체
System.out.println(a.equals(c)); // true — 내용은 같음
equals() 오버라이드
사용자 정의 클래스에서 equals()를 오버라이드하지 않으면 Object의 기본 구현(==)이 사용됩니다.
class Point {
int x, y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
}
Point p1 = new Point(1, 2);
Point p2 = new Point(1, 2);
System.out.println(p1 == p2); // false — 다른 객체
System.out.println(p1.equals(p2)); // false — Object 기본 구현은 == 과 동일
오버라이드 후:
class Point {
int x, y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public boolean equals(Object o) {
if (this == o) return true; // 같은 참조면 바로 true
if (!(o instanceof Point)) return false; // 타입 확인
Point other = (Point) o;
return this.x == other.x && this.y == other.y;
}
@Override
public int hashCode() {
return Objects.hash(x, y); // equals와 함께 반드시 오버라이드
}
}
Point p1 = new Point(1, 2);
Point p2 = new Point(1, 2);
System.out.println(p1.equals(p2)); // true — 내용 비교
hashCode()를 함께 오버라이드해야 하는 이유
equals()를 오버라이드할 때 반드시 hashCode()도 함께 오버라이드해야 합니다. Java 명세에 따르면:
equals()가true를 반환하는 두 객체는 반드시 같은hashCode()를 반환해야 한다.
hashCode()를 오버라이드하지 않으면 HashMap, HashSet 등 해시 기반 컬렉션이 제대로 동작하지 않습니다.
// hashCode를 오버라이드하지 않은 경우 문제 발생
Set<Point> set = new HashSet<>();
set.add(new Point(1, 2));
System.out.println(set.contains(new Point(1, 2))); // false! — 다른 해시버킷을 탐색
// hashCode를 올바르게 오버라이드한 경우
Set<Point> set = new HashSet<>();
set.add(new Point(1, 2));
System.out.println(set.contains(new Point(1, 2))); // true
비교 정리
| 구분 | == | equals() |
|---|---|---|
| 비교 대상 | 참조(메모리 주소) | 논리적 동등성(내용) |
| 원시 타입 | 값 비교 | 사용 불가 |
| 참조 타입 기본 | 주소 비교 | Object 기본은 주소 비교 |
| String | 주소 비교 | 내용 비교 (오버라이드됨) |
| 오버라이드 가능 | 불가 | 가능 |
| null 비교 | 가능 (a == null) | NPE 주의 (null.equals(...)) |
실무 팁
// null 안전한 비교
String s = null;
// s.equals("hello") — NPE 발생
"hello".equals(s); // false, 안전
Objects.equals(s, "hello"); // false, null 안전
equals() 비교 시 null을 먼저 체크하거나, Objects.equals()를 활용하면 NPE를 방지할 수 있습니다.