# 7장 연산자 오버로딩과 기타 관례
### 이번장에서는...
#### 배운점, 느낀점
- 연산자 오버로딩
- 관례: 여러 연산을 지원하기 위해 특별한 이름이 붙은 메서드
- 위임 프로퍼티
---
- 표준 라이브러리와 밀접하게 연관된 언어 기능
- for ...in 루프 : java.lang.Iterable
- try문(try-with-resource statement) : java.lang.AutoCloseable
- 어떤 언어 기능과 미리 정해진 이름의 함수를 연결해주는 기법을 코틀린에서는 관례라고 부른다
- 코틀린은 어떤 클래스 안에 plus라는 이름의 특별한 메서드를 정의하면 그 클래스 인스턴스에 대해 + 연산자를 사용할 수 있다.
- 언어 기능을 타입에 의존하는 자바와 달리 코틀린은(함수 이름을 통한)관례에 의존한다.
#### 산술 연산자 오버로딩
- 코틀린에서 관례를 사용하는 가장 단순한 예는 산술 연산자다.
#### 이항 산술 연산 오버로딩
```kotlin
data class Point(val x: Int, val y: Int) {
// operator fun plus(other: Point): Point { // "plus"라는 이름의 연산자 함수를 정의한다.
// return Point(x + other.x, y + other.y) // 좌표를 성분별로 더한 새로운 점을 반환한다.
// }
}
operator fun Point.times(scale: Double): Point {
return Point((x * scale).toInt(), (y * scale).toInt())
}
operator fun Point.plus(other: Point): Point {
return Point(x + other.x, y + other.y)
}
operator fun Point.unaryMinus(): Point { // 단항 Minus(음수) 함수는 파라미터가 없다.
return Point(-x, -y) // 좌표에서 각 성분의 음수를 취한 새점을 반환한다.
}
fun main(args: Array<String>) {
val p1 = Point(10, 20)
val p2 = Point(30, 40)
println(p1 + p2) // + 로 계산하면 "plus" 함수가 호출된다.
val p = Point(10, 20)
println(p * 1.5)
println(-p)
}
operator modifier is required...
(operator 변경자를 추가해야
함) 오류를 통해 이름이 겹쳤다는 사실을 알고 문제를 해결할 수 있다,operator fun Point.plus(other: Point): Point {
return Point(x + other.x, y + other.y)
}
식 | 함수 이름 |
---|---|
a * b | times |
a / b | div |
a % b | mod(1.1 부터 rem) |
a + b | plus |
a - b | minus |
operator fun Char.times(count: Int): String {
return toString().repeat(count)
}
fun main(args: Array<String>) {
println('a' * 3)
}
일반 함수와 마찬가지로 operator 함수도 오버로딩할 수 있다. 따라서 이름은 같지만 파라미터 타입이 서로 다른 연산자 함수를 여럿 만들 수 있다.
비트 연산자에 대해 특별한 연산자 함수를 사용하지 않는다.
val number = ArrayList<Int>()
numbre += 42
println(number[0])
// 42
operator fun <T> MutableCollection<T>.plusAssign(element: T) {
this.add(element)
}
val list = arrayListOf(1, 2)
list += 3 // += 는 list를 변경한다.
val newList = list + listOf(4, 5) // +는 두 리스트의 모든 원소를 포함하는 새로운 리스트를 반환한다.
println(list)
// [1, 2, 3]
println(newList)
// [1, 2, 3, 4, 5]
operator fun Point.unaryMinus(): Point { // 단항 Minus(음수) 함수는 파라미터가 없다.
return Point(-x, -y) // 좌표에서 각 성분의 음수를 취한 새점을 반환한다.
}
식 | 함수 이름 |
---|---|
+a | unaryPlus |
-a | unaryMinus |
!a | not |
++a , a++ | inc |
--a, a-- | dec |
operator fun BigDecimal.inc() = this + BigDecimal.ONE
fun main(args: Array<String>) {
var bd = BigDecimal.ZERO
println(bd++) // 후위 증가 연산자는 println이 실행된 다음에 값을 증가시킨다
println(++bd) // 전위 층가 연산자는 println이 실행되기 전에 값을 증가시킨다.
}
class Point(val x: Int, val y: Int) {
override fun equals(obj: Any?): Boolean { // Any에 정의된 메서드를 오버리딩 한다.
if (obj === this) return true // 최적화: 파라미터가 this와 같은 객체인지 살펴본다
if (obj !is Point) return false // 파라미터 타입을 검사한다
return obj.x == x && obj.y == y // Point로 스마트 캐스트해서 x와 y 프로퍼티에 접근한다.
}
}
class Person(
val firstName: String, val lastName: String
) : Comparable<Person> {
override fun compareTo(other: Person): Int {
return compareValuesBy(this, other, Person::lastName, Person::firstName) // 인자를 받은 함수를 차례로 호출하면서 값을 비교한다.
}
}
mutableMap[key] = newValue
data class MutablePoint(var x: Int, var y: Int)
operator fun MutablePoint.set(index: Int, value: int) { // set이라는 연산자 함수를 정의한다
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)
}
data class Rectangle(val upperLeft: Point, val lowerRight: Point)
operator fun Ractangle.contains(p: Point): Boolean {
return p.x in upperLeft.x until lowerRight.x && // 범위를 만들고 x 좌표가 그 범위 안에 있는지 검사한다.
p.y in upperLeft.y until lowerRight.y // until 함수를 사용해 열린 범위를 만든다
}
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)
}
operator fun <T : Comparable<T>> T.rangeTo(that: T): ClosedRange<T>
fun main(args: Array<String>) {
val now = LocalDate.now()
val vacation = now..now.plusDays(10) // now(오늘) 부터 시작해 10일짜리 범위를 만든다.
println(now.plusWeeks(1) in vacation) // 특정 날짜가 날짜 범위 안에 들어가는지 검사한다.
}
fun main(args: Array<String>) {
val n = 9
println(0..(n + 1)) // 0..n + 1 이라고 써도 되지만 괄호를 치면 더 뜻이 명확해진다.
}
fun main(args: Array<String>) {
(0..n).forEach { print(it) } // 컴파일 x, 범위의 메서드를 호출하려면 범위를 괄호로 둘러싸라
}
operator fun CharSequense.iterator(): CharIterator // 이 라이브러리 함수는 문자열을 이터레이션할 수 있게 해준다
for (c in "abc") {
} // 이 라이브러리 함수는 문자열을 이터레이션할 수 있게 해준다,
operator fun ClosedRange<LocalDate>.iterator(): Iterator<LocalDate> =
object : Iterator<LocalDate> { // 이 객체는 LocalDate원소에 대한 Iterator를 구현한다.
var current = start
override fun hasNext() =
current <= endInclusive // compareTo 관례를 사용해 날짜를 비교한다.
override fun next() = current.apply { // 현재 날짜를 지정한 다음에 날짜를 변경한다. 그 후 저장해둔 날짜를 반환한다
current = plusDays(1) // 현재 날짜를 1일 뒤로 변경한다.
}
}
fun main(args: Array<String>) {
val newYear = LocalDate.ofYearDay(2017, 1)
val daysOff = newYear.minusDays(1)..newYear
for (dayOff in daysOff) {
println(dayOff)
}
}
val p = Point(10, 20)
val (x, y) = p
println(x)
// 10
println(y)
// 20
구조 분해 선언은 일반 변수 선언과 비슷해 보인다. 다만 = 의 좌변에 여러 변수를 괄호로 묶었다는 점이 다르다
내부에서 구조 분해 선언은 다시 관례를 사용한다. 구조 분해 선언의 각 변수를 초기화하기 위해 componentN이라는 함수를 호출한다.
구조 분해 선언은 componentN 함수 호출로 변환된다.
data 클래스의 주 생성자에 들어있는 프로퍼티에 대해서는 컴파일러가 자동으로 componentN 함수를 만들어준다.
class Point(val x: Int, val y: Int)
operator fun component1() = x
operator fun component2() = y
data class NameComponents(val name: String, val extension: String) // 값을 저장하기 위한 데이터 클래스를 선언한다
fun splitFileName(fullName: String): NameComponents {
val result = fullName.split('.', limit = 2)
return NameComponents(result[0], result[1]) // 함수에서 데이터 클래스의 인스턴스를 반환한다
}
fun main(args: Array<String>) {
val (name, ext) = splitFileName("example.kt") // 구조 분해 선언 구문을 사용해 데이터 클래스를 푼다
println(name)
println(ext)
}
fun printEntries(map: Map<String, String>) {
for ((key, value) in map) {
println("$key -> $value") // 루프 변수에 구조 분해 선언을 사용힌다.
}
}
fun main(args: Array<String>) {
val map = mapOf("Oracle" to "Java", "JetBrains" to "Kotlin")
}
printEntries(map)
class Foo {
private val delegate = Delegate() // 컴파일러가 생성한 도우미 프로퍼티다
var p: Type
set(value: Type) = delegate.setValue(..., value) // p 프로퍼티를 위해 컴파일러가 생성한 접근자는 delegate의 getValue와 setValue 메서드를 호출한다.
get() = delegate.getValeu(...)
}
class Delegate {
operator fun getValue(...) { // getValue는 게터를 구현하는 로직을 담는다
...
}
operator fun setValue(...) { // setValue 메서드는 세터를 구현하는 로직을 담는다
...
}
}
class Foo {
val p: Type by Delegate() // by 키워드는 프로퍼티와 위임 객체를 연결한다.
}
class Email {/*...*/ }
fun loadEmails(person: Person): List<Email> {
println("${person.name}의 이메일을 가져옴")
return listOf(/*...*/)
}
class Person(val name: String) {
private var _emails: List<Email>? = nill // 데이터를 저장하고 emails 의 위임 객체 역할을 하는 _email 프로퍼티
val emails: List<Email>
get() {
if (_emails == null) {
_emails = loadEmails(this) // 최초 접근 시 이메일을 가져온다
}
return _emails!! // 저장해 둔 데이터가 있으면 그 데이터를 반환한다
}
}
fun main(args: Array<String>) {
val p = Person("Alice")
println(p.emails) // 최초로 emails를 읽을 때 단 한 번만 이메일을 가져온다,
}
class Person(val name: String) {
val emails by lazy { loadEmail }
}
class Person {
// 추가 정보
private val _attribute = hashMapOf<String, String>()
fun setAttribute(attrName: String, value: String) {
_attribute[attrName] = value
}
// 필수 정보
val name: String
get() = _attribute["name"]!! // 수동으로 맵에서 정보를 꺼낸다
}
class Person {
// 추가 정보
private val _attribute = hashMapOf<String, String>()
fun setAttribute(attrName: String, value: String) {
_attribute[attrName] = value
}
val name: String by _attribute // 위임 프로퍼티로 맵을 사용한다
}