Backend
home

9. 클래스 객체 인터페이스

생성일
2025/02/03 13:11
태그
Kotlin

인터페이스, 상속, 가시성

인터페이스와 구현
package ch08 interface Clickable { fun click() } class Button: Clickable { override fun click() { println("Button is clicked.") } } fun main() { Button().click() }
Kotlin
복사
자바의 implements와 extends는 “:”으로 대체됨 (둘다 공통)
인터페이스의 메서드 구현은 override 변경자를 반드시 붙여야 함
다중 인터페이스 구현
Clickable과 Focusable이 둘다 showOff()라는 기본구현 메서드가 있는 경우 컴파일 오류 발생
무조건 구현을 변경해야 한다.
open, final, abstract 변경자 : 기본적으로 final
무분별한 상속과 오버라이드는 취약한 기반 클래스(fragile base class) 문제를 일으킬 수 있음
Java : 기본적으로 모든 클래스, 메서드는 상속할 수 있고, final class는 상속을 할 수 없음
Kotlin : 기본적으로 모든 클래스, 메서드는 final class(아무 표시가 없어도 final), open class인 경우에만 상속 가능
// 오픈 클래스 : 상속 가능 open class RichButton : Clickable { // final method : 오버라이드 불가 fun disable() {} // open method : 오버라이드 가능 open fun animate() {} // 오버라이드 메서드 : 오버라이드 가능 override fun click() {} }
Kotlin
복사
public, private, internal, protected : 가시성 변경자
자바는 기본적으로 package-private(코틀린에서는 사라짐)
코틀린은 기본적으로 public
자바와의 차이점
internal : 신규 가시성이며, 모듈(gradle 모듈이나 intellij 프로젝트 세팅에서 보이는 모듈 단위) 안에서만 볼 수 있는 가시성
private : 클래스 내에서 사용하면 자바처럼 해당 클래스에서만 사용 가능, 파일 내에서 사용하면 해당 파일 안에서만 볼 수 있음

봉인된 클래스(sealed class)

의도적으로 확장을 제한하여 편의성을 향상시키는 클래스 타입
<sealed class가 없는 경우>
interface Error class FileError(val fileName: String) : Error class DatabaseError(val dbmsType: DbmsType) : Error enum class DbmsType { MYSQL, MARIA, ORACLE, H2 } fun getCharacter(error: Error) = when (error) { is FileError -> "Error is occurred at ${error.fileName}" is DatabaseError -> "Error on DBMS : ${error.dbmsType}" else -> throw IllegalArgumentException("Unknown error type") }
Kotlin
복사
when에서 불필요한 else 추가 필요
컴파일러는 Error Interface 구현체에 어떤 것들이 있는지 알 수가 없음
이런 분기가 여러군데에 있는 경우 그리고 새로운 Error Interface 구현체를 생성하는 경우 모든 곳에 신규 클래스를 추가해야 하는데 누락할 경우 심각한 문제가 발생할 수 있음
<sealed class가 있는 경우>
sealed class Error { class FileError(val fileName: String) : Error() class DatabaseError(val dbmsType: DbmsType) : Error() class OsError(val osType: OsType) : Error() } enum class OsType { WINDOWS, MACOS, LINUX } enum class DbmsType { MYSQL, MARIA, ORACLE, H2 } fun getCharacter(error: Error) = when (error) { is FileError -> "Error is occurred at ${error.fileName}" is DatabaseError -> "Error on DBMS : ${error.dbmsType}" is Error.OsError -> "Error on OS : ${error.osType}" } fun main() { println(getCharacter(Error.OsError(OsType.LINUX))) }
Kotlin
복사
sealed class 내부에 해당 클래스의 구현체를 모두 나열함
when 절에서 모든 구현체를 조건에 넣으면 else 구문 불필요
신규 구현체 만들 때는 자동으로 컴파일 에러 발생하여 수정이 쉬움

다양한 생성자, 프로퍼티

