전체 목록
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를 방지할 수 있습니다.