Kotlin 연산자 Overloading
개인 광고 영역
Kotlin에서 기본으로 제공하는 산술 연산자 plus, minus
등을 +, -
로 접근한다. 이러한 기법을 Kotlin에서는 Convention이라고 한다.
이번 글에서는 이러한 Convention을 확장하여 사용할 수 있는 Kotlin의 기법을 살펴보려고 한다. 대부분 산술 연산자이며, List와 Map에 접근할 때 사용하는 []
등에 대해서 살펴본다.
우선 아래의 표를 기준으로 Overloading
이 가능한데 산술 연산자와 단항 산술 연산자이다.
Function | code |
---|---|
plus | a + b |
minus | a - b |
div | a / b |
rem | a % b |
times | a * b |
not | !a |
unaryPlus | +a |
unaryMinus | -a |
inc | ++a, a++ |
dec | –a, a– |
연산자 Overloading
연산자를 Overloading
할 수 있는 방법은 아주 간단하다.
위의 산술 연산자 표 정의 function 중에 operator
키워드 만 추가하면 확장이 가능한데 아래와 같다.
operator fun Int.plus(b: Int) = this + b
단순하게 위와 같이 확장하는 게 가능하지만 아래 이미지와 같이 custom function을 정의하는 경우 오류가 발생한다.
또한 plus
을 확장하는 과정에서 원 함수에 있던 파라메터를 누락하는 경우에도 오류가 발생한다.
operator
을 이용해서 아래와 같은 확장이 가능한데 Int와 Any
을 받아 String
으로 합쳐 리턴하는 것도 가능하다.
@Test
fun test() {
operator fun Int.plus(b: Any): String {
return "$this $b"
}
println(10 + "ABC")
}
// 10 ABC
또는 return 없이 Unit 형태로 사용하는것도 가능하다.
@Test
fun test() {
operator fun Int.plus(b: Any) {
println("$this $b")
}
10 + "ABC"
}
// 10 ABC
산술 연산자 확장하기
operator
을 사용하여 산술 연산자 plus
확장하고, 이를 Convention 정의에 따라 +
접근할 수 있음을 확인하였다.
아래 Position data class에 plus 구현해보았다.
data class Position(val a: Int, val b: Int) {
operator fun plus(item: Position): Position {
return Position(a + item.a, b + item.b)
}
}
class ExampleUnitTest {
@Test
fun test() {
val positionOne = Position(1, 2)
val positionTwo = Position(3, 4)
println(positionOne + positionTwo)
}
}
위와 같은 샘플인데 출력하면 Position(a=4, b=6)
의 결과를 확인할 수 있다.
그 외 기본 산술 연산자도 위와 같이 확장이 가능하다.
단항 산술 연산자
이번엔 단항(++, --, +, -
) 산술 연산자의 확장이 가능한데 아래와 같이 구현할 수 있다.
먼저 -
인 unaryMinus
을 아래와 같이 확장할 수 있다.
아래 샘플에서 1, 2
를 마이너스로 변경하고, 하나는 -20, -10
을 다시 마이너스로 변경하여 +
로 변경하는 샘플이다.
class ExampleUnitTest {
@Test
fun test() {
println(-Position(1, 2))
println(-Position(-20, -10))
}
}
data class Position(var a: Int, var b: Int) {
operator fun unaryMinus(): Position {
return Position(-a, -b)
}
}
// Position(a=-1, b=-2)
// Position(a=20, b=10)
이번에는 ++(inc)
을 이용하여 플러스하는 샘플인데, 이번엔 ++의 위치에 따라 더해지는 시점을 함께 확인할 수 있다.
operator
의 코드를 보면 a.inc()
함수로 접근함을 확인할 수 있다.
class ExampleUnitTest {
@Test
fun test() {
var position = Position(1, 2)
println(position++)
println(++position)
}
}
data class Position(var a: Int, var b: Int) {
operator fun inc(): Position {
return Position(a.inc(), b.inc())
}
}
위의 결과는 아래와 같이 확인 가능하다.
Position(a=1, b=2)
Position(a=3, b=4)
Collection get/set 확장하기
Collection의 list/map에서는 []
을 통해 get/set을 접근할 수 있다.
class ExampleUnitTest {
@Test
fun test() {
val list = mutableListOf("A", "B", "C")
println(list)
list[0] = "D"
println(list)
val map = mutableMapOf(0 to "A", 1 to "B", 2 to "C")
println(map)
map[0] = "D"
println(map)
}
}
이러한 []
을 확장하여, Collection
이 아닌 곳에서도 사용 가능하게 만들 수 있는데 set
을 확장하여 아래와 같은 코드를 만들어 볼 수 있다.
그러면 [0]
을 통해 값을 변경하고, get 대신 [0]
의 값을 가져올 수 있다.
class ExampleUnitTest {
@Test
fun test() {
val position = Position(10, 20)
println(position)
position[0] = 30
println(position)
println(position[1])
}
}
data class Position(var a: Int, var b: Int) {
operator fun set(position: Int, value: Int) {
when (position) {
0 -> a = value
1 -> b = value
else -> throw IndexOutOfBoundsException("Invalid coordinate $position")
}
}
operator fun get(position: Int): Int = when (position) {
0 -> a
1 -> b
else -> throw IndexOutOfBoundsException("Invalid coordinate $position")
}
}
Destructuring Declarations(분할)
kotlin의 특징 중 하나인 Destructuring Declarations이다.
val position = Position(10, 20)
으로 선언하고, println(position.a)
, println(position.b)
로 접근하는 게 아닌 아래와 같은 접근이 가능하다.
val (a, b) = Position(10, 20)
println(a)
println(b)
a, b
의 변수에 바로 접근할 수 있도록 만드는 것인데 kotlin 디컴파일을 통해 아래와 같은 코드를 확인해볼 수 있다.
@Test
public final void test() {
Position var3 = new Position(10, 20);
int a = var3.component1();
int b = var3.component2();
System.out.println(a);
System.out.println(b);
}
이러한 접근 방법은 최대 componentN
까지 제공한다.
map에서는 key/value
을 한 번에 아래와 같이 받는 것도 가능하다.
val map = mutableMapOf(0 to "A", 1 to "B", 2 to "C")
for ((key, value) in map) {
println("$key to $value")
}
forEach에서는?
추가로 forEach
문을 제공하는데, Java 1.8의 기법과 kotlin의 접근 방법 2가지가 있다.
Java 1.8 버전을 사용하는 경우에는 아래와 같이 forEach
문 작성이 가능하다.
val map = mutableMapOf(0 to "A", 1 to "B", 2 to "C")
map.forEach { t, u ->
// ...
}
그리고 Java 1.7 이하의 경우 Kotlin에서 아래와 같이 접근해야 한다.
val map = mutableMapOf(0 to "A", 1 to "B", 2 to "C")
map.forEach { (key, value) ->
// ...
}
split에서 사용하기
split에서 아래와 같이 사용하는 게 가능한데, split의 결과를 (fileName, extension)
으로 정의하고, 데이터 클래스를 초기화한 리턴을 받을 수 있다.
class ExampleUnitTest {
@Test
fun test() {
println("ABC.md".split())
}
}
data class DataName(val fileName: String, val extension: String)
fun String.split(): DataName {
val (fileName, extension) = split(".", limit = 2)
return DataName(fileName, extension)
}
마무리
여기까지 kotlin에서 제공하는 Convention
의 확장과 Destructuring Declarations(분할)
을 살펴보았다.
Destructuring Declarations(분할)
의 경우 이미 익숙하게 사용하고 있었지만, Convention
의 확장은 처음 알아보았다.
아무래도 클래스끼리 합치는 부분에서 자주 사용할 수 있을 것 같다.
Comments