주 생성자(primary constructor)와 초기화 블록(init block)
// 실제로 가장 많이 활용되는 버전 class User(val userName: String)
Kotlin
복사
클래스 이름 뒤에 오는 () 부분이 주 생성자
// 생성자 및 초기화에 가장 명시적인 버전 class User constructor(_userName: String) { val userName: String init { userName = _userName } }
Kotlin
복사
위 생성자를 가장 명시적인 방법으로 변경한 버전
생성자는 constructor 키워드로 명시적으로 생성
init block에서는 클래스 객체가 생성될 때 생성자에서 받은 값으로 프로퍼티를 초기화 함
// 위 버전을 다소 간소화 한 버전 class User(_userName: String) { val userName: String = _userName }
Kotlin
복사
constructor 키워드 생략
init 블록 대신 프로퍼티에 바로 초기화
생성자 파라미터에 val 또는 var가 붙으면
해당 파라미터와 동일한 이름으로 프로퍼티를 생성해준다는 뜻
생성자 파라미터에 val(var)이 없는 것과 있는 것을 혼합하는 것도 가능
// 기본 파라미터 값을 갖는 생성자 data class User( val userName: String, val level: Int = 1 ) fun main() { println(User("Jekyll")) println(User("Hyde", 10)) }
Kotlin
복사
생성자의 파라미터 옆에 기본 값을 적어주면 해당 값이 기본 값이 되어 생략 가능
// java public class Parent { private final String familyName; public Parent(String familyName) { this.familyName = familyName; } } public class Child extends Parent { private final String subName; public Child(String familyName, String subName) { super(familyName); this.subName = subName; } } // kotlin open class Parent( val familyName: String ) class Child( val subName: String, familyName: String ) : Parent(familyName)
Kotlin
복사
확장하는 클래스의 생성자를 호출해줘야 함
확장하는 클래스에 프로퍼티 없는 생성자만 있는 경우도 호출이 필요함
부 생성자(secondary constructor)
open class Parent( val familyName: String ) class Child : Parent { private val subName: String constructor(subName: String) : this(subName, "") constructor(subName: String, familyName: String) : super(familyName) { this.subName = subName } }
Kotlin
복사
클래스 구현부에 constructor 키워드를 활용해서 여러 개의 부 생성자 생성 가능
this() : 다른 생성자 호출
super() : 베이스 클래스 생성자 호출
Backing field(뒷받침하는 필드)
프로퍼티의 getter/setter 를 커스터마이징 하는 방법
class Account { var balance: Long = 0 set(value) { if (value < 0) throw IllegalStateException("잔액은 0원 이상만 가능합니다.") field = value } var accountName: String = "" get() = "계좌이름:$field" } fun main() { val account = Account() account.balance = 100 account.accountName = "급여계좌" }
Kotlin
복사
프로퍼티 아래에 get(), set(value) 두 개를 활용해서 단순 getter/setter 가 아닌 커스터마이징 가능
get(), set() 에서만 사용가능한 키워드
field : backing field에 접근
접근자의 가시성 변경
private set을 통해 setter만 감추고 getter만 열어줄 수 있음
package ch11 class Account { var balance: Long = 0 private set var accountName: String = "" get() = "계좌이름:$field" fun increaseBalance(value: Int) { if (this.balance + value > 1_000_000) throw IllegalStateException("Balance cannot be over 1 million") this.balance += value } fun decreaseBalance(value: Int) { if (this.balance - value < 0) throw IllegalStateException("Balance cannot be exception") this.balance -= value } } fun main() { val account = Account() account.increaseBalance(100) account.decreaseBalance(100) account.accountName = "급여계좌" println("${account.accountName} 잔액 : ${account.balance}") }
Kotlin
복사
계좌의 잔액을 수정하는 것과 같이 중요한 수정이 발생하는 필드는 private set 을 통해 함부로 수정되는 것을 방지할 수 있다.

data class

데이터를 저장하는 주 용도로 활용하는 클래스 타입, 다양한 기본 기능을 제공한다. (lombok의 @Data 와 유사)
data class User(val name: String, val age: Int)
Kotlin
복사
클래스 키워드에 data만 붙이면 된다.
기본 제공 기능
toString() : Object의 기본 toString() 대신 “User(name=John, age=42)” 와 같은 형식으로 내부의 데이터를 알아보기 쉽게 표현해주는 메서드 제공
equals(), hashCode(): 두 클래스를 기본적인 동일성 검증 대신 동등성 검증으로 확인할 수 있도록 함

object 키워드 : 클래스 선언 + 객체 생성 (싱글턴)

object Family { val members = mutableListOf<Person>() } fun main() { Family.members.add(Person("snow", true)) }
Kotlin
복사
자바에서의 싱글톤과 같은 목적으로 클래스 선언과 객체 생성을 동시에 진행
직접 생성할 필요가 없으며, 별도 인스턴스 생성 불가
싱글톤 패턴과 같이 어플리케이션에 인스턴스가 단 한 개만 필요한 경우에 활용하면 좋다.
Comparator의 구현처럼 단 한 번만 사용되는 경우에도 사용 가능
자바에서의 Util Class처럼 내부에 static method만 존재하고 별도 인스턴스 생성하지 않는 경우에도 사용 가능
하지만 이런 경우에는 클래스 없이 파일에 바로 함수를 만들어서 사용하는(최상위 함수, top level function)이 더 낫다
익명 클래스에도 object 키워드 사용 가능
window.addMouseListener(object : MouseAdapter() { override fun mouseClicked(e: MouseEvent) { /*...*/ } override fun mouseEntered(e: MouseEvent) { /*...*/ } })
Kotlin
복사

companion object : 자바의 static method를 대체하는 용도

class NewChild( private val subName: String, familyName: String, private val age: Int ) : Parent(familyName) { companion object { const val MAX_CHILDREN_COUNT = 4 fun ofDefaultAge( subName: String, familyName: String ): NewChild = NewChild(subName, familyName, 10) fun ofDefaultAge( age: Int ): NewChild = NewChild( "defaultName", "defaultFamilyName", 10 ) } } data class User(val userName: String, val age: Int) class Knight(val grade: String) fun main() { println(User("Jonathan", 24)) }
Kotlin
복사
자바에서 static으로 상수 값을 저장하거나 factory 생성자를 만들던 방식을 동일하게 활용 가능