관리 메뉴

너와 나의 스토리

[Kotlin]  연산자 오버로딩 본문

Programming Language/Kotlin

[Kotlin]  연산자 오버로딩

노는게제일좋아! 2021. 6. 22. 09:04
반응형

연산자 오버로딩

  • 자바에는 표준 라이브러리와 밀접하게 연관된 언어 기능이 몇 가지 있다.
  • 예를 들어 for ... in 루프에 java.lang.Iterable을 구현한 객체를 사용할 수 있다.
  • 코틀린에서는 이러한 언어 기능이 어떤 타입(클래스)과 연관되기보다는 특정 함수 이름과 연관된다.
  • 예를 들어 어떤 클래스 안에 plus라는 이름의 특별한 메서드를 정의하면 그 클래스의 인스턴스에 대해 + 연산자를 사용할 수 있다.
  • 이런 식으로 어떤 언어 기능과 미리 정해진 이름의 함수를 연결해주는 기법을 코틀린에서는 관례(convention)라고 부른다.

 

예: Point 클래스

data class Point(val x: Int, val y: Int)

 

산술 연산자 오버로딩

이항 산술 연산 오버로딩

  • Point에서 지원하고픈 첫 번째 연산은 두 점을 더하는 연산이다. 이 연산은 두 점의 X 좌표와 Y 좌표를 각각 더한다.
    • data class Point(val x: Int, val y: Int) {
          operator fun plus(other: Point): Point {
              return Point(x + other.x, y + other.y)
          }
      }
      
      fun main(args: Array<String>) {
          val p1 = Point(10, 20)
          val p2 = Point(30, 40)
          println(p1 + p2) // output: Point(x=40, y=60)
      }
       
    • plus 함수 앞에 operator 키워드를 붙여야 한다.
    • operator 키워드를 붙임으로써 어떤 함수가 관례를 따르는 함수임을 명확히 할 수 있다.
  • 연산자를 멤버 함수로 만드는 대신 확장 함수로 정의할 수도 있다.
    • data class Point(val x: Int, val y: Int)
      
      operator fun Point.plus(other: Point): Point {
          return Point(x + other.x, y + other.y)
      }

 

코틀린은 교환 법칙을 지원하지 않는다.

operator fun Point.times(scale: Double): Point {
    return Point(x * scale, y * scale)
}

fun main(args: Array<String>) {
    val p1 = Point(10.0, 20.0)
    println(p1*0.1)
//    println(0.1*p1) <- error
}
  • (0.1*p1)을 하기 위해서는 다음과 같이 연산자 함수를 추가로 작성해줘야 한다.
operator fun Double.times(p: Point): Point {
    return Point(toDouble() * p.x, toDouble()*p.y)
}

 

오버로딩 가능한 이항 산술 연산자

함수 이름
a + b times
a / b  div
a % b  mod (1.1부터 rem)
a + b  plus
a - b  minus

 

 

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

인덱스로 원소에 접근: get과 set 연산자

  • get 관례
    • Point의 x좌표는 인덱스 0, y 좌표는 인덱스 1로 생각하고 접근한다.
data class Point(var x: Int, var y: Int)

operator fun Point.get(index: Int): Int {
    return when (index) {
        0 -> x
        1 -> y
        else -> throw IndexOutOfBoundsException("Invalid coordinate $index")
    }
}

fun main(args: Array<String>) {
    val p = Point(10,20)
    println(p[0]) // output: 10 (x 좌표)
    println(p[1]) // output: 20 (y 좌표)
}
  • set 관례
operator fun Point.set(index: Int, value: Int){
    when(index){
        0 -> x = value
        1 -> y = value
        else -> throw IndexOutOfBoundsException("Invalid coordinate $index")
    }
}

fun main(args: Array<String>) {
    val p = Point(10,20)
    p[1]=30
    println(p[0]) // output: 10 (x 좌표)
    println(p[1]) // output: 20 (y 좌표)
}

 

in 관례

  • in은 객체가 컬렉션에 들어있는지 검사한다.
    • a in x unitl y
      • a가 [x,y] 범위에 속하는가? True or False
  • 이러한 in 연산자와 대응하는 함수는 contains다.
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
}

fun main(args: Array<String>) {
    val rect = Rectangle(Point(10, 20), Point(50, 50))
    val p1 = Point(10, 20)
    val p2 = Point(5, 5)
    println(p1 in rect) // output: true
    println(p2 in rect) // output: false
}

 

 

rangeTo 관례

  • .. 연산자는 범위를 만들 때 사용된다.
    • 이는 rangeTo 함수와 대응된다.
val now = LocalDate.now()
val vacation = now..now.plusDays(10)
val vacation2 = now.rangeTo(now.plusDays(10))
println(now.plusWeeks(1) in vacation) // output: true
println(now.plusWeeks(1) in vacation2) // output: true

 

 

for 루프를 위한 iterator 관례

  • for (x in list) {...}는 list.iterator()를 호출해서 이터레이터를 얻은 다음, 그 이터레이터에 대해 hasNext와 next 호출을 반복하는 식으로 변환된다.
  • 코틀린에서는 이 또한 iterator 메서드를 확장 함수로 정의할 수 있다.
  • 예: 날짜 범위에 대한 이터레이터 구현
    • operator fun ClosedRange<LocalDate>.iterator(): Iterator<LocalDate> =
          object : Iterator<LocalDate> {
              var current = start
              override fun hasNext() = current <= endInclusive
              override fun next() = current.apply {
                  current = plusDays(1)
              }
          }
      
      fun main(args: Array<String>) {
          val newYear = LocalDate.ofYearDay(2017, 1)
          val daysOff = newYear.minusDays(1)..newYear
          for (dayOff in daysOff) {
              println(dayOff)
          }
          /* output:
              2016-12-31
              2017-01-01
          * */
      }

 

 

구조 분해 선언(destructuring declaration)과 component 함수

  • 구조 분해를 사용하면 복합적인 값을 분해해서 여러 다른 변수를 한꺼번에 초기화할 수 있다.
  • 구조 분해 사용 방법:
    • val p = Point(10, 20)
      val (x, y) = p
      println(x) // output: 10
      println(y) // output: 20
  • 구조 분해 선언은 .componentN으로 변환된다.
    • 즉, x의 경우 p.component1(), y의 경우 p.component2() 함수를 호출한 것이다.
  • 맵의 원소에 대해 이터레이션
    • fun printEntries(map: Map<String, String>) {
          for ((key, value) in map) {
              println("$key -> $value")
          }
      }

 

 

 

 

 

출처:

- [Kotlin IN ACTION]

 

 

반응형
Comments