본문 바로가기
Kotlin

Kotlin - 코틀린의 속성과 Getter, Setter

by JHBang 2024. 2. 28.

프로젝트 진행 중 작성한 코드이다.

@MappedSuperclass
@EntityListeners(AuditingEntityListener::class)
abstract class BaseEntity {

    @Column(columnDefinition = "TIMESTAMP(6)", name = "created_at", nullable = false, updatable = false)
    @CreatedDate
    var createdAt: LocalDateTime = LocalDateTime.now()
        protected set

    @Column(columnDefinition = "TIMESTAMP(6)", name = "updated_at", nullable = false)
    @LastModifiedDate
    var updatedAt: LocalDateTime = LocalDateTime.now()
        protected set
}

 

 

여기서 볼 건 protected set 이다.

protected set 은?

속성의 게터를 제한하는 방법이다. 클래스 자체 또는 상속받은 하위 클래스 내부에서만 수정이 가능하게 해 주며 이 필드들이 클래스 외부에서 임의로 수정되는 것을 막아준다. 이게 무슨 말일까?

 

코틀린의 속성(property)

코틀린에서 속성이란 클래스, 객체 인터페이스같은 요소에 속하는 변수를 말한다. 데이터를 저장하거나 특정 로직에 따라 데이터를 제공하기도 한다. 속성은 필드, 게터(getter), 세터(setter)로 구성된다.

필드(Field): 데이터를 저장하는 공간

게터(Getter): 속성의 값을 읽는 메서드. 코틀린에서는 모든 속성이 자동으로 게터를 생성한다.

세터(Setter): 속성의 값을 설정하는 메서드. 'var' 로 선언된 변경 가능한 속성에 대해 자동 생성된다.

 

속성 정의의 예시를 보자.

class Person {
    var name: String = "John Doe" // 'name' 속성에 대한 게터와 세터가 자동 생성.
    val age: Int = 30 // 'age' 속성에 대한 게터가 자동 생성. 읽기 전용이므로 세터는 없음.
}

 

var 를 사용하면 게터와 세터가 자동으로 생성된다. 하지만 val은 읽기 전용으로 게터만 생성된다.

 

kotlin에서는 java와 다르게 게터와 세터를 자동 생성해 준다. 그 말은 java에서는 일일히 게터와 세터를 생성해 줘야 한다는 말이다.

 

java도 예시를 들어보자.

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

    // 생성자
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // name의 게터
    public String getName() {
        return name;
    }

    // name의 세터
    public void setName(String name) {
        this.name = name;
    }

    // age의 게터
    public int getAge() {
        return age;
    }

    // age의 세터
    public void setAge(int age) {
        this.age = age;
    }
}

 

위 코드를 코틀린으로 작성하면?

 

class Person(var name: String, var age: Int)

단 한줄이면 끝난다.

 

이렇게만 보면 어떤 차이인지 잘 모르겠다. 실제 사용 예시를 보자.

 

만약 위 클래스에서 name 속성을 가져오고 싶다면 어떻게 써야 할까?

Person person = new Person("Bang", 25);
String name = person.getName(); // getter 메서드를 호출하여 name 값을 얻음

이런 식으로 직접 생성한 게터를 이용해  name속성을 가져온다.

 

코틀린의 경우에는 이렇다.

val person = Person("John Doe", 30)
val name = person.name

직접 접근하는 것처럼 보이지만, 실제로는 getter가 호출되어 동작한다.

 

코틀린은 java와 다르게 속성의 선언 만으로도 필드, 게터, 세터를 한 번에 정의할 수 있다. 이로써 코드가 훨씬 간결해진다.

 

 

게터와 세터를 자동 생성하지 않고 개발자가 임의로 커스텀도 가능하다.

class Person(var name: String, var age: Int) {
    var email: String = ""
        get() = field
        set(value) {
            field = if (value.contains("@")) value else throw IllegalArgumentException("Invalid email")
        }
}

위 코드에서 email 프로퍼티는 커스텀 게터와 세터를 가지고 있다. get()과 set() 메서드를 이용해서 간단하게 이메일 형식인지 확인하는 커스텀 세터를 구현하고 있다.

 

 

그래서 protected set 가 뭐지?

여기까지 속성의 게터와 세터에 대해 알아봤다. 그럼 위에서 사용한 protected set  이 뭘 의미하는지 확인해보자.

 

protected set이 없는 경우 코틀린은 기본적으로 public setter를 제공한다. 이는 외부에서도 해당 필드의 값을 변경할 수 있다는 말인데, 이는 클래스의 무결성이 깨지거나 예상된 동작에서 벗어난 동작을 할 위험성이 생긴다.

 

예를 들어서 createdAt에는  protected set이 없다고 해 보자.

 

@MappedSuperclass
@EntityListeners(AuditingEntityListener::class)
abstract class BaseEntity {

    @Column(columnDefinition = "TIMESTAMP(6)", name = "created_at", nullable = false, updatable = false)
    @CreatedDate
    var createdAt: LocalDateTime = LocalDateTime.now()
    // protected set 삭제

    @Column(columnDefinition = "TIMESTAMP(6)", name = "updated_at", nullable = false)
    @LastModifiedDate
    var updatedAt: LocalDateTime = LocalDateTime.now()
        protected set
}

 

위 코드에서 createdAt 필드는 외부에서 값을 변경할 수 있는 상태이다.

테스트를 해 보자.

 

createdAt은 값이 변경되지만 updatedAt은 값을 변경할 수 없음을 알려주고 있다.

 

BaseEntity에서 정의하는 두 필드는 데이터의 생성일과 수정일을 나타내고 있다. 이 값들은 외부에서 변경하면 안되는 값들이며, protected set 을 이용해 보호되어야하는 값으로 판단할 수 있다.

 

 

여담

protected set 대신 var 앞에  private 를 붙혀서  private var 이런 식으로 쓰면 안되는 건가? 라는 생각도 들었다.

하지만 두 방법에는 가시성에서 큰 차이점이 있었는데, protected set 을 사용하면 외부 클래스에서 값을 읽는 접근이 가능하다. 하지만 값을 설정하는 것은 해당 클래스 또는 하위 클래스 내부에서만 가능하다.

하지만 private를 사용하면 값에 대한 읽기와 쓰기 모두 클래스 내부에서만 가능하게 된다. 하위 클래스에서도 사용이 불가능하다.

 

정리하자면 protected set은 외부 클래스에서의 세터를 제한한다면 private var는 외부 또는 하위 클래스에서의 접근을 완전히 차단한다. 즉 상속하는 클래스 내부에서의 접근조차 차단한다는 것이다. 이렇게 되면 엔티티를 dto로 옮기는 from()같은 메서드에서 문제가 발생하게 된다.

 

평소에 코드를 칠 때 별 생각 없이 var과 val을 사용해 왔었다. 하지만 그 안에 이런 기능들이 포함되어 있는지는 몰랐다.

게터와 세터도 사실 들어만 봤지 자세한 내용은 몰랐다. 그래도 이번에 공부하며 확실하게 안 것 같다. Java에 대한 지식이 전무한 채로 코틀린을 공부하기 시작해서 그런지 이런 부분에서 지식의 구멍이 느껴진다.