ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [코틀린 완벽 가이드] 4장 클래스와 객체 다루기 (4.1 클래스 정의하기)
    코틀린 공부/코틀린 2023. 12. 29. 16:27

    4.1 클래스 정의하기

    기본적으로 클래스 선언은 참조 타입(referential type)을 정의한다. 즉, 이런 참조 타입의 값은 특정 클래스 인스턴스의 실제 데이터 위치를 가르키는 참조다

     

    4.1.1 클래스 내부 구조

    class Person {
        var firstName: String = ""
        var familyName: String = ""
        var age: Int = 0
    
        fun fullName() = "$firstName $familyName"
    
        fun showMe() {
            println("${fullName()} : $age")
        }
    }

     

    이 정의는 모든 Person 클래스의 인스턴스마다 firstName, familyName, age라는 프로퍼티와 fullName() 및 showMe()라는 두 함수가 들어있음을 알려준다.

     

    val person = Person()
    person.firstName = "John"
    person.familyName = "Doe"
    person.age = 25
    
    person.showMe() // John Doe : 25

     

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

     

    기본적으로 코틀린 클래스는 public이다.

     

    4.1.2 생성자

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

     

    주생성자

    class Person1(firstName:String, familyName:String){
        val fullName = "$firstName $familyName"
    }
    val person1 = Person1("John", "Doe")
    println(person1.fullName) // John Doe

     

     

    클래스 해더의 파라미터 목록을 주 생성자선언이라고 부른다. 주생성자는 함수와 달리 본문이 하나가 아니다. 대신 주생성자는 클래스 정의 내에서 프로퍼티 초기화와 초기화 블록이 등장하는 순서대로 구성된다. 초기화 블록이란 init이라는 키워드가 앞에 붙은 블록이다.

     

    init 블록 안에서 프로퍼티를 초기화 하는 것도 허용한다. 

    class Person1(firstName:String, familyName:String){
        val fullName = "$firstName $familyName"
        init {
            println("Created new Person instance: $fullName")
        }
    }
    

     

     

     

    위와 같은 경우 본문을 아예 생략 가능하다.

    class Person2(val firstName: String, val familyName: String) 
    

     

     

     

    함수와 마찬가지로 디폴트 값과 vararg를 생성자 파라미터에 사용할 수 있다.

    class Room(vararg val persons: Person){
        fun showNames() {
            for(person in persons) println(person.fullName())
        }
    }
    

     

     

    부생성자: constructor 키워드를 사용

    class Person3 {
        val firstName: String
        val familyName: String
        constructor(firstName: String, familyName: String) {
            this.firstName = firstName
            this.familyName = familyName
        }
    }
    

     

    부생성자에 반환 타입을 지정할 수는 없지만, 기본적으로는 부생성자는 Unit 타입 값을 반환하는 함수와 마찬가지 형태다.

     

    클래스에 주생성자를 선언하지 않은 경우, 모든 부생성자는 자신의 본문을 실행하기 전에 프로퍼티 초기화와 init 블록을 실행한다. 이렇게 하면 어떤 부생성자를 호출하든지 호출하든지 공통적인 초기화 코드가 정확히 한 번만 실행되게 보장할 수 있다.

     

    다른 방법으로는 부생성자가 생성자 위임 호출을 사용해 다른 부생성자를 호출하는 것이 있다.

    class Person4 {
        val fullName: String
        constructor(firstName: String, familyName: String):
            this("$firstName $familyName")
        
        constructor(fullName: String){
            this.fullName = fullName
        }
    }

     

    생성자 파라미터 목록 뒤에 콜론(:)을 넣고 그 뒤에 일반 함수를 호출하는 것처럼 코드를 작성하되, 함수 이름 대신 this를 사용하면 생성자 위임 호출이 된다.

     

    만약 클래스에 주생성자가 있다면, 모든 부생성자는 위임을 하거나 다른 부생성자에게 위임을 해야한다. 

    class Person5(val fullName: String) {
        constructor(firstName: String, familyName: String) :
                this("$firstName $familyName")
    }

     

    4.1.3 맴버 가시성

    코틀린에서는 클래스 맴버의 가시성을 다음과 같은 변경자 키워드로 지정할 수 있다.

    • public : 맴버를 어디서나 볼 수 있다. 디폴트 가시성이 바로 public이다. 따라서 명시적으로 표기할 필요가 없다.
    • internal (모듈 내부) : 맴버를 맴버가 속한 클래스가 포함된 컴파일 모듈 내부에서만 볼 수 있다.
    • protected: 맴버가 속한 클래스와 맴버가 속한 클래스의 모든 하위 클래스 안에서 볼 수 있다.
    • private: 맴버가 속한 클래스 내부에서만 볼 수 있다.

    함수뿐만 아니라 함수의 프로퍼티, 주생성자, 부생성자에 대해서도 가시성 변경자가 지원된다. 주생성자에 가시성을 지정하려면 constructor 키워드를 꼭 명시해줘야한다. 

    class Empty private constructor() {
        fun showMe() = println("Empty")
    }

     

    Empty 클래스의 유일한 생성자가 private이므로 이 클래스를 클래스 본문 외부에서 인스턴스화 시킬수 없다.

     

    4.1.4 내포된 클래스

    코틀린 클래스는 다른 클래스도 맴버로 가질 수 있다. 이런 클래스를 내포된 클래스(nested class)라고 한다.

    class Person6(val id: Id, val age: Int){
        class Id(val firstName: String, val familyName: String)
        fun showMe() = println("${id.firstName} ${id.familyName}, $age")
    }
    val id = Person6.Id("John", "Doe")
    val person6 = Person6(id, 45)
    person6.showMe() // John Doe, 45

     

    다른 맴버와 마찬가지로 내포된 클래스에도 여러가지 가시성을 지정할 수 있다. 바깥쪽 클래스는 자신에게 내포된 클래스의 비공개 맴버에 접근할 수 없다.

     

    내포된 클래스에 inner를 붙이면 자신을 둘러싼 외부 클래스의 인스턴스에 접근할 수 있다.

    class Person7(val firstName: String, val familyName: String){
        inner class Possession(val description: String){
            fun showOwner() = println(fullName())
        }
        private fun fullName() = "$firstName $familyName"
    }

     

     

     

    일반적으로 this는 항상 가장 내부의 클래스 인스턴스를 가리킨다. 따라서 내부 클래스 본문에서 this는 내부 클래스 자신을 가르킨다. 내부 클래스 본문에서 외부 클래스 인스턴스를 가리켜야한다면 한정시킨 this를 사용해야 한다.

    class Person8(val firstName: String, val familyName: String){
        inner class Possession(val description: String){
            fun getOwner() = this@Person8
        }
    }

     

     

    4.1.5 지역 클래스

     

    함수 본문에서 클래스를 정의할 수 있다. 이런 지역 클래스는 자신이 둘러싼 코드 블록 안에서만 쓰일 수 있다. 

    fun localClass(){
        class Point(val x: Int,val y: Int){
            fun shift(dx:Int, dy:Int): Point = Point(x + dx, y + dy)
            override fun toString() = "$x, $y"
        }
        
        val p = Point(10, 10)
        println(p.shift(-1, 3)) // (9, 13)
    }

     

    내포된 클래스와 달리 지역 클래스에는 가시성 변경자를 붙일 수 없다.  지역 클래스의 영역은 항상 자신을 둘러싼 블록으로 제한된다. 

     

    지역 클래스의 내부 클래스는 반드시 inner 클래스여야만 한다. 

    fun localClass(){
        class Point(val x: Int,val y: Int){
            fun shift(dx:Int, dy:Int): Point = Point(x + dx, y + dy)
            override fun toString() = "$x, $y"
            
            inner class ClassInClass{
                val hi = "hi"
            }
        }
    
        val p = Point(10, 10)
        println(p.shift(-1, 3)) // (9, 13)
    }
    
Designed by Tistory.