본문 바로가기
에러 모음/단거리

[ERROR] 롬복 @RequiredArgsConstructor 기능, incompatible types 에러

by 지지 2022. 2. 7.

인프런에서 김영한님의 스프링강의를 듣다가 에러가 나서 이틀 동안 끙끙거렸다.

바로 @RequiredArgsConstructor 어노테이션 때문인데 의문점(에러)과 해결 방법, 그리고 왜 그랬는지에 대한 설명은 아래와 같다.


1. 의문점 (에러)

1. @RequiredArgsConstructor어노테이션과 생성자를 동시에 작성했음에도 에러가 나지 않는 것이 첫 번째 의문점. (이미 이미지만으로도 문제점을 눈치채신 분들이 계실 것 같다.....)

 

2. 그 상태(1번)에서 생성자로 테스트를 돌리면 문제없이 잘 돌아가지만, 롬복을 사용해 테스트를 돌리면 에러가 난다는 것이 두 번째 의문점.

생성자를 이용해 테스트 코드를 실행한 화면
롬복 @RequiredArgsConstructor를 사용해 테스트 코드를 실행한 화면

그렇다면, 코드는 문제가 없고 @RequiredArgsConstructor 어노테이션의 문제인가? 롬복이 제대로 설치가 안됐나? 아니면 다른 곳에서 코드를 잘못썼는데 내가 찾지 못하는 건가? 하는 생각이 마구마구 들었다. 난 아직 초짜이기에 선생님의 코드를 완벽하게 이해하지 못해서 에러를 찾으려면 생각을 많이 해야 했다.

 

일단 이 어노테이션의 기능을 먼저 살펴보자.

 


2. @RequiredArgsConstructor의 기능

롬복이 제공하는 @RequiredArgsConstructor 어노테이션을 사용하면, final키워드나 @NonNull 이 붙은 필드를 모아서 자동으로 생성자를 만들어준다. 또한 이 경우 생성자가 하나이기 때문에 @Autowired 어노테이션까지 생략가능한 코드가 완성된다.  즉, 생성자 없이 생성자 주입을 할 수 있는 것이다.

 

수업 때 사용한 코드를 예로 들어보자면, 아래의 코드처럼 코드가 깔끔해지는 효과를 얻을 수 있다!

@Component
public class OrderServiceImpl implements OrderService {
	private final MemberRepository memberRepository;
	private final DiscountPolicy discountPolicy;
	
    	//@Autowired //생성자가 하나일 땐 생략 가능
	public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
		this.memberRepository = memberRepository;
		this.discountPolicy = discountPolicy;
	}
}

//---------------------------두 코드는 동일한 코드이다.--------------------------------//

@Component
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService {
	private final MemberRepository memberRepository;
	private final DiscountPolicy discountPolicy;
}

3. 나의 해결 방법

@RequiredArgsConstructor 어노테이션이 하는 역할은 알겠고... 그래서 내 코드는 왜 에러가 안 나고(?), 에러가 날까(?)????

코드를 요리조리 살펴보아도 다르거나 틀린 부분이 없는데 왜?? 밤새 고민하고 생각하다가 설마... 하는 마음에 선언한 필드의 위치를 바꿔봤다.

//기존
private final DiscountPolicy discountPolicy;
private final MemberRepository memberRepository;

//변경
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;

필드의 위치를 변경한 코드 (@RequiredArgsConstructor에 빨간줄이 생긴 것을 확인할 수 있다.)

드디어!!!! @RequiredArgsConstructor에 빨간줄이빨간 줄이 생기면서 며칠간 나를 괴롭혔던 문제가 해결이 되었다!! 빨간 줄이 생긴 이유는, @RequiredArgsConstructor는 자동으로 final키워드를 가진 필드를 모아 생성자를 만들어주는데, 밑에 또 같은 이름, 같은 매개변수를 받는 생성자가 있으니 중복으로 인식되어 에러가 생긴 것이다!

에러가 이렇게 반가운 건 또 처음인 것 같다.... 드디어 @RequiredArgsConstructor가 적용이 안된 이유를 찾았으니 이제 왜 그런지에 대해 알아보도록 하자.

(참고로, 필드의 위치를 바꾼 상태에서 @RequiredArgsConstructor를 이용해 테스트 코드를 실행했을 때 잘 돌아가는 것을 확인했다!)


4. WHY?

