티스토리 뷰

💡 본 포스팅에서는 코틀린의 프로퍼티의 개념과 원리 그리고 활용 방법에 대해 설명합니다.

기초강의 시리즈 전편 바로가기 : Kotlin 기초강의#4 :: 코틀린 클래스의 선언과 생성자

 

#1> 개요


이전 포스팅에서 사람의 이름과 나이를 저장하는 자바빈즈 JavaBeans 클래스인 Person을 만들었습니다.

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

자바빈즈 클래스란 규약에 따르는 게터와 세터를 구현하는 프로퍼티를 가져야 합니다. 하지만 Person 클래스에는 게터와 세터가 보이지 않습니다. 그런데 어떻게 자바빈즈라고 할 수 있을까요?

 

코틀린의 프로퍼티는 자동으로 게터와 세터를 구현합니다. 더 정확히 말하면 val 의 경우 게터만, var의 경우 게터와 세터 모두를 제공합니다. 그렇기 때문에 Person Class의 name과 age 프로퍼티는 보이지는 않지만 자동으로 구현된 getter가 있으므로 자바빈즈라고 할 수 있겠습니다.

 

자동으로 구현된다는 말이 이해가 안되시죠? 아래 내용들을 읽어보시면 이해가 되실 겁니다.

 

우선 자바에서 말하는 프로퍼티의 개념에 대해 알아보고 코틀린의 프로퍼티에 대해 이야기하도록 하겠습니다. 같이 살펴보시죠.

 

 

게터와 세터에 대해서는 이전에 다룬 적이 있으니 궁금하신 분은 아래 링크를 참고하세요.

(게터와 세터 포스팅 바로가기 : Java :: 게터와 세터 :: getter 와 setter)

 

#2> 자바의 프로퍼티


자바에서는 클래스의 필드와 접근자 메서드(accessor method / getter, setter)를 한데 묶어 프로퍼티라 칭합니다.

 

프로퍼티(property)라는 개념이 생겨나게 된 이유는 자바 클래스의 목적과 깊은 연관이 있습니다. 왜냐하면 클래스의 목적이 데이터를 캡슐화 하는데 있기 때문입니다.

 

자바 클래스는 기본적으로 필드(field)를 private으로 설정하고 외부에서 값을 확인할 필요가 있는 경우 getter를, 값을 변경할 필요가 있는 경우 setter를 제공합니다. 그리고 이를 통해 캡슐화된 클래스의 고유한 기능은 유지하면서 클라이언트의 요구에 따라 속성 값을 변경(set) 혹은 확인(get) 할 수 있도록 합니다.

 

프로퍼티(property)는 직역하면 속성입니다. 왜 이런 이름이 붙었는지 아시겠죠?

public class AudioPlayer {
	private MediaCodec audioDecoder; // 외부에서 접근하지 못하도록 게터와 세터를 제공하지 않음
  private String vesion = "1.3.2"; // 외부에서 확인만 가능하도록 게터만 제공
  private int volume = 5; // 해당 필드는 외부에서 변경 가능하도록 게터와 세터를 제공

  public String getVersion() {
    return version;
  }

  public int getVolume() {
    return volume;
  }

	public void setVolume(int volume) {
    this.volume = volume;
    changeVolume();
  }
  ... 
}

일반적으로 위와 같이 음악을 재생하는 클래스에서 우리는 내부적으로 어떻게 미디어 파일을 재생하는지는 관심이 없습니다. 단지 볼륨, 재생속도와 같은 속성값을 변경하길 원합니다. 음악을 재생한다 라는 고유의 기능은 유지하면서 필요에 따라 볼륨, 재생속도와 같은 속성을 변경할 수 있도록 해주는 게 캡슐화의 핵심입니다. 그리고 이런 캡슐화를 일관성 있게 제공하기 위해 프로퍼티라는 개념이 사용됩니다.

 

📌 용어정리
- 필드(field) : 클래스 내의 맴버변수
- 프로퍼티(property) : 필드와 게터 세터를 한데 묶어서 부르는 단어

 

#3> 코틀린의 프로퍼티


코틀린은 맴버변수라는 말 대신 프로퍼티라는 말을 사용합니다. 코틀린은 맴버변수에 대한 기본 접근자 메서드(default accessor method)를 자동으로 구현해주기 때문입니다.

 

아래와 같이 코틀린 프로퍼티의 선언 키워드에 따라 자동으로 생성되는 접근자 메서드가 다릅니다.

 

  • val 인 경우 - 불변 값이므로 getter가 자동으로 구현됩니다.

  • var 인 경우 - 가변 값이므로 getter와 setter가 자동으로 구현됩니다.

