RECENT POSTS

최근에 작성한 글

01 / 03

전체 글

전체 보기

[CS] Java equals와 hashCode, Spring DI

1. equals() 와 ==

1. == 연산자

기본형에서는 실제 값을 비교 (int, long, char, boolean)

int a = 10;
int b = 10;

System.out.println(a == b); // true

 

참조형에서는 두 변수가 같은 객체를 참조하고 있는지 주소값을 비교

2. equals() 메서드

두 객체의 내용이 같은지 비교하는 메서드

String a = new String("hello");
String b = new String("hello");

System.out.println(a == b);      // false
System.out.println(a.equals(b)); // true

 

모든 객체에서 equals()를 호출한다고 자동으로 내용 비교가 되는 것은 아니다.

//기본 구현은 사실항 동일한 객체인지 비교
public boolean equals(Object obj) {
    return (this == obj);
}

 

그래서 직접 만든 클래스에서 equals()를 재정의하지 않으면 내용이 같아도 서로 다른 객체라 false를 리턴!

class User {
    String name;

    User(String name) {
        this.name = name;
    }
}

User a = new User("은콩");
User b = new User("은콩");

a == b;       // false
a.equals(b);  // false

3. equals() 재정의와 hashCode

자바의 모든 객체는 equals()와 hashCode() 라는 두 메서드를 가지고 있음.

  • equals() : 두 객체가 논리적으로 같은가?
  • hashCode() : 객체를 대표하는 정수값을 반환
class User {
    String name;

    User(String name) {
        this.name = name;
    }
}

User a = new User("은콩");
User b = new User("은콩");

 

name 이 같으면 같은 User라고 정의하고 싶다면

a.equals(b); // true

 

가 되도록 재정의할 수 있음. 이때 자바에는 중요한 규칙이 있다.

 

equals() 가 true인 두 객체는 반드시 같은 hashCode()를 가져야 한다.

 

보통:

@Override
public boolean equals(Object o) {
    // name을 기준으로 동등성 판단
}

@Override
public int hashCode() {
    // 동일하게 name을 기준으로 hashCode 생성
}

처럼 동일한 필드를 기준으로 둘을 함께 재정의해서 사용한다.

2. Spring의 의존성 주입

1. 의존성 주입이란?

내가 필요한 객체를 직접 만들지 않고 외부에서 만들어 넣어준다.

 

예를 들어 orderService가 동작하려면 OrderRepository가 필요

public class OrderService {

    private OrderRepository orderRepository
            = new OrderRepository();
}

 

이럴 때 "OrderService가 OrderRepository에 의존한다" 라고 함. 근데 위에서 필요한 객체를 new 로 직접 생성하고 있음. 이러면 OrderServie 와 OrderRepository가 강하게 결합됨.

OrderService 가 직접 객체를 만들지 않도록 해보자.

 

public class OrderService {

    private final OrderRepository orderRepository;

    public OrderService(OrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }
}

 

이러면 OrderService는 이렇게 작동한다.

 

나는 OrderRepository가 필요해. 누군가가 만들어 줘!

 

2. Spring 이 하는 일

 

Spring에서는 외부에서 객체를 만들고 넣어주는 역할은 Spring Container가 해준다.

@Repository
public class OrderRepository {
}
@Service
@RequiredArgsConstructor
public class OrderService {

    private final OrderRepository orderRepository;
}

 

우리가 직접 new 로 객체를 생성하지 않아도 Spring이

1. OrderRepository 객체 생성
        ↓
2. Spring Container가 객체 관리
        ↓
3. OrderService 객체를 만들려고 보니
   OrderRepository가 필요함
        ↓
4. 이미 만들어둔 OrderRepository를
   OrderService 생성자에 전달

해준다. Spring이 이렇게 생성하고 관리하는 객체를 Bean 이라고 한다.

3. 의존성 주입 방법

Spring 에서 의존성을 주입하는 방법에는 생성자 주입, 필드 주입, Setter 주입이 있다.

 

1. 필드 주입

@Service
public class OrderService {

    @Autowired
    private OrderRepository orderRepository;
}

 

Spring이 OrderService 객체를 생성한 후 OrderRepository Bean을 찾아 필드에 주입하는 방식이다.

 

2. 생성자 주입

@Service
public class OrderService {

    private final OrderRepository orderRepository;

    public OrderService(OrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }
}

 

Spring이 OrderService 객체를 생성할 때 필요한 OrderRepository Bean을 생성자를 통해 전달한다.

Lombok의 @RequiredArgsConstructor를 사용하면 생성자를 직접 작성하지 않아도 된다.

4. 생성자 주입을 권장하는 이유

1. 객체 생성 시점에 필요한 의존성을 보장할 수 있다.

 

생성자 주입에서는 OrderRepository가 없으면 OrderService를 생성할 수 없다.

new OrderService(orderRepository); // 의존성 필요

 

따라서 필요한 의존성이 누락된 상태로 객체가 생성되는 것을 방지할 수 있다.

 

2. final을 사용할 수 있다.

private final OrderRepository orderRepository;

 

생성 시점에 주입된 의존성이 이후 변경되지 않도록 할 수 있다.

 

3. 의존관계를 명확하게 확인할 수 있다.

public OrderService(
        OrderRepository orderRepository,
        PaymentClient paymentClient
) {
    ...
}

 

생성자만 확인해도 OrderService가 어떤 객체에 의존하고 있는지 알 수 있다.

 

4. 테스트하기 편리하다.

Spring Container를 실행하지 않고 필요한 객체를 직접 전달하여 테스트할 수 있다.

OrderRepository mockRepository = mock(OrderRepository.class);


OrderService orderService =
        new OrderService(mockRepository);