Kotlin

Kotlin Class 알아보기 1(Class, Data Class, Enum Class, Abstract Class)

빈코더 2024. 12. 19. 14:53
728x90

Class

class란?

코틀린에서 일반 클래스는 특별한 키워드나 제약 없이 선언하는 기본적인 클래스입니다.
속성(필드)메서드(함수)를 포함할 수 있으며, 객체를 생성하고 관리하는 데 사용됩니다.

class 특징

  1. 생성자
    • 코틀린의 클래스는 주 생성자(Primary Constructor)와 보조 생성자(Secondary Constructor)를 가질 수 있습니다.
  2. 속성과 메서드
    • 클래스 내부에는 상태(속성, var/val)와 동작(메서드)을 정의할 수 있습니다.
  3. 객체 생성
    • new 키워드 없이 클래스 이름 뒤에 괄호 ()를 사용해 객체를 생성합니다.
  4. 상속
    • 기본적으로 코틀린 클래스는 final입니다. 다른 클래스가 상속받을 수 있도록 하려면 open 키워드를 사용해야 합니다.

class 사용 예제 

1. 기본 문법

코틀린에서 클래스를 선언하는 기본 형식은 다음과 같습니다.

  class 클래스이름 {
      // 속성(프로퍼티)
      var 변수이름: 타입 = 초기값
      val 상수이름: 타입 = 초기값

      // 메서드(함수)
      fun 메서드이름() {
          // 실행할 코드
      }
  }

2. 일반 클래스의 속성과 메서드

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

  fun main() {
      val person = Person("Alice", 25)
      person.introduce() // Hi, my name is Alice and I am 25 years old.
  }
  • name: val이므로 읽기 전용 속성입니다.
  • age: var이므로 읽고 쓸 수 있는 속성입니다.
  • introduce()는 클래스의 메서드입니다.

3. 주 생성자와 보조 생성자

   class Person(
      val name: String, 
    var age: Int,
  ) {
      // 보조 생성자
      constructor(name: String) : this(name, 0) {
          println("Secondary constructor called")
      }

      fun introduce() = println("Hi, I'm $name, $age years old.")
  }

  fun main() {
      val person1 = Person("Alice", 25)
      person1.introduce() // Hi, I'm Alice, 25 years old.

      val person2 = Person("Bob") // 보조 생성자 사용
      person2.introduce() // Hi, I'm Bob, 0 years old.
  }
  • 주 생성자: 클래스 이름 옆에 선언되는 생성자입니다.
  • 보조 생성자: constructor 키워드를 사용하여 선언합니다.

4. 클래스 상속

  open class Animal(
      val name: String,
  ) {
      open fun sound() = println("Some sound...")
  }

  class Dog(name: String) : Animal(name) {
      override fun sound() = println("Woof! Woof!")
  }

  fun main() {
      val dog = Dog("Buddy")
      println(dog.name) // Buddy
      dog.sound()       // Woof! Woof!
  }
  • open: 클래스를 상속받을 수 있게 합니다.
  • override: 부모 클래스의 메서드를 재정의합니다.

클래스의 주요 사용 예

  1. 데이터를 캡슐화
    • 속성과 메서드를 하나로 묶어 객체 단위로 관리합니다.
  2. 코드 재사용
    • 클래스 상속과 메서드 오버라이드를 통해 코드의 재사용성을 높입니다.
  3. 객체지향 설계
    • 상태(속성)와 행동(메서드)을 클래스 단위로 설계하여 유연하고 명확한 코드를 작성할 수 있습니다.

Data Class

data class란?

코틀린에서 data class는 데이터를 저장하는 목적으로 설계된 클래스입니다.
주로 객체 간 데이터 저장, 비교, 복사 등의 작업을 더 간단하고 효율적으로 처리할 수 있도록 지원합니다.

data class의 특징

  1. 자동으로 생성되는 메서드
    • equals() : 객체 간 내용이 같은지 비교합니다.
    • hashCode() : 객체의 해시코드를 반환합니다.
    • toString() : 객체의 내용을 문자열로 반환합니다.
    • copy() : 객체를 복사하고 일부 필드를 변경할 수 있습니다.
    • componentN() : 프로퍼티를 순서대로 반환하는 메서드를 제공합니다.
  2. 불변성
    • 대부분의 경우 val 키워드를 사용해 속성을 불변으로 선언합니다.
    • 필요에 따라 var로 가변 프로퍼티도 사용할 수 있습니다.
  3. 주 생성자 필요
    • data class는 반드시 주 생성자(primary constructor)에 프로퍼티를 선언해야 합니다.

