Java

Java, 동등성과 동일성 정의 및 활용 예시 (VO 사용 예시)

greenyellow-s 2025. 4. 2. 16:26
728x90
반응형

 

 

 

 

 

 

 

두 인스턴스화 객체가 "같다"라는 개념은 "동일성(Identity)""동등성(Equality)"로 나뉜다.

 

 

동일성(Identity) 확인 → ==연산자

  • 두 객체의 메모리 주소가 같은지 비교
  • 같은 인스턴스를 가리키고 있는지 확인
  • 완전히 같은 객체인지 체크하는 용도(ex. 싱글톤, 캐시된 객체)

 

동등성(Equality) 확인 equals() 메서드 재정의

  • 두 객체의 내용(값)이 같은지 비교
  • equals()를 오버라이딩하여 원하는 기준을 정의해야 함
  • VO(Value Object)에서 중요 → 같은 값을 가지면 같은 객체로 인식

비교 기준

비교 기준 설명 예제
== 연산자 메모리 주소(동일성) 비교 p1 == p2
equals() 값(동등성)비교 (오버라이딩 필요) p1.equals(p2)
hashCode() 같은 객체라면 같은 해시코드를 가져야 함 HashSet, HashMap 활용
record 자동으로 equals(), hashCode() 제공 PersonRecord("Alice", 25)

 

 

equals(), hashCode() : 동등성을 확인하는 코드

== (참조 비교) : 동일성을 확인하는 코드

 

예시)

Student s1 = new Student(1, "홍길동");
Student s2 = new Student(2, "니모");
Student s3 = new Student(1, "홍길동");

// 1.
System.out.println(s1.equals(s2)); // false (값 비교)
// 2.
System.out.println(s1.equals(s3)); // true (값 비교)

// 3.
System.out.println(s1 == s3); // false (주소)

// 4.
System.out.println(s1.hashCode()); // 1239731077
System.out.println(s2.hashCode()); // 557041912
System.out.println(s3.hashCode()); // 1134712904

 

1. s1과 s2는 값이 다르기 때문에 false가 출력된다.

2. s1과 s2는 값이 "홍길동"으로 같기 때문에 true가 출력된다.

 

3. s1과 s3는 은 값은 같지만 서로 다른 인스턴스로 생성되어었기 때문에 메모리 주소가 달라 false로 출력된다.

 

4. hasCode()를 구할 때 hashCode가 재정의 되지 않았다면 값이 동일해도 다른 해시코드로 반환된다.


equals()와 hashCode()는 재정의 필요하다.

equals()와 hashCode()는 동등성을 판단하는데 중요한 역할을 한다.

 

Java의 Object 클래스는 기본적으로 hashCode()를 구현하고 있지만, 공개된 내부 구현은 없다.

기본적으로 객체의 메모리 주소를 기반으로 해시코드로 반환한다.

 

즉, hashCode를 재정의 하지 않으면 ==(참조 비교)와 같은 방식으로 동작한다.

(Java의 Hash기반 자료 구조는 hashCode() 를 이용하여 객체를 저장하고 검색한다.)

 

 

중요한 규칙

만약 equals()가 true이면, 두 객체의 hashCode()값도 동일해야한다.

-> 만약  equals()true인데 hashCode() 값이 다르면 Hash 자료구조에서 객체를 올바르게 찾지 못하는 문제가 발생된다.

 


VO로 알아보는 동일성과 동등성의 중요성

VO는 불변성이 보장되어야 하기 때문에 setter 메서드가 포함되지 않으며 equals()와 hashCode() 메서드를 오버라이딩하여 사용해야한다. 또한, 객체의 참조값이 다르더라도 값이 같다면 동일한 것으로 봐야한다.

 

즉, VO를 사용하는 이유는 값이 변형될 가능성이 낮기 때문이다.

 

불변 객체가 필요한 이유 →

 

 

 

 

예를 들어,

한 계정의 포인트가 3,000포인트 있다고 가정할 때,

 

가능한 문제 발생은 아래와 같다.

 

- 중복 결제잘못된 트랜잭션 처리로 인해 발생

- 한 계정으로 두명 이상의 사용자가 동시에 포인트를 사용하는 동시성 문제 발생

 

VO가 없다면?

new Integer(1000), new Integer(1000)처럼 같은 값을 가지고 있어도 서로 다른 객체로 인식될 가능성이 있음.

컬렉션(Set, Map)에서 중복 데이터 처리가 어려움.

포인트를 직접 int로 관리할 경우, 검증 로직이 여러 곳에 중복될 수 있음. - 여러 곳에서 반복적으로 구현해야된다.

 

VO를 사용하면?

불변 객체로 관리되어 포인트 값이 변형되지 않음.

같은 값은 동일한 객체로 인식되어 비교가 쉬워짐.

포인트 사용 로직을 캡슐화하여 중복 코드가 줄어듦.

VO 내부에서 값에 대한 검증 로직을 캡슐화하여 잘못된 값을 방지할 수 있음.

 


VO를 사용해야 하는 실제 상황 (동시성 문제 발생 예시)

시나리오

한 계정에서 두 명이 동시에 포인트를 사용

 

상황 가정

한 계정(Account)에는 3,000 포인트가 있다.

두 명의 사용자가 동시에 결제를 진행하려고 한다.

사용자 A: 1,000 포인트 사용

사용자 B: 500 포인트 사용

결제가 동시에 처리되면서 예상치 못한 문제가 발생할 수 있다.


VO를 사용하지 않은 경우 (문제 발생)

동시 요청 처리 과정

사용자 A가 1,000 포인트를 사용하기 위해 현재 포인트(3,000)를 조회

사용자 B도 동시에 500 포인트를 사용하기 위해 현재 포인트(3,000)를 조회
(서로의 트랜잭션이 아직 반영되지 않았음)

 

사용자 A의 요청이 먼저 완료되어 포인트가 2,000으로 갱신됨

사용자 B는 여전히 3,000 포인트가 있다고 생각하고 500 포인트 차감 → 결과적으로 2,500으로 업데이트됨

 

최종적으로 실제 사용된 포인트는 1,000 + 500이지만, 잔여 포인트는 2,500이므로 데이터가 꼬인다. (1,000포인트 손실)

 

결과

Lost Update 발생

기대값: 3,000 - (1,000 + 500) = 1,500 포인트

실제값: 2,500 포인트 (사용자 A의 변경 사항이 사용자 B에 의해 덮어씌워짐)
→ 사용자 A의 1,000 포인트 사용이 일부 손실됨

 

 

VO를 사용한 경우 (데이터 정합성 유지)

 

해결 방법

불변 객체(Immutable) VO 활용

 

사용자 A가 현재 포인트(3,000)를 가져옴

사용자 B도 동일하게 현재 포인트(3,000)를 가져옴

사용자 A가 PointVO.subtract(1000)을 호출하여 새로운 VO 객체를 생성 (잔액 2,000)

사용자 B가 PointVO.subtract(500)을 호출하여 새로운 VO 객체를 생성 (잔액 2,500)

JPA의 @Transactional이 적용되면서 데이터베이스가 변경되는 시점을 자동 조정하여 충돌을 방지함

최종적으로 데이터 정합성이 유지됨 (잔여 포인트 1,500)

728x90
반응형