티스토리 뷰
17강. 연산자 오버로딩
연산자 오버로딩 특징
operator
키워드가fun
앞에 붙음- 함수의 이름과 파라미터가 정해져있음
예시 - Point
data class Point (
val x: Int,
val y: Int
) {
fun zeroPointSymmetry() = Point(-x, -y)
}
fun main() {
val point = Point(20, -10)
println(point.zeroPointSymmetry())
}
zeroPointSymmetry()
메서드를 통해 점대칭을 구하고 있음
하지만, 연산자 오버로딩을 통해 좀더 간단히 사용 가능
unaryMinus()
: 단항 마이더스 연산자
data class Point (
val x: Int,
val y: Int
) {
operator fun unaryMinus(): Point = Point(-x, -y)
}
fun main() {
val point = Point(20, -10)
println(-point)
}
inc()
: 증가 연산자
data class Point (
val x: Int,
val y: Int
) {
operator fun inc() = Point(x + 1, y + 1)
}
fun main() {
var point = Point(20, -10)
println(++point)
}
// Point(x=21, y=-9)
plus()
: 덧셈 연산자
data class Days(val day: Long)
operator fun LocalDate.plus(days: Days): LocalDate {
return this.plusDays(days.day)
}
// 확장 프로퍼티
val Int.days: Days
get() = Days(this.toLong())
fun main() {
println(LocalDate.now().plusDays(Days(1).day))
println(LocalDate.now() + Days(1))
println(LocalDate.now() + 3.days)
}
복합 대입 연산자 (+=)
적용 순서
flowchart TD
A[복합 대입 연산자 시작] --> B{오버로딩 확인}
B -->|오버로딩 있음| C[오버로딩 적용]
B -->|오버로딩 없음| D{변수 타입 확인}
D -->|var 변수| E[산술 연산자 적용]
E --> F[변수 갱신]
D -->|val 변수| G[에러 발생]
C --> H[종료]
F --> H
G --> H
다양한 연산자들이 존재
==
/!=
: equals>
,>=
,<
,<=
: compareTo- List, Map 편의 기능도
get
/set
연산자list[1]
,map['key']
invoke()
: 함수 호출도 하나의 연산자
- 이전예시에 적용
enum class Operator( private val oper: Char, val calcFun: (Int, Int) -> Int ) { PLUS('+', { a, b -> a + b }), MINUS('-', { a, b -> a - b }), MULTIPLY('*', { a, b -> a * b }), DIVIDE('/', { a, b -> if (b == 0) { throw IllegalArgumentException("0으로 나눌수 없습니다.") } a / b });
operator fun invoke(num1: Int, num2: Int): Int {
return this.calcFun(num1, num2)
}
}
fun calculate(num1: Int, num2: Int, oper: Operator): Int = oper.calcFun(num1, num2)
fun calculateOperatorOverload(num1: Int, num2: Int, oper: Operator): Int = oper(num1, num2)
**연산자 오버로딩은 그 의미에 맞게 사용하는것이 중요!!**
---
# 18강. Kotlin DSL 직접 만들어보기
## DSL?
- Domain Specipic Language: 특정 목적을 위해 존재하는 언어
## 목표
- YAML을 렌더링하는 Kotlin DSL 만들기
```yaml
version: '3'
services:
db:
image: mysql
environment:
- USER: myuser
- PASSWORD: mypassword
port:
- "9999:3306"
1단계
- DockerCompose 클래스에 version 추가해서 yaml로 나타내기
class DockerCompose { private var version: Int by onceNotNull() fun version(init: () -> Int) { version = init() } fun render(indent: String): String { val builder = StringBuilder() builder.appendNew("version: '$version'") return builder.toString() } override fun toString(): String { return "DockerCompose(version=$version)" } }
fun onceNotNull() = object : ReadWriteProperty<Any?, T> {
private var value: T? = null
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
if (this.value == null) {
throw IllegalArgumentException("변수가 초기화되지 않았습니다.")
}
return this.value!!
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
if (this.value != null) {
throw IllegalArgumentException("이 변수는 한번만 초기화 가능합니다.")
}
this.value = value
}
}
fun dockerCompose(init: DockerCompose.() -> Unit): DockerCompose {
val dockerCompose = DockerCompose()
dockerCompose.init()
// or init(dockerCompose)
return dockerCompose
}
fun StringBuilder.appendNew(str: String, indent: String = "", times: Int = 0) {
(1..times).forEach { _ -> this.append(indent) }
this.append(str)
this.append("\n")
}
fun String.addIndent(indent: String, times: Int = 0): String {
val allIndent = (1..times).joinToString("") { indent }
return this.split("\n")
.joinToString("\n") { "$allIndent$it" }
}
fun main() {
val yml = dockerCompose {
version { 3 }
}
println(yml.render(" "))
}
## 2단계
- `service`추가하기
```kotlin
class DockerCompose {
private var version: Int by onceNotNull()
private val services = mutableListOf<Service>()
fun version(init: () -> Int) {
version = init()
}
fun service(name: String, init: Service.() -> Unit) {
val service = Service(name)
service.init()
this.services.add(service)
}
fun render(indent: String): String {
val builder = StringBuilder()
builder.appendNew("version: '$version'")
builder.appendNew("services:")
builder.appendNew(services.joinToString("\n") { it.render(indent) }.addIndent(indent, 1))
return builder.toString()
}
override fun toString(): String {
return "DockerCompose(version=$version)"
}
}
class Service(
val name : String,
) {
private var image: String by onceNotNull()
fun image(init: () -> String) {
this.image = init()
}
fun render(indent: String): String {
val builder = StringBuilder()
builder.appendNew("$name:")
builder.appendNew("image: $image", indent, 1)
return builder.toString()
}
}
fun <T> onceNotNull() = object : ReadWriteProperty<Any?, T> {
private var value: T? = null
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
if (this.value == null) {
throw IllegalArgumentException("변수가 초기화되지 않았습니다.")
}
return this.value!!
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
if (this.value != null) {
throw IllegalArgumentException("이 변수는 한번만 초기화 가능합니다.")
}
this.value = value
}
}
fun dockerCompose(init: DockerCompose.() -> Unit): DockerCompose {
val dockerCompose = DockerCompose()
dockerCompose.init()
// or init(dockerCompose)
return dockerCompose
}
fun StringBuilder.appendNew(str: String, indent: String = "", times: Int = 0) {
(1..times).forEach { _ -> this.append(indent) }
this.appe
3단계: env, port 추가
class DockerCompose {
private var version: Int by onceNotNull()
private val services = mutableListOf<Service>()
fun version(init: () -> Int) {
version = init()
}
fun service(name: String, init: Service.() -> Unit) {
val service = Service(name)
service.init()
this.services.add(service)
}
fun render(indent: String): String {
val builder = StringBuilder()
builder.appendNew("version: '$version'")
builder.appendNew("services:")
builder.appendNew(services.joinToString("\n") { it.render(indent) }.addIndent(indent, 1))
return builder.toString()
}
override fun toString(): String {
return "DockerCompose(version=$version)"
}
}
class Service(
val name : String,
) {
private var image: String by onceNotNull()
private val environments = mutableListOf<Environment>()
private val portRules = mutableListOf<PortRule>()
fun image(init: () -> String) {
this.image = init()
}
fun env(environment: Environment) {
this.environments.add(environment)
}
fun port(host: Int, container: Int) {
this.portRules.add(PortRule(host, container))
}
fun render(indent: String): String {
val builder = StringBuilder()
builder.appendNew("$name:")
builder.appendNew("image: $image", indent, 1)
builder.appendNew("environment:")
builder.appendNew(
environments
.joinToString("\n") { "- ${it.key}: ${it.value}" }
.addIndent(indent, 2)
)
builder.appendNew("port:")
portRules.joinToString("\n") { "- \"${it.host}:${it.container}\"" }
.addIndent(indent, 2)
.also { builder.appendNew(it) }
return builder.toString()
}
}
data class Environment (
val key : String,
val value : String
)
data class PortRule (
val host: Int,
val container: Int
)
operator fun String.minus(other: String): Environment {
return Environm
@DslMarker
- 가장 가까운 수신객체에 대해서만
this
를 생략 가능
dockerCompose {
service(name = "db") {
service(name = "db_another") {
}
}
}
dockerCompose
하위 어떤 계층이라도service()
호출가능- 문법적으로는 가능하지만 계층적으로는 어색함
@DslMarker
annotation class YamlDsl
@YamlDsl
class DockerCompose {
}
@YamlDsl
class Service() {}
dockerCompose {
service(name = "db") {
service(name = "db_another") { // error!!
// 가장 가까운 수신객체의 this만 사용가능
// 명시적으로 상위 계층의 this를 사용하고 싶다면 this@dockerCompose 로 사용해야함
}
}
}
위 내용은 인프런 코틀린 고급편강의를 시청하고 작성했습니다
인프런
'개발 언어 > 코틀린' 카테고리의 다른 글
kotlin - coroutine (1) 코루틴의 기초 개념 (0) | 2024.09.05 |
---|---|
코틀린 고급편 - 어노테이션 & 리플렉션 활용 (0) | 2024.07.18 |
인프런 - 코틀린 고급편 (3) 함수형 프로그래밍 활용 (0) | 2024.07.04 |
인프런 - 코틀린 고급편 (2) 지연과 위임 (1) | 2024.07.01 |
인프런 - 코틀린 고급편 (1) 제네릭 (1) | 2024.07.01 |