Kotlin 기초 - 클래스 정의

안녕하세요 이번 시간에는 코틀린에서 클래스 정의하는 방법에 대하여 개꿀떡 해보겠습니다.  


1. 클래스 내부구조

- 자바와 마찬가지로 "class" 키워드 다음에 클래스명이 오고 그다음에 클래스 본문이 오는 형태로 정의.

- 클래스 본문은 멤버 정의가 들어있는 블록.

- 코틀린에서는 멤버변수라는 말 대신 "프로퍼티"라고 하고 기본 접근자 메서드는 자동으로 구현.

- "val 프로퍼티"는 불변의 값으로 getter가 자동으로 구현.

- "var 프로퍼티"는 가변 값으로 getter와 setter가 자동으로 구현.

- 클래스를 접근하기위해서 생성된 인스턴스를 "객체"라고 함.

- 클래스 내부에서는 "this" 키워드를 사용해서 수신 객체를 참조 할 수 있다.

- this 생락도 가능하지만 반드시 명시 해야할 경우도 있다. (어떤 클래스의 프로퍼티와 메서드 파라미터 이름이 같은 경우)

- 클래스 인스턴스의 프로퍼티나 메서드를 사용하려면 우선 인스턴스를 명시적으로 생성해야 한다.

- 생성자 호출을 사용하면 프로그램이 새 인스턴스에 대한 힙 메모리를 할당한 다음, 인스턴스의 상태를 초기화해주는 생성자 코드를 호출해준다.

- 코틀린 클래스는 디폴트로 public이다.(internal이나 private 설정 가능)

 

① vs Java

- 코틀린에서 클라이언트(호출하는쪽) 코드를 바꾸지 않아도 원하는대로 프로퍼트의 구현을 바꿀 수 있기 때문에 코틀린 프로퍼티는 캡슐화에 위배되지 않는다.

- 자바에서는 클래스를 어느곳에서 쓸 수 있게 하려면 명시적으로 public 변경자를 붙여야한다.

 

② 예시

< 코드 - class.kt >

package com.example.kotlinbase.DefClass

class Person {
    var sFirstName: String  = ""     // 프로퍼티
    var sFamilyName: String = ""
    var iAge: Int           = 0      // var 프로퍼티
    val sGender: String     = "남성"  // val 프로퍼티

    fun fullName()  = "$sFirstName $sFamilyName"
    fun fullName2() = "${this.sFirstName} ${this.sFamilyName}"  // this 키워드 사용하여 수신 객체 참조

    fun showMe() {
        println("저의 이름은 ${fullName()} 이며 나이는 $iAge 입니다.")
    }

    //  메서드의 파라미터와 클래스의 프로퍼티의 이름이 같은경우 this 명시
    fun setName(sFirstName: String, sFamilyName: String) {
        this.sFirstName  = sFirstName
        this.sFamilyName = sFamilyName
    }
}

 

< 코드 - main.kt >

package com.example.kotlinbase.DefClass

fun showAge(p: Person) = println(p.iAge)    // 'p'는 수신객체, 프로퍼티 쓰기

// 프로퍼티 읽기
fun readAge(p: Person) {
    p.iAge = readLine()!!.toInt()
}

fun main() {
    val p = Person() // Person 클래스 인스턴스 생성

    p.sFirstName  = "꿀떡"
    p.sFamilyName = "개"
    p.iAge        = 32

    p.showMe()
}

 

< 결과 >

 

2. 생성자

- 클래스 인스턴스를 초기화해주고 인스턴스 생성 시 호출되는 특별한 함수다.

- 클래스 헤더의 파라미터 목록을 "주생성자"라고 한다.

- 초기화 블록이란 "init" 키워드 앞에 붙은 블록이다.

- 초기화 블록은 return문에 들어가지 못한다.

- 보통 프로퍼티 값을 정의 시 초기화 해주지만 하나의 식으로 표현하기 어려울 때 초기화 블록을 사용한다.

- 모든 프로퍼티는 확실 초기화 되는지 확인해야 한다.

- 주생성자는 프로퍼티 초기화나 init블록 밖에서 사용 하지 못한다.

- 코틀린에서는 "val / var"를 붙여 간단히 주생성자 파라미터의 값을 멤버 프로퍼티로 만들수있다.

- 함수와 마찬가지로 주생성자 파라미터에 "디폴트 값""vararg" 키워드 사용이 가능하다.

- "부생성자" 키워드인 "constructor"를 사용하면 클래스의 인스턴스를 서로 다른 방법으로 초기화가 가능하다.

- 부생성자는 return문을 사용할 수 있지만 반환타입 지정이 불가능하다.

- 부생성자는 기본적으로 Unit 타입 값을 반환한다.

- 주생성자 없이 부생성자만 있을 경우 부생성자 본문을 실행하기 전 프로퍼티 초기화와 init블록을 먼저 실행시킨다.

