Rooted In Develop

Kotlin - 연산자 오버로딩 본문

Back End/Kotlin

Kotlin - 연산자 오버로딩

RootedIn 2023. 5. 27. 21:45

산술 연산자 오버로딩

이항 산술 연산 오버로딩

연산자 키워드
a * b times
a / b div
a % b mod
a + b plus
a - b minus
data class Point(val x: Int, val y: Int) {
  operator fun plus(other: Point) : Point {
    return Point(x + other.x, y + other.y)
  }
}
>>> val p1 = Point(10, 20)
>>> val p2 = Point(30, 40)
>>> println(p1 + p2)
Point(x=40, y=60)

 

피연산자 타입이 다른 경우

operator fun Point.times(scale: Double) : Point {
  return Point((x * scale).toInt(), (y * scale).toInt())
}
>>> val p = Point(10, 20)
>>> println(p * 1.5)
Point(x=15, y=30)

- 자동으로 교환 법칙을 지원하지 않음
- 1.5 * p 를 오버로딩하려면 operator fun Double.times(p: Point) : Point 를 정의해야함

결과 타입이 다른 경우

operator fun Char.times(count: Int) : String {
  return toString().repeat(count)
}
>>> println('a' * 3)
aaa

복합 대입 연산자 오버로딩

연산자 키워드
+= plusAssign
-= minusAssign
*= timesAssign

- ex) a += b 는 a = a.plus(b) 또는 a.plusAssign(b)

컬렉션
- +=, -= : 새로운 컬렉션 반환
- +, - : 객체 상태 변화

단항 연산자 오버로딩

연산자 키워드
+a unaryPlus
-a unaryMinus
!a not
++a, a++ inc
--a, a-- dec
operator fun Point.unaryMinus() : Point {
  return Point(-x, -y)
}
>>> val p = Point(10, 20)
>>> println(-p)
Point(x=-10, y=-20)

비교 연산자 오버로딩

동등성 연산자 : equals

class Point(val x: Int, val y: Int) {
  override fun equals(obj: Any?) : Boolean {
    if (obj === this) return true
    if (obj !is Point) return false
    return obj.x == x && obj.y == y
  }
}
>>> println(Point(10, 20) == Point(10, 20))
true
>>> println(Point(10, 20) != Point(5, 5))
true
>>> println(null == Point(1, 2))
false

순서 연산자 : compareTo

a >= b : a.compareTo(b)

class Person(val firstName: String, val lastName: String) : Comparable<Person> {
  override fun compareTo(other: Person) : Int {
    return compareValuesBy(this, other, Person::lastName, Person::firstName)
  }
}
>>> val p1 = Person("Alice", "Smith")
>>> val p2 = Person("Bob", "Johnson")
>>> println(p1 < p2)
false

 

컬렉션과 범위에 대해 쓸 수 있는 관례

인덱스로 원소에 접근 : get, set

operator fun Point.get(index: Int) : Int {
  return when(index) {
    0 -> x
    1 -> y
    else -> throw IndexOutOfBoundsException("Invalid coordinate $index")
  }
}
>>> val p = Point(10, 20)
>>> println(p[1])
20
data class MutablePoint(var x: Int, var y: Int)
operator fun MutablePoint.set(index: Int, value: Int) {
  when(index) {
    0 -> x = value
    1 -> y = value
    else -> throw IndexOutOfBoundsException("Invalid coordinate $index")
  }
}
>>> val p = MutablePoint(10, 20)
>>> p[1] = 42
>>> println(p)
MutablePoint(x=10, y=42)

in 관례

data class Rectangle(val upperLeft: Point, val lowerRight: Point)
operator fun Rectangle.contains(p: Point) : Boolean {
  return p.x in upperLeft.x until lowerRight.x && p.y in upperLeft.y until lowerRight.y
}
>>> val rect = Rectangle(Point(10, 20), Point(50, 50))
>>> println(Point(20, 30) in rect)
true
>>> println(Point(5, 5) in rect)
false

rangeTo 관례

>>> val now = LocalDate.now()
>>> val vacation = now..now.plusDays(10)
>>> println(now.plusWeeks(1) in vacation)
true

for 루프를 위한 iterator 관례

operator fun ClosedRange<LocalDate>.iterator() : Iterator<LocalDate> = object : Iterator<LocalDate> {
  val current = start
  override fun hasNext() = current <= endInclusive
  override fun next() = current.apply {
    current = plusDays(1)
  }
}

>>> val newYear = LocalDate.ofYearDay(2017, 1)
>>> val daysOff = newYear.minusDays(1)..newYear
>>> for (dayOff in daysOff) { println(dayOff) }
2016-12-31
2017-01-01

구조 분해 선언과 component 함수

>>> val p = Point(10, 20)
>>> val (x, y) = p
>>> println(x)
10
>>> println(y)
20

val (a, b) = p
 - val a = p.component1()
 - val b = p.component2()
 - componentN 함수 호출로 변환됨

data class NameComponents(val name: String, val extension: String)
fun splitFilename(fullName: String) : NameComponents {
  val result = fullName.split('.', limit = 2)
  return NameComponents(result[0], result[1])
}
>>> val (name, ext) = splitFilename("example.txt")
>>> println(name)
example
>>> println(ext)
txt
data class NameComponents(val name: String, val extension: String)
fun splitFilename(fullName: String) : NameComponents {
  val (name, extension) = fullName.split('.', limit = 2)
  return NameComponents(name, extension)
}
fun printEntries(map: Map<String, String>) {
  for ((key, value) in map) {
    println("$key -> $value")
  }
}
>>> val map = mapOf("Oracle" to "Java", "JetBrains" to "Kotlin")
>>> printEntries(map)
Oracle -> Java
JetBrains -> Kotlin

