본문 바로가기

Study/Kotlin Study

[Kotlin] 클래스 정의하기

+ 항공대학교 김철기 교수님의 객체 지향 프로그래밍 과목 내용를 정리한 글입니다.

클래스

- 프로퍼티 (변수)와 메소드 (함수/연산)를 가지는 타입을 정의하는 단위

class Person {
    // 프로퍼티
    var firstName: String = "" // 이름
    var familyName: String = "" // 성
    var age: Int = 0 // 나이

    // 메소드
    fun fullName() = "$firstName $familyName" // 이름 + 성
    fun showMe() {
        println("${fullName()}: $age")
    }
}

객체 (클래스의 인스턴스)의 생성

- Person 클래스 타입을 가지는 변수는 객체를 가리키는 참조자를 가진다.

// 인스턴스(객체)의 생성
fun main() {
    val person = Person()
    // person이라는 객체의 참조자 변수가 새롭게 생성한 Person 객체를 가리킴

    person.firstName = "John"
    person.familyName = "Doe"
    person.age = 25

    person.showMe() // John Doe: 25
}

생성자

- 클래스 머리 부분을 함수처럼 선언하여 생성자를 정의한다.

- 이렇게 정의하는 생성자는 1개만 가능하므로 '주생성자'라고 부른다.

fun main() {
    val person =Person("John", "Doe")
    println(person.fullName)
}

class Person(firstName: String, familyName: String) { // 클래스 생성자 정의
    val fullName = "$firstName $familyName"
}

init 블록

- 하나의 식으로 표현하기 힘든 초기화 로직을 실행해야 할 때는 init 블록을 사용한다.

fun main() {
    val person =Person("John Doe") // fullName을 생성자의 인자로 받아
    println(person.firstName) // firstName 출력
}

class Person(fullName: String) { // 클래스 생성자 정의
    val firstName: String
    val familyName: String
    init { // 클래스의 인스턴스가 생성될 때 호출. 클래스안의 요소 초기화한다.
        val names = fullName.split(" ")
        if (names.size != 2) {
            throw IllegalArgumentException("Invalid name: $fullName")
            // 매서드의 매개변수가 유효하지 않은 값 또는 범위를 가지고 있을 때 발생하는 예외
        }
        firstName = names[0]
        familyName = names[1]
    }
}

+ pdf 코드 오류: Illegal ArgumentException => IllegalArgumentException


프로퍼티의 초기화

- 프로퍼티가 초기화 되지 않을 가능성이 있다면 컴파일러가 에러를 발생한다.

=> 모든 프로퍼티는 반드시 초기화 돼야한다.

fun main() {
    val person =Person("John Doe") // fullName을 생성자의 인자로 받아
    println(person.firstName) // firstName 출력
}

class Person(fullName: String) { // 클래스 생성자 정의
    val firstName: String
    val familyName: String
    init { // names의 크기가 2가 아닐 경우 firstName, familyName이 초기화 되지 않으므로 error 발생
        val names = fullName.split(" ")
        if (names.size == 2) {
            firstName = names[0] 
            familyName = names[1]
        }
    }
}

주생성자 파라미터의 이용

- 주생성자 파라미터는 프로퍼티 초기화나 init 블록 안에서만 사용할 수 있다.

class Person(firstName: String, familyName: String) {
    val fullName: String = "$firstName $familyName"
    fun printFirstName() {
        println(firstName) // Error: Name is not available here
        // 생성자의 매개변수로만 정의되어 있기 때문에 클래스 내부의 다른 매서드나 프로퍼티에서 사용 불가
    }
}

- 생성자의 파라미터 정의 앞에 val이나 var를 붙이면 파라미터를 프로퍼티로 만들 수 있다.

class Person(val firstName: String, val familyName: String) {
    val fullName: String = "$firstName $familyName"
    fun printFirstName() {
        println(firstName) // Error: Name is not available here
        // 생성자의 매개변수 + 프로퍼티로 정의했기 때문에 클래스 내부의 다른 매서드나 프로퍼티에서 사용 가능
    }
}

부생성자

- 하나 이상의 생성자를 정의하고 싶을 때는 fun 함수 대신 constructor라는 예약어로 추가 생성자를 정의한다.

- Kotlin 개발자는 주생성자 개념에 익숙하기 때문에 부생성자만을 정의하는 것은 좋지 않다.

class Person {
    val fullName: String

    constructor(firstName: String, familyName: String) : // :는 다른 생성자를 호출할 때 사용
        this("$firstName $familyName") // 두 번째 부생성자 호출

    constructor(fullName: String) {
        this.fullName = fullName
    }
}

fun main() {
    val person1 = Person("John", "Doe")
    println(person1.fullName)
    val person2 = Person("John Doe")
    println(person2.fullName)
}

 

- 주생성자와 부생성자를 섞어서 정의할 수 있다.