data class의 기본 문법

    data class 클래스이름(
        val property1: 타입, 
        val property2: 타입
    )

data class 예제

1. 기본 사용법

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

  fun main() {
      val user1 = User("Alice", 25)
      val user2 = User("Alice", 25)

      println(user1)         // toString(): User(name=Alice, age=25)
      println(user1 == user2) // equals(): true (내용이 같으면 true)
  }
  • 자동으로 toString, equals 메서드가 구현되어 객체의 내용르 문자열로 출력하고 비교합니다.

2. copy() 메서드 사용

copy() 메서드를 사용하면 객체를 복사하면서 일부 속성만 변경할 수 있습니다.

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

  fun main() {
      val user1 = User("Alice", 25)
      val user2 = user1.copy(age = 26) // age만 변경

      println(user1) // User(name=Alice, age=25)
      println(user2) // User(name=Alice, age=26)
  }

3. component() 함수 사용(구조 분해 선언)

componentN() 메서드를 통해 객체의 프로퍼티를 순서대로 꺼낼 수 있습니다.

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

  fun main() {
      val user = User("Bob", 30)
      val (name, age) = user // 구조 분해 선언 (component1, component2 사용)

      println("Name: $name") // Name: Bob
      println("Age: $age")   // Age: 30
  }

4. 가변 프로퍼티 사용(var)

data class는 val 대신 var를 사용할 수 있지만, 일반적으로 불변성을 유지하는 것이 권장됩니다.

  data class User(
      var name: String, 
    var age: Int,
  )

  fun main() {
      val user = User("Charlie", 28)
      user.age = 29 // 가변 속성 변경 가능

      println(user) // User(name=Charlie, age=29)
  }
  • val 키워드와 copy() 메서드의 동작 원리
    • val은 불변(immutable) 속성을 의미합니다. 즉, 객체가 생성된 후에는 속성 값을 변경할 수 없습니다.
    • 그러나 copy() 메서드는 새로운 객체를 생성하는 것이지, 기존 객체를 변경하는 것이 아닙니다. 새로운 객체를 생성할 때 새로운 값을 전달할 수 있습니다.

5. 상속 불가

  • data class는 기본적으로 final입니다. 따라서 상속할 수 없습니다.
  • 만약 상속을 허용하려면 open 키워드를 명시해야 하지만, 권장되지 않습니다.

data class의 장점

  1. 자동으로 유용한 메서드를 생성
    • equals(), hashCode(), toString(), copy() 등 자주 사용되는 메서드를 자동으로 제공해 코드 작성이 간결해집니다.
  2. 불변성을 쉽게 유지
    • 주로 val을 사용하므로 객체의 상태를 불변으로 유지하기 쉽습니다.
  3. 구조 분해 선언 지원
    • componentN() 메서드를 통해 객체의 데이터를 쉽게 추출할 수 있습니다.
  4. 코드 간결화
    • 데이터 클래스는 동일한 내용을 일반 클래스보다 훨씬 적은 코드로 작성할 수 있습니다.

data class의 단점

  1. 로직을 포함하기에 부적합
    • data class는 데이터를 저장하고 처리하는 목적이므로 복잡한 로직이 포함되면 불필요하게 오버헤드가 발생할 수 있습니다.
  2. 상속 불가
    • 기본적으로 data class는 final이기 때문에 상속이 불가능합니다.
  3. 필수 조건
    • data class는 주 생성자에 최소 하나의 프로퍼티가 선언되어 있어야 합니다.

결론

data class는 데이터를 표현하고 관리하기 위해 코틀린에서 제공하는 강력한 기능입니다.
객체의 비교, 출력, 복사 등을 자동으로 제공하므로 간결하고 효율적으로 데이터를 다룰 수 있습니다.

사용 예시:

  • API 응답 객체, 데이터 모델, DTO(Data Transfer Object), 불변 객체 생성 등에 유용합니다.

Enum Class

enum class란?

코틀린의 enum class는 열거형 클래스로, 미리 정의된 상수 값을 나타내는 데이터 타입입니다.
주로 고정된 값들의 집합을 표현할때 사용되며, 각 열거형 상수는 객체로 취급됩니다.

