Strong 감자의 공부

Ch_07 클래스 관계 등 추가 사항 알아보기 본문

CS/문법_Kotlin

Ch_07 클래스 관계 등 추가 사항 알아보기

ugyeong 2023. 8. 25. 19:19

-목차

1. 속성과 메서드 재정의

2. 특정 자료를 다루는 클래스 알아보기

 


1. 속성과 메서드 재정의

🔶 1.1 속성 정의

⚫ 속성의 메서드 세터를 비공개 처리하기

특정 속성의 변경 제한을 처리하려면 속성의 세터 메서드를 private 처리한다. 

아래 코드는 ++을 어디에 두느냐에 따라 getter호출 수가 다르다.

책 예제의 get호출되는 수가 예상과 달라서 ++과 print문을 옮겨가며 찍어봤다.

fun main() {
    val counter = Counter()
    for (i in 1..2) {
        counter.inc()
        println(counter.value)
    }

}

class Counter {
    var value: Int = 0
        get() {
            println("get value $field")
            return field
        }
        private set // 값 수정은 비공개 처리 -> 클래스 안 메서드로 접근

    fun inc() = ++value // value값 가져올때 1, 더해서 return시 2, 위에서 println 할때 또 get 3
}
// 결과값 
get value 0
get value 1
get value 1
1
get value 1
get value 2
get value 2
2

아래는 디컴파일 한 것이다.

// ExpKt.java

public final class ExpKt {
   public static final void main() {
      Counter counter = new Counter();
      int var1 = 1;

      for(byte var2 = 2; var1 <= var2; ++var1) {
         counter.inc();
         int var3 = counter.getValue();
         System.out.println(var3);
      }

   }

   // $FF: synthetic method
   public static void main(String[] var0) {
      main();
   }
}
// Counter.java

public final class Counter {
   private int value;

   public final int getValue() {
      String var1 = "get value " + this.value;
      System.out.println(var1);
      return this.value;
   }

   public final int inc() {
      this.value = this.getValue() + 1;
      return this.getValue();
   }
}
fun main() {
    val counter = Counter()
    for (i in 1..2) {
        counter.inc()
        println(counter.value)
    }

}

class Counter {
    var value: Int = 0
        get() {
            println("get value $field")
            return field
        }
        private set // 값 수정은 비공개 처리 -> 클래스 안 메서드로 접근

    fun inc() = value++ // 더해서 return시 1, 위에서 print 2
}
// 결과값
get value 0
get value 1
1
get value 1
get value 2
2

아래는 디컴파일 한 것이다.

// ExpKt.java

public final class ExpKt {
   public static final void main() {
      Counter counter = new Counter();
      int var1 = 1;

      for(byte var2 = 2; var1 <= var2; ++var1) {
         counter.inc();
         int var3 = counter.getValue();
         System.out.println(var3);
      }

   }

   // $FF: synthetic method
   public static void main(String[] var0) {
      main();
   }
}

// Counter.java

public final class Counter {
   private int value;

   public final int getValue() {
      String var1 = "get value " + this.value;
      System.out.println(var1);
      return this.value;
   }

   public final int inc() {
      int var1;
      this.value = (var1 = this.getValue()) + 1;
      return var1; // var1++이라 var1을 return
   }
}

아래는 책에 있는 예제이다.

fun main() {
    val counter = Counter()
    for (i in 1..2) {
        counter.inc()
    }
    println(counter.value)
}

class Counter {
    var value: Int = 0
        get() {
            println("get value $field")
            return field
        }
        private set // 값 수정은 비공개 처리 -> 클래스 안 메서드로 접근

    fun inc() = value++
}
// 결과값
get value 0 // inc
get value 1 // inc
get value 2 // print 할 때 접근
2 // print(valuea)

 

⚫ 배킹핑드 사용하지 않기

class Thing(val name: String) 

class Container(private val maxCapacity: Int) {
    private val things = mutableListOf<Thing>()
    val capacity: Int
        get() = maxCapacity - things.size
    val isFull: Boolean
        get() = things.size == maxCapacity

    fun put(thing: Thing): Boolean =
        if (isFull) false
        else {
            things += thing // 삽입
            true
        }

    fun take(): Thing = things.removeAt(0)
    fun query(): List<String> = things.map { it.name }
}

fun main() {
    var container = Container(3)
    container.put(Thing("침대"))
    container.put(Thing("의자"))
    container.put(Thing("받침대"))

    println(container.isFull)
    println(container.query())
    println(container.take().name)
    println(container.query())

}
// 결과값 
true
[침대, 의자, 받침대]
침대
[의자, 받침대]

 

🔶 1.2 연산자 오버로딩

⚫ 연산자 오버로딩 처리 규직

1. operator fun 으로 작성

2. 클래스 내부의 메서드나 확장함수로 정의 가능

3. 메서드나 함수의 오버로딩 규칙에 따라 매개변수의 개수나 자료형이 다르면 여러개 정의 가능

 

