📝 개요
프로젝트를 진행하다보면 값을 비교해야되는 경우가 많이 있다. 예를 들어 사용자의 권한 상태가 “일반” 인지, “관리자” 인지 혹은 삭제 상태가 Y 인지, N인지 값의 일치를 비교한다.
Java 에서 객체(값) 비교는 상황에 따라 3가지 방식이 대표적으로 쓰인다. 비교 대상의 객체에 따라서 값 비교하는 방식이 다르고(배열, 컬렉션) 비교하는 기준에 따라서(정렬, 크기 등 : Comparator 인터페이스 활용) 개발자가 커스텀하여 비교하기도 한다.
다음은 값의 비교(일치/불일치) 가장 기본적으로 사용되는 방식에 대해서 알아보겠다.
🚀 “==” 비교 연산자 (Equality Operator)
1️⃣ 개념
== 는 Java 에서 비교연산자 이다. 기본 의미는 좌변과 우변이 같은가 를 비교하는 연산자이다. 비교 연산자는 비교 대상에 따라서 의미가 달라지는데
기본 타입(Primitive Type) : int, double, boolean, char, long, shor, float, byte 등 에서는 “값 자체” 가 같은지 비교 한다.
참조 타입(Reference Type) : String, 배열, 객체 등에서는 “객체가 동일한 인스턴스(메모리 상에서 완전히 같은 객체)” 인가를 비교(즉, ‘주소값’ 비교) 한다.
참조 타입에서 메모리 상에서 완전히 같은 객체라는 것은 메모리상에서 딱 하나, 같은 주소를 가리키고 있다는 것을 의미한다. 따라서 String 등 new 연산자로 생성한 객체는 ‘내용:값’ 이 같아도 == 로 비교하면 false 가 나올 수 있다.
2️⃣ 동작방식/원리
- 기본 타입(Primitive Type) 에서
==int a = 1; int b = 1; a == b→ true- 내부적으로 값 자체(비트)가 같은 지 비교
- 참조 타입(Reference Type) 에서
==- 겍체의 메모리 주소(참조값, Reference Value)를 비교
- “Heap 에 올라간 객체가 같은가” / 즉, 동일한 객체(인스턴스)인가?
String a = new String("hi");
String b = new String("hi");
a == b // false (다른 주소, 각각 new로 생성)
String c = a;
a == c // true (같은 객체)
- String 리터럴의 예외 상황
- Java 는 String 리터럴 풀(Pool)을 이용해서 같은 리터럴은 공유한다.
String s1 = "hello";
String s2 = "hello";
s1 == s2 // true (둘 다 String Pool에서 같은 객체 가리킴)
String s3 = new String("hello");
s1 == s3 // false (new는 항상 새 인스턴스)
String Constant Pool 및 리터럴 방식에 대해서는 다른 포스팅에서 다룰 예정이다.
- 배열, List, 객체 등 모든 참조 타입 동일
- 배열, 객체 등 모든 참조 타입은 주소값만 비교한다.
int[] arr1 = {1,2,3};
int[] arr2 = {1,2,3};
arr1 == arr2 // false (내용 같아도 주소 다름)
arr1 == arr1 // true
- null 비교
- 어떤 객체가 null 인지 확인할 때
==을 쓴다.
- 어떤 객체가 null 인지 확인할 때
String s = null;
if (s == null) { ... }
// null == null 은 true (둘 다 같은 null 값)
- Singleton Pattern, ENUM, 캐시 객체 비교
- enum, 싱글턴 객체 등 “유일한 인스턴스”를 비교할 때만
==을 의도적으로 사용한다.
- enum, 싱글턴 객체 등 “유일한 인스턴스”를 비교할 때만
if (status == Status.DONE) { ... }
[ Java 에서의 메모리 구조 기본 ]
Stack
- 메서드 실행 시 지역 변수, 메서드 파라미터, 기본형(int, boolean 등) 이 저장 되는 공간
- 함수 호출마다 Push/Pop 됨
- 속도 빠름, 크기 작음
Heap
- new 연산자로 생성한 “객체” 들이 저장되는 공간
- 크기가 큼, 동적으로 객체(클래스 인스턴스) 저장)
- GC(Garbage Collector)가 주기적으로 사용 안 하는 객체 정리
- 객체의 실제 데이터(필드, 참조 등)와 주소(참조값)가 저장됨
Heap에 올라간 객체는 프로그램이 끝날 때까지, 또는 GC가 제거할 때까지 메모리에 남아있는다. Stack 변수는 함수/스코프가 끝나면 바로 사라진다. Heap 메모리에 올라간 실제 데이터를 Stack 에 참조값으로 저장한다. 이는 함수나 스코프가 끝나면 Stack 의 참조값은 사라지지만, 실제 데이터는 Heap에서 유지된다는 것을 의미한다.
🚀 equals()
1️⃣ 개념
equals() 메서드는 객체의 “동등성(내용)” 을 비교하는 메서드이다. 기본은 Object 클래스에서 상속받는다. 동등성이란 두 객체가 “논리적으로 같은 값/의미를 가지는가” 를 말한다.
- 동일성 : ==, 같은 인스턴스인가(메모리 주소 비교)
- 동등성 : equals(), 내용이 같은가(값 비교)
equals()는 객체의 “값” 이 같으면 같은 것으로 취급해야할 때 사용한다. 실무에서는 VO, DTO, 비즈니스 객체, 컬렉션의 중복 체크 등에 사용된다.
2️⃣ 동작방식/원리
[ Object 객체 정의 equals 함수 ]
public boolean equals(Object obj) {
return (this == obj);
}
Object 클래스에서의 equals()는 사실 == 과 똑같이 주소 비교만 한다. 즉, 오버라이딩 하지 않으면 == 와 결과는 완전히 동일하다. equals()는 반드시 hashCode() 와 함께 오버라이딩 되어야한다. HashMap/HashSet/Hash 기반 컬렉션에서 동등성을 유지해야하기 때문이다.
String, Integer, List, Date 등 거의 모든 표준 타입은 equals() 메서드에 대한 오버라이딩이 값만 비교하도록 되어있다. 그래서 표준 타입 객체 이외에 직접 만든 객체 같은 경우, equals() 와 hashCode() 대한 오버라이드가 되어 있지 않으면 == 연산자를 사용하는 것과 같다.
[ String 객체에서의 equals 함수 ]
// String.equals()
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);
}
// 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;
}
[ equals() 메서드 오버라이딩 후 사용 예시 ]
public class User {
private String name;
public User(String name) { this.name = name; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof User)) return false;
User user = (User) o;
return Objects.equals(name, user.name);
}
@Override
public int hashCode() {
return Objects.hash(name);
}
}
User u1 = new User("name");
User u2 = new User("name");
System.out.println(u1.equals(u2)); // true
Set<User> users = new HashSet<>();
users.add(u1);
System.out.println(users.contains(u2)); // true (equals/hashCode 오버라이딩된 경우)
// equals(), == 연산자 결과
User u1 = new User("name");
User u2 = new User("name");
System.out.println(u1 == u2); // false (주소 비교)
System.out.println(u1.equals(u2)); // true (내용 비교)
🚀 Objects.equals()
1️⃣ 개념
Objects.equals() 는 기존 equals() 메서드에서 두 비교 대상 값이 nullable 한 값이 들어오더라도 Null-safety 하게 비교 가능한 메서드이다. 두 객체를 비교할 때, 둘 중 하나라도 null 이면 NPE 없이 안전하게 비교하도록 만들어진 유틸리티 메서드이다.
2️⃣ 동작원리/방식
// Object a = nullable, Object b = nullable
public static boolean equals(Object a, Object b) {
return (a == b) || (a != null && a.equals(b));
}
둘 중 하나라도 null 이 될 수 있는 상황에서 equals 비교가 필요할 때 사용된다. 예를 들어 데이터베이스에서 값이 비어있을 수도 있을 때, 파라미터로 들어온 객체가 null 일 수도 있을 때, if문에서 바로 비교하고 싶을 때 사용한다.
'Study > Java' 카테고리의 다른 글
| [Java] Utility Class (0) | 2025.06.03 |
|---|---|
| [Java] record (0) | 2025.06.03 |
