[Kotlin] Ch3-2. 다양한 클래스와 연산자 오버로딩

leeeha·2022년 8월 22일
0

코틀린

목록 보기
20/29
post-thumbnail

출처: https://www.boostcourse.org/mo234/lecture/154292?isDesc=false

데이터 클래스와 기타 클래스

data class

데이터 전달을 위한 객체를 DTO (Data Transfer Object)라고 부른다. 자바에서는 POJO (Plain Old Java Object)라고 부르기도 한다.

  • 구현 로직 없이 순수한 데이터 객체 표현
  • 데이터에 접근하는 게터, 세터 포함
  • toString(), equals() 등과 같은 데이터 표현 및 비교 메서드 포함

자바로 DTO를 표현하면, 데이터 필드와 게터/세터, 데이터 표현 및 비교 메서드를 모두 작성해줘야 한다. 하지만, 코틀린으로 DTO를 표현하면 프로퍼티만 신경써서 작성하면 되고, 나머지는 내부적으로 자동 생성해주기 때문에 매우 편리하다!

cf) 데이터 클래스에도 간단한 로직을 포함하고 싶으면, 부 생성자나 init 블록을 사용하면 된다.

data class Customer(var name: String, var email: String) {
	var job: String = "Unknown"
    constructor(name: String, email: String, _job: String): this(name, email) {
    	job = _job
    }
    init {
    	// 간단한 로직은 여기에 
    }
}

데이터 클래스의 조건

  • 주 생성자는 최소한 하나의 매개변수를 가져야 한다.
  • 주 생성자의 모든 매개변수는 val, var로 지정된 프로퍼티여야 한다.
  • 데이터 클래스는 abstract, open, sealed, inner 키워드를 사용할 수 없다.

데이터 클래스에서 자동 생성되는 메서드들

package chap03.section3

data class Customer(var name: String, var email: String)

fun main() {
    val cus1 = Customer("Sean", "sean@mail.com")
    val cus2 = Customer("Sean", "sean@mail.com")
    println(cus1 == cus2) // true (값을 비교)
    println(cus1 === cus2) // false (참조 주소를 비교)
    println(cus1.equals(cus2)) // true (값을 비교) 
    println("${cus1.hashCode()}, ${cus2.hashCode()}") // equal 

    val cus3 = cus1.copy(name = "Alice")
    println(cus1.toString())
    println(cus3.toString())
} 

true
false
true
-1521025108, -1521025108
Customer(name=Sean, email=sean@mail.com)
Customer(name=Alice, email=sean@mail.com)

destructuring (분해)

데이터 클래스의 객체가 갖고 있는 프로퍼티를 개별 변수들로 분해하는 것

val (name, email) = cus1
println("name = $name, email = $email")

// 특정 프로퍼티를 가져올 필요가 없을 때 
//val (_, email) = cus1 
val name2 = cus1.component1()
val email2 = cus1.component2()
println("name = $name2, email = $email2")

val cus1 = Customer("Sean", "sean@mail.com")
val cus2 = Customer("Sean", "sean@mail.com")
val bob = Customer("Bob", "bob@mail.com")
val erica = Customer("Erica", "erica@mail.com")
val customers = listOf(cus1, cus2, bob, erica)
for((name, email) in customers){
    println("name = $name, email = $email")
}
data class Customer(var name: String, var email: String)

fun myFunc(): Customer {
    return Customer("Mickey", "mic@mail.com")
}

fun main() {
  // 함수로부터 객체가 반환되는 경우 
  val (myName, myEmail) = myFunc()
  println("$myName $myEmail")

  // 람다식에서 사용하는 경우
  val myLambda = {
       // destructuring 
       (nameLa, emailLa): Customer -> 
       println(nameLa)
       println(emailLa)
  }
  val cus1 = Customer("Sean", "sean@mail.com")
  myLambda(cus1) // 인자로 객체를 전달  
}

inner class, nested class

자바의 내부 클래스 종류

종류역할
정적 클래스 (static class)static 키워드를 가지며, 외부 클래스를 인스턴스화 하지 않고 바로 사용 가능한 클래스 (주로 빌더 클래스에 이용)
멤버 클래스 (member class)인스턴스 클래스로도 불리며 외부 클래스의 필드나 메서드와 연동하는 내부 클래스
지역 클래스 (local class)초기화 블록이나 메서드 내의 블록에서만 유효한 클래스
익명 클래스 (anonymous class)이름이 없고, 주로 일회용 객체를 인스턴스화 하면서 오버라이드 메서드를 구현하는 내부 클래스. 가독성이 떨어지는 단점이 있다.

자바와 코틀린의 내부 클래스 비교

자바코틀린
정적 클래스중첩 클래스: 객체 생성 없이 사용 가능
멤버 클래스이너 클래스: 필드나 메서드와 연동하는 내부 클래스로 inner 키워드가 필요하다.
지역 클래스지역 클래스: 클래스의 선언이 블록 내에 있다면 지역 클래스이다.
익명 클래스익명 객체: 이름이 없고, 주로 일회용 객체를 사용하기 위해 object 키워드로 선언된다.

