클래스와 프로퍼티

2018, Jun 18    

자바에서 데이터 저장을 위한 객체를 선언할때 보통 다음과 같은 모습을 가지게 된다.


//흔한 자바 클래스 구성
public class Person {

  //필드
  private final String name;

  //필드를 초기화 하는 생성자
  public Person(String name) {
    this.name = name;
  }

  //accessor method
  public String getName() {
    return name;
  }
}
  • 자바에서는 데이터를 필드(field)에 저장하며, 멤버 필드의 가시성은 보통 비공개(private)이다.
  • 데이터에 접근하기 위해 접근자 메소드(accessor method)-getter,setter가 필요하다.
  • 자바에서 필드(field)접근자(accessor)를 한데 묶어 프로퍼티(property)라고 부르며,
    프로퍼티라는 개념을 활용하는 프레임워크가 많다.
  • 필드가 둘 이상 늘어나면 그에 따른 대입 로직도 추가로 생기고, 접근 메소드도 추가 되어야 하며, 자바에서는 이와 같은 코드가 반복적으로 들어가는 경우가 많다.

코틀린의 프로퍼티


  • 코틀린 프로퍼티는 자바의 필드와 접근자 메소드를 완전히 대신하기 때문에, 그런 필드 대입 로직을 훨씬 더 적은 코드로 작성할 수 있다.
  • 코틀린은 값을 저장하기 위한 비공개 필드(backing field)와 getter, setter로 이루어진 디폴트 접근자 구현을 제공한다.

class Person {
    var name: String = ""
    var isMarried: Boolean = false
}

>>> val p = Person()
>>> p.name = "charlse" //accessors are called

게터와 세터(Getters and Setters)


property 선언의 full syntax는 아래와 같다.

var <프로퍼티명>[: <프로퍼티타입>] [= <프로퍼티 초기화>]
  [<게터>]
  [<세터>]

프라퍼티 초기화(initializer)(초기값), getter와 setter, 프로퍼티 타입은 선택사항이다.
properties의 타입은 initializer로 부터 가능한 경우 추론된다.

클래스의 프로퍼티를 선언할때는 val이나 var를 사용한다. val로 선언한 프로퍼티는 읽기 전용이므로 setter가 존재하지 않는다. var는 변경 가능하므로 getter, setter둘다 적용된다.


//추론을 통해 Int타입으로 정의됨
//기본으로 getter, setter를 가진다.
var initialized = 1

//오류 발생
//default getter setter를 사용한 경우로, 명시적인 초기화가 필요하다.
var allByDefault: Int?

//추론을 통해 Int타입으로 정의됨
//default getter를 가짐
val inferredType = 1

//오류 발생
//명시적인 초기화가 필요하다.
val simple: Int?

커스텀 접근자(Custom accessors)


getter나 setter는 아래와 같이 custom이 가능하다.

class SampleClass(val size:Int) {

  val isEmpty: Boolean
      get() = this.size == 0

  var stringRepresentation: String
      get() = this.toString()
      set(value) {
          value+"in SampleClass"
      }
}

>>> val ss = SampleClass(4);
println(ss.isEmpty)

관례상, setter의 파라미터명은 value로 지정되지만, 원한다면 변경이 가능하다.

  • SampleClass의 프로퍼티 isEmpty의 경우 별도의 초기화(property_initializer)가 필요 없다.
  • get()의 수정으로 isEmpty를 참조할때 바로바로 값을 알수 있기 때문이다. (이것은 객체가 생성하는 시기에 초기화 단계에서 이루어 지는 것이 아니라 참조 할때 마다 getter의 본문을 실행하게 된다.)

  • 코틀린 1.1 부터는 컴파일러가 추론할 수 있는 상황에서는 프로퍼티 타입을 생략 할 수 있다.
var isEmpty get() = this.size == 0 // has type Boolean
  • accessor의 블록 또한 사용하여도 되지만, 위와 같이 생략도 가능하다.

  • accessor에 가시성 변경이 필요하거나, 어노테이션이 필요한 경우 기본 accessor의 수정 없이 body없는 accessor를 통해 정의 가능하다.

var setterVisibility: String = "abc"
  private set // the setter is private and has the default implementation

var setterWithAnnotation: Any? = null
    @Inject set // annotate the setter with Inject

Backing Field(지원 필드)


  • 코틀린에서는 자바에서와 같은 필드가 존재하지 않고, 프로퍼티의 값을 저장하기위한 비공개 필드를 제공하다.
  • 이것을 프로퍼티를 지원하는 필드(backing field)라고 한다.
  • backing field는 accessor안에서 field식별자를 이용해 사용한다.
class CustomSetter {

    var strSetterName : String = "default" //initializer가 backing field를 할당한다.
        get() = field
        set(name) {
            field = "setterName is $name"
        }
}

>>> val s = CustomSetter()
>>>     s.strSetterName = "mine"
>>>
>>>     println(s.strSetterName)
setterName is mine
  • 코틀린 컴파일러는 디폴트로 접근자 구현을 사용하건, 커스텀 getter나 setter를 사용하건 backing field를 생성해 준다.
    하지만 filed를 사용하지 않는 커스텀 접근자 구현을 정의한다면, backing 필드는 존재 하지 않는다.
  
  //backing field가 생성되지 않는 예
  val isEmpty:Boolean
      get() = this.size == 0