- 부생성자는 블록 대신 콜론( : )을 넣어 코드 작성이 가능하다.(함수 처럼)

- 부생성자 파라미터는 "val / var"를 붙일 수 없다.

 

① vs Java

- 코틀린에서 생성자를 호출할 때 Java의 new 같은 특별한 키워드를 사용하지 않는다.

 

② 예시

< 코드 - class1.kt >

package com.example.kotlinbase.DefClass

// 클래스의 주생성자 표현
class Person2(sFullName: String) {
    val sFirstName: String
    val sFamilyName: String

    // 주생성자는 프로퍼티 초기화나 init블록 밖에서 사용 불가
    // sFullName = ""
    // fun readFullName() = println("주생성자는 $sFullName 입니다.")

    // 초기화 블록
    init {
        val sName = sFullName.split(" ")
        if(sName.size != 2) {
            // 초기화 블록에서는 return 사용불가
            // return
            throw IllegalArgumentException("유효하지 않는 성명입니다.")
        } else {
            sFirstName  = sName[0]
            sFamilyName = sName[1]
        }
    }
}

 

< 코드 - class2.kt >

package com.example.kotlinbase.DefClass

// 클래스의 주생성자 표현2(주생성자를 프로퍼티로 사용 가능), 주생성자 디폴트 값 사용
class Person3(val sFirstName: String, val sFamilyName: String = "") {
    fun fullName() = "$sFirstName $sFamilyName"

    fun printFirstName() {
        // 주생성자는 프로퍼티 초기화나 init블록 밖에서 사용 가능
        println(sFirstName)
    }
}

 

< 코드 - class3.kt >

package com.example.kotlinbase.DefClass

// 클래스의 주생성자에 vararg 사용
class Room(vararg val p3: Person3) {
    fun showNames() {
        for (person in p3) println(person.fullName())
    }
}

 

< 코드 - class4.kt >

package com.example.kotlinbase.DefClass

// 부생성자 예시
class Person4 {
    val sFirstName: String
    val sFamilyName: String

    // 부생성자 형태
    constructor(sFirstName: String, sFamilyName: String) {
        this.sFirstName  = sFirstName
        this.sFamilyName = sFamilyName
    }

    // 부생성자에는 val/var 사용시 오류
    // constructor(val sFullName: String) {
    constructor(sFullName: String) {
        val sName = sFullName.split(" ")
        if(sName.size != 2) {
            throw IllegalArgumentException("유효하지 않는 성명입니다 : $sFullName")
        } else {
            sFirstName  = sName[0]
            sFamilyName = sName[1]
        }
    }
}

 

< 코드 - main.kt >

package com.example.kotlinbase.DefClass

fun main() {
    println("----------결과 1------------")
    val p2 = Person2("꿀떡 개") // 새 인스턴스 생성
    println(p2.sFirstName)

    println("----------결과 2------------")
    val p3 = Person3("꿀떡", "개") // 새 인스턴스 생성
    p3.printFirstName()

    println("----------결과 3------------")
    val r = Room(Person3("꿀떡"), Person3("꿀떡", "개")) // 주생성자에 vararg 있을 경우
    r.showNames()

    println("----------결과 4------------")
    val p4_1 = Person4("꿀떡", "개")
    val p4_2 = Person4("꿀떡 개")
    println(p4_1.sFamilyName)
    println(p4_2.sFamilyName)
}

 

< 결과 >

 

3. 멤버 가시성

- 클래스 멤버마다 다른 가시성으로 지정 가능하다.

- "public"은 멤버를 어디서나 볼 수 있다.

- "internal"은 멤버를 멤버가 속한 클래스가 포함된 모듈 내부에서만 볼 수 있다.

- "protected"는 멤버를 멤버가 속한 클래스와 멤버가 속한 클래스의 모든 하위 클래서에서 볼 수 있다.

- "private"는 멤버를 멤버가 속한 클래스 내부에서만 볼 수 있다.

- 주생성자의 가시성을 지정하려면 "constructor" 키워드를 꼭 명시 해야한다.

 

예시

< 코드 - class1.kt >

package com.example.kotlinbase.DefClass

// 멤버변수의 가시성 예시
class Person5(private val sFirstName: String,
              private val sFamilyName: String) {
    fun sFullName() = "$sFirstName $sFamilyName"
}

 

< 코드 - class2.kt >

package com.example.kotlinbase.DefClass

// 기본 주생성자 형태
class Person6 constructor(val iAge: Int) {
    fun showAge() = println("내 나이는 $iAge 입니다.")
}

// 기본 주생성자 형태에서 일반적으로 constructor 생략 해서 사용
class Person7(val iAge: Int) {
    fun showAge() = println("내 나이는 $iAge 입니다.")
}

