클래스

클래스 (Classes)

Kotlin은 클래스와 객체를 이용한 객체 지향 프로그래밍(object-oriented programming)을 지원해요. 객체(object)는 프로그램에서 데이터를 저장하는 데 유용해요. 클래스(class)는 객체의 특성 집합을 선언할 수 있게 해 줘요. 클래스로 객체를 만들면 이 특성들을 매번 선언하지 않아도 되니 시간과 노력을 아낄 수 있어요.

클래스를 선언하려면 class 키워드를 사용해요:

class Customer

출처: Kotlin 공식 문서

본문

프로퍼티 (Properties)

클래스 객체의 특성은 프로퍼티(property)로 선언할 수 있어요. 클래스의 프로퍼티는 다음과 같이 선언할 수 있어요:

  • 클래스 이름 뒤 괄호 () 안에 선언해요.
class Contact(val id: Int, var email: String)
  • 중괄호 {}로 정의한 클래스 본문 안에 선언해요.
class Contact(val id: Int, var email: String) {
    val category: String = ""
}

클래스 인스턴스가 만들어진 뒤 바뀔 필요가 없는 프로퍼티는 읽기 전용(val)으로 선언할 것을 권장해요.

괄호 안에서 val이나 var 없이 프로퍼티를 선언할 수도 있지만, 인스턴스가 만들어진 뒤에는 이런 프로퍼티에 접근할 수 없어요.

Note: 괄호 () 안에 들어 있는 내용을 클래스 헤더(class header)라고 해요. 클래스 프로퍼티를 선언할 때는 후행 쉼표(trailing comma)를 사용할 수 있어요.

  • 함수 매개변수처럼 클래스 프로퍼티도 기본값을 가질 수 있어요:
class Contact(val id: Int, var email: String = "[email protected]") {
    val category: String = "work"
}

인스턴스 만들기 (Create instance)

클래스로 객체를 만들려면 생성자(constructor)를 이용해 클래스 인스턴스를 선언해요.

기본적으로 Kotlin은 클래스 헤더에 선언된 매개변수를 가진 생성자를 자동으로 만들어 줘요.

예를 들어:

class Contact(val id: Int, var email: String)

fun main() {
    val contact = Contact(1, "[email protected]")
}

예시에서:

  • Contact는 클래스예요.
  • contactContact 클래스의 인스턴스예요.
  • idemail은 프로퍼티예요.
  • idemail은 기본 생성자에 사용되어 contact를 만들어요.

Kotlin 클래스는 직접 정의한 생성자를 포함해 여러 생성자를 가질 수 있어요. 여러 생성자를 선언하는 방법은 Constructors 문서를 참고하세요.

프로퍼티에 접근하기 (Access properties)

인스턴스의 프로퍼티에 접근하려면 인스턴스 이름 뒤에 마침표 .를 붙인 다음 프로퍼티 이름을 쓰면 돼요:

class Contact(val id: Int, var email: String)

fun main() {
    val contact = Contact(1, "[email protected]")
    
    // Prints the value of the property: email
    println(contact.email)           
    // [email protected]

    // Updates the value of the property: email
    contact.email = "[email protected]"
    
    // Prints the new value of the property: email
    println(contact.email)           
    // [email protected]
}
println("Their email address is: ${contact.email}")

Tip: 프로퍼티의 값을 문자열의 일부로 연결하려면 문자열 템플릿(string template) $를 사용할 수 있어요. 예를 들어:

멤버 함수 (Member functions)

객체의 특성으로 프로퍼티를 선언하는 것에 더해, 멤버 함수(member function)로 객체의 동작(behavior)을 정의할 수도 있어요.

Kotlin에서 멤버 함수는 반드시 클래스 본문 안에 선언해야 해요. 인스턴스에서 멤버 함수를 호출하려면 인스턴스 이름 뒤에 마침표 .를 붙인 다음 함수 이름을 써요. 예를 들어:

class Contact(val id: Int, var email: String) {
    fun printId() {
        println(id)
    }
}

