Today I Learned

코틀린에서 구현하는 유창성 (1) 본문

Kotlin/다재다능 코틀린 프로그래밍

코틀린에서 구현하는 유창성 (1)

하이라이터 2023. 10. 13. 14:26
728x90

연산자 오버로딩

  • 코틀린에서는 +, -, * 같은 연산자를 오버로딩해서 숫자 타입 뿐만 아니라 객체에도 사용할 수 있다.
operator fun Pair<Int, Int>.plus(other: Pair<Int, Int>) = 
  Pair(first + other.first, second + other.second)
  • 연산자를 오버로딩하기 위해서 함수는 operator 키워드로 정의되어 있어야 한다.
  • 연산자와 대응하는 메소드 이름은 아래 표와 같다.
연산자 대응하는 메소드 주의사항
+x x.unaryPlus()  
-x x.unaryMinus()  
!x x.not()  
x + y x.plus(y)  
x - y x.minus(y)  
x * y x.times(y)  
x / y x.div(y)  
x % y x.rem(y)  
++x x.inc() x는 할당 가능해야 함
x++ x.inc() x는 할당 가능해야 함
--x x.dec() x는 할당 가능해야 함
x-- x.dec() x는 할당 가능해야 함
x == y x.equals(y)  
x != y !(x.equals(y))  
x < y x.compareTo(y) <=, >, >=도 사용 가능
x[i] x.get(i)  
x[i] = y x.set(i , y)  
y in x x.contains(y) !in으로도 사용 가능
x..y x.rangeTo(y)  
x() x.invoke()  
x(y) x.invoke(y)  

 

확장 함수와 속성으 이용한 인젝팅

확장 함수를 이용한 메소드 인젝팅

data class Point(val x: Int, val y: Int)
data class Circle(val cx: Int, val cy: Int, val radius: Int)
  • 점이 원 안에 있는지 찾는 기능을 구현하기 위해서 위의 두 클래스를 활용해보자.
fun Circle.contains(point: Point) = 
  (point.x - cx) * (point.x - cx) + (point.y - cy) * (point.y - cy) < 
    radius * radius
  • contains() 확장 함수 안에서 암시적으로 Circle 클래스의 인스터스 멤버에 접근한다.
  • 이 확장 함수가 클래스 내부의 인스턴스 메소드로 정의되었을 때와 동일하게 접근하는 것이다.
  • 이 메소드를 Circle 클래스 내부에 작성할 경우 차이점은 fun Circle.contains(point: Point) 대신에 fun contains(point: Point)로 정의하는 것 뿐이다.
val circle = Circle(100, 100, 25)
val point1 = Point(110, 110)
val point2 = Point(10, 100)
  • 확장 함수가 인스턴스 메소드와 같은 이름을 가지고 있어서 충돌이 발생하면, 항상 인스턴스 메소드가 실행된다.
  • 인스턴스의 캡슐화된 부분에 접근할 수 있는 인스턴스 메소드와 다르게 확장 함수는 정의된 패키지 안에서 보이는 부분(public)에만 접근 가능하다.

 

확장 함수를 이용한 연산자 인젝팅

  • 확장 함수는 연산자도 될 수 있다. contains() 메소드와 매핑되는 in 연산자를 사용해보자.
operator fun Circle.contains(point: Point) = 
  (point.x - cx) * (point.x - cx) + (point.y - cy) * (point.y - cy) < 
    radius * radius

println(circle.contains(point1)) //true
println(point1 in circle) //true
println(point2 in circle) //false

 

확장 속성을 이용한 속성 인젝팅

  • 마찬가지로 확장 속성도 추가할 수 있다.
  • 확장 속성은 클래스 내부에 존재하는 것이 아니기 때문에 백킹 필드를 가질 수 없다. 즉, field에 접근할 수 없다.
val Circle.area: Double
  get() = kotlin.math.PI * radius * radius
  
  val circle = Circle(100, 100, 25)
  println("Area is ${circle.area}") //1963.49...

 

서드파티 클래스 인젝팅

fun String.isPalindrome(): Boolean {
  return reversed() == this
}
  • isPalindrome() 메소드는 코틀린의 확장 함수인 reversed()를 이용해서 현재 주어진 문자열이 palindrome인지 결정한다. 코드블록을 정의하는 대신, 이미 존재하는 메소드를 호출하는 단일 표현식을 사용할 수도 있다.
fun String.shout() = toUpperCase()
val str = "dad"
println(str.isPalindrome()) //true
println(str.shout()) //DAD

 

Static 메소드 인젝팅

  • 클래스의 컴패니언 객체를 확장해서 static 메소드를 인젝팅할 수 있다.
fun String.Companion.toURL(link: String) = java.net.URL(link)

 

클래스 내부에서 인젝팅

  • 확장 함수를 클래스 안에서 만든다면 해당 확장 함수는 해당 클래스와 해당 클래스의 이너 클래스에서만 볼 수 있다.
  • 또한 확장 함수 안에는 2개의 리시버가 있다.
class Point(x: Int, y: Int) {
  private val pair = Pair(x, y)

  private val firstsign = if (pair.first < 0) "" else "+"
  private val secondsign = if (pair.second < 0) "" else "+"

  override fun toString() = pair.point2String()
  fun Pair<Int, Int>.point2String() =
    "(${firstsign}${first}, ${this@Point.secondsign}${this.second})"
}

println(Point(1, -3)) //(+1, -3)
println(Point(-3, 4)) //(-3, +4)
  • Point 클래스에서 Pair<Int, Int>로 확장 함수를 인젝트했다. 그렇기 때문에 클래스 외부에서 Pair<Int, Int>의 확장 함수를 사용하려고 하면 컴파일 에러가 난다.
  • 확장 함수가 클래스 내부에 생성되었기 때문에 확장 함수에는 this와 this@Point 두개의 리시버를 가지고 있다.
  • 익스텐션 리시버는 확장 함수가 실행되는 객체이다.
  • 디스패치 리시버는 확장 함수를 만들어 추가한 클래스의 인스턴스이다.
728x90
Comments