그렇다면 왜??? 일단 해결은 했는데..... 왜지?? 필드에 선언된 순서가 중요한 것이었나??? 자바 기초를 더 공부해야 하나?? 하는 생각이 들었다.

일단 @RequiredArgsConstructor를 사용했을 때 나온 에러 메세지는 다음과 같다.

< java: incompatible types: hello.core2.member.MemoryMemberRepository cannot be converted to hello.core2.discount.DiscountPolicy >

타입이 맞지 않아 A(MemoryMemberRepository)는 B(DiscountPolicy)로 변환할 수 없다는 내용의 에러메세지이다.

아래 이미지는 @RequiredArgsConstructor를 사용하여 테스트 코드를 돌렸을 때 에러가 나는 부분이다.

여기서 알아챘어야 했다. 그러나 바보처럼 자바 기본서의 생성자 부분을 다시 읽어보고 나서야 문제점이 머릿속에 스쳐 지나갔다.

 

원인은 이러했다.

 

내가 선언한 필드의 순서는

①private final DiscountPolicy discountPolicy ②private final MemberRepository memberRepository 였고

 

내가 수동으로 만든 생성자의 매개 변수 순서는

MemberRepository memberRepositoryDiscountPolicy discountPolicy 였다.

 

그럼 롬복이 자동으로 만들어주는 생성자의 매개변수 순서는?

DiscountPolicy discountPolicyMemberRepository memberRepository

 

그렇다.. 원인은 생성자 오버로딩이었다. 그렇기 때문에 첫 번째 의문점인 어노테이션과 생성자를 동시에 작성했을 때 에러가 나지 않았던 것이었으며, 두 번째 의문점인 롬복을 통해 테스트 코드를 돌렸을 때 에러가 나는 것이 당연했다. 자동 생성된 생성자의 매개변수 타입과, 객체를 생성하며 넣어준 매개변수의 타입이 맞지 않았으니까...

 

롬복은 내가 선언한 필드의 순서에 따라 생성자를 만들고, 매개변수를 넣어줬다. 실제로 롬복을 사용했을 때 컴파일된 코드를 살펴보면, ①DiscountPolicy discountPolicy ②MemberRepository memberRepository 의 순서로 매개변수를 받는 것 을 확인할 수 있다.

자바 디컴파일러로 확인한 .class 파일

 

결국, 선언한 필드의 순서로 인해 수동 생성과 자동 생성의 매개변수 순서에 차이가 발생한 것이었다.


5. 생각

이번 에러는 기초적인 내용을 생각하지 못한 내 실수였다.

사실 오버로딩을 공부할 때 '매개변수 타입의 개수가 같아도 순서(위치)가 다르면 오버로딩이라고? 왜?? 굳이 왜 이렇게 했을까??'라는 의문을 가진 적이 있다. 그리고 오버로딩을 그렇게 많이 사용해보지 않아서 그 의문은 서서히 머릿속에서 잊혀졌던 것 같다. 그때 의문점에 대해 더 파고들고 조금이라도 더 공부했다면 이렇게까지 오래 걸리지는 않았을 것 같다.

그리고 롬복의 자동완성 기능의 단점(?)을 미처 생각하지 못했던 부분도 있다. 당연히 내가 만든 생성자와 겹쳐야 하는 게 맞다는 생각에 갇혀있었다. (실제로 롬복을 사용하면서 생각지도 못한 롬복의 기능에 애를 먹었던 사람들이 많다고 한다.)

스프링 공부를 한다면서 자바 기초도 생각 못하고 에러를 못 잡다니... 문제를 해결하고 나서는 허무하면서 분한 마음이 들었다. 이렇게 오래 걸렸다니!!!!라는 생각에...

 

그래도 나 스스로 칭찬해주고 싶은 점도 있다.

이 예제는 엄청나게 좋은 기능을 구현하는 예제가 아니라, 진짜 그냥 기초 수업용 테스트 예제였기 때문에 문제가 해결된 시점에서는 어? 되네? 하고 넘어갈 수도 있었을 것 같다.

그러나 나는 정말 너무 궁금해서 구글링을 하고, 자바 기본서도 다시 읽으며 문제의 원인을 찾아보려고 노력했다.

지금은 이런 실수를 했다는 것이 부끄럽긴 하지만... 기초가 중요하다는 것을 나 자신에게 말하기 위해 기록해 놓는다.

 

더 열심히 공부해야지 갑자기 의욕 활활!!!!!!🔥🔥🔥

 

반응형

댓글