클래스의 사용 방법

코틀린에서 클래스는 객체를 만들기 위한 설계도라고 할 수 있습니다. 클래스는 속성(프로퍼티)와 행동(메서드)을 정의하며, 이를 기반으로 여러 개의 객체(인스턴스)를 만들 수 있습니다.

class Person {
    var name: String = ""
    var age: Int = 0

    fun introduce() {
        println("Hi, my name is $name, and I'm $age years old.")
    }
}

위 예제에서, Person이라는 클래스가 정의되었습니다. 이 클래스는 nameage라는 속성(프로퍼티)를 가지고 있으며, introduce()라는 행동(메서드)을 가집니다. 이제 이 클래스를 사용해 인스턴스를 생성할 수 있습니다.

프로퍼티(property)와 메서드(method)

  • 프로퍼티: 클래스 내부에서 선언된 변수로, 객체의 상태를 나타냅니다. 코틀린에서는 프로퍼티에 대해 자동으로 gettersetter가 생성됩니다.
  • 메서드: 클래스 내부에서 선언된 함수로, 객체의 행동을 정의합니다. 메서드는 클래스가 가진 데이터를 처리하거나 특정 기능을 수행합니다.

인스턴스(instance)란?

클래스는 설계도라면, 그 설계도로 실제 사용할 수 있는 객체를 만드는 과정이 인스턴스화입니다. 이때 만들어진 객체가 인스턴스입니다.

인스턴스를 생성하면 클래스의 속성과 동작이 메모리에 올라가서 활성화됩니다.
즉, 인스턴스를 통해서만 클래스에 정의된 프로퍼티와 메서드를 사용할 수 있습니다.

클래스 초기화 (인스턴스 생성)

클래스를 초기화한다는 것은 클래스를 기반으로 새로운 인스턴스를 생성하는 과정입니다.
이를 통해 클래스의 속성과 메서드를 메모리에 올려 사용할 수 있게 합니다.
클래스를 초기화하지 않으면 해당 클래스는 단순한 설계도로 남아 있으며, 실제로 데이터를 담거나 동작을 수행할 수 없습니다.

class Person(val name: String, var age: Int) {
    fun introduce() {
        println("Hi, my name is $name, and I'm $age years old.")
    }
}

val person1 = Person("Alice", 25)  // 인스턴스 생성
person1.introduce()  // Hi, my name is Alice, and I'm 25 years old.

이 예시에서 Person 클래스는 사람을 정의하는 설계도이고, person1이라는 인스턴스가 만들어지면서 그 설계도에 맞는 실제 데이터를 가지고 동작할 수 있게 됩니다. Person 클래스를 초기화하지 않으면 nameage라는 정보를 사용할 수 없습니다.

컴패니언 객체 (인스턴스 없이 클래스 사용)

인스턴스를 만들지 않고도 클래스를 사용할 수 있는 방법이 있습니다. 그것이 바로 컴패니언 객체(Companion Object)입니다.
컴패니언 객체는 클래스 자체에 속하는 메서드나 프로퍼티를 정의할 수 있게 해줍니다.

컴패니언 객체를 만들려면 companion object 키워드를 사용해야 합니다.
컴페니언 객체를 만들면 인스턴스를 생성하지 않고 클래스 이름을 통해 바로 호출할 수 있습니다.

class Person(val name: String, var age: Int) {
    companion object {
        fun createDefaultPerson(): Person {
            return Person("Unknown", 0)
        }
    }
}

val defaultPerson = Person.createDefaultPerson()  // 인스턴스 없이 메서드 호출
println(defaultPerson.name)  // Unknown

createDefaultPerson 메서드는 인스턴스 없이 Person 클래스 이름을 통해 바로 호출됩니다.
컴패니언 객체는 주로 공통된 기능을 제공할 때 유용하게 사용됩니다.

언제 컴패니언 객체를 사용하는가?

컴패니언 객체는 인스턴스가 필요 없는 경우에 사용합니다. 예를 들어, 클래스 전체에 관련된 공통된 작업이나 유틸리티 함수를 정의할 때 적합합니다. 하지만 개별 인스턴스마다 다른 데이터를 처리해야 하는 경우에는 인스턴스화가 필수적입니다.

상속 (Inheritance)

클래스는 다른 클래스를 상속받을 수 있습니다. 상속은 기존 클래스의 기능을 그대로 물려받고, 그 위에 새로운 기능을 추가하거나 기존 기능을 덮어쓸 수 있는 기능입니다.

부모 클래스의 메서드를 수정하거나 재정의하려면 openoverride 키워드를 사용해야 합니다.

  • open: 부모 클래스에서 자식 클래스가 메서드를 재정의할 수 있도록 허용하는 키워드.
  • override: 자식 클래스에서 부모 클래스의 메서드를 재정의할 때 사용.

상속받은 클래스는 하위 클래스라고 하며, 상속해주는 클래스는 상위 클래스라고 부릅니다.

예시:

// 상위 클래스
open class Animal(val name: String) {  // 상속을 허용하려면 클래스에 open 키워드를 붙여야 함
    fun eat() {
        println("$name is eating.")
    }

    open fun sound() {
        println("$name is making a sound.")
    }
}

// 하위 클래스
class Dog(name: String) : Animal(name) {
    override fun sound() {  // 부모 클래스의 메서드를 재정의 (override)
        println("$name is barking.")
    }
}

val myDog = Dog("Buddy")
myDog.eat()   // Buddy is eating.
myDog.sound() // Buddy is barking.
  • Animal 클래스는 상위 클래스입니다. 이 클래스는 eat()sound() 메서드를 정의하고 있습니다.
  • Dog 클래스는 Animal 클래스를 상속받고 있으며, sound() 메서드를 재정의(override)하여 강아지의 소리를 출력하도록 변경했습니다.

상속을 통해 상위 클래스의 기능을 물려받으면서, 하위 클래스는 자신만의 메서드를 추가할 수 있습니다.

class Dog(name: String) : Animal(name) {
    override fun sound() {
        println("$name is barking.")
    }

    fun fetch() {
        println("$name is fetching the ball.")
    }
}

val myDog = Dog("Buddy")
myDog.eat()   // Buddy is eating.
myDog.sound() // Buddy is barking.
myDog.fetch() // Buddy is fetching the ball.

위 예제에서 Dog 클래스는 fetch()라는 새로운 메서드를 추가했습니다.
이 메서드는 Animal 클래스에는 없는 메서드로, Dog 클래스의 인스턴스에서만 사용할 수 있습니다.