클래스
데이터를 저장하는 것이 목적이라면 클래스를 만들기 전에 데이터 클래스(data class) 사용을 고려해 보세요. 또는, 새로운 클래스를 처음부터 만들기보다 확장(extension)을 사용하여 기존 클래스를 확장하는 방법도 생각해 보세요.
다른 객체 지향 언어와 마찬가지로, 코틀린은 데이터(프로퍼티)와 동작(함수)을 캡슐화하여 재사용 가능하고 구조화된 코드를 만들기 위해 _클래스(classes)_를 사용합니다.
클래스는 객체를 만들기 위한 청사진 또는 템플릿이며, [생성자(#constructors-and-initializer-blocks)]를 통해 객체를 생성합니다. 클래스의 인스턴스를 생성하는 것은 해당 청사진을 기반으로 구체적인 객체를 만드는 과정입니다.
코틀린은 클래스 선언을 위한 간결한 구문을 제공합니다. 클래스를 선언하려면 class 키워드 뒤에 클래스 이름을 입력합니다.
class Person { /*...*/ }클래스 선언은 다음과 같이 구성됩니다.
- 클래스 헤더(Class header): 다음 내용을 포함할 수 있습니다(선택 사항 포함).
class키워드- 클래스 이름
- 타입 파라미터 (있는 경우)
- 주 생성자(Primary constructor) (선택 사항)
- 클래스 본문(Class body) (선택 사항): 중괄호
{}로 둘러싸여 있으며, 다음과 같은 클래스 멤버를 포함합니다.
클래스 헤더와 본문을 최소한으로 유지할 수 있습니다. 클래스에 본문이 없으면 중괄호 {}를 생략할 수 있습니다.
// 주 생성자는 있지만 본문은 없는 클래스
class Person(val name: String, var age: Int)다음은 헤더와 본문이 있는 클래스를 선언하고, 이를 사용하여 인스턴스를 생성하는 예제입니다.
// name 프로퍼티를 초기화하는
// 주 생성자가 있는 Person 클래스
class Person(val name: String) {
// age 프로퍼티가 포함된 클래스 본문
var age: Int = 0
}
fun main() {
// 생성자를 호출하여 Person 클래스의 인스턴스를 생성
val person = Person("Alice")
// 인스턴스의 프로퍼티에 접근
println(person.name)
// Alice
println(person.age)
// 0
}인스턴스 생성하기
인스턴스는 클래스를 청사진으로 사용하여 프로그램에서 실제로 작업할 객체를 구축할 때 생성됩니다.
클래스의 인스턴스를 생성하려면 함수 호출과 유사하게 클래스 이름 뒤에 괄호 ()를 사용합니다.
// Person 클래스의 인스턴스 생성
val anonymousUser = Person()코틀린에서는 다음과 같이 인스턴스를 생성할 수 있습니다.
- 인자 없이 생성 (
Person()): 클래스에 기본값이 선언되어 있는 경우, 기본값을 사용하여 인스턴스를 생성합니다. - 인자와 함께 생성 (
Person(value)): 특정 값을 전달하여 인스턴스를 생성합니다.
생성된 인스턴스를 가변(var) 또는 읽기 전용(val) 변수에 할당할 수 있습니다.
// 기본값을 사용하여 인스턴스를 생성하고
// 가변 변수에 할당
var anonymousUser = Person()
// 특정 값을 전달하여 인스턴스를 생성하고
// 읽기 전용 변수에 할당
val namedUser = Person("Joe")인스턴스는 main() 함수 내부, 다른 함수 내부 또는 다른 클래스 내부 등 필요한 곳 어디에서나 생성할 수 있습니다. 또한, 다른 함수 내부에서 인스턴스를 생성하고 main()에서 해당 함수를 호출할 수도 있습니다.
다음 코드는 이름을 저장하기 위한 프로퍼티를 가진 Person 클래스를 선언합니다. 또한 기본 생성자 값과 특정 값을 모두 사용하여 인스턴스를 생성하는 방법을 보여줍니다.
// name을 기본값으로 초기화하는
// 주 생성자가 포함된 클래스 헤더
class Person(val name: String = "Sebastian")
fun main() {
// 기본 생성자 값을 사용하여 인스턴스 생성
val anonymousUser = Person()
// 특정 값을 전달하여 인스턴스 생성
val namedUser = Person("Joe")
// 인스턴스의 name 프로퍼티에 접근
println(anonymousUser.name)
// Sebastian
println(namedUser.name)
// Joe
}코틀린은 다른 객체 지향 프로그래밍 언어와 달리 클래스 인스턴스를 생성할 때
new키워드가 필요하지 않습니다.
중첩 클래스, 내부 클래스 및 익명 내부 클래스의 인스턴스 생성에 대한 정보는 중첩 클래스(Nested classes) 섹션을 참조하세요.
생성자와 초기화 블록
클래스 인스턴스를 생성할 때 해당 클래스의 생성자 중 하나를 호출합니다. 코틀린의 클래스는 하나의 주 생성자(primary constructor)와 하나 이상의 부 생성자(secondary constructors)를 가질 수 있습니다.
주 생성자는 클래스를 초기화하는 주요 방법이며 클래스 헤더에 선언합니다. 부 생성자는 추가적인 초기화 로직을 제공하며 클래스 본문에 선언합니다.
주 생성자와 부 생성자 모두 선택 사항이지만, 클래스에는 적어도 하나의 생성자가 있어야 합니다.
주 생성자
주 생성자는 인스턴스가 생성될 때 초기 상태를 설정합니다.
주 생성자를 선언하려면 클래스 이름 뒤의 클래스 헤더에 배치합니다.
class Person constructor(name: String) { /*...*/ }주 생성자에 애노테이션이나 가시성 수정자가 없다면 constructor 키워드를 생략할 수 있습니다.
class Person(name: String) { /*...*/ }주 생성자는 파라미터를 프로퍼티로 선언할 수 있습니다. 읽기 전용 프로퍼티를 선언하려면 인자 이름 앞에 val 키워드를 사용하고, 가변 프로퍼티를 선언하려면 var 키워드를 사용합니다.
class Person(val name: String, var age: Int) { /*...*/ }이러한 생성자 파라미터 프로퍼티는 인스턴스의 일부로 저장되며 클래스 외부에서 접근할 수 있습니다.
프로퍼티가 아닌 주 생성자 파라미터를 선언할 수도 있습니다. 이러한 파라미터는 앞에 val이나 var이 붙지 않으므로 인스턴스에 저장되지 않으며 클래스 본문 내에서만 사용할 수 있습니다.
// 프로퍼티이기도 한 주 생성자 파라미터
class PersonWithProperty(val name: String) {
fun greet() {
println("Hello, $name")
}
}
// 주 생성자 파라미터일 뿐인 경우 (프로퍼티로 저장되지 않음)
class PersonWithAssignment(name: String) {
// 나중에 사용할 수 있도록 프로퍼티에 할당해야 함
val displayName: String = name
fun greet() {
println("Hello, $displayName")
}
}주 생성자에서 선언된 프로퍼티는 클래스의 멤버 함수에서 접근할 수 있습니다.
// 프로퍼티를 선언하는 주 생성자가 있는 클래스
class Person(val name: String, var age: Int) {
// 클래스 프로퍼티에 접근하는 멤버 함수
fun introduce(): String {
return "Hi, I'm $name and I'm $age years old."
}
}주 생성자의 프로퍼티에 기본값을 할당할 수도 있습니다.
class Person(val name: String = "John", var age: Int = 30) { /*...*/ }인스턴스 생성 시 생성자에 값이 전달되지 않으면 프로퍼티는 기본값을 사용합니다.
// name과 age에 대한 기본값이
// 포함된 주 생성자가 있는 클래스
class Person(val name: String = "John", var age: Int = 30)
fun main() {
// 기본값을 사용하여 인스턴스 생성
val person = Person()
println("Name: ${person.name}, Age: ${person.age}")
// Name: John, Age: 30
}주 생성자 파라미터를 사용하여 클래스 본문에서 직접 추가 클래스 프로퍼티를 초기화할 수 있습니다.
// name과 age에 대한 기본값이
// 포함된 주 생성자가 있는 클래스
class Person(
val name: String = "John",
var age: Int = 30
) {
// 주 생성자 파라미터로부터
// description 프로퍼티를 초기화
val description: String = "Name: $name, Age: $age"
}
fun main() {
// Person 클래스의 인스턴스 생성
val person = Person()
// description 프로퍼티에 접근
println(person.description)
// Name: John, Age: 30
}함수와 마찬가지로 생성자 선언에서도 후행 쉼표(trailing commas)를 사용할 수 있습니다.
class Person(
val name: String,
val lastName: String,
var age: Int,
) { /*...*/ }초기화 블록
주 생성자는 클래스를 초기화하고 프로퍼티를 설정합니다. 대부분의 경우 간단한 코드로 이를 처리할 수 있습니다.
인스턴스 생성 중에 더 복잡한 작업을 수행해야 한다면 클래스 본문 내부의 _초기화 블록(initializer blocks)_에 해당 로직을 작성하세요. 이 블록들은 주 생성자가 실행될 때 함께 실행됩니다.
초기화 블록은 init 키워드 뒤에 중괄호 {}를 붙여 선언합니다. 초기화 중에 실행하려는 코드를 중괄호 안에 작성합니다.
// name과 age를 초기화하는 주 생성자가 있는 클래스
class Person(val name: String, var age: Int) {
init {
// 인스턴스가 생성될 때 초기화 블록이 실행됨
println("Person created: $name, age $age.")
}
}
fun main() {
// Person 클래스의 인스턴스 생성
Person("John", 30)
// Person created: John, age 30.
}필요한 만큼 초기화 블록(init {})을 추가할 수 있습니다. 초기화 블록은 프로퍼티 초기화와 함께 클래스 본문에 나타나는 순서대로 실행됩니다.
// name과 age를 초기화하는 주 생성자가 있는 클래스
class Person(val name: String, var age: Int) {
// 첫 번째 초기화 블록
init {
// 인스턴스 생성 시 가장 먼저 실행됨
println("Person created: $name, age $age.")
}
// 두 번째 초기화 블록
init {
// 첫 번째 초기화 블록 이후에 실행됨
if (age < 18) {
println("$name is a minor.")
} else {
println("$name is an adult.")
}
}
}
fun main() {
// Person 클래스의 인스턴스 생성
Person("John", 30)
// Person created: John, age 30.
// John is an adult.
}초기화 블록에서 주 생성자 파라미터를 사용할 수 있습니다. 예를 들어, 위 코드의 첫 번째와 두 번째 초기화 블록은 주 생성자의 name 및 age 파라미터를 사용합니다.
init 블록의 일반적인 사용 사례는 데이터 검증입니다. 예를 들어, require 함수를 호출할 수 있습니다.
class Person(val age: Int) {
init {
require(age > 0) { "age must be positive" }
}
}부 생성자
코틀린에서 부 생성자는 클래스가 주 생성자 외에 가질 수 있는 추가 생성자입니다. 부 생성자는 클래스를 초기화하는 여러 방법이 필요하거나 자바 상호운용성(Java interoperability)을 위해 유용합니다.
부 생성자를 선언하려면 클래스 본문 내에서 constructor 키워드를 사용하고 괄호 () 안에 생성자 파라미터를 작성합니다. 생성자 로직은 중괄호 {} 안에 추가합니다.
// name과 age를 초기화하는 주 생성자가 있는 클래스 헤더
class Person(val name: String, var age: Int) {
// 나이를 String으로 받아서
// Int로 변환하는 부 생성자
constructor(name: String, age: String) : this(name, age.toIntOrNull() ?: 0) {
println("$name created with converted age: ${this.age}")
}
}
fun main() {
// 나이를 String으로 전달하여 부 생성자 사용
Person("Bob", "8")
// Bob created with converted age: 8
}
age.toIntOrNull() ?: 0식은 엘비스(Elvis) 연산자를 사용합니다. 자세한 정보는 Null 안정성(Null safety)을 참조하세요.
위 코드에서 부 생성자는 this 키워드를 통해 name과 정수로 변환된 age 값을 전달하며 주 생성자에게 위임(delegate)합니다.
코틀린에서 부 생성자는 반드시 주 생성자에게 위임해야 합니다. 이러한 위임은 부 생성자의 로직이 실행되기 전에 모든 주 생성자의 초기화 로직이 실행되도록 보장합니다.
생성자 위임은 다음과 같을 수 있습니다.
- 직접적 위임: 부 생성자가 즉시 주 생성자를 호출하는 경우.
- 간접적 위임: 한 부 생성자가 다른 부 생성자를 호출하고, 그 부 생성자가 다시 주 생성자에게 위임하는 경우.
다음은 직접 위임과 간접 위임이 작동하는 방식을 보여주는 예제입니다.
// name과 age를 초기화하는 주 생성자가 있는 클래스 헤더
class Person(
val name: String,
var age: Int
) {
// 주 생성자에게
// 직접 위임하는 부 생성자
constructor(name: String) : this(name, 0) {
println("Person created with default age: $age and name: $name.")
}
// 간접 위임을 사용하는 부 생성자:
// this("Bob") -> constructor(name: String) -> 주 생성자
constructor() : this("Bob") {
println("New person created with default age: $age and name: $name.")
}
}
fun main() {
// 직접 위임을 기반으로 인스턴스 생성
Person("Alice")
// Person created with default age: 0 and name: Alice.
// 간접 위임을 기반으로 인스턴스 생성
Person()
// Person created with default age: 0 and name: Bob.
// New person created with default age: 0 and name: Bob.
}초기화 블록(init {})이 있는 클래스에서 해당 블록 내의 코드는 주 생성자의 일부가 됩니다. 부 생성자는 항상 주 생성자에게 먼저 위임하므로, 부 생성자의 본문이 실행되기 전에 모든 초기화 블록과 프로퍼티 초기화가 먼저 실행됩니다. 클래스에 주 생성자가 없더라도 이러한 위임은 암시적으로 발생합니다.
// 주 생성자가 없는 클래스 헤더
class Person {
// 인스턴스 생성 시 초기화 블록 실행
init {
// 부 생성자보다 먼저 실행됨
println("1. First initializer block runs")
}
// 정수 파라미터를 받는 부 생성자
constructor(i: Int) {
// 초기화 블록 이후에 실행됨
println("2. Person $i is created")
}
}
fun main() {
// Person 클래스의 인스턴스 생성
Person(1)
// 1. First initializer block runs
// 2. Person 1 created
}생성자가 없는 클래스
주 생성자나 부 생성자를 선언하지 않은 클래스는 파라미터가 없는 암시적 주 생성자를 가집니다.
// 명시적 생성자가 없는 클래스
class Person {
// 선언된 주 생성자나 부 생성자가 없음
}
fun main() {
// 암시적 주 생성자를 사용하여
// Person 클래스의 인스턴스 생성
val person = Person()
}이 암시적 주 생성자의 가시성은 public이며, 이는 어디에서나 접근할 수 있음을 의미합니다. 클래스가 public 생성자를 갖지 않도록 하려면 기본값이 아닌 가시성을 가진 빈 주 생성자를 선언하세요.
class Person private constructor() { /*...*/ }JVM 환경에서 주 생성자의 모든 파라미터에 기본값이 있는 경우, 컴파일러는 이러한 기본값을 사용하는 파라미터 없는 생성자를 암시적으로 제공합니다.
이는 파라미터 없는 생성자를 통해 클래스 인스턴스를 생성하는 Jackson이나 Spring Data JPA와 같은 라이브러리를 코틀린과 함께 더 쉽게 사용할 수 있게 해줍니다.
다음 예제에서 코틀린은 기본값
""을 사용하는 파라미터 없는 생성자Person()을 암시적으로 제공합니다.kotlinclass Person(val personName: String = "")
상속
코틀린의 클래스 상속을 사용하면 기존 클래스(기본 클래스)에서 새로운 클래스(파생 클래스)를 만들어 프로퍼티와 함수를 상속받으면서 동작을 추가하거나 수정할 수 있습니다.
상속 계층 구조와 open 키워드 사용 방법에 대한 자세한 정보는 상속(Inheritance) 섹션을 참조하세요.
추상 클래스
코틀린에서 추상 클래스는 직접 인스턴스화할 수 없는 클래스입니다. 추상 클래스는 실제 동작을 정의하는 다른 클래스에 의해 상속되도록 설계되었습니다. 이러한 동작 정의를 _구현(implementation)_이라고 합니다.
추상 클래스는 추상 프로퍼티와 추상 함수를 선언할 수 있으며, 이는 서브클래스에서 반드시 구현해야 합니다.
추상 클래스도 생성자를 가질 수 있습니다. 이러한 생성자는 클래스 프로퍼티를 초기화하고 서브클래스에 필요한 파라미터를 강제합니다. 추상 클래스는 abstract 키워드를 사용하여 선언합니다.
abstract class Person(val name: String, val age: Int)추상 클래스는 추상 멤버와 비추상 멤버(프로퍼티 및 함수)를 모두 가질 수 있습니다. 멤버를 추상으로 선언하려면 명시적으로 abstract 키워드를 사용해야 합니다.
추상 클래스나 함수는 기본적으로 상속이 가능하므로 open 키워드를 붙일 필요가 없습니다. open 키워드에 대한 자세한 내용은 상속(Inheritance)의 open 키워드를 참조하세요.
추상 멤버는 추상 클래스 내에 구현을 가지지 않습니다. 서브클래스나 상속받는 클래스에서 override 함수나 프로퍼티를 사용하여 구현을 정의합니다.
// name과 age를 선언하는 주 생성자가 있는 추상 클래스
abstract class Person(
val name: String,
val age: Int
) {
// 추상 멤버
// 구현을 제공하지 않으며,
// 서브클래스에서 반드시 구현해야 함
abstract fun introduce()
// 비추상 멤버 (구현이 있음)
fun greet() {
println("Hello, my name is $name.")
}
}
// 추상 멤버에 대한 구현을 제공하는 서브클래스
class Student(
name: String,
age: Int,
val school: String
) : Person(name, age) {
override fun introduce() {
println("I am $name, $age years old, and I study at $school.")
}
}
fun main() {
// Student 클래스의 인스턴스 생성
val student = Student("Alice", 20, "Engineering University")
// 비추상 멤버 호출
student.greet()
// Hello, my name is Alice.
// 오버라이드된 추상 멤버 호출
student.introduce()
// I am Alice, 20 years old, and I study at Engineering University.
}동반 객체
코틀린에서 각 클래스는 하나의 동반 객체(companion object)를 가질 수 있습니다. 동반 객체는 클래스 인스턴스를 생성하지 않고 클래스 이름을 사용하여 해당 멤버에 접근할 수 있게 해주는 객체 선언의 한 종류입니다.
클래스 인스턴스를 생성하지 않고 호출할 수 있어야 하지만 논리적으로 클래스와 연결된 함수(예: 팩토리 함수)를 작성해야 한다고 가정해 보겠습니다. 이 경우 클래스 내의 객체 선언(object declaration)인 동반 객체 내부에 해당 함수를 선언할 수 있습니다.
// name 프로퍼티를 선언하는 주 생성자가 있는 클래스
class Person(
val name: String
) {
// 동반 객체가 있는 클래스 본문
companion object {
fun createAnonymous() = Person("Anonymous")
}
}
fun main() {
// 클래스 인스턴스를 생성하지 않고 함수 호출
val anonymous = Person.createAnonymous()
println(anonymous.name)
// Anonymous
}클래스 내부에 동반 객체를 선언하면 클래스 이름만을 한정자(qualifier)로 사용하여 그 멤버에 접근할 수 있습니다.
자세한 정보는 동반 객체(Companion objects)를 참조하세요.