여기서 기본 접근자 메서드란, 단순히 프로퍼티의 값을 변경하고 리턴하는 형태의 기본적인 getter와 setter를 의미합니다.

 

실제로 자바에서는 아래와 같이 코틀린 언어로 작성된 Person 클래스 프로퍼티 값을 자동으로 생성된 게터를 활용해 확인합니다.

 

class Person(val name: String, val age: Int)
Person p0 = new Person("Bob", 23);
System.out.println(p0.getName()); 
// Bob
System.out.println(p0.getAge()); 
// 23

단, 코틀린에서는 getter와 setter를 통하지 않고 직접 프로퍼티에 접근하는 것만을 허용합니다.

 

Java에서 Person 클래스의 프로퍼티에 접근하는 경우
Kotlin에서 Person 클래스의 프로퍼티에 접근하는 경우

코틀린에서 프로퍼티에 직접 접근하도록 강제한 정확한 이유는 모르겠지만, 아마 가독성을 위한 것이라 생각됩니다. 몇 년 전부터 구글에서는 java클래스 작성 시 게터와 세터가 아닌 필드를 public으로 만들어 직접 접근하도록 구현하는 것을 권장해왔거든요. 이런 개발 트렌드가 반영된 것이 아닐까 예상해봅니다.

 

그런데 왜 setter는 보이지 않을까요? 그 이유는 위 예제에서 Person 프로퍼티를 모두 val로 선언했기 때문입니다. 아래와 같이 age를 var로 선언한 경우 자바에서 setter에 접근할 수 있습니다.

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

프로퍼티를 var로 선언 시 Java에서 setter가 노출됨을 확인 할 수 있다.

접근자 메서드 네이밍 규칙

코틀린 프로퍼티의 접근자 메서드(default accessor method)는 아래와 같은 규칙에 따라 이름이 결정됩니다.

 

  • getter의 경우 프로퍼티 이름 앞에 get을 붙이고 카멜 표기법에 따라 생성됩니다.

  • setter의 경우 프로퍼티 이름 앞에 set을 붙이고 카멜 표기법에 따라 생성됩니다.

  • 프로퍼티 이름이 is로 시작하는 경우 getter는 프로퍼티 이름 그대로 생성되고, setter는 is가 set으로 변경되며 카멜 표기법을 따릅니다. (e.g. isResume → getter: isResume, setter: setResume)

코틀린에서 게터와 세터를 자동으로 생성해주니 아주 편리합니다. 하지만 값이 변경되거나 조회될 때 이에 따른 부수적인 로직을 추가하고 싶다면 어떻게 해야 할까요? 걱정하지 마세요 접근자 메서드는 필요에 따라 명시적으로 선언이 가능합니다.

 

📌 카멜 표기법이란 두 단어 이상의 문장을 표기할 때 공백 대신에 단어의 첫 글자를 대문자로 사용해 읽기 쉽게 표기하는 방식을 말합니다. 그 모양이 마치 낙타의 등과 비슷하다고 하여 카멜 표기법이라는 이름이 붙었다고 하네요! e.g) showAllTextView, kotlinIsPowerfulThanJava .....

camelCase.png

#4> 프로퍼티의 접근자 메서드


코틀린은 친절하게 게터와 세터를 자동으로 구현해주지만, 원한다면 명시적으로 선언할 수 있습니다. 그 방법에 대해 살펴보시죠.

 

게터(getter)와 세터(setter)의 명시적 선언

프로퍼티의 접근자 메서드를 명시적으로 선언하는 규칙은 다음과 같습니다.

 

  1. 프로퍼티는 클래스 내에 선언되어야 한다.

  2. 게터의 경우 프로퍼티 아래 get() 메서드를 선언한다. 리턴값은 프로퍼티의 데이터 타입과 동일해야 한다.

이런 규칙으로 인해 default constructor에 지정된 프로퍼티에는 접근자 메서드를 명시적으로 선언할 수 없습니다.

 

명시적으로 선언한 기본 접근자 메서드

Person Class에서 자동으로 생성되는 기본 접근자 메서드를 명시적으로 선언하면 아래와 같은 형태가 됩니다.

class Person(name: String, age: Int) {
  val name = name
    get() {
      return field
    }

  var age = age
    get() {
      return field
    }
    set(value) {
      field = value
    }
}

