Kotlin

[Kotlin] 클래스와 프로퍼티 기초

sh1mj1 2023. 12. 27. 17:25

Kotlin in Action 을 공부하고 Effective kotlin 의 내용을 조금 참조하여 정리한 글입니다.

 

이미지 출처  https://commons.wikimedia.org/wiki/File:Kotlin_Icon.svg

 

 

이번에는 클래스를 선언하는 기본 문법, 그리고 프로퍼티를 다룬다.

 

JavaBean 클래스를 코틀린 코드로 보기

 

먼저 간단한 `JavaBean` 클래스 `Person` 을 봅시다. 

public class Person {
    private final String name;

    public Person(String name) {
        this.name = name;
        // 필드가 늘어날 수록 생성자 바디에 비슷한 코드가 반복적으로 들어간다.
    }

    public String getName() {
        return name;
    }
}

`Person` 클래스는 필드를 오직 `String` 타입의 `name` 만을 가진다.  만약 필드가 늘어난다면 생성자의 바디에는 비슷한 코드가 반복적으로 들어갈 것이다. 아래처럼 말이다.

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

 

 

코틀린에서는 필드 대입 로직을 더 적은 코드로 가능하다.

자바 코드를 코틀린으로 변환해주면 아래와 같아진다.

class Person(val name: String)

코틀린의 기본 가시성은 `public` 이므로 `pulic` 키워드도 사라진 것을 볼 수 있다.

 

이렇게 별다른 로직없이 데이터만 저장하는 클래스를 `Value Object`(VO) 라고 부른다. 

우리는 VO 를 사용하여 다양한 값 객체를 간결하게 기술할 수 있다.

JavaBean 이 뭔데??
name 등의 특정한 정보를 가지고 있는 클래스를 표현하고 있는 하나의 규칙이다. 데이터를 표현하기 위한 목적을 가진다.
JavaBean 의 컨벤션은 아래와 같다.
* 클래스는 패키지화되어야 함.
* 멤버 변수(인스턴스 변수)는 property 라고 함.
* 멤버 변수를 private 으로 지정하고 외부 접근은 public 으로 선언한 접근자 메서드인 getter/setter 로 한다.

Property (프로퍼티)

클래스는 데이터를 캡슐화하고, 캡슐화한 데이터를 다루는 코드를 하나의 객체에 가두는 수단이다. 위 JavaBean 의 설명과 같이 자바에서는 데이터를 `private` 필드로 두고, 외부에서 접근하려면 접근자 메서드를 사용한다.

 

자바에서는 필드와 접근자를 `property` 라고 한다. 

코틀린에서는 property 를 언어 기본 기능으로 제공하며, 코틀린 property 는 자바의 필드, 접근자 메서드를 완전히 대신한다.

class Person(
    val name: String,
    var isMarried: Boolean,
)
  • `val` 프로퍼티인 `name` 은 읽기 전용 프로퍼티이다. 코틀린이 비공개 필드와 `getter` 를 만들어준다.
  • `var` 프로퍼티인 `isMarried` 는 쓸 수 있는 프로퍼티이다. 코틀린은 비공개 필드와 `getter`, `setter` 를 만들어 준다.

즉, 위에서 처음 Java 코드로 만든 `Person` 클래스는 `class Person(val name: String)` 만으로 원래 자바 코드와 똑같은 구현을 가지는 것이다.

 

코틀린의 `name` 프로퍼티는 자바 코드로는 `getName` 으로 읽을 수 있다.

이렇게 getter 와 setter 이름은 get + 필드 이름(첫글자 대문자)(`getName`) 혹은 set + 필드 이름(첫글자 대문자)(`setName`) 이다.

 

만약 필드 이름이 is 로 시작한다면 getter와 setter 이름이 조금 달라진다.

이 경우 getter 의 이름은 필드의 이름과 같아지고(`isMarried`) setter 의 이름은 is 를 set 으로 바꾼 것이 된다(`setMarried`).

public static void main(String[] args) {
    Person person = new Person("Bob", true);
    System.out.println(person.getName());
    person.setMarried(false);
    System.out.println(person.isMarried());  
}

 

반면에 코틀린으로 사용한다면 getter 와 setter 를 호출하는 대신 프로퍼티를 직접 사용하면 된다.

fun main() {
    val person = Person("Bob", true)
    println(person.name)
    person.isMarried = false
    println(person.isMarried)
}

 

당연히 자바 코드로 선언한 VO 도 코틀린 코드로 사용이 가능하다.

Backing field 와 커스텀 접근자

대부분의 프로퍼티에는 그 프로퍼티의 값을 저장하기 위한 필드가 있다.

이 필드를 프로퍼티를 뒷받침하는 필드(backing field)라고 한다.

 

하지만 프로퍼티 값을 저장(뒷받침)하지 않고 프로퍼티 값을 그때그때 계산할 수도 있다.

커스텀 getter 를 작성하여 그런 프로퍼티를 만들 수 있다.

 

직사각형 클래스 `Rectangle` 을 정의하고, 클래스 내에서 자신이 정사각형인지를 알려주는 기능을 만들어보자. (메서드가 아닌 프로퍼티로 만들어보자)

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

직사각형이 정사각형인지를 별도의 필드에 저장할 필요는 없다.

사각형의 너비와 높이가 같은지 검사하면 정사각형 여부를 그때그때 알수 있다.

`isSquare` 프로퍼티에는 자체 값을 저장하는 필드가 없으며, 자체적인 구현을 가지는 getter 만이 존재한다.

클라이언트에서 프로퍼티에 접근할 때마다 getter 가 프로퍼티 값을 매번 다시 계산한다.

🤔🤔 읭? 그렇다면 isSqaure 는 사실상 메서드나 다를바 없지 않나?

그렇다. 파라미터가 없는 함수로 구현하는 것과 커스텀 게터를 정의하는 방식 모두 비슷하다.

구현이나 성능상 차이는 없다. 오직 가독성 면에서만 차이가 존재한다. 

 

코틀린 코딩 컨벤션에서는 아래와 같은 순서로 구현하라고 권고한다.

class A {
    프로퍼티

    init 블록

    부 생성자

    메서드

    동반 객체
}

프로퍼티는 클래스 선언 시작부분 바로 아래에 존재한다.

반면에 메서드는 비교적 하단에 위치한다. 

보통, 클래스의 특성을 정의하고 싶다면, 클래스의 특성이라는 것을 강조하고 싶다면, 프로퍼티로 그 특성을 정의하는 것이 좋다.

메서드보다 커스텀 게터를 사용한 프로퍼티로 구현한다면 특성을 강조하면서 상단에 위치시켜 자주 눈에 띄도록 만들 수 있다.