Kotlin

[Kotlin] Class (constructor, getter/setter, companion object) 기초

노소래 2021. 8. 4. 12:45

간략한 소개

class

classs 는 object 의 청사진이다.

붕어빵틀과 붕어빵 개념? 일 것 같다.

codelabs 에서는 Objects are instances of classes 라고 한다.

 

참고. interface

Interface 는 다른 Class 가 구현해야하는 함수에 대한 specification 이다. 

codelabs 에서는 어항 클래스가 있고 청소 인터페이스가 있다고 했을 때, 청소는 어항말고도 가전제품, 집, 사무실 등 여러 다른 클래스에서도 쓰일 수 있기 때문에 청소를 인터페이스로 빼고 어항 클래스에서 청소 인터페이스를 구현하는 예시를 들어주었다.

 

 

 

- 생성자

- 생성자를 클래스 옆에 constructor() 안에 함수 parameter 처럼 쓸 수 있다. ( 그래서 default value 지정도 함수 인자처럼 할 수 있다. )  그리고 객체를 생성할 때 해당 argument 를 넘겨야한다.

이것을 Primary Construtor 라고 부른다.

primary constructor 는 하나만 가질 수 있고 다수의 Secoindary Constructor 를 가질 수 있다. 

 

참고1.  추상 클래스가 아닌 클래스에 어떠한 생성자도 선언하지 않았다면, 코틀린은 자동으로 파라미터없는 primary constructor 를 생성해준다.

class Aquarium constructor(length: Int = 100, width: Int = 20, height: Int = 40) {
   // Dimensions in cm
   var length: Int = length
   var width: Int = width
   var height: Int = height
...
}

인자로 받아와서 property 를 초기화하고 있다. 자바였다면 Aquarium 이라는 생성자 함수를 따로 만들어야했을 것이다. 이 코드는 아래와 같이 더 간결하게사용할 수 있다.

참고. 여기서 property 는 멤버변수 + getter/setter 를 말한다.

참고2. primary constructor 의 default 접근제어자는 public 이며 constructor 키워드를 아래 코드와 같이 생략할 수 있다.  private 을 붙이고 싶다면 private constructor 를 사용하면된다. 

class Aquarium(var length: Int = 100, var width: Int = 20, var height: Int = 40) {
...
}​

그냥 생성자에 properties 를 넣어서 코드가 더 간결해졌다. default value 기능도 사용할 수 있어서 생성자 overloading 을 없앨 수 있다.

 

- init, primary constructor 의 연장선

primary construtor 에서 단순히 properties 를 정의하고 값을 할당하는 것 이외에 초기화 코드를 작성하고 싶다면  다음과 같이 init 키워드를 사용해서 코드를 작성한다.

참고3.  init 블록은 primary constructor 의 일부라고 생각하면 된다.

class Aquarium (var length: Int = 100, var width: Int = 20, var height: Int = 40) {
    init {
        println("aquarium initializing")
    }
    init {
        // 1 liter = 1000 cm^3
        println("Volume: ${width * length * height / 1000} l")
    }
}

위와 같이 init 을 여러번 사용할 수도 있고, 객체를 생성할 때 쓰여진 순서대로 호출된다. 

 

 

- secondary constructor ( 키워드는 constructor, 생략 불가 ) 

package aquarium

class Aquarium(var width: Int = 20, var height: Int = 40, var length: Int = 100) {

    init {
        println("Aquarium Init")
    }

    init {
        println("Aquarium Second Init")
    }

    fun printSize() {
        println("PrintSize : Width: $width cm " +
                "Length: $length cm " +
                "Height: $height cm ")
    }

    constructor(numberOfFish: Int) : this() {
        println("constructor 호출")
        // 2,000 cm^3 per fish + extra room so water doesn't spill
        val tank = numberOfFish * 2000 * 1.1
        // calculate the height needed
        height = (tank / (length * width)).toInt()

    }
}

primary constructor 가 없다면 this 를 생략해도 되지만 지금처럼 primary constructor 가 있다면 this() 생성자를 이용해서 직간접적으로 primary constuctor 에 위임해야한다.

위와 같은 클래스의 객체를 생성하고 아래와 같이 main 에서 호출해주면

package aquarium

fun buildAquarium() {
    val aquarium6 = Aquarium(numberOfFish = 124)
    aquarium6.printSize()
    println("Volume: ${aquarium6.width * aquarium6.length * aquarium6.height / 1000} l")
}
fun main() {
    buildAquarium()
}
Aquarium Init
Aquarium Second Init
constructor 호출
PrintSize : Width: 20 cm Length: 100 cm Height: 136 cm 
Volume: 272 l

위와 같은 결과가 나온다. 

정리하면 primary constructor, init, property  순서대로 초기화 -> secondary constructor 

 

- 자동으로 getter 와 setter 추가

- properties 의 getter setter 를 자동으로 생성해줘서 property 에 바로 접근할 수 있다.