명시적인 접근자 메서드 정의를 위해 아래와 같은 변경사항이 생겼습니다.

 

  1. 프로퍼티의 접근자 메서드(accessor method)를 선언하는 경우 프로퍼티를 클래스 내부에 선언해야 합니다. 따라서 default constructor에 선언했던 val와 var를 제거했습니다.

  2. 그리고 클래스 내부에 동일한 이름의 프로퍼티를 선언 후 매개변수 값으로 초기화하도록 변경했습니다.

  3. 명시적으로 게터(getter)와 세터(setter)를 선언했습니다.

 

접근자 메서드의 선언 규칙

프로퍼티의 접근자 메서드는 아래와 같은 규칙에 따라 선언되어야 합니다. 공통 규칙은 반드시 프로퍼티 아래에 선언이 되어야 한다는 것입니다.

 

  1. 게터(getter)는 get이라는 이름의 파라미터를 포함하지 않은 메서드 형태로 선언한다.

  2. 게터(getter)는 반드시 프로퍼티와 동일한 타입의 데이터를 리턴해야 한다.

  3. 세터(setter)는 var로 선언된 프로퍼티에만 선언이 가능하다.

  4. 세터(setter)는 set이라는 이름의 파라미터 1개를 포함하는 메서드 형태로 구현한다. 파라미터의 데이터 타입은 프로퍼티와 동일하며 명시적으로 선언하지 않는다.

위 4가지 규칙에 따라 접근자 메서드를 명시적으로 선언할 수 있습니다. 그런데 위 예제에서 보이는 field는 무엇일까요?

 

뒷받침하는 필드(Backing Fields)

앞서 코틀린에서는 필드라는 말 대신에 프로퍼티라는 용어를 사용한다고 말씀드렸습니다. 그리고 프로퍼티는 필드와 접근자 메서드를 통틀어 칭하는 단어라고 말씀드렸고요. 위 예제에서 보이는 field는 프로퍼티의 실제 값을 저장하는 변수를 칭하는 키워드입니다.

 

아래 코드는 field를 통해 프로퍼티의 값을 리턴하는 게터를 보여줍니다.

val name = "Kim"
  get() {
    return field
  }

이 프로퍼티의 값을 저장하는 field를 코틀린에서는 뒷받침하는 필드, Backing Fields라고 합니다.

 

접근자 메서드(accessor method) 커스터마이징

접근자 메서드를 명시적으로 선언하는 방법을 알게 되었으니 이제 게터와 세터에 원하는 로직을 추가할 수 있습니다. 아래 코드는 name 프로퍼티의 getter가 호출되는 횟수를 저장해 출력하는 예제를 보여줍니다.

private var getNameCount = 0
val name = "Kim"
  get() {
    println("name getter call count : ${++getNameCount}")
    return field
  }

위 예제와 같이 field를 반드시 리턴해야 하는 것은 아닙니다. 아래와 같이 사각형의 넓이와 높이를 입력받는 경우 게터를 통해 정사각형인지 여부를 리턴할 수도 있습니다.

class Rectangle(val width: Int, val height) {
  val isSquare : Boolean
    get() {
      return width == height
    }
}

 

물론 동일한 역할을 하는 메서드를 구현해서 정사각형인지 여부를 판단할 수 있습니다.

fun isSquare(w: Int, h: Int) = (w == h)

상황에 따라 읽기 쉽고 쓰기 편한 방식으로 구현하시면 됩니다.

 

 

#5> 마치며


이번 포스팅을 통해 코틀린의 프로퍼티의 개념과 활용 방법에 대해 알아봤습니다. 프로퍼티의 접근자 메서드의 명시적 선언을 통해 단순히 프로퍼티의 값을 변경하는 것만으로도 다양한 로직을 실행시킬 수 있다는 점에서 매력적입니다.

 

하지만 프로퍼티라는 단어가 의미하는 바와 같이 가능하면 프로퍼티는 해당 클래스의 상태 혹은 속성을 나타내는 목적으로만 사용하는 것을 권고드립니다. 프로퍼티의 접근자 메서드에 너무 많은 책임을 할당하면 가독성이 떨어질뿐더러 불필요한 종속성들이 발생하기 때문입니다.

프로퍼티에 많은 책임과 종속성이 생기면 일어나는 일.jpg

코틀린 프로퍼티 필드에 적용된 Backing 기법은 프로퍼티뿐만 아니라 프로퍼티(Backing Properties)라는 기법을 통해 프로퍼티의 활용을 극대화하는 방법도 있습니다. 이러한 패턴에 대해서는 추후 다뤄보도록 하겠습니다.

 

긴 글 읽어주셔서 감사합니다. 좋은 하루 되세요.

 


 

하트와 구독, 그리고 광고 클릭은 포스팅 제작에 큰 도움이 됩니다.
댓글