프로퍼티 접근자 로직 재활용 : 위임 프로퍼티

위임 프로퍼티

class Delegate {
  operator fun getValue() {}
  operator fun setValue() {}
}
class Foo {
  var p: Type by Delegate()
}
>>> val foo = Foo()
>>> val oldValue = foo.p
>>> foo.p = newValue

프로퍼티 초기화 지연 : by lazy()

class Email {}
fun loadEmails(person: Person) : List<Email> {
  println("${person.name}")
  return listOf(/*...*/)
}
class Person(val name: String) {
  private var _emails: List<Email>? = null
  val emails: List<Email>
    get() {
      if (_emails == null) {
        _emails = loadEmails(this)
      }
      return _emails!!  // 저장해 둔 데이터가 있으면 그 데이터를 반환
    }
}

class Person(val name: String) {
  val emails by lazy ( loadEmails(this) )
}

위임 프로퍼티 구현

open class PropertyChangeAware {
  protected val changeSupport = PropertyChangeSupport(this)
  fun addPropertyChangeListener(listener: PropertyChangeListener) {
    changeSupport.addPropertyChangeListener(listener)
  }
  fun removePropertyChangeListener(listener: PropertyChangeListener) {
    changeSupport.removePropertyChangeListener(listener)
  }
}
// 프로퍼티 변경 통지 직접 구현
class Person(val name: String, age: Int, salary: Int) : PropertyChangeAware() {
  var age: Int = age
    set(newValue) {
      val oldValue = field
      field = newValue
      changeSupport.firePropertyChange("age", oldValue, newValue)
    }
  var salary: Int = salary
    set(newValue) {
      val oldValue = field
      field = newValue
      changeSupport.firePropertyChange("salary", oldValue, newValue)
    }
}
// 도우미 클래스로 프로퍼티 변경 통지 구현
class ObsservableProperty(val propName: String, var propValue: Int, val changeSupport: PropertyChangeSupport ) {
  fun getValue() : Int = propValue
  fun setValue(newValue: Int) {
    val oldValue = propValue
    propValue = newValue
    changeSupport.firePropertyChange(propName, oldValue, newValue)
  }
}
class Person(val name: String, age: Int, salary: Int) : PropertyChangeAware() {
  val _age = ObservableProperty("age", age, changeSupport)
  var age: Int
    get() = _age.getValue()
    set(value) { _age.setValue(value) }
    val _salary = ObservableProperty("salary", salary, changeSupport)
    var salary: Int
      get() = _salary.getValue()
      set(value) { _salary.setValue(value) }
}
// ObservableProperty를 프로퍼티 위임에 사용할 수 있도록 변경
class ObservableProperty(var propValue: Int, val changeSupport: PropertyChangeSupport) {
  operator fun getValue(p: Person, prop: KProperty<*>): Int = propValue
  operator fun setValue(p: Person, prop: KProperty<*>, newValue: Int) {
    val oldValue = propValue
    propValue = newValue
    changeSupport.firePropertyChange(prop.name, oldValue, newValue)
  }
}
// 위임 프로퍼티를 통해 프로퍼티 변경 통지 받기
class Person(val name: String, age: Int, salary: Int) : PropertyChangeAware() {
  var age: Int by ObservableProperty(age, changeSupport)
  var salary: Int by ObservableProperty(salary, changeSupport)
}
// Delegates.observable 사용해서 프로퍼티 변경 통지 구현
class Person(val name: String, age: Int, salary: Int) : PropertyChangeAware() {
  private val observer = {
    prop: KProperty<*>, oldValue: Int, newValue: Int -> changeSupport.firePropertyChange(prop.name, oldValue, newValue)
    var age: Int by Delegates.observable(age, observer)
    var salary: Int by Delegates.observable(salary, observer)
  }
}

위임 프로퍼티 컴파일 규칙

val x = c.prop / val x = <delegate>.getValue(c, <property>)
c.prop = x / <delegate>.setValue(c, <property>, x)

프로퍼티 값을 맵에 저장

class Person {
  // 추가 정보
  private val _attributes = hashMapOf<String, String>()
  fun setAttribute(attrName: String, value: String) {
    _attributes[attrName] = value
  }
  // 필수 정보
  val name: String
  get() = _attributes["name"]!!
}

위임 프로퍼티로 맵에 저장

class Person {
  private val _attributes = hashMapOf<String, String>()
  fun setAttribute(attrName: String, value: String) {
    _attributes[attrName] = value
  }
  val name: String by _attributes
}

프레임워크에서 위임 프로퍼티 활용

object Users : IdTable() {
  val name = varchar("name", length = 50).index()
  val age = integer("age")
}

class User(id: EntityID) : Entity(id) {
  var name: String by Users.name
  var age: Int by Users.age
}

'Back End > Kotlin' 카테고리의 다른 글

Kotlin - 제네릭스  (0) 2023.06.10
Kotlin - 고차 함수  (0) 2023.06.03
Kotlin - 타입 시스템  (0) 2023.05.06
Kotlin - 클래스, 객체, 인터페이스  (0) 2023.05.01
Kotlin - 람다 프로그래밍  (0) 2023.05.01
Comments