Kotlin - 프로퍼티(Property)를 정의하는 다양한 방법

코틀린에서, 클래스에 val/var로 정의되는 변수를 프로퍼티라고 합니다. 코틀린의 프로퍼티는 자바에서는 멤버변수인 field와는 다릅니다. 프로퍼티는 자바의 field + getter/setter 메소드라고 볼 수 있습니다. 프로퍼티를 생성하면 getter와 setter가 자동으로 생성되기 때문입니다. (private val/var은 자바의 field와 동일합니다. private은 getter와 setter를 만들지 않기 때문입니다.)

아래 코틀린 코드에서 name은 프로퍼티입니다. 변수 선언과 동시에 get/set 메소드도 함께 정의되었습니다.

class Person() {
  val name = "chacha"
}

Kotlin - 클래스 생성자(Constructor)와 get/set 메소드 정의 방법에서 클래스의 생성자에서 프로퍼티를 정의하는 방법에 대해서 알아보았습니다.

여기서는 클래스 내부에 정의되는 프로퍼티에 대해서 자세히 알아보겠습니다.

프로퍼티 정의

클래스 내부에 프로퍼티를 이렇게 정의할 수 있습니다. 아래와 같이 Person 클래스에 세개의 프로퍼티를 정의하였습니다.

class Person() {
  val name = "chacha"
  var age = 10
  var isStudent = false
}

// 위의 코드는 코틀린에서 이렇게 사용할 수 있습니다.
fun main(args: Array<String>) {
  val person = Person()
  println(person.name)
  println(person.age)
  person.age = 20
  if (person.isStudent) {
  }
}

위의 코틀린 코드를 보면 객체의 public 변수에 직접 접근하는 것처럼 보입니다. 하지만 Java로 변환해서 보면 모두 get/set 메소드를 사용하고 있습니다.

위의 코드를 자바로 변환해보겠습니다.

public final class Person {
    private int age = 10;
    private boolean isStudent;
    @NotNull
    private final String name = "chacha";

    @NotNull
    public final String getName() {
        return this.name;
    }

    public final int getAge() {
        return this.age;
    }

    public final void setAge(int <set-?>) {
        this.age = <set-?>;
    }

    public final boolean isStudent() {
        return this.isStudent;
    }

    public final void setStudent(boolean <set-?>) {
        this.isStudent = <set-?>;
    }
}

public static final void main(@NotNull String[] args) {
    Intrinsics.checkParameterIsNotNull(args, "args");
    Person person = new Person();
    System.out.println(person.getName());
    System.out.println(person.getAge());
    person.setAge(20);
    if (person.isStudent()) {
    }
}

코틀린에서 프로퍼티를 정의하면 getter/setter 함수를 만들지 않고 그냥 직접 접근하여 사용하면 됩니다. 하지만 실제로 직접 접근하지 않습니다. 내부적으로 get/set을 사용하며 코틀린 코드에서는 이 부분이 생략이 되었다고 볼 수 있습니다. 이것은 코틀린으로 컴파일된 jar파일을 자바로 변환해보면 알 수 있습니다. 자바로 변환된 코드는 모두 get/set 메소드로 객체의 변수에 접근합니다.

자바에서 코틀린 코드가 사용될 수 있기 때문에, 코틀린의 프로퍼티는 getter/setter를 자동으로 생성해줍니다. 자바에서는 이것을 사용할 수 있고 이 때문에 자바와 코틀린이 함께 쓰일 수 있습니다.

코틀린의 프로퍼티는 다음과 같은 규칙으로 getter/setter를 생성합니다.

  • val은 불변(immutable)이기 때문에 getter만 생성됩니다.
  • var은 변하기(mutable) 때문에 setter/getter가 모두 생성됩니다.
  • getter의 이름은 get + 변수이름 으로 정해집니다. setter의 이름은 set + 변수이름 으로 정해집니다.
  • isStudent처럼 변수 이름에 is가 붙는다면, getter는 is + (is를 제거한 이름), setter는 set + (is를 제거한 이름) 이 됩니다.
  • private 변수는 getter/setter가 생성되지 않습니다.

Kotlin의 빌드 산출물인 class를 Java로 decompile 하는 툴은 Android 앱(apk)을 decompile하는 방법에서 소개하는 jadx를 사용하였습니다.

커스텀 getter, setter 만들기

프로퍼티를 정의하면 setter/getter가 생성됩니다만, 변수에 값을 변경하거나 리턴만 합니다. 다른 계산을 하거나 로그를 출력하는 코드를 넣을 수는 없습니다. 디테일한 작업을 추가하려면 프로퍼티에 get(), set() 함수를 정의해줘야 합니다.

아래 코드는 프로퍼티에 get/set 함수를 추가하여 재정의한 코드입니다. width와 height를 설정할 때 2로 나눈 값을 설정하도록 하였습니다. area를 리턴할 때는 width와 height를 곱을 리턴하도록 하였습니다.

