#1 메소드 오버로딩 (Method overloading)
class Calculator {
// 정수형 두 개를 더하는 메소드
fun add(x: Int, y: Int): Int {
return x + y
}
// 실수형 두 개를 더하는 메소드
fun add(x: Double, y: Double): Double {
return x + y
}
}
fun main() {
val calculator = Calculator()
// 정수형 덧셈
val result1 = calculator.add(3, 4)
println("Result of integer addition: $result1") // 출력 결과: 7
// 실수형 덧셈
val result2 = calculator.add(2.5, 3.5)
println("Result of double addition: $result2") // 출력 결과: 6.0
}
메소드의 선언부에 명시되는 매개변수의 리스트를 메소드 시그니처(Method signature)라고 한다. 메소드 오버로딩은 이름이 같지만 메소드 시그너처가 다른 메소드들을 각각 서로 다른 메소드로 취급하는 프로그래밍 기법이다.
#2 연산자 오버로딩 (Operator overloading)
#2-1 개념
메소드 오버로딩이 '메소드를 중복해서 정의'이라면, 연산자(Operator) 오버로딩은 '연산자를 중복해서 정의'다. 연산자란 예를 들면 -1, 2 + 2, 4* 7 등의 수식에 있는 연산자 기호들을 말한다.
메소드 오버로딩에서 메소드를 구분하는 핵심은 '메소드 시그니처'였다. 연산자 오버로딩에도 이른바 '연산자 시그니처'의 역할을 수행하는 무언가가 있을까? 그건 바로 피연산자(Operation)다. 예를 들어, 71 - 16에서 연산자는 마이너스 기호고 피연산자는 Int다. "구구" + "콘"에서 연산자는 플러스 기호고, 피연산자는 String이다.
#2-2 문법
// report card: 성적표
class ReportCard(val name: String, val score: Int) {
operator fun plus(increment: Int): ReportCard {
return ReportCard(name, score + increment)
}
// '연산자 오버로딩'을 '메소드 오버로딩'함
operator fun plus(reportCard: ReportCard): ReportCard {
return ReportCard(name + "와(과) " + reportCard.name, score + reportCard.score)
}
operator fun minus(decrement: Int): ReportCard {
return ReportCard(name, score - decrement)
}
// Kotlin 내 모든 클래스의 최상위 클래스인 Any 클래스의 메소드 equals()를 override
override operator fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as ReportCard
if (name != other.name) return false
if (score != other.score) return false
return true
}
override fun hashCode(): Int {
var result = name.hashCode()
result = 31 * result + score
return result
}
}
fun main() {
var s1 = ReportCard("철수", 100)
s1 = s1 - 50 + ReportCard("영희", 200)
if (s1 == ReportCard("철수와(과) 영희", 250)) {
println("${s1.name}의 점수는 ${s1.score}점") // 출력 결과: 철수와(과) 영희의 점수는 250점
}
}
인터페이스나 클래스에서, 함수 키워드인 fun 앞에 operator 키워드를 달아주면 된다. 단, operator는 몇몇 지정된 이름의 함수에만 붙일 수 있다. 말 그대로 연산자를 오버로딩하는 것이기 때문에, 프로그래머들 사이에서 명시적으로 합의된 연산자의 이름들을 정해두었다. 그 정해진 이름을 가진 앞에 앞에만 operator 키워드를 붙일 수 있다. 여담으로, Any.equal()을 override할 때는 Any.hashCode()도 같이 override해주어야 한다고 한다.
다음은 operator 키워드를 붙일 수 있는 함수의 이름과 그 역할이다.
#3 operator 키워드용 함수
#3-1 일반적인 함수
위 링크에서 #2-2의 코드와 같은 일반적이고 기본적인 operator용 함수 이름을 찾을 수 있다.
#3-2 invoke()
interface Calculator {
operator fun invoke(x: Int, y: Int): Int
operator fun invoke(x: Double, y: Double): Double
}
class AddCalculator : Calculator {
override operator fun invoke(x: Int, y: Int): Int {
return x + y
}
override operator fun invoke(x: Double, y: Double): Double {
return x + y
}
}
fun main() {
val calculator: Calculator = AddCalculator()
val resultInt = calculator(3, 5)
println("답은 $resultInt") // 답은 8
val resultDouble = calculator(3.5, 5.5)
println("답은 $resultDouble") // 답은 9.0
}
이와 같이 () 연산자의 동작도 override할 수 있다. 해당 동작에 해당하는 함수 이름은 영어로 '호출하다'라는 의미를 지닌 invoke다.
#3-3 get(), set()
class DataContainer {
private val data = mutableMapOf<String, Any>()
operator fun get(key: String): Any? {
return data[key]
}
operator fun set(key: String, value: Any) {
data[key] = value
}
}
fun main() {
val container = DataContainer()
// set() 연산자 오버로딩 사용
container["name"] = "철수"
container["age"] = 25
// get() 연산자 오버로딩 사용
val name = container["name"]
val age = container["age"]
println("이름은 $name(이)고 나이는 $age") // 출력 결과: 이름은 철수(이)고 나이는 25
}
Array의 어떤 원소에 접근할 때 쓰던 대괄호 기호도 연산자다. 그렇기에 연산자 오버로딩이 가능하다.
#3-4 getValue(), setValue()
getValue()와 setValue()는 위임 프로퍼티(Delegated properties) 전용 연산자다. 위 게시글에서 정리했다.
#4 요약
연산자 오버로딩으로 사용자 정의 클래스를 유연하고 우아하게 사용한다.
'깨알 개념 > Kotlin' 카테고리의 다른 글
[Kotlin] 람다(Lambda) 표현식 (0) | 2024.02.01 |
---|---|
[Kotlin] 함수 타입(Fuction types) 표현식 (0) | 2024.01.31 |
[Kotlin] 함수형 인터페이스 (Single Abstract Method Interface) (0) | 2024.01.30 |
[Kotlin] 프로퍼티(Property) (0) | 2024.01.17 |
[Kotlin] 확장 함수(Extension functions) (0) | 2023.12.16 |