⚫ 아래는 오버로딩 규칙에 따라 두개의 plus 메서드를 정의한 예이다.

fun main() {
    val amt01 = Amount(200, 100)
    val amt02 = Amount(300, 100)
    val amt03 = amt01 + amt02
    val amt04 = amt02 + 100 
    
    println(amt03) // Amount(500, 200)
    println(amt04) // Amount(400, 200)
}

class Amount(var total: Int, var balance: Int) {
    operator fun plus(other: Amount) = Amount(
        this.total + other.total, 
        this.balance + other.balance
    )

    operator fun plus(scale: Int) = Amount(this.total + scale, this.balance + scale)
    override fun toString(): String = "Amount($total, $balance)"
}

 

 ⚫ 확장함수 연산자 오버로딩

class Amount(var total: Int, var balance: Int) {
    operator fun plus(other: Amount) = Amount(
        this.total + other.total,
        this.balance + other.balance
    )

    operator fun plus(scale: Int) = Amount(this.total + scale, this.balance + scale)
    override fun toString(): String = "Amount($total, $balance)"
}

// 확장함수 연산자 오버로딩
operator fun Amount.times(other: Amount): Amount {
    // 타 클래스안 메서드처럼 리시버클래스 안 속성 접근이 가능하다. 
    return Amount(total * other.total, balance * other.balance)
}

fun main() {
    val amt01 = Amount(200, 100)
    val amt02 = Amount(300, 100)
    val amt05 = amt01 * amt02
    print(amt05) // Amount(60000, 10000)
}

 

⚫infix 처리

1. infix fun 처럼 함수나 메서드 정의시 맨 앞에 둔다.

2. 매개변수가 1개여야 한다.

2. 매개변수에 초기값을 지정할 수 없다.

fun main() {
    val a = Add(100)
    println(a add 20)
    println(a.add(200))
    println(a times 200)
    println(a * 200)
    println(a div 200)

    // 결과값
    120
    300
    20000
    20000
    0

}

class Add(var x: Int = 0) {
    infix fun add(y: Int) = x + y
    infix operator fun times(y: Int) = x * y
    infix fun divide(y: Int) = x / y
}

infix fun Add.div(y: Int) = this.divide(y) // 확장함수에 infix 지정하기


 

🔶 1.3 메서드 재정의

⚫ 메서드 재정의 규칙

1. method overload : 클래스 안에 동일한 이름의 메서드를 여러개 정의. 매개변수의 개수, 자료형에 따라 다르게 처리

2. method override : 클래스 상속관계에서 슈퍼클래스의 메서드를 서브클래스 메서드에 동일한 이름과 매개변수로 정의.

- 서브클래스에서 추가로 기능을 재정의 가능. 항상 서브클래스가 먼저 참조되고 없으면 슈퍼클래스의 메서드 검색.

- 서브클래스에서 앞으로의 재정의를 막을려면 "final override fun 메서드이름" 처럼 쓴다.

- 부모클래스에 있는 것을 재정의 할 때, 부모클래스꺼엔 open을 붙이고, 자식클래스에선 override를 붙인다.

- 부모클래스에 있는 속성들은 기본이 final이라 open을 붙이지 않으면 재정의 할 수 없다. 

open class Person(var name: String = "돌자반", var food: String = "치킨") {
    fun eat() {
        println("$name 은 굽네를 먹는다.")
    }

    open fun sleep() {
        println("$name 은 오늘도 일찍 일어난다.")
    }
}

class Student(name: String) : Person("감자", "설렁탕") {

    override fun sleep() {
        println("$name 은 늦게 일어났다...뽈")
    }

    fun activity() {
        println("$name 신나게 $food 먹는다")
    }

    fun doAll() {
        eat()
        sleep()
        activity()
    }
}

fun main() {
    var st = Student("돌자르르르ㅡ반")
    st.doAll()
    // 결과값
    감자 은 굽네를 먹는다.
    감자 은 늦게 일어났다...뽈 
    감자 신나게 설렁탕 먹는다
}

 


2. 특정 자료를 다루는 클래스 알아보기(데이터, 이넘 클래스)

🔶 데이터 클래스 

클래스일 때 == 객체끼리 연산이 false

fun main() {
    val p1 = Person("감자")
    val p2 = Person("감자")

    println(p1)
    println(p2)
    println(p1 == p2)
    println(p1 === p2)
    
    // 결과값
    Person@27d6c5e0
    Person@4f3f5b24
    false
    false
}

class Person(name: String)

데이터 클래스 일 때 == 연산이 true

fun main() {
    val p1 = Person("감자")
    val p2 = Person("감자")

    println(p1)
    println(p2)
    println(p1 == p2)
    println(p1 === p2)

    // 결과값
    Person(name=감자)
    Person(name=감자)
    true
    false
}

data class Person(val name: String)

 

💛 데이터 클래스는 주생성자에 정의된 속성값이 같으면 동일한 값의 객체로 본다. 즉 주생성자의 속성 값이 같을 때 == 연산시 true가 반환된다.