enum class의 특징

  1. 상수 값의 집합 표현
    • enum class는 고정된 값을 그룹화하여 코드 가독성과 안정성을 높입니다.
    • 예: 방향(NORTH, SOUTH, EAST, WEST), 상태(LOADING, SUCCESS, ERROR) 등.
  2. 객체로 취급
    • 각 상수는 Enum 클래스의 객체로 취급됩니다.
    • 고유한 속성이나 메서드를 가질 수 있습니다.
  3. 컴파일 시 타입 안정성 제공
    • enum값 외에 다른 값을 사용하려고 하면 컴파일 에러가 발생합니다.
  4. 메서드와 속성 추가 가능
    • 열거형 상수마다 특정 로직이나 데이터를 추가할 수 있습니다.
  5. 기본 제공 메서드
    • name: 상수 이름 반환
    • ordinal: 상수의 순서(0부터 시작) 반환
    • values(): 모든 상수의 배열 반환
    • valueOf(name: String): 문자열 이름에 해당하는 상수 반환

enum class의 기본 문법

  enum class Enum이름 {
      상수1, 상수2, 상수3
  }

enum class 예제

1. 기본 사용

  enum class Direction {
      NORTH, SOUTH, EAST, WEST
  }

  fun main() {
      val direction = Direction.NORTH

      println(direction.name)    // NORTH
      println(direction.ordinal) // 0
  }

2. 열거형 상수에 속성 추가

  enum class Color(val hex: String) {
      RED("#FF0000"),
      GREEN("#00FF00"),
      BLUE("#0000FF")
  }

  fun main() {
      val color = Color.RED
      println(color.hex) // #FF0000
  }
  • 각 상수(RED, GREEN, BLUE)는 hex라는 속성을 가집니다.

3. 열거형 상수에 메서드 추가

  enum class Operation {
      ADD {
          override fun apply(x: Int, y: Int) = x + y
      },
      SUBTRACT {
          override fun apply(x: Int, y: Int) = x - y
      };

      abstract fun apply(x: Int, y: Int): Int
  }

  fun main() {
      val result = Operation.ADD.apply(5, 3)
      println(result) // 8
  }
  • 각 열거형 상수가 고유의 메서드 구현을 가질 수 있습니다.

4. when과 함께 사용

enum class는 when 표현식에서 자주 사용됩니다. 컴파일러가 열거형 상수의 모든 경우를 처리했는지 확인해줍니다.

  enum class TrafficLight {
      RED, YELLOW, GREEN
  }

  fun getAction(light: TrafficLight): String {
      return when (light) {
          TrafficLight.RED -> "Stop"
          TrafficLight.YELLOW -> "Slow Down"
          TrafficLight.GREEN -> "Go"
      }
  }

  fun main() {
      println(getAction(TrafficLight.RED)) // Stop
  }

5. values()와 valueOf()

  enum class Level {
      LOW, MEDIUM, HIGH
  }

  fun main() {
      val levels = Level.values() // 모든 상수 배열
      levels.forEach { println(it) } // LOW, MEDIUM, HIGH

      val high = Level.valueOf("HIGH") // 이름으로 상수 찾기
      println(high) // HIGH
  }

enum class의 내부 동작

  • enum class는 기본적으로 상수 값들을 정적 객체로 관리하며, 클래스 내부에 선언된 객체와 유사한 방식으로 동작합니다.
  • 각 상수는 enum 클래스의 객체이며, 상수마다 고유 속성과 메서드를 정의할 수 있습니다.

enum class의 장점

  1. 가독성과 안정성
    • 고정된 값을 의미 있게 정의하여 가독성을 높이고, 허용되지 않은 값 사용을 방지해 타입 안전성을 제공합니다.
  2. 명확한 표현
    • 상수 값의 의미를 명확히 표현할 수 있습니다.
      • 예: 숫자 1, 2, 3 대신 LOW, MEDIUM, HIGH를 사용.
  3. 상수에 추가 속성 및 메서드 제공
    • 열거형 상수에 속성과 메서드를 추가할 수 있어 유연한 데이터 구조를 제공합니다.
  4. when과의 강력한 조합
    • when에서 열거형(enum)을 사용하면 컴파일러가 모든 경우를 처리햇는지 확인하므로, 버그를 예방할 수 있습니다.

