Kotlin 기본 문법6 - 클래스

KWANWOO·2022년 1월 3일
0

Kotlin 언어 배우기

목록 보기
6/8
post-thumbnail

코틀린 기본 문법 정리6 - 클래스

안드로이드 네이티브 앱 개발 공부를 위해 Kotlin의 기본적이면서도 중요한 문법들을 정리해보았다. 이 글은 클래스에 관련된 내용이다.

1. 클래스

class 키워드를 사용하여 클래스를 정의할 수 있다.

class Car

2. 속성

클래스는 속성을 사용하여 상태를 나타낸다. 속성은 getter, setter 및 backing 필드를 포함할 수 있는 클래스 수준 변수이다.

  • 자동차를 운전하려면 바퀴가 필요하므로 Wheel 객체 목록을 Car의 속성으로 추가할 수 있다.
class Car {
    val wheels = listOf<Wheel>()
}

wheels는 public val이다. 즉, Car 클래스 외부에서 wheels에 액세스할 수 있지만 재할당할 수는 없다.

  • Car의 인스턴스를 가져오려면 먼저 생성자를 호출해야 한다. 그러면 액세스 가능한 모든 속성에 접근 가능하다.
val car = Car() // construct a Car
val wheels = car.wheels // retrieve the wheels value from the Car

속성을 맞춤설정하려면 클래스 속성을 초기화하는 방법을 지정하는 맞춤 생성자를 정의하면 된다.

  • 클래스 생성자는 List을 생성자 인수로 취하고 인수를 사용하여 wheels 속성을 초기화한다.
class Car(val wheels: List<Wheel>)

객체가 생성될 때 무조건 처음 실행되고 싶은 기능은 init을 사용할 수 있다.

class Car(val wheels: List<Wheel>){
    init {
        println("new Car")
    }
}

주 생성자가 외에 부 생성자를 생성하고 싶은 경우 constructor를 사용한다.

  • this를 사용하여 주 생성자의 값을 상속받아야한다. 단 부 생성자는 init 다음에 실행된다.
class Car(val wheels: List<Wheel>){
    constructor(wheels: List<Wheel>, carName: String):this(wheels){
        println("car name is ${carName}")
    }
    init {
        println("new Car")
    }
}

3. 클래스 함수 및 캡슐화

클래스는 함수를 사용하여 동작을 모델링한다. 함수는 상태를 수정할 수 있으므로 노출하려는 데이터만 노출할 수 있다. 이 액세스 제어는 캡슐화라는 더 큰 객체 지향 개념의 일부이다.

  • doorLock 속성은 Car 클래스 외부의 모든 항목에서 비공개로 유지된다. 자동차를 잠금 해제하려면 유효한 키를 전달하는 unlockDoor() 함수를 호출해야 한다.
class Car(val wheels: List<Wheel>) {

    private val doorLock: DoorLock = ...

    fun unlockDoor(key: Key): Boolean {
        // Return true if key is valid for door lock, false otherwise
    }
}

속성을 참조하는 방법을 맞춤설정하려면 맞춤 getter 및 setter를 제공하면 된다.

  • 속성의 setter에 액세스하는 것을 제한하면서 속성의 getter를 노출하려면 setter를 private으로 지정한다..
class Car(val wheels: List<Wheel>) {

    private val doorLock: DoorLock = ...

    var gallonsOfFuelInTank: Int = 15
        private set

    fun unlockDoor(key: Key): Boolean {
        // Return true if key is valid for door lock, false otherwise
    }
}

속성과 함수를 조합하여 모든 유형의 객체를 모델링하는 클래스를 만들 수 있다.

4. 클래스 상속

같은 파일에서 클래스는 기본적으로 final로 설정되기 때문에 상속이 불가능하다. 상속을 받고 싶은 경우 open을 사용해야 한다.

open class Car(val wheels: List<Wheel>) {

    open fun printCar() {
        println("this is a car"
    }
}
  
class Truck:Car(){
}

상속받은 부모의 함수를 override 할 수도 있다. 단 이 경우에도 부모 함수에 open을 사용해야 한다.

open class Car(val wheels: List<Wheel>) {
    
    open fun printCar() {
        println("this is a car")
    }
}
  
class Truck:Car(){
    override fun printCar() {
        println("this is a truck")
    }
}

부모의 함수나 속성에 접근할 경우 super를 사용한다.

open class Car(val wheels: List<Wheel>) {
    