// 주생성자의 가시성을 지정하게 되면 "constructor" 생략 불가능
class Person8 private constructor(val iAge: Int) {
    fun showAge() = println("내 나이는 $iAge 입니다.")
}

 

< 코드 - main.kt >

package com.example.kotlinbase.DefClass

fun main(){
    println("----------결과 1------------")
    val p5 = Person5("꿀떡", "개")
    // println(p5.sFirstName) // 오류
    println(p5.sFullName()) // 사용가능

    println("----------결과 1------------")
    val p6 = Person6(32)
    val p7 = Person7(32)
//    val p8 = Person8(32) // 주생성자를 접근 못하게 private 사용했으므로 다른곳에서 사용시 오류 발생!
    p6.showAge()
    p7.showAge()
}

 

< 결과 >

 

 

4. 내포된 클래스

- 코틀린 클래스는 함수, 프로퍼티, 생성자 외에 다른 클래스도 멤버로 가질 수 있다.

- 내포된 클래스도 여러 가시성을 지정할 수 있다.

- 내포된 클래스에 "inner"키워드를 붙이면 외부 클래스의 현재 인스턴스에 접근 가능하다.

- inner 클래스 생성자 호출 시 this 생략 가능

- inner 클래스 본문에서 외부클래스 인스턴스를 가리켜야 한다면 this 사용해야한다.(this@외부클래스이름)

 

 

① vs Java

- 코틀린에서 바깥쪽 클래스는 자신에게 내포된 클래스의 비공개 멤버에 접근할 수 없다.

- 코틀린에서는 내포된 클래스가 내부클래스가 되려면 inner를 붙여야하지만 Java는 디폴트이고 연관되길 원하지 않으면 명시적으로 "static"을 붙여야한다.

 

② 예시

< 코드 - class1.kt >

package com.example.kotlinbase.DefClass

class Person9 (val id: Id, val id2: Id2, val iAge: Int) {
    // 내포된 클래스
    class Id(val sFirstName: String, val sFamilyName: String)
    fun showMe() = println("내 이름은 ${id.sFamilyName} ${id.sFirstName}이고 나이는 $iAge 입니다.")

    class Id2(private val sFullName: String)
//    fun showFullName() = println("내 이름은 ${id2.sFullName}입니다.") // 멤버변수 가시성 지정 가능(sFullName 접근 불가)
}

 

< 코드 - class2.kt >

package com.example.kotlinbase.DefClass

class Person10(val sFirstName: String, val sFamilyName: String) {
    // "inner" 키워드가 없다면 "fullName()" 접근 불가
//    class MyName(val sDescription: String) {
//        fun showOwner() = println("내 이름은 ${fullName()} 입니다.")
//    }

    // "inner" 키워드로 "fullName()" 접근 가능
    inner class MyName(val sDescription: String) {
        fun showOwner() = println("내 이름은 ${fullName()} 입니다.")
    }
    private fun fullName() = "$sFamilyName $sFirstName"
}

 

< 코드 - main.kt >

package com.example.kotlinbase.DefClass

fun main() {
    println("----------결과 1------------")
    val id  = Person9.Id("꿀떡", "개")
    val id2 = Person9.Id2("개꿀떡")
    val p9  = Person9(id, id2,32)
    p9.showMe()

    println("----------결과 2------------")
    val p10    = Person10("꿀떡", "개")
    val myname = p10.MyName("내 이름")
    myname.showOwner()
}

 

< 결과 >

 

5. 지역 클래스

- Java와 동일하게 함수 본문에서 클래스를 정의할 수 있다.

- 지역클래스는 자신을 둘러싼 코드 블록 안에서만 쓰일 수 있다.

- 지역클래스 본문안에 접근할 수 있는 값을 포획 및 변경 가능.

- 가시성 변경자를 붙일 수 없다.

 

① vs Java

- Java에서는 포획한 변수의 값을 변경 불가능, 포획한 변수 사용 시 "final" 키워드 사용

 

② 예시

< 코드 >

package com.example.kotlinbase.DefClass

fun main() {
    // 지역 클래스
    class Person11(val sFirstName: String, val sFamilyName: String) {
        fun fullName() = "$sFamilyName $sFirstName"
    }

    val p11 = Person11("꿀떡", "개")
    println("내 이름은 ${p11.fullName()} 입니다.")
}

// 지역 클래스를 다른 곳에 호출 시 에러
fun subMain() {
//    val p11 = Person11("꿀떡", "개")
//    println("내 이름은 ${p11.fullName()} 입니다.")
}

 

< 결과 >


이번 시간에는 클래스 정의와 멤버, 생성자, 부생성자, 멤버가시성, 내포된 클래스와 지역 클래스 관련하여 개꿀떡 해보았습니다. 다음 시간에는 널 가능성 관련해서 개꿀떡 해보겠습니다. 감사합니다 :)

  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유