
코딩 테스트에서 자바를 사용할 때, equals() 메서드와 == 연산자의 차이를 정확히 아는 것은 정말 중요합니다!
이 둘은 언뜻 비슷해 보이지만, 실제로는 객체를 비교하는 방식이 완전히 다르기 때문에 혼동하면 예상치 못한 버그를 만나기 쉽습니다.
이 글에서 그 차이를 예시와 함께 깔끔하게 정리해 드릴게요. 💡
가장 핵심적인 차이는 다음과 같습니다:
== 연산자: 메모리 주소(참조) 비교 또는 기본 타입(Primitive Type) 값 비교equals() 메서드: 객체의 내용(값) 비교 (논리적 동등성)1-1. 기본 타입(Primitive Type) 비교: 값 비교
int, double, boolean 등과 같은 기본 타입 변수에서는 == 연산자가 변수 안에 저장된 실제 값을 비교합니다.
public class EqualityOperators {
public static void main(String[] args) {
// 1. 기본 타입 비교 (int)
int a = 10;
int b = 10;
int c = 20;
System.out.println("a == b: " + (a == b)); // 출력: true (값이 같으므로)
System.out.println("a == c: " + (a == c)); // 출력: false (값이 다르므로)
// 2. 기본 타입 비교 (char)
char char1 = 'A';
char char2 = 'A';
char char3 = 'B';
System.out.println("char1 == char2: " + (char1 == char2)); // 출력: true
System.out.println("char1 == char3: " + (char1 == char3)); // 출력: false
}
}
// 출력 결과
a == b: true
a == c: false
char1 == char2: true
char1 == char3: false
1-2. 참조 타입(Reference Type) 비교: 메모리 주소 비교
String, Integer, 사용자 정의 클래스(예: Person) 등과 같은 객체(참조 타입)에서는 == 연산자가 두 변수가 동일한 객체 인스턴스를 참조하는지, 즉 메모리 주소가 같은지를 비교합니다.
public class ReferenceEquality {
public static void main(String[] args) {
// 1. String 객체 비교
String s1 = "hello"; // 문자열 리터럴은 String Pool에 저장되어 재사용됩니다.
String s2 = "hello"; // s1과 같은 "hello"를 참조
String s3 = new String("hello"); // new 연산자는 항상 새로운 객체를 힙(Heap) 영역에 생성합니다.
String s4 = new String("hello"); // s3와 다른 새로운 객체
System.out.println("s1 == s2: " + (s1 == s2)); // 출력: true (같은 String Pool 객체 참조)
System.out.println("s1 == s3: " + (s1 == s3)); // 출력: false (String Pool vs 힙 영역의 다른 객체)
System.out.println("s3 == s4: " + (s3 == s4)); // 출력: false (서로 다른 힙 영역의 객체)
// 2. 사용자 정의 객체 비교
Person p1 = new Person("Alice", 25);
Person p2 = new Person("Alice", 25); // p1과 내용이 같지만 다른 객체
Person p3 = p1; // p1과 동일한 객체를 참조
System.out.println("p1 == p2: " + (p1 == p2)); // 출력: false (다른 메모리 주소)
System.out.println("p1 == p3: " + (p1 == p3)); // 출력: true (같은 메모리 주소 참조)
}
}
class Person {
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// equals()와 hashCode()를 오버라이드하지 않았다고 가정
}
// 출력 결과
s1 == s2: true
s1 == s3: false
s3 == s4: false
p1 == p2: false
p1 == p3: true
equals() 메서드는 Object 클래스에 정의되어 있으며, 기본적으로는 == 연산자와 동일하게 메모리 주소를 비교합니다.
그러나 대부분의 클래스(특히 String, Integer 등 자바 표준 라이브러리 클래스)에서는 객체의 내용이 같은지 비교하도록 이 메서드를 오버라이드합니다.
만약 사용자 정의 클래스에서 객체의 내용이 같을 때 equals()가 true를 반환하도록 하고 싶다면, 개발자가 직접 오버라이드해야 합니다.
2-1. String 클래스의 equals
String 클래스는 equals()를 오버라이드하여 문자열의 실제 내용이 같은지 비교하도록 구현되어 있습니다.
public class StringEquals {
public static void main(String[] args) {
String s1 = "hello";
String s2 = "hello";
String s3 = new String("hello");
String s4 = new String("world");
System.out.println("s1.equals(s2): " + s1.equals(s2)); // 출력: true (내용이 같으므로)
System.out.println("s1.equals(s3): " + s1.equals(s3)); // 출력: true (내용이 같으므로, s1 == s3는 false였음!)
System.out.println("s1.equals(s4): " + s1.equals(s4)); // 출력: false (내용이 다르므로)
}
}
// 출력 결과
s1.equals(s2): true
s1.equals(s3): true
s1.equals(s4): false
2-2. 사용자 정의 클래스에서 equals() 오버라이드
Person 클래스에서 name과 age가 모두 같으면 같은 사람으로 간주하고 싶다면, equals() 메서드를 직접 오버라이드해야 합니다. equals()를 오버라이드할 때는 일반적으로 hashCode()도 함께 오버라이드해야 합니다 (나중에 자세히 다룰 기회가 있다면 설명해 드릴게요. 일단은 같이 오버라이드해야 한다고 알아두세요).
import java.util.Objects; // Objects.equals()와 Objects.hash() 사용을 위해 임포트
class Person {
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// equals() 메서드 오버라이드
@Override
public boolean equals(Object obj) {
// 1. 자기 자신과의 비교
if (this == obj) {
return true;
}
// 2. null 체크 및 타입 체크
if (obj == null || getClass() != obj.getClass()) {
return false;
}
// 3. 필드 값 비교
Person other = (Person) obj; // 다운캐스팅
// String은 equals로 비교하고, int는 ==로 비교합니다.
return age == other.age && Objects.equals(name, other.name);
// Objects.equals()는 null-safe하게 비교해줍니다.
// name.equals(other.name) 대신 Objects.equals(name, other.name) 사용 권장
}
// equals()를 오버라이드하면 hashCode()도 오버라이드하는 것이 일반적입니다.
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + '}';
}
}
public class CustomEquals {
public static void main(String[] args) {
Person p1 = new Person("Alice", 25);
Person p2 = new Person("Alice", 25);
Person p3 = new Person("Bob", 30);
Person p4 = p1; // p1과 동일한 객체 참조
System.out.println("p1 == p2: " + (p1 == p2)); // 출력: false (다른 메모리 주소)
System.out.println("p1.equals(p2): " + p1.equals(p2)); // 출력: true (내용이 같으므로, 오버라이드된 equals)
System.out.println("p1 == p4: " + (p1 == p4)); // 출력: true (같은 메모리 주소)
System.out.println("p1.equals(p4): " + p1.equals(p4)); // 출력: true (같은 메모리 주소이므로 당연히 내용도 같음)
System.out.println("p1.equals(p3): " + p1.equals(p3)); // 출력: false (내용이 다르므로)
}
}
// 출력 결과
p1 == p2: false
p1.equals(p2): true
p1 == p4: true
p1.equals(p4): true
p1.equals(p3): false
p1과 p2는 == 비교 시에는 false가 나왔지만, 우리가 직접 equals()를 오버라이드했기 때문에 p1.equals(p2)에서는 true가 나오는 것을 확인할 수 있습니다.
| 상황 | 권장 사용 |
|---|---|
기본 타입(int, double, boolean 등)의 값을 비교할 때 | == |
| 객체(참조 타입)가 동일한 인스턴스인지(메모리 주소 동일) 확인할 때 | == |
| 객체의 내용(값)이 같은지(논리적 동등성) 확인할 때 | equals() |
표준 라이브러리 클래스(String, Integer, ArrayList 등) 비교할 때 | equals() (대부분 올바르게 오버라이드되어 있음) |
| 사용자 정의 클래스 객체를 비교할 때 | equals() 직접 오버라이드 + hashCode()도 함께 오버라이드 |
코딩 테스트에서는 대부분 객체의 내용을 비교하는 경우가 많으므로, equals() 메서드의 올바른 사용법을 익히는 것이 훨씬 중요합니다. 특히 문자열 비교 시 ==를 사용하는 실수를 조심하세요!
이해가 되셨기를 바랍니다. 혹시 더 궁금한 점이 있다면 언제든지 질문해주세요! 😊