nested class

코틀린에서 중첩 클래스는 기본적으로 static 클래스처럼 다뤄진다.

package chap03.section3

class Outer {
    val ov = 5

    // Nested -> Outer (X)
    class Nested {
        val nv = 10
        fun greeting() = "[Nested] Hello! $nv" // 외부의 ov는 접근 불가
    }

    // Outer -> Nested (O)
    fun outside(){
        // 중첩 클래스의 메서드 및 프로퍼티 접근
        val msg = Nested().greeting()
        println("[Outer] $msg, ${Nested().nv}")
    }
}

fun main() {
    // static처럼 Outer 객체 생성 없이, Nested 객체 생성하여 사용 가능함.
    val output = Outer.Nested().greeting()
    println(output)

    val outer = Outer()
    outer.outside()
    
    //Outer.outside() // error
}

중첩 클래스에서 외부 클래스에 접근하는 방법

class Outer {
    val ov = 5

    // Nested -> Outer (X)
    class Nested {
        val nv = 10
        fun greeting() = "[Nested] Hello! $nv" // 외부의 ov는 접근 불가

        fun accessOuter(){ // 컴페니언 객체에 접근 가능 
            println(country)
            getSomething()
        }
    }

    // Outer -> Nested (O)
    fun outside(){
        // 중첩 클래스의 메서드 및 프로퍼티 접근
        val msg = Nested().greeting()
        println("[Outer] $msg, ${Nested().nv}")
    }

    companion object { // 컴페니언 객체는 static처럼 접근 가능
        const val country = "Korea"
        fun getSomething() = println("Get something...")
    }
}

inner class

inner 키워드로 선언된 이너 클래스는 바깥 클래스의 멤버에 접근 가능하다. (private 멤버 포함)

package chap03.section3

class Smartphone(val model: String){
    private val cpu = "Exynos"

    inner class ExternalStorage(val size: Int){
        // 외부 클래스의 프로퍼티에 접근
        fun getInfo() = "${model}: Installed on $cpu with ${size}GB"
    }
}

fun main() {
	// 이너 클래스의 객체 생성 
    val mySdcard = Smartphone("S7").ExternalStorage(32)
    println(mySdcard.getInfo())
}

local class

  • 특정 메서드의 블록이나 init 블록과 같이 블록 범위에서만 유효한 클래스
  • 블록 범위를 벗어나면 더 이상 사용되지 않음.
package chap03.section3

class Smartphone(val model: String){
    private val cpu = "Exynos"

    // 이너 클래스
    inner class ExternalStorage(val size: Int){
        // 외부 클래스의 프로퍼티 접근 가능
        fun getInfo() = "${model}: Installed on $cpu with ${size}GB"
    }

    fun powerOn(): String {
        // 지역 클래스
        class LED(val color: String){
            // 외부 클래스의 프로퍼티 접근 가능
            fun blink(): String = "Blinking $color on $model"
        }
        val powerStatus = LED("Red")
        return powerStatus.blink()
    }
}

fun main() {
    // 이너 클래스의 객체 생성
    val mySdcard = Smartphone("S7").ExternalStorage(32)
    println(mySdcard.getInfo())

    val myPhone = Smartphone("Note9")
    println(myPhone.powerOn()) // 지역 클래스의 사용
}

익명 객체

  • 자바에서는 익명 내부 클래스로 일회성 객체를 생성하여 사용한다.
  • 코틀린에서는 object 키워드로 같은 기능을 수행할 수 있다.
package chap03.section3

interface Switcher {
    fun on(): String
}

class Smartphone(val model: String){
    private val cpu = "Exynos"

    // 이너 클래스
    inner class ExternalStorage(val size: Int){
        // 외부 클래스의 프로퍼티 접근 가능
        fun getInfo() = "${model}: Installed on $cpu with ${size}GB"
    }

    fun powerOn(): String {
        // 지역 클래스
        class LED(val color: String){
            // 외부 클래스의 프로퍼티 접근 가능
            fun blink(): String = "Blinking $color on $model"
        }
        val powerStatus = LED("Red")

        // 익명 객체 생성하여 인터페이스의 추상 메서드 구현 
        // 하위 클래스를 따로 만들 필요 없음. 
        val powerSwitch = object: Switcher {
            override fun on(): String {
                return powerStatus.blink()
            }
        }

        return powerSwitch.on()
    }
}

fun main() {
    // 이너 클래스의 객체 생성
    val mySdcard = Smartphone("S7").ExternalStorage(32)
    println(mySdcard.getInfo())

    // 지역 클래스와 익명 객체의 사용
    val myPhone = Smartphone("Note9")
    println(myPhone.powerOn())
}

sealed class, enum class