예를 들어, width 라는 변수에 접근하려면 객체이름.width 로 값을 가져올 수 있는데 이는 getter 함수로 가져온 것이라고 이해하면된다.

그리고 객체이름.width = 5 이런식으로 값을 넣을 때는 setter 함수로 가져온 것이라고 이해하면된다. 

property 가 val 로 선언되어있다면 당연히 한번만 값을 할당할 수 있을 것이다.  ( 참고로 코틀린은 가능하면 val 을 더 선호한다. 가능하면 val 을 사용하자 )

 

- 명시적으로 getter 와 setter 추가

property 의 값을 단순히 가져오기만 하는 것이 아니라 가공해서 가져오고, 

지금까지의 클래스에 volume 이라는 property 를 추가하고 getter 와 setter 를 명시적으로 선언하는 코드를 보자

var volume: Int
    get() = width * height * length / 1000
    set(value) {
        height = (value * 1000) / (width * length)
    }

다음과 같이 property 아래에 get() set() 을 써주면 된다. fun 키워드와 반환타입이 생략된 함수처럼 느껴진다.

fun buildAquarium() {
    val aquarium6 = Aquarium(numberOfFish = 29)
    aquarium6.printSize()
    aquarium6.volume = 70
    aquarium6.printSize()
}
fun main() {
    buildAquarium()
}

위 코드를 실행한 결과는 아래와 같다.

Aquarium Init
Aquarium Second Init
constructor 호출
PrintSize : Width: 20 cm Length: 100 cm Height: 31 cm 
PrintSize : Width: 20 cm Length: 100 cm Height: 35 cm

 

- companion object vs object class

클래스 안에 상수를 정의하고 싶다면 companion 키워드를 사용하면된다.

companion object 는 클래스 안에 있는 싱글톤 객체가 된다.

그렇다면 object class 에 쓰인 싱글톤 객체와는 어떤 차이가 있을까? 초기화 되는 시점이 다르다.

object class 는 프로그램 실행중에 처음으로 사용될 때 lazy 로 초기화되는 반면 companion object 는 해당 클래스가 만들어질 때 함께 만들어진다.

 

 

 

 

- const val 과 val 의 차이

val 과 var 의 차이는 익히 알고 있을 것이다.

그렇다면 const val 과 val 의 차이는 무엇일까?

const val 은 컴파일되는 시점에 정의된다. 반면에 val 은 실행시점에 정의된다.  

그래서 val 은 프로그램을 실행하는 동안에 함수로 값을 할당할 수 있지만 const val 은 그럴 수 없다.

const val 은 object class 나 companion object 에 구성한다.

 

 

- 확장함수 ( extension functions )

클래스의 소스코드에 직접 접근하지 않고도 해당 클래스에 함수를 정의하여 기능을 추가할 수 있게 만들어준다.

실제로 클래스를 수정하는 것은 아니다.

당연히 해당 클래스에 private 으로 지정된 변수엔 접근할 수 없다.

확장 당한 클래스를 revceiver 라고 부른다.

open class AquariumPlant(val color: String, private val size: Int)

class GreenLeafyPlant(size: Int) : AquariumPlant("green", size)

fun AquariumPlant.print() = println("AquariumPlant")
fun GreenLeafyPlant.print() = println("GreenLeafyPlant")

val plant = GreenLeafyPlant(size = 10)
plant.print()
println("\n")
val aquariumPlant: AquariumPlant = plant
aquariumPlant.print()
GreenLeafyPlant
AquariumPlant

확장 당한 클래스를 receiver 라고 부른다.

아래코드와 같이 이 receiver 가 null 일 수 있음을 가정할 수도 있다.

fun AquariumPlant?.pull() {
   this?.apply {
       println("removing $this")
   }
}

val plant: AquariumPlant? = null
plant.pull()

출력되는 것이 없다.


사용 방법은 아래 링크를 참고

https://nosorae.tistory.com/18

 

[Android/Kotlin] 기존 클래스의 확장함수 사용

확장함수란 이미 주어진 클래스에 없는 기능이 필요할 때 해당 클래스의 함수를 직접 만들어서 사용할 수 있는 기능이다. 형식은 다음과 같다. fun 클래스명.함수명(인자): 반환형 {} 굵은 글씨 처

nosorae.tistory.com

 

- 확장 프로퍼티 ( extension properties )

확장 함수와 같은 개념이다. 아래와 같이 코드를 작성해서 확장 프로퍼티를 가질 수 있다.

val AquariumPlant.isGreen: Boolean
   get() = color == "green"

 

상속과 다양한 class 는 아래링크에서 확인할 수 있다.

 

[Kotlin] 상속/ interface/ abstract class/ object/ data class/ enum class

- open 키워드와 상속 다른 클래스를 상속하는 방법은 현재클래스이름(생성자) : 상속클래스이름(생성자) 코틀린은 default 로 부모 클래스의 properties 와 member variables 를 private 이 아닌 이상 접근은

nosorae.tistory.com

https://nosorae.tistory.com/24

 

- 출처 : codelabs https://readystory.tistory.com/124