fun main() {
    val contact = Contact(1, "[email protected]")
    // Calls member function printId()
    contact.printId()           
    // 1
}

데이터 클래스 (Data classes)

Kotlin에는 데이터 저장에 특히 유용한 데이터 클래스(data class)가 있어요. 데이터 클래스는 클래스와 기능이 같지만, 추가 멤버 함수가 자동으로 함께 제공돼요. 이 멤버 함수들을 이용하면 인스턴스를 읽기 좋은 출력으로 쉽게 출력하고, 클래스의 인스턴스를 비교하고, 인스턴스를 복사하는 등의 작업을 할 수 있어요. 이런 함수들이 자동으로 제공되기 때문에 각 클래스마다 같은 보일러플레이트(boilerplate) 코드를 일일이 작성할 시간을 아낄 수 있어요.

데이터 클래스를 선언하려면 data 키워드를 사용해요:

data class User(val name: String, val id: Int)

Kotlin 컴파일러는 멤버 함수를 만들 때 기본 생성자 안에 정의된 프로퍼티만 사용해요. 데이터 클래스 본문에서 프로퍼티를 선언하면, 생성된 함수의 결과에 포함되지 않아요.

데이터 클래스의 가장 유용한 미리 정의된 멤버 함수는 다음과 같아요:

함수 설명
toString() 클래스 인스턴스와 그 프로퍼티를 읽기 좋은 문자열로 출력해요.
equals() 또는 == 클래스의 인스턴스를 비교해요.
copy() 다른 인스턴스를 복사해, 프로퍼티 일부를 바꿔 클래스 인스턴스를 만들어요.

각 함수의 사용 예시는 다음 섹션에서 볼 수 있어요:

  • 문자열로 출력하기
  • 인스턴스 비교하기
  • 인스턴스 복사하기

문자열로 출력하기 (Print as string)

클래스 인스턴스를 읽기 좋은 문자열로 출력하려면 toString() 함수를 명시적으로 호출하거나, 자동으로 toString()을 호출하는 출력 함수(println()print())를 사용하면 돼요:

data class User(val name: String, val id: Int)

fun main() {
    //sampleStart
    val user = User("Alex", 1)
    
    // Automatically uses toString() function so that output is easy to read
    println(user)            
    // User(name=Alex, id=1)
    //sampleEnd
}

이 기능은 디버깅이나 로그를 만들 때 특히 유용해요.

인스턴스 비교하기 (Compare instances)

데이터 클래스 인스턴스를 비교하려면 동등 연산자(equality operator) ==를 사용해요:

data class User(val name: String, val id: Int)

fun main() {
    //sampleStart
    val user = User("Alex", 1)
    val secondUser = User("Alex", 1)
    val thirdUser = User("Max", 2)

    // Compares user to second user
    println("user == secondUser: ${user == secondUser}") 
    // user == secondUser: true
    
    // Compares user to third user
    println("user == thirdUser: ${user == thirdUser}")   
    // user == thirdUser: false
    //sampleEnd
}

인스턴스 복사하기 (Copy instance)

데이터 클래스 인스턴스의 정확한 복사본을 만들려면 인스턴스에서 copy() 함수를 호출해요.

데이터 클래스 인스턴스의 복사본을 만들고 일부 프로퍼티를 바꾸려면 인스턴스에서 copy() 함수를 호출하고, 함수 매개변수로 프로퍼티의 대체 값을 넘겨줘요.

예를 들어:

data class User(val name: String, val id: Int)

fun main() {
    //sampleStart
    val user = User("Alex", 1)

    // Creates an exact copy of user
    println(user.copy())       
    // User(name=Alex, id=1)

    // Creates a copy of user with name: "Max"
    println(user.copy("Max"))  
    // User(name=Max, id=1)

    // Creates a copy of user with id: 3
    println(user.copy(id = 3)) 
    // User(name=Alex, id=3)
    //sampleEnd
}

