안녕하세요. 오늘은 코틀린의 연산자 오버로딩에 대해서 알아보도록 하겠습니다.
연산자 오버로딩
Java, javascript 만 사용하여 코딩을 하셨던 분들은 연산자 오버로딩이라는 개념이 생소하실 수 있습니다. 만약 C++, C#, python 등의 언어를 사용해 본적이 있으시다면 해당 언어에서도 연산자 오버로딩을 제공하기 때문에 알고 있으실 수 도 있습니다.
연산자 오버로딩이란 위키에서 객체 지향 프로그래밍에서 다형성의 특별한 경우로 다른 연산자들이 함수 연사자를 통해서 구현을 할 때를 말한다. 라고 정의되어 있습니다. 이 말인 즉슨 +, - 등 과 같은 연산자가 어떤 값과 함께 사용하느냐에 따라서 다르게 동작할 수 있도록 그것을 커스터마이징 할 수 있다는 의미입니다.
java 에서도 연산자 오버로딩은 아니지만 같은 연산자에서 다르게 동작하는 예제가 있습니다. 아래코드를 보시겠습니다. String
을 +
연산자를 이용하면 내부에서 StringBuilder
를 이용하여 String
을 이어줍니다. 그리고 int를 +
를 이용하면 내부에서 값을 더하기만 합나디.
String a = "sabarada is";
String b = "karol";
String result = a + " " + b // sabarada is karol
int c = 1
int d = 2
int result_2 = c + d // 3
코틀린에서의 연산자 오버로딩
코틀린은 이 뿐만 아니라 연산자 오버로딩이 가능하기 때문에 아래와 같은 방식이 가능합니다. 아래 코드 처럼 Price 라는 커스텀 객체를 만들고 이를 +
이어줄 수 있습니다. 연산자 오버로딩을 구현하기 위해서는 operator
라는 키워드를 이용합니다. 그리고 코틀린에 이미 정해져 있는 함수를 새롭게 정의하면 됩니다.
data class Price(val value: Int) {
operator fun plus(b: Price): Price {
return Price(value + b.value)
}
}
val a: Price = Price(10)
val b: Price = Price(50)
val result: Price = a + b // 결과 Price(60)
예시
이미 예약되어있는 함수 이름을 이용하여 연산자 오버로딩이 가능합니다. 아래에서 그 리스트를 예제와 함께 보도록 합시다. 오늘은 아래 코드를 예제로 하도록 하겠습니다.
data class Price(val value: Int)
단항 연산자(Unary operations)
1개의 값을 올리고 내리는 연산자(operator)의 대표적인 예로는 ++
또는 --
가 있습니다. 일반적인 ++
은 값을 1 올리는 연산자이며 --
는 1 내리는 연산자입니다. ++
는 inc
함수이며 --
는 dec
함수로 오버라이딩하여 커스텀이 가능합니다. 아래 코드는 연산자 오버로딩을 사용해서 커스텀 한 후 사용한 코드입니다.
data class Price(val value: Int) {
operator fun inc(): Price {
return Price(value + 100)
}
operator fun dec(): Price {
return Price(value - 100)
}
}
var a = Price(500)
println("value = ${a++}") // 결과 : Price(value=500)
println("value = ${a}") // 결과 : Price(value=600)
println("value = ${++a}") // 결과 : Price(value=700)
println("value = ${a--}") // 결과 : Price(value=700)
println("value = ${a}") // 결과 : Price(value=600)
println("value = ${--a}") // 결과 : Price(value=500)
사칙 연산
더하기, 빼기, 곱하기, 나누기에 더해서 나머지, 그리고 +=
, -=
연산자를 한번 보도록 하겠습니다. 여기서 참고하실 점은 +
를 오버라이딩한다고 하여 +=
도 동일한 연산을 하는 것은 아니라는 것입니다. 사칙연산에 대응되는 연산자 함수는 아래와 같습니다. 연산자 오버라이딩을 위해서는 아래의 표처럼 대응되는 각 함수를 오버라이딩하여 사용하시면 됩니다.
표현 | 함수 |
---|---|
a + b | a.plus(b) |
a - b | a.minus(b) |
a * b | a.times(b) |
a / b | a.div(b) |
a % b | a.rem(b) |
a += b | a.plusAssign(b) |
a -= b | a.minusAssign(b) |
a *= b | a.timesAssign(b) |
a /= b | a.divAssign(b) |
a %= b | a.remAssign(b) |
그렇다면 몇가지 예시를 보도록 하겠습니다. 대표적인 예로 +
, %
그리고 +=
를 한번 코드로 나타내보도록 하겠습니다. 아래 코드는 +
연산자를 나타내는 plus 함수를 연산자 오버로딩한 후 사요안 것입니다. Price 끼리 +
하여 새로운 Price가 생성되는 것을 확인할 수 있습니다.
data class Price(val value: Int) {
operator fun plus(b: Price): Price {
return Price(value + b.value)
}
}
val a: Price = Price(10)
val b: Price = Price(50)
val result: Price = a + b // result : Price(60)
나머지 연산 또한 마찬가지입니다. rem 이라는 함수를 재정의하여 새로운 형태의 나머지 연산을 만들어내었습니다.
data class Price(val value: Int) {
operator fun rem(price: Price): Price {
return Price(value + price.value)
}
}
val a = Price(500)
val b = Price(499)
println("rem = ${a % b}") // 결과 : rem = Price(1)
+=
연산은 이후에 나오는 값을 기존의 값에 더하는 연산입니다. 이 연산은 plusAssign
함수를 오버로딩하여 커스텀 할 수 있습니다.
data class Price(var value: Int) {
operator fun plusAssign(value: Int) {
this.value += value
}
}
val a = Price(500)
val b = Price(499)
a += b
println("a = $a") // 결과 : a = Price(999)
get / set
get과 set 또한 중요한 연산자 오버로딩이 가능합니다. 연산자 오버로딩을 통해서 내부의 값을 조금 더 가독성 좋고 짧게 호출하거나 세팅하는 것이 가능합니다. 아래코드는 내부의 값을 가져오는 코드입니다. 아래처럼 get을 operator로 정의하면 []
연선자롤 통해 가져올 수 있게됩니다.
data class Price(var value: Int) {
operator fun get(num: Int): Int {
return value
}
}
val a = Price(500)
println("a_price = ${a[100/* dummy 숫자 */]}") // 결과 : a = 500
set
함수를 정의하면 []
연산자를 통해 값을 새로 대입할 수 있습니다. 내부에 만약 collection이 있다면 좀 더 간단하게 사용할 수 있음을 알 수 있습니다.
data class Price(var value: Int) {
operator fun set(num: Int, value: Int) {
this.value = value
}
}
val a = Price(500)
a[100] = 10
println("a_price = ${a[100/* dummy 숫자 */]}") // 결과 : a = 10
생성자
마지막으로 알아볼 연산자 오버로딩은 생성자입니다. invoke
함수는 ()
연산자를 나타냅니다. 즉 아래의 코드는 Price()
를 호출하는 것이 operator fun invoke(): Price
연산자를 호출하는 결과를 가져올 수 있는 것입니다. 이를 이용하여 factory method
를 추가적인 메서드 없이 사용할 수 있는 것처럼 구현할 수 있습니다.
data class Price private constructor(val value: Int) {
companion object {
operator fun invoke(): Price {
return Price(100)
}
operator fun invoke(value: Int): Price {
return Price(value)
}
}
}
val a = Price(500)
println("a = $a") // 결과 : a = Price(500)
val b = Price()
println("b = $b") // 결과 : b = Price(100)
마무리
오늘은 이렇게 코틀린에서의 연산자 오버로딩에 대해서 알아보는 시간을 가져보았습니다.
알아본 연산자 이외에도 범위를 나타내는 ..
, in
등이 있습니다.
이 부분에 대해서는 커스터 마이징을 해보려고 했으나 자료가 부족하여 진행하지 못했습니다.
다음에 기회가 되면 다시한번 도전하여 별도의 포스팅으로 찾아뵙고자 합니다.
감사합니다.
참조
'language, framework, library > kotlin' 카테고리의 다른 글
[kotlin + Spring] 코틀린 환경에서 Spring Boot 사용하기 (0) | 2021.07.25 |
---|---|
[kotlin] 코틀린 차곡차곡 - 10. 인라인(inline) 함수와 reified 키워드 (3) | 2021.07.14 |
[kotlin] 코틀린 차곡차곡 - 8. Scope Function (0) | 2021.06.28 |
[kotlin] 코틀린 차곡차곡 - 7. NPE(NullPointException) 안전하게 코딩하기 (2) | 2021.06.20 |
[kotlin] 코틀린 차곡차곡 - 6. 클래스 - static(companion)과 접근 제어자 (1) | 2021.06.19 |
댓글