    open fun printCar() {
        println("this is a car")
    }
}
  
class Truck:Car(){
    override fun printCar() {
        super.printCar()
        println("this is a truck")
    }
}

5. 데이터 클래스

POJO(Plain Old Java Object) 클래스는 딱히 비즈니스 로직을 갖고 있지 않으면서도 너무 많은 보일러 플레이트 코드를 필요로 한다.
Kotlin에서는 이러한 데이터만을 다루는 클래스에 대해 아주 간편한 문법을 제공한다.

  • data class 키워드로 생성하여 생성자에 parameter들을 정의한다.
data class Person(var name: String, var age: Int)

data class는 생성자부터 getter, setter, 그리고 canonical methods까지 생성해준다.

Canonical Methods
equlas(other: Any?): Boolean - 이 메소드는 참조가 아니라 데이터 클래스 간 값의 일치를 비교한다.

hashCode(): Int - 해쉬코드는 인스턴스의 숫자 표현이다. hashCode()가 같은 인스턴스에서 여러 번 호출될 때 항상 동일한 값을 반환해야 한다. equals()로 비교할 때 참을 반환하는 두 인스턴스는 같은 hashCode()를 가져야만 한다.

toString(): String - 인스턴스의 문자열 표현이다. 데이터 클래스는 이를 멤버 변수의 값을 나열하도록 자동으로 재정의 한다.

깊은 복사에 해당하는 copy() 메소드 역시 자동으로 생성한다.

val james = Person("James", 20, "male")
val tom = james.copy(name = "Tom")  // 이름만 Tom으로 바꾸고 나이와 성별은 james에서 복사

1. 얕은 복사(Shallow Copy)

  • 객체를 복사할 때, 해당 객체만 복사하여 새 객체를 생성한다.
  • 복사된 객체의 인스턴스 변수는 원본 객체의 인스턴스 변수와 같은 메모리 주소를 참조한다.
  • 해당 메모리 주소의 값이 변경되면 원본 객체 및 복사 객체의 인스턴스 변수 값은 같이 변경된다.

2. 깊은 복사(Deep Copy)

  • 객체를 복사 할 때, 해당 객체와 인스턴스 변수까지 복사하는 방식이다.
  • 전부를 복사하여 새 주소에 담기 때문에 참조를 공유하지 않는다.

Destructuring도 제공한다.

val james: Person = Person("James", 20, "male")
val (name, age, gender) = james

Destructuring 에서 필요하지 않은 값은 _로 작성하면 된다.

val (name, _, gender) = james

내부 클래스와 중첩 클래스

자바에서는 A 클래스 안에 B 클래스를 정의하면 B 클래스는 자동으로 내부 클래스가 된다. 하지만 코틀린에서는 반대이다. 한 클래스안에 다른 클래스를 정의하면 기본적으로는 중첩 클래스가 된다.

  • 중첩 클래스에서는 외부 클래스를 참조하지 않기 때문에 Outer.Nested().foo()의 값이 2가 된다.
// nested class 중첩 클래스
class Outer {
    private val bar: Int = 1
    class Nested {
        fun foo() = 2
    }
}

val demo = Outer.Nested().foo() // 2

내부 클래스로 만들고 싶다면 inner 키워드로 클래스를 선언해야 한다.

  • 내부 클래스에서는 외부 클래스를 항상 참조하고 있기 때문에 Outer().Inner().foo()의 값이 1이 된다.
// inner class 내부 클래스
class Outer {
    private val bar: Int = 1
    inner class Inner {
        fun foo() = bar
    }
}

val demo = Outer().Inner().foo() // 1

하지만 내부 클래스를 사용하면 항상 외부 클래스의 객체를 참조하기때문에 객체가 적절한 시점에 삭제되지 못하고 메모리 누수의 문제가 발생할 수 있기 때문에 특별한 경우가 아니면 내부 클래스는 사용하지 않는 편이 좋다.

클래스 내용은 중요한것 같다

이번에 다른 사람들이랑 안드로이드 코틀린 스터디를 시작하게 되었다. 그래서 오랜만에 코틀린 기본 문법 정리를 이어서 다시 시작했다. 이번에는 클래스 부터 정리를 시작해보았다. 클래스도 함수만큼 새로운 내용들이 꽤 있지만 많이 사용해보고 익히면 자바보다 편리하게 사용할수 있을것 같다.

📄 Reference

profile
관우로그

0개의 댓글