인스턴스의 복사본을 만드는 건 원본 인스턴스를 수정하는 것보다 안전해요. 원본 인스턴스에 의존하는 코드는 복사본과 그 복사본으로 무슨 일을 하든 영향을 받지 않기 때문이에요.

데이터 클래스에 대한 자세한 내용은 Data classes 문서를 참고하세요.

이 투어의 마지막 장은 Kotlin의 null 안전성(null safety)에 관한 내용이에요.

연습 (Practice)

연습 1 (Exercise 1)

두 개의 프로퍼티를 가진 데이터 클래스 Employee를 정의하세요. 하나는 이름용이고, 다른 하나는 월급용이에요. 월급 프로퍼티는 변경 가능(mutable)해야 해요. 그래야 연말에 월급 인상을 받을 수 있으니까요! main 함수는 이 데이터 클래스를 어떻게 사용하는지 보여 줘요.

// Write your code here

fun main() {
    val emp = Employee("Mary", 20)
    println(emp)
    emp.salary += 10
    println(emp)
}
data class Employee(val name: String, var salary: Int)

fun main() {
    val emp = Employee("Mary", 20)
    println(emp)
    emp.salary += 10
    println(emp)
}

연습 2 (Exercise 2)

이 코드가 컴파일되도록 필요한 추가 데이터 클래스를 선언하세요.

data class Person(val name: Name, val address: Address, val ownsAPet: Boolean = true)
// Write your code here
// data class Name(...)

fun main() {
    val person = Person(
        Name("John", "Smith"),
        Address("123 Fake Street", City("Springfield", "US")),
        ownsAPet = false
    )
}
data class Person(val name: Name, val address: Address, val ownsAPet: Boolean = true)
data class Name(val first: String, val last: String)
data class Address(val street: String, val city: City)
data class City(val name: String, val countryCode: String)

fun main() {
    val person = Person(
        Name("John", "Smith"),
        Address("123 Fake Street", City("Springfield", "US")),
        ownsAPet = false
    )
}

연습 3 (Exercise 3)

코드를 테스트하려면 임의의 직원을 만드는 생성기(generator)가 필요해요. 잠재적 이름 목록을 고정으로 가진 RandomEmployeeGenerator 클래스를 정의하세요(클래스 본문 안에). 최소·최대 월급으로 클래스를 설정하세요(클래스 헤더 안에). 클래스 본문 안에서 generateEmployee() 함수를 정의하세요. 역시 main 함수가 이 클래스를 사용하는 방법을 보여 줘요.

Tip: 이 연습에서 Random.nextInt() 함수를 사용할 수 있도록 패키지를 import해요. 패키지 import에 대한 자세한 내용은 Packages and imports 문서를 참고하세요.

Hint 1: 리스트에는 리스트 안의 항목 중 무작위 항목 하나를 반환하는 .random()이라는 확장 함수가 있어요.

Hint 2: Random.nextInt(from = ..., until = ...)는 지정한 범위 안에서 무작위 Int 숫자를 줘요.

import kotlin.random.Random

data class Employee(val name: String, var salary: Int)

// Write your code here

fun main() {
    val empGen = RandomEmployeeGenerator(10, 30)
    println(empGen.generateEmployee())
    println(empGen.generateEmployee())
    println(empGen.generateEmployee())
    empGen.minSalary = 50
    empGen.maxSalary = 100
    println(empGen.generateEmployee())
}
import kotlin.random.Random

data class Employee(val name: String, var salary: Int)

class RandomEmployeeGenerator(var minSalary: Int, var maxSalary: Int) {
    val names = listOf("John", "Mary", "Ann", "Paul", "Jack", "Elizabeth")
    fun generateEmployee() =
        Employee(names.random(),
            Random.nextInt(from = minSalary, until = maxSalary))
}

fun main() {
    val empGen = RandomEmployeeGenerator(10, 30)
    println(empGen.generateEmployee())
    println(empGen.generateEmployee())
    println(empGen.generateEmployee())
    empGen.minSalary = 50
    empGen.maxSalary = 100
    println(empGen.generateEmployee())
}