enum class의 단점

  1. 고정된 값만 사용
    • 열거형(enum)은 미리 정의된 고정된 값들만 허용되므로, 런타입에 동적으로 값을 추가하거나 변경할 수 없습니다.
  2. 복잡한 논리를 포함하면 불편
    • 열거형(enum) 상수 각각에 고유한 복잡한 로직이 포함되면 코드가 복잡해질 수 있습니다.
  3. 범용성이 떨어짐
    • 열거형은 상수 집합에 적합하지만, 보다 복잡한 데이터 구조를 다룰 때는 다른 클래스를 사용하는 것이 더 적합합니다.

Abstract Class

abstract class란?

코틀린에서 abstract class는 추상적인 개념을 정의하는 클래스 이빈다.

  • 객체를 직접 생성할 수 없고, 이를 상속받아 구현한 구체적인 하위 클래스를 통해 사용됩니다.
  • 주로 공통 동작이나 상속 구조에서의 기본 뼈대를 정의하는 데 사용됩니다.

abstract class의 특징

  1. 추상 클래스는 객체를 직접 생성할 수 없음
    • abstract로 선언된 클래스는 인스턴스화할 수 없습니다.
    • 반드시 하위 클래스에서 상속받아 사용해야 합니다.
  2. 추상 메서드를 포함할 수 있음
    • abstract 메서드는 구현되지 않은 메서드로, 반드시 하위 클래스에서 구현해야 합니다.
    • 추상 클래스 내에는 일반 메서드와 추상 메서드를 함께 포함할 수 있습니다.
  3. 생성자 사용 가능
    • 다른 클래스와 동일하게 생성자를 가질 수 있습니다.
    • 생성자를 통해 공통 속성을 초기화 할 수 있습니다.
  4. 상속만 가능
    • abstract class는 상속의 뼈대를 제공하며, open 키워드가 포함된 것처럼 작동합니다.

abstract class의 기본 문법

  abstract class 클래스이름 {
      // 일반 속성 및 메서드
      var 일반속성: 타입 = 초기값
      fun 일반메서드() { /* 구현 */ }

      // 추상 속성 및 메서드
      abstract var 추상속성: 타입
      abstract fun 추상메서드()
  }

abstract class의 예제

1. 추상 클래스와 메서드 사용

  abstract class Animal(val name: String) {
      abstract fun sound() // 추상 메서드
      fun sleep() = println("$name is sleeping") // 일반 메서드
  }

  class Dog(name: String) : Animal(name) {
      override fun sound() = println("Woof! Woof!") // 추상 메서드 구현
  }

  fun main() {
      val dog = Dog("Buddy")
      dog.sound() // Woof! Woof!
      dog.sleep() // Buddy is sleeping
  }
  • Animal은 추상 클래스이므로 직접 인스턴스화할 수 없습니다.
  • Dog는 Animal의 하위 클래스이며, sound() 메서드를 구현해야만 사용할 수 있습니다.

