[Kotlin] 2. 클래스와 프로퍼티, 소스코드 구조

2023. 3. 21. 22:07Programming Languages/Kotlin

Java와 Kotlin의 클래스 비교

Java

public class Person {
    private final String name;
    
    public Person(String name) {
        this.name = name;
    }
    
    public String getName() {
        return name;
    }
}

Kotlin

class Person(val name: String)

1. 프로퍼티

프로퍼티(property)는 필드와 접근자(getter, setter)를 함께 부르는 말로 Kotlin은 이를 언어 차원에서 지원한다. 클래스에서 프로퍼티를 선언할 때는 val과 var를 사용한다. val은 읽기 전용이며 var는 변경 가능하다.

class Person(
    val country: String,   // 읽기 전용 프로퍼티. 비공개 필드와 게터 제공
    var name: String,      // 변경 가능 프로퍼티. 비공개 필드와 게터/세터 제공
    var isMarried: Boolean // 변경 가능 프로퍼티. 비공개 필드와 게터/세터 제공
)

Java에서는 getter 함수를 명시적으로 사용해서 접근해야 했지만 Kotlin에서는 프로퍼티 접근법으로 더 간결하고 직관적으로 필드에 접근할 수 있다.

val p = Person("USA", "Bob", true)
println(p.getName()) //   Bob, Java Bean Style
p.setName("Alice")   //        Java Bean Style
println(p.name)      // Alice, Kotlin Style
p.name = "Sam"       //        Kotlin Style

일반 프로퍼티와 is로 시작되는 프로퍼티의 경우 생성되는 접근자에 차이가 있다.

일반 프로퍼티

  • getter - get + 필드명
  • setter - set + 필드명
val p = Person("USA", "Bob", true)
println(p.getName()) // Bob
p.setName("Alice")
println(p.name)      // Alice

is로 시작되는 프로퍼티

  • getter - 필드명
  • setter - is가 set으로 바뀐 필드명
val p = Person("Bob", true)
println(p.isMarried()) // true
p.setMarried(false)
println(p.isMarried)   // false

2. 커스텀 접근자

자동으로 생성되는 접근자 대신 직접 접근자를 만들 수도 있다.

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

또는 다음과 같이 간단하게 작성할 수도 있다.

class Rectangle(val height: Int, val width: Int) {
    val isSquare: Boolean
	    get() = height == width
}
💡자동 생성된 접근자와 커스텀 접근자의 성능 차이는 없으며 가독성에만 차이가 있다.

3. Kotlin 소스코드 구조: 디렉터리, 패키지

Java와 비슷한 개념이다. 패키지를 선언하면 그 파일의 모든 내용(클래스, 함수 등)이 해당 패키지에 들어간다. 키워드는 package이다. 다른 패키지에 있는 정의를 선언하려면 임포트를 통해 불러와야 한다. Java와 마찬가지로 파일의 맨 앞에 와야 하며 import 키워드를 사용한다.

package geometry.shapes

import java.util.Random

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

fun createRandomRectangle(): Rectangle {
    val random = Random()
    return Rectangle(random.nextInt(), random.nextInt())
}

함수와 클래스의 임포트 방식에는 차이가 없으며, 모든 선언은 import로 가져올 수 있다.

package geometry.example

import geometry.shapes.createRandomRectangle

fun main(args: Array<String>) {
	println(createRandomRectangle().isSquare)
}

패키지 이름 뒤에 .* 을 추가하면 패키지 안의 모든 선언을 임포트할 수 있다. 이때 모든 클래스뿐만 아니라 최상위에 정의된 함수 및 프로퍼티 또한 임포트된다.

vs Java

  • Java
    • 패키지 구조와 일치하는 디렉터리 계층 구조를 만들고 클래스의 소스 코드를 그 클래스가 속한 패키지와 같은 디렉터리에 위치시켜야 한다.
  • Kotlin
    • 한 파일에 여러 클래스를 넣을 수 있고 파일 이름도 마음대로 정할 수 있다.
    • 디스크 상의 어느 디렉터리에 소스코드 파일을 위치시키든지 상관없다.
👉코틀린이 자유도가 더 높기는 하지만 대부분의 경우 자바와 같은 방식으로 구성하는 편이 낫다. 특히 자바와 코틀린을 함께 사용하는 프로젝트인 경우 자바 규칙을 따르는 게 중요하다. 자바 클래스를 코틀린 클래스로 마이그레이션할 때 문제가 생길 수 있기 때문이다. 하지만 여러 클래스를 한 파일에 넣는 것을 주저하면 안 된다. 클래스의 크기가 작을수록 더더욱 그렇다.