연산자 오버로딩
operator
키워드를 붙여야 한다.
- 연산자를 멤버함수로 정의할 수 있다.
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)
println(p1.plus(p2))
}
- 연산자를 확장함수로 정의할 수 있다.
- 연산자를 정의할 때는 관례(Convention)을 따르는 이름의 확장 함수로 구현하는게 일반적인 패턴이다.
data class Point(val x: Int, val y: Int)
operator fun Point.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)
println(p1.plus(p2))
}
오버로딩 가능한 이항 산술 연산자 (Binary Operator)
식 | 함수 이름 |
---|
a * b | times |
a / b | div |
a % b | mod (1.1부터 rem ) |
a + b | plus |
a - b | minus |
- 연산자 우선순위는 언제나 표준 숫자 타입에 대한 연산자 우선순위가 같다.
두 피연산자가 같은 타입일 필요는 없다.
data class Point(val x: Int, val y: Int)
operator fun Point.times(scale: Double): Point {
return Point((x * scale).toInt(), (y * scale).toInt())
}
fun main(args: Array<String>) {
val p = Point(10, 20)
println(p * 1.5)
}
연산자 함수의 반환 타입이 두 피연산자의 타입과 달라도 된다.
operator fun Char.times(count: Int): String {
return toString().repeat(count)
}
fun main(args: Array<String>) {
println('a' * 3)
}
- 일반 함수와 마찬가지로
operatpor
함수도 오버로딩할 수 있다.
비트 연산자에 대해 특별한 연산자 함수를 지원하지 않는다.
Kotlin 중위 연산자 | Java 연산자 |
---|
shl | 왼쪽 시프트 << |
shr | 오른쪽 시프트 >> |
ushr | 오른쪽 시프트 >>> |
and | 비트 곱 & |
or | 비트 합 ` |
xor | 비트 배타 합 ^ |
inv | 비트 반전 ~ |
fun main(args: Array<String>) {
println(0x0F and 0xF0)
println(0x0F or 0xF0)
println(0x1 shl 4)
println(0b101010.inv())
}
복합 대입 연산자 오버로딩
Compound Assignment Operator
a += b
// 1. plus
a = a.plus(b)
// 2. plusAssign
a.plusAssign(b)
- `+=`는 `plus`와 `plusAssign` 양쪽으로 컴파일할 수 있다.
- `plus`, `plusAssign` 동시에 정의하지 말라.
```kotlin
fun main(args: Array<String>) {
val list = arrayListOf(1, 2)
list += 3 // plusAssign
val newList = list + listOf(4, 5)
println(list)
println(newList)
}
단항 연산 오버로딩 (Unary Operator)
식 | 함수 이름 |
---|
+a | unaryPlus |
-a | unaryMinus |
!a | not |
++a , a++ | inc |
--a , a-- | dec |
data class Point(val x: Int, val y: Int)
operator fun Point.unaryMinus(): Point {
return Point(-x, -y)
}
fun main(args: Array<String>) {
val p = Point(10, 20)
println(-p)
}
operator fun BigDecimal.inc() = this + BigDecimal.ONE
fun main(args: Array<String>) {
var bd = BigDecimal.ZERO
println(bd++)
println(++bd)
}
비교 연산자 오버로딩
동등성 연산자 equals
a == b
--> a?.equals(b) ?: (b == null)
- 식별자 비교 연산자
===
의 경우 오버로딩 할 수 없다.
Any
에 이미 operator
로 정의된 메서드
override
만 명시하면 된다.
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
}
}
fun main(args: Array<String>) {
println(Point(10, 20) == Point(10, 20))
println(Point(10, 20) != Point(5, 5))
println(null == Point(1, 2))
}
순서 연산자 compareTo
a >= b
--> a.compareTo(b) >= 0
- Comparable
인터페이스에 operator
로 정의 된 메서드
- override
만 명시하면 된다.
class Person(
val firstName: String, val lastName: String
) : Comparable<Person> {
override fun compareTo(other: Person): Int {
return compareValuesBy(this, other,
Person::lastName, Person::firstName)
}
}
fun main(args: Array<String>) {
val p1 = Person("Alice", "Smith")
val p2 = Person("Bob", "Johnson")
println(p1 < p2)
}
컬렉션과 범위에 대해 쓸수 있는 Convention
Convention | 함수 이름 |
---|
x[a, b] | x.get(a, b) |
x[a, b] = c | x.set(a, b, c) |
a in list | list.contains(a) |
start..endInclusive | start.rangeTo(endIclusive) |
for (x in a..b) | (a..b).iterator() |
인덱스로 원소에 접근 get
, set
x[a, b]
--> x.get(a, b)
x[a, b] = c
--> x.set(a, b, c)
data class Point(val x: Int, val 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]}, ${p[1]}")
println(p[2])
}
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")
}
}
fun main(args: Array<String>) {
val p = MutablePoint(10, 20)
p[1] = 42
println(p)
}
in
관례
data class Point(val x: Int, val y: Int)
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))
println(Point(20, 30) in rect)
println(Point(5, 5) in rect)
println(Point(15, 50) in rect)
}
rangeTo
관례
start..end
--> start.rangeTo(end)
Comparable
인터페이스를 구현했다면, rangeTo
를 정의할 필요가 없다.
- Kotlin 표준 라이브러리에는 모든
Comparable
객체에 대해 rangeTo
함수가 정의되어 있다.
- Comparable에 대한 확장함수로 정의되어 있다.
operator fun <T: Comparable<T>> T.rangeTo(that: T): ClosedRange<T>
- rangeTo 연산자
..
는 다른 산술 연산자보다 우선순위가 낮다.
- 하지만 혼동을 피하기 위해 과라호로 인자를 감싸주는걸 추천한다.
for loop를 위한 iterator
관례
for (x in a..b) {
}
val it = (a..b).iterator()
while (it.hasNext()) {
val x = it.next()
}
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) }
}