본문 바로가기
Backend/Kotlin

코틀린 OOP 핵심 총정리 – class, data, object, enum, interface까지

by burning-man 2025. 5. 15.

 

개요

이번 글에서는 코틀린의 객체지향 프로그래밍(OOP) 개념을 자바와 비교해서 정리해보려한다. 특히 자바에 익숙한 입장에서 코틀린의 클래스 선언, 생성자, 상속, 인터페이스, 싱글턴 객체, enum, data class 등의 구조를 학습해보자.

 

 

1. 클래스 선언 - 기본 구조와 생성자

자바에서는 클래스를 선언하고, 필드를 만들고, 생성자를 통해 초기화를 진행하는 방식이 일반적이다.

  • 자바
public class Person {
	private String name;
    private int age;
    
    public Person(String name, int age) {
    	this.name = name;
        this.age = age;
    }
}

 

  • 코틀린
class Person(val name: String, val age: Int)

 

코틀린은 생성자를 클래스 선언부에 바로 작성 가능하다. 이를 주 생성자(Primary Constructor) 라고 한다. 

val, var 를 붙이면 자동으로 멤버 변수 선언 + 초기화 까지 수행된다.

 

 

생성자 종류

주 생성자 + 부 생성자 

  • 주 생성자 (Primary Constructor): 클래스 선언부에서 정의
  • 부 생성자 (Secondary Constructor): 클래스 본문 내부에서 constructor 키워드로 정의 
class Person(val name: String) {
    var age: Int = 0
    
    // 부 생성자: name과 age 모두 받는 생성자
    constructor(name: String, age: Int) : this(name) {
    	this.age = age
    }
}

 

  1. 주 생성자는 name 하나만 받는 간단한 생성자이다.
  2. 부 생성자는 name과 age를 다 받고 싶을 때 사용하는데, 내부적으로 주 생성자(this(name)을 먼저 호출하고 추가로 age 값을 설정하는 구조이다

즉, 기본 구조를 재활용하면서 확장하는 방법이다.

 

default value를 이용한 기본 생성자

 코틀린은 매개변수 자체에 기본 값을 줄 수 있다! 

-> 굳이 부 생성자를 만들 필요가 없을 때가 많다.

class Person(val name: String, val age: Int = 0)

 

실무에서 쓰는 방식

data class User{
    val name: String,
    val age: Int = 0,
    val email: String? = null
}

 

이렇게하면 다양한 생성자 조합이 없어도 모두 가능하다.

  • User("버닝맨")
  • User("버닝맨", 20)
  • User("버닝맨", 20, "burningman@gamil.com")

 

2. 클래스 상속과 멤버 오버라이드 - open, override

자바에서는 클래스가 기본적으로 상속 가능하지만,

코틀린은 기본적으로 모든 클래스가 final 이기 때문에 상속을 원한다면 open 키워드를 붙여야한다.

open class Anmal {
    open fun sound() {
    	println("소리를 낸다.")
    }
}

class dog : Anmal() {
    override fun sound() {
    	println("멍멍")
    }
}
  • open: 클래스나 함수의 상속 가능을 설정
  • override: 부모 클래스의 함수 재정의

 

3. data clss - 데이터 객체 선언을 간결하게

자바에서 DTO, VO를 만들 때는 equals(), hashcode(), toString()을 수동으로 작성해야 하지만, 코틀린의 data class 는 자동 생성해준다.

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

---

val user = User("버닝맨", 20)
val copied = user.copy(age = 30)
println(user) // User(name=버닝맨, age=20)
  • equals(), hashCode(), toString() 자동 생성
  • copy(), componentN() 등 구조 분해 지원 -> 구조 분해에 유용

 

4. object - 싱글톤 객체 선언

자바에서는 싱글톤을 만들기 위해 별도의 패턴을 구성해야했지만,

코틀린에서는 object 키워드 하나로 간단하게 싱글톤을 선언할 수 있다.

object Config {
    val version = "1.0"
}
  • 객체는 앱 전체에 하나만 존재함
  • 클래스 선언과 동시에 인스턴스가 생성됨

 

5. interface - 인터페이스 구현

  • 자바
public interface Worker {
    void work();
}

 

  • 코틀린
interface Worker {
	fun work()
}

---

class Developer : Worker {
    override fun work() {
        println("개발 중")
    }
}
  • 코틀린에서는 fun 키워드를 이용해 메서드를 선언
  • 클래스에서 구현할 때는 반드시 override fun 으로 명시

 

6. enum class - 열거형 선언

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

 각 enum 값은 Direction.NORTH 처럼 사용한다.

 

enum class Status(val code: Int) {
    SUCCESS(200),
    ERROR(500)
}

생성자와 프로퍼티도 정의 가능하다.

 

 

마무리

요약

구분 자바 코틀린
클래스 기본 상속 가능 기본 final, open 키워드 필요
생성자 클래스 내부에 작성 클래스 선언부에 주 생성자 작성 가능
상속 자동 허용 open 키워드 필요
싱글톤 별도 패턴 구현 object 키워드 하나로 구현
데이터 객체 수동 구현 data class로 자동 생성
인터페이스 구현 강제 fun, override fun

 

실습

// 인터페이스 정의
interface Worker {
	fun work(): String
}

// 부모 클래스
open class Person(val name: String, val age: Int) {
	open fun introduce(): String {
    	return "안녕하세요. 저는 $name이고 ${age}살 입니다."
    }
}

// 자식 클래스
class Developer(name: String, age: Int, val language: String) : Person(name, age), Worker {
	override fun work(): String {
    	return "$language 언어로 개발을 시작합니다."
    }
    
    override fun introduce(): String {
    	return super.introduce() + " 저는 개발자이고, $language 를 사용합니다."
    }
}

// enum class
enum class LoginStatus(val code: Int) {
	SUCCESS(200),
    FAIL(401),
    TIMEOUT(408)
}

// data class
data class User(val name: String, val job: String, val age: Int = 0)

// object 
object Logger {
	fun log(status: LoginStatus, message: String) {
    	println("[${status.name}] (${status.code}) $message")
    }
}

fun main() {
	val user = User("버닝맨", "Developer", 20)
    
    val person = when (user.job) {
    	"Developer" -> Developer(user.name, user.age, "Kotlin")
        else -> Person(user.name, user.age)
    }
    
    println(person.introduce())
    
    if (person is Worker) {
    	val result = person.work()
        println(result)
        Logger.log(LoginStatus.SUCCESS. "{user.name} 작업 시작")
    } else {
    	Logger.log(LoginStatus.FAIL, "${user.name}은 Worker가 아닙니다.")
    }
}