class Person (fullName: String){
    val fullName = fullName

    constructor(firstName: String, familyName: String) :
        this("$firstName $familyName") // 주생성자 호출
}

fun main() {
    val person1 = Person("John", "Doe")
    println(person1.fullName)
    val person2 = Person("John Doe")
    println(person2.fullName)
}

 

- 파라미터를 프로퍼티로 생성하면, 프로퍼티 초기화 과정을 생략할 수 있다.

class Person (val fullName: String){
    constructor(firstName: String, familyName: String) :
        this("$firstName $familyName") // 주생성자 호출
}

fun main() {
    val person1 = Person("John", "Doe")
    println(person1.fullName)
    val person2 = Person("John Doe")
    println(person2.fullName)
}

멤버 가시성

- 클래스 내부의 멤버 (프로퍼티, 메소드)마다 가시성 설정이 가능하다.

 

1. public: 멤버를 어디서나 볼 수 있다. (default)

2. internal: 컴파일 모듈 내부에서만 멤버를 볼 수 있다. (함께 컴파일 곳이라면 가능)

3. protected: 멤버가 속한 클래스와 그 하위 클래스에서 볼 수 있다. (상속)

4. private: 멤버가 속한 클래스 내부에서만 볼 수 있다.

class Person(private val firstName: String, private val familyName: String) {
    fun fullName() = "$firstName $familyName"
    // firstName과 familyName은 클래스 밖에서는 볼 수 없다.
    // fullName은 어디서나 볼 수 있다.
}

 

=> 클래스의 내부 구현을 변경할 때, 클래스 외부의 다른 코드에 영향을 미치지 않게 한다.

=> 무분별한 외부 접근을 방지하여 코드의 안정성을 높인다.


주생성자 가시성

- 주생성자에 public외의 가시성을 주고 싶을 경우 다음과 같이 한다.

class Empty private constructor() {
    fun showMe() = println("Empty")
}
// Empty의 유일한 생성자가 private이므로 class 외부에서 인스턴스화 할 수 없음에 유의

내포된 클래스 (nested class)

- 클래스 안에 클래스를 선언할 수 있다.

class Person (val id: Id, val age: Int) { // 인자로 Id 객체를 받는다.
    class Id (val firstName: String, val familyName: String) // 내포된 클래스 (클래스 안의 클래스)
    fun showMe() = println("${id.firstName} ${id.familyName}, $age")
}

fun main() {
    val id = Person.Id("John", "Doe") // 먼저 내부 객체 생성자 호출
    val person = Person(id, 25) // 생성한 객체를 다른 생성자의 인자로 전달하며 객체 생성자 호출
    person.showMe()
}

내부 클래스 (inner class)

-  내포된 클래스에 inner를 선언하면 내부 클래스(inner class)가 되어 내부 인스턴스가 외부 인스턴스 1개에 소속되게 된다.

 

1. 내부 인스턴스는 자신이 소속된 외부 인스턴스의 멤버에 접근할 수 있다.

2. 내부 인스턴스는 생성 시 자신이 소속된 외부 인스턴스를 명시해야 한다.

class Person (val firstName: String, val familyName: String) {
    inner class Possession(val description: String) {
        fun showOwner() = println(fullName())
        fun getOwner() = this@Person //  외부 인스턴스를 반환
        // this는 속한 클래스의 인스턴스, @Person은 외부 클래스의 이름 명시
    }
    val myWallet = Possession("Wallet") // this.Possession("Wallet")과 같음
    fun fullName() = "$firstName $familyName"
}

fun main() {
    val person = Person("John", "Doe")
    // 외부 인스턴스 person 생성

    val wallet = person.Possession("Wallet")
    // 외부 인스턴스 person의 내부 인스턴스로 선언된 wallet (외부 인스턴스 person 명시)

    wallet.showOwner() // John Doe
    // 내부 인스턴스 wallet이 외부 인스턴스의 멤버(함수)에 접근

    val owner = wallet.getOwner()
    // owner = person (wallet의 외부 인스턴스)
    
    println(owner.fullName()) // John Doe
}

 


지역 클래스 (local class)

- 함수 내에 class를 정의할 경우 이를 지역 클래스라 부른다.

- 지역 클래스는 자신을 둘러싼 코드의 멤버에 접근 가능하다.

fun main() {
    var x = 1
    class Counter {
        fun increment() {
            x ++
        }
    }
    Counter().increment()
    println(x)
}

'Study > Kotlin Study' 카테고리의 다른 글

[Kotlin] 널 가능성  (0) 2023.09.12
[Kotlin] 함수  (4) 2023.09.03
[Kotlin] 예외 처리  (0) 2023.08.25
[Kotlin] 루프 (반복문)  (2) 2023.08.24
[Kotlin] 조건문  (2) 2023.08.23