class Rectangle {
    var width = 10
        set(value) {
            field = value / 2
        }
    var height = 10
        set(value) {
            field = value / 2
        }
    var area: Int = 0
        get() = width * height
}

// 위의 객체는 이렇게 사용할 수 있습니다.
fun main(args: Array<String>) {
    val rect = Rectangle()
    println("width: ${rect.width}")
    println("height: ${rect.height}")
    println("area: ${rect.area}")

    rect.width = 40
    rect.height = 40
    println("width: ${rect.width}")
    println("height: ${rect.height}")
    println("area: ${rect.area}")
}

출력 결과

width: 10
height: 10
area: 100
width: 20
height: 20
area: 400

위의 코드를 자바로 변환해보겠습니다. 예상한 것처럼 getter와 setter의 내용이 변경된 것을 볼 수 있습니다.

public final class Rectangle {
    private int area;
    private int height = 10;
    private int width = 10;

    public final int getWidth() {
        return this.width;
    }

    public final void setWidth(int value) {
        this.width = value / 2;
    }

    public final int getHeight() {
        return this.height;
    }

    public final void setHeight(int value) {
        this.height = value / 2;
    }

    public final void setArea(int <set-?>) {
        this.area = <set-?>;
    }

    public final int getArea() {
        return this.width * this.height;
    }
}

public static final void main(@NotNull String[] args) {
    Rectangle rect = new Rectangle();
    System.out.println("width: " + rect.getWidth());
    System.out.println("height: " + rect.getHeight());
    System.out.println("area: " + rect.getArea());
    rect.setWidth(40);
    rect.setHeight(40);
    System.out.println("width: " + rect.getWidth());
    System.out.println("height: " + rect.getHeight());
    System.out.println("area: " + rect.getArea());
}

위의 set()는 아래처럼 쓸 수 있습니다.

var width = 10      
    set(value) {
        field = value / 2
    }
  • "var width = 10"는 프로퍼티의 초기값을 정의하는 코드입니다.
  • "set(value)"에서 value는 인자를 의미합니다.
  • "field = value"는 프로퍼티에 값을 설정하는 코드입니다. 주의할 점은 "width = value"로 사용하면 안됩니다.

이 코드는 자바에서 "setWidth(value)"를 의미하게 되고, 재귀적으로 setWidth가 호출될 수 있습니다.

위의 get()은 아래처럼 쓸 수 있습니다.

var area: Int = 0
    get() = width * height
  • "var area: Int = 0"는 프로퍼티 초기값을 정의하는 코드입니다.
  • "get() { ... return 값 }"은 구현 및 값을 리턴하는 코드입니다.
  • "get() = ..." 처럼 한줄로 함수를 정의할 수도 있습니다.

물론 하나의 프로퍼티에 get()과 set()을 동시에 정의할 수도 있습니다.

Private setter 만들기

지금까지 만든 프로퍼티는 외부에서 변수를 변경(set)하고 읽을(get) 수 있었습니다. getter, setter를 모두 만들었는데요. 외부에서 변수를 변경(set)하지 못하도록 할 수 있습니다. set()을 private으로 선언하면 됩니다. 그럼 private setter가 생성되어 내부에서만 설정할 수 있게 됩니다.

이렇게 private set()을 정의할 수 있습니다.

class Rectangle {
    var width = 10
        private set(value) {
            field = value / 2
        }
    var height = 10
        private set(value) {
            field = value / 2
        }
    var area: Int = 0
        get() = width * height
}

// 외부에서 객체의 변수를 변경할 수 없습니다
val rect = Rectangle()
//rect.width = 40
//rect.height = 40

위 코드도 자바로 확인해보겠습니다. setHeightsetWidth가 private으로 생성되었습니다.

public final class Rectangle {
    ...
    public final int getWidth() {
        return this.width;
    }

    private final void setWidth(int value) {
        this.width = value / 2;
    }

    public final int getHeight() {
        return this.height;
    }

    private final void setHeight(int value) {
        this.height = value / 2;
    }
    ...
}

정리

코틀린의 프로퍼티에 대해서 알아보았습니다. 코틀린에서 객체의 변수를 getter/setter를 따로 만들어서 접근하지 않습니다. 그냥 직접 변수의 이름에 값을 넣거나 읽으면 됩니다. 하지만 자바 관점에서 이것은 객체의 내부 변수를 직접 접근한 것이 아닙니다. 코틀린 코드를 자바로 변경해보면 프로퍼티는 private 변수를 만들고 getter/setter 메소드를 생성합니다. 그리고 이 메소드들로 객체의 변수에 접근합니다.

그리고 val/var로 getter만 생성할지 둘다 생성할지 정할 수 있으며, var이지만 setter를 private으로 만들어 외부에서 변경할 수 없도록 할 수 있습니다. 마지막으로, 자동으로 생성된 getter/setter를 custom할 수 있습니다. custom하려면 프로퍼티에 get()과 set() 함수를 정의하면 됩니다.

참고

Loading script...
codechachaCopyright ©2019 codechacha