2. 추상 속성 사용

  abstract class Shape {
      abstract val area: Double // 추상 속성
      abstract val perimeter: Double // 추상 속성
  }

  class Circle(val radius: Double) : Shape() {
      override val area: Double
          get() = Math.PI * radius * radius

      override val perimeter: Double
          get() = 2 * Math.PI * radius
  }

  fun main() {
      val circle = Circle(5.0)
      println("Area: ${circle.area}")       // Area: 78.53981633974483
      println("Perimeter: ${circle.perimeter}") // Perimeter: 31.41592653589793
  }
  • Shape 클래스는 area와 perimeter라는 추상 속성을 정의하고 이를 Circle 클래스에서 구현합니다.
  • get()이란?
    1. Math.PI * radius * radius에서 사용된 get()은 kotlin의 사용자 정의 getter를 구현하는 방식입니다.
    2. 값이 매번 계산됩니다.
      • area를 호출할 때마다 get()이 실행되어 계산 결과를 반환합니다.
      • 따라서 값이 고정되지 않고, 필요에 따라 동적으로 계산됩니다.
    3. 값을 저장하지 않습니다.
      • 별도의 저장 공간을 사용하지 않고, 매번 새로운 계산하므로 메모리를 절약할 수 있습니다.
      • 계산식이 복잡하거나 동적인 경우에 유용합니다.
    4. 복잡한 로직을 정의할 수 있습니다.
      • 단순히 값을 반환하는 것이 아니라 조건문, 다른 함수 호출 등 복잡한 로직을 포함할 수 있습니다.
  • 직접 초기화 방식
    1. override val area: Double = Math.PI * radius * radius 방식을 직접 초기화 방식이라고 합니다.
    2. 값이 한 번 계산되어 저장 됩니다.
      • 객체 생성 시 값이 초기화되고, 이후에는 이 값을 메모리에서 직접 참조합니다.
      • 따라서 계산 비용이 없고 빠르지만, 메모리를 더 많이 사용할 수 있습니다.
    3. 고정된 값입니다.
      • radius가 변경되지 않는 경우에는 문제가 없지만, 동적으로 계산이 필요한 경우 적합하지 않습니다.
      • 만약 radius를 var로 변경하면 area값은 변경되지 않습니다.(재계산되지 않음)
    4. 간단한 값 표현에 적합합니다.
      • 복잡한 계산이 필요 없는 경우, 코드가 간결하고 읽기 쉽습니다.
  • get() vs 직접 초기화
    • get(): 을 사용한 사용자 정의 Getter는 값이 동적으로 계산되어야 하거나 메모리를 절약하고 싶을 때 적합합니다.
    • get() 예: 속성이 다른 속성에 의존하거나 값이 매번 바뀌는 경우.
    • 직접 초기화: 직접 초기화는 값이 한 번 계산된 후 변하지 않아야 하며, 간단하고 고정된 값을 사용하는 경우에 적합합니다.
    • 직접 초기화 예: 객체 생성 시 값이 확정되는 경우.
  • 결론
    • 위 예제에서는 radius가 변경될 때마다 area를 자동으로 재계산해야 하므로 get()을 사용하는 방식이 더 적합합니다.

3. 생성자를 사용하는 추상 클래스

  abstract class Vehicle(val brand: String) {
      abstract fun drive()
  }

  class Car(brand: String, val model: String) : Vehicle(brand) {
      override fun drive() = println("Driving a $brand $model")
  }

  fun main() {
      val car = Car("Toyota", "Corolla")
      car.drive() // Driving a Toyota Corolla
  }
  • 추상 클래스 Vehicle은 생성자를 가지며, 하위 클래스에서 이를 초기화 합니다.

abstract class vs interface

특징 abstract class interface
객체 생성 직접 인스턴스화할 수 없음 직접 인스턴스화할 수 있음
다중 상속 단일 상속만 가능 다중 상속 가능
속성 및 메서드 구현 추상 속성과 메서드뿐 아니라 일반 속성과 메서드도 포함 추상 속성 및 메서드 포함, 일반 속성과 메서드도 가능(default)
생성자 생성자 사용 가능 생성자 선언 불가
용도 상속 관계에서 기본적인 구현과 뼈대를 제공 클래스 간의 공통 동작을 정의(다중 상속 목적)

추상 클래스의 장점

  1. 코드 재사용성 증가
    • 공통적인 구현(메서드, 속성)을 추상 클래스에 정의해 중복 코드를 줄일 수 있습니다.
  2. 상속 구조의 뼈대 제공
    • 추상 클래스를 사용해 상속 구조를 명확히 정의할 수 있습니다.
  3. 공통 구현과 추상 정의 혼합 가능
    • 공통 동작은 구현하고, 구체적인 동작만 하위 클래스에서 구현하도록 강제할 수 있습니다.

추상 클래스 단점

  1. 다중 상속 불가
    • 코틀린에서 클래스 단위로 다중 상속을 지원하지 않으므로, 하나의 추상 클래스만 상속받을 수 있습니다.
  2. 유연성이 인터페이스보다 낮음
    • 다중 상속이 필요한 경우 추상 클래스 대신 인터페이스가 더 적합합니다.
  3. 객체 생성 불가
    • 직접 객체를 생성할 수 없으므로 추상 클래스를 사용하지 않는 상황에서는 불필요한 구조가 될 수 있습니다.

abstract class 정리

  • 추상 클래스는 상속 구조를 정의하고 공통 로직을 재사용하며, 인터페이스보다 강력한 기능을 제공할 때 유용합니다.
  • 하지만 다중 상속이 필요한 경우에는 인터페이스를 고려하는 것이 더 적합합니다.
728x90