[Spring] @All/NoArgsConstructor 제대로 알기

gwonsang247·2024년 1월 29일
0

Java

목록 보기
11/11

코드 참고를 하다보면 @AllArgsConstructor, @NoArgsConstructor를 사용하여 Entity 클래스를 정의한 것들을 자주 볼 수 있다.

나도 별 생각없이 사용하고 있었는데 어느 순간 NO <-> ALL 이 상충되는 의미 아닌가? 라는 의문이 생겨 이번 기회에 제대로 공부해 보려한다.

먼저 Lombok 공식 문서를 보면 아래 처럼 @NoArgsConstructor / @RequiredArgsConstructor / @AllArgsConstructor 이렇게 세개가 묶여있다.

이유는 결국 세 어노테이션의 역할이 생성자를 생성하는 기능을 하고, 앞에 붙은 Prefix가 생성자의 형태를 정하기 때문이다.

먼저 세개의 어노테이션의 기능을 간략하게 나타내면 아래와 같다.

  • @NoArgsConstructor : 파라미터가 없는 디폴트 생성자를 생성
  • @AllArgsConstructor : 모든 필드 값을 파라미터로 받는 생성자를 생성
  • @RequiredArgsConstructor : final이나 @NonNull으로 선언된 필드만을 파라미터로 받는 생성자를 생성

@NoArgsConstructor

Lombok 공식 문서의 내용을 보면 @NoArgsConstructor 어노테이션은 파라미터가 없는 디폴트 생성자를 자동으로 생성한다.
이 어노테이션을 사용하면, 클래스에 명시적으로 선언된 생성자가 없더라도 인스턴스를 생성할 수 있다. 즉 코드로 보면,
아래와 같이 Person 이라는 클래스를 생성했다고 가정하자

public class Person {
    private String name;
    private int age;
}

이 클래스에 @NoArgsConstructor 를 사용하면 아래와 같은 코드이고

@NoArgsConstructor
public class Person {
    private String name;
    private int age;
}

이 코드는 실직적으로 아래처럼 기본 생성자를 생성한 것과 동일하게 동작한다.

public class Person {
    private String name;
    private int age;
    
    // 기본 생성자
    public Person(){
    	
    }
}

📌 아래 코드와 같이 클래스에 final 처럼 초기화가 필요한 field에 @NoArgsConstructor을 사용한다면 compile error가 발생할 것이다!! 하지만, @NoArgsConstructor(force = true)처럼 force라는 옵션에 true 값을 주면, 모든 final fields는 0 / false / null로 초기화된다.

즉 아래 코드에서 name, age 에 대해서는 기본 생성자가 적용되지만 final로 선언한 Id에 대해서는 기본 생성자 적용이 아니라 Long 타입에 맞게 0 이 할당 될 것이다.

@NoArgsConstructor(force = true)
public class Person {
    private String name;
    private int age;
    private final Long Id;
    
    // 기본 생성자
    public Person(){
    	
    }
}

@AllArgsConstructor


Lombok 공식 문서의 내용을 보면 @AllArgsConstructor 어노테이션은 클래스의 모든 필드 값을 파라미터로 받는 생성자를 자동으로 생성한다고 한다. 이 어노테이션을 사용하면, 클래스의 모든 필드를 한 번에 초기화할 수 있다. 즉, 클래스 내부에 선언된 모든 field마다 하나의 parameter를 가진 생성자를 생성한다.

아래 코드처럼 Person 클래스에 @AllArgsConstructor 어노테이션을 적용하면

@AllArgsConstructor
public class Person {
    private String name;
    private int age;
}

아래와 같이 각 field에 맞는 생성자를 생성해 동작한다.

public class Person {
    private String name;
    private int age;
	
    public Person(String name, int age) {
    	this.name = name;
        this.age = age;
    }
}

@RequireArgsConstructor


Lombok 공식 문서의 내용을 보면 @RequiredArgsConstructor 어노테이션은 final이나 @NonNull으로 선언된 필드만을 파라미터로 받는 생성자를 자동으로 생성한다. 이 어노테이션을 사용하면, 클래스가 의존하는 필드를 간단하게 초기화할 수 있다. 즉, 특별한 처리가 필요한 각 field마다 하나의 parameter를 갖는 생성자를 생성해준다.

초기화되지 않은 모든 final fields와,선언될 때 초기화되지 않은 @NonNull로 표시된 field까지 parameter를 가진다.
특히 @NonNull이 달려있는 field의 경우, 생성되는 생성자 내부에 명시적인 null 체크 로직 또한 생성된다.

아래 코드처럼 final, Nullable=false 로 선언된 feild에 대해 아래와 같이 생성자를 생성해서 동작한다.

@RequiredArgsConstructor
public class Person {
    private final String name;
    private final int age;
    
    @NonNull(message = )
    privaate final Long Id
    private String address;
    // getters and setters
}
public class Person {
    private final String name;
    private final int age;
    private String address;

	public Person(final String name, final int age, Long Id) {
    	this.name = name;
        this.age = age;
        this.Id = Id
    }
}

📌만약 @NonNull이 붙어있는 field 중 어떠한 것이라도 null 값을 포함한다면 NullPointerException이 발생하게 된다.


주의사항

생성자의 파라미터 순서는 클래스의 feild 순서로 정해지기 때문에 순서에 맞추지 않으면 의도와 다른 결과가 나타날 수 있다. 아래 코드처럼 @AllArgsConstructor 를 사용해 선언된 모든 field마다 하나의 parameter를 가진 생성자를 생성하면

@AllArgsConstructor
public class Person {
    private String name;
    private int age;
}

아래와 같이 동작하고

public class Person {
    private String name;
    private int age;
	
    public Person(String name, int age) {
    	this.name = name;
        this.age = age;
    }
}

사용은 다음과 같이 하게된다. 여기서 만약 파라미터에 입력하는 값의 순서를 바꿔서 입력하면 아예 다른 결과가 발생하게 된다.
해당 예시의 경우 feild의 type이 다르기 때문에 괜찮지만 두 feild의 type이 같다면 입력 순서가 문제가 될 수 있다.

Person person = new Person("Alex",46);

이런 상황을 방지하는 방법으로는 아래처럼 @Build 어노테이션을 사용해 클래스를 구현하는 것이다.

public class Person {
    private String name;
    private int age;
    
    @Builder
        public Person(String name, int age) {
    	this.name = name;
        this.age = age;
    }
}

사용은 아래처럼 한다.

Person person = Person.builder().name("Alex").age(46).build();

0개의 댓글