sealed class

  • Sealed는 '봉인된'이라는 뜻으로 무언가 안전하게 보관하기 위해서 묶어두는 것
  • 실드 클래스 자체로는 추상 클래스와 같기 때문에 객체를 생성할 수는 없다.
  • 생성자도 기본적으로 private이며, private이 아닌 생성자는 허용하지 않는다.
  • 실드 클래스는 같은 파일 안에서는 상속이 가능하다. (다른 파일에서는 상속 불가)
  • 블록 안에 선언된 클래스는 상속이 필요한 경우 open 키워드로 선언해야 한다.
package chap03.section3

// 상태를 정의하고 관리하는 데 사용되는 실드 클래스
sealed class Result{
    open class Success(val message: String): Result()
    class Error(val code: Int, val message: String): Result()
}

class Status: Result() // 실드 클래스의 상속은 같은 파일 내에서만 가능
class Inside: Result.Success("status") // open 되어있는 내부 클래스 상속

// 상태를 검사하기 위한 함수
fun eval(result: Result): String = when(result){
    is Status -> "in progress"
    is Result.Success -> result.message
    is Result.Error -> result.message
    // Result 클래스의 모든 상태를 처리하므로 else 필요 없음. (실드 클래스의 장점) 
}

fun main() {
    val result = Result.Success("good")
    println(eval(result))

    val result2 = Result.Error(10, "No disk")
    println(eval(result2))
}

enum class

열거형 클래스는

  • 여러 개의 상수를 선언하고 열거된 값을 조건에 따라 선택할 수 있는 특수한 클래스
  • 자료형이 동일한 상수를 나열할 수 있다. (실드 클래스처럼 다양한 자료형을 다루지 못한다.)
// 각 상수는 Direction 클래스의 객체로 취급됨. 
enum class Direction {
	NORTH, SOUTH, WEST, EAST
}

enum class DayOfWeek(val num: Int) {
	MONDAY(1), TUESDAY(2), WEDNESDAY(3), THURSDAY(4),
    FRIDAY(5), SATURDAY(6), SUNDAY(7) 
}

val day = DayOfWeek.SATURDAY
when(day.num) {
	1, 2, 3, 4, 5 -> println("Weekday")
    6, 7 -> println("Weekend")
}
package chap03.section3

enum class Color(val r: Int, val g: Int, val b: Int){
    RED(255, 0, 0), ORANGE(255, 165, 0),
    YELLOW(255, 255, 0), GREEN(0, 255, 0),
    BLUE(0, 0, 255), INDIGO(75, 0, 130),
    VIOLET(238, 130, 238); // 세미콜론 필수

    fun rgb() = (r * 256 + g) * 256 + b // 메서드 포함
}

fun main() {
    println(Color.BLUE.rgb())
}
package chap03.section3

interface Score {
    fun getScore(): Int
}

// 인터페이스를 구현하는 열거형 클래스
enum class MemberType(var prio: String): Score {
    NORMAL("Third"){
        override fun getScore(): Int = 100
    },
    SILVER("Second"){
        override fun getScore(): Int = 500
    },
    GOLD("First"){
        override fun getScore(): Int = 1500
    }
}

fun main() {
    println(MemberType.NORMAL.getScore())
    println(MemberType.GOLD)
    println(MemberType.valueOf("SILVER"))
    println(MemberType.SILVER.prio)

    for(grade in MemberType.values()){
        println("name = ${grade.name}, priority = ${grade.prio}")
    }
}

어노테이션 클래스

어노테이션 (annotation) 이란?

  • 코드에 부가 정보를 추가하는 표기법
  • @ 기호와 함께 사용하며 주로 컴파일러나 프로그램 실행 시간에 사전 처리를 하기 위해 사용함.
    • @Test (유닛 테스트 코드 작성)
    • @JvmStatic (자바 코드에서 컴페니언 객체에 접근)

어노테이션의 속성

  • @Target: 어노테이션이 지정되어 사용할 종류 (클래스, 함수, 프로퍼티 등)을 정의
  • @Retention: 어노테이션을 컴파일 된 클래스 파일에 저장할 것인지 실행 시간에 반영할 것인지 결정
  • @Repeatable: 어노테이션을 같은 요소에 여러 번 사용 가능하게 할지 결정
  • @MustBeDocumented: 어노테이션이 API의 일부분으로 문서화 하기 위해 사용


연산자 오버로딩

  • 연산자에 여러가지 다른 의미의 작동을 부여
  • 코틀린에서는 특정 연산자의 역할을 함수로 정의
  • operator 키워드를 사용해 정의

연산자의 작동 방식

예시

package chap03.section4

class Point(var x: Int = 0, var y: Int = 10){
    operator fun plus(p: Point): Point {
        return Point(x + p.x, y + p.y)
    }
    operator fun dec() = Point(--x, --y)
}

fun main() {
    val p1 = Point(3, -8)
    val p2 = Point(2, 9)

    var point = p1 + p2 // 연산자 오버로딩 (객체의 프로퍼티끼리 더함)
    println("point = (${point.x}, ${point.y})")

    --point // 연산자 오버로딩
    println("point = (${point.x}, ${point.y})")
}
profile
습관이 될 때까지 📝

0개의 댓글