데이터 클래스가 상속 받더라도 값이 같은지(==)의 기준은 데이터 클래스의 주생성자이다.

 

🔶 2.2 이넘 클래스 -> kotlin 1.9.0 에서는 entries 추천

⚫ 이넘 클래스 작성법 

1. enum class 이렇게 작성한다.

2. 생성할 객체를 모두 내부에 정의하고 객체의 이름을 상수처럼 사용하고 대문자로 작성해야한다.

fun main() {
    println(Card.PLATINUM.ordinal) // 객체 순서번호 출력
    println(Card.PLATINUM.name) // 객체 이름 출력
    println(Card.GOLD < Card.SIVER) // 객체 간 순서 비교
    
    // 결과값
    2
    Card.PLATINUM
    false

}

enum class Card {
    SIVER, GOLD, PLATINUM // 0번, 1번, 2번 순서
}

 

웬만하면 oridinary를 쓰지 않는다. 새로운 값이 들어왔을 때 순서가 바뀌기 때문 

순서가 필요하다면 속성을 추가하자.

 

3. 이넘 클래스에 속성 추가

import java.awt.Color.orange
import java.awt.Color.red

fun main() {
    println(Card.PLATINUM.color) // 내부 속성 조회
    println(Card.valueOf("GOLD")) // 객체 있는지 조회
    val type = enumValueOf<Card>("GOLD") // GOLD 검색
    println(type)
    
    println(Card.PLATINUM.name)
    println(Card.PLATINUM.color)

    // 결과값 1
    orange
    Card.GOLD
    Card.GOLD
    Card.PLATINUM
    orange
    
    val array1 = Card.values() // Array타입
    array1.forEach { println("${it.name}=${it.color}") }
    
    // 결과값 2
    Card.SIVER =red
    Card.GOLD =gold
    Card.PLATINUM =orange

    val array2 = enumValues<Card>() // Array타입
    array2.forEach { println("${it.name}=${it.color}") }
    
    // 결과값 3
    Card.SIVER =red
    Card.GOLD =gold
    PLATINUM=orange
    
    array2.filter { it.color == "red" }.map { it.color }.forEach { println(it) }
    
    // 결과값 4
    red
    
}

enum class Card(var color: String) {
    SIVER("red"), GOLD("gold"), PLATINUM("orange") // 0번, 1번, 2번 순서
}

 

4. 이넘 클래스에 동반객체를 작성할 수 있다. -> 메서드를 클래스 이름으로 바로 접근 가능

fun main() {
    for (i in Card.getIter()) { 
        println(i)
    }
}
// 결과값
SIVER
GOLD
PLATINUM

enum class Card(var color: String) {
    SIVER("red"), GOLD("gold"), PLATINUM("orange"); // 마지막 객체엔 ;를 넣어줘야한다!!

    companion object {
        fun getIter() = values() // 반복자
    }
}

 

5. 이넘 클래스 내부에 추상메서드를 정의하면 모든 객체 내부에 메서드를 재정의한다.

fun main() {
println(Card.SIVER.calculate(3)) // 6000
}

enum class Card(var color: String) {
    SIVER("red") {
        override fun calculate(grade: Int): Int = when (grade) {
            in 0..3 -> 6000
            in 4..5 -> 404
            else -> 3333
        }
    },
    GOLD("gold") {
        override fun calculate(grade: Int): Int = when (grade) {
            in 0..3 -> 6000
            in 4..5 -> 404
            else -> 3333
        }
    };

    abstract fun calculate(grade: Int): Int // 객체 선언 후 뒤에 적어야 함
}

 

6. 인터페이스를 상속받아 내부에 구현할 수 있다. 상수가 객체마다 다른 경우에는 각각 구현하고 전체가 같을 경우엔 하나만 구현. 

fun main() {
    println(Card.SIVER.calculate(3)) // 6000
}

interface Calculable {
    fun calculate(grade: Int): Int
}

enum class Card(var color: String) : Calculable {
    SIVER("red"),
    GOLD("gold");

    override fun calculate(grade: Int): Int = when (grade) {
        in 0..3 -> 6000
        in 4..5 -> 404
        else -> 3333
    }
}

 

kotli 1.9.0에서 인라인 클래스에 세컨더리 컨스트럭터가 생겼다!

data object가 생겼고 toString(), equals(), hashcode 으로 비교도 가능하다. 

용도 : 데이터의 성격을 띤 오브젝트 명시, data class에서 속성이 없을 때 

'CS > 문법_Kotlin' 카테고리의 다른 글

Ch_ 13 파일 입출력과 스레드 처리  (0) 2023.10.31
Ch_10 함수 추가 사항 알아보기  (0) 2023.09.13
Ch_ 06 내장자료형 알아보기  (0) 2023.08.19
Ch_05 클래스 알아보기  (0) 2023.08.11
Ch_04 함수 알아보기  (0) 2023.08.04