LostCatBox

코틀린 기본 문법

Word count: 1.4kReading time: 8 min
2024/08/17 Share

참조

코틀린 특성

JAVA와 비슷한점

  • OOP 등 4가지 기본 원칙

JAVA와 다른점

getter, setter

  • 클래스 속성에는 기본값으로 getter, setter가 기본값(public)하게 붙는다
    • var의 경우 getter, setter
    • val의 경우 getter
  • 하위방식으로 필드값에 대한 getter, setter가 설정가능하다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var speakerVolume = 2
get() = field
set(value) {
field = value
}

//응용
var speakerVolume = 2
//getter정의는 생략
set(value) {
if (value in 0..100) {
field = value
}
}

기본생성자, 보조생성자

  • 기본생성자는 생략가능(매개변수화된 생성자 정의)(=constructor arguments)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class SmartDevice constructor() {
    ...
    }

    //생략시
    class SmartDevice (val name: String, val category: String) {
    var deviceStatus = "online"

    fun turnOn() {
    println("Smart device is turned on.")
    }

    fun turnOff() {
    println("Smart device is turned off.")
    }
    }
  • 보조생성자는 기본생성자 매개변수와 다를때, 정의가능

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class SmartDevice(val name: String, val category: String) {
    var deviceStatus = "online"

    //해당 부분은 statusCode 매개변수를 받아서 보조생성자로 객체생성, this=기본생성자를 뜻함
    constructor(name: String, category: String, statusCode: Int) : this(name, category) {
    deviceStatus = when (statusCode) {
    0 -> "offline"
    1 -> "online"
    else -> "unknown"
    }
    }
    ...
    }

기본적으로 닫혀있는 상속

  • 코틀린은 기본적으로 클래스가 final 이다. 따라서 다른 클래스에서 상속이 불가능하다.

  • 따라서 상속을 허용하려면 open 변경자가 붙어야한다.

  • 또한 오버라이드를 허용하고 싶은 메서드나 프로퍼티의 앞에도 open 필요하다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    open class Car {

    // 이 메서드는 하위 클래스에서 override 불가능
    fun getNumberOfTires(): Int {
    return 4
    }

    // 하위 클래스에서 override 가능
    open fun hasSunRoof() :Boolean {
    return false
    }
    }

    // open 클래스는 상속이 가능하다!
    class Benz() : Car() {

    // getNumberOfTires 메서드는 override 불가능
    // hasSunRoof 메서드는 open변경자가 붙어서 override 가능
    override fun hasSunRoof(): Boolean {
    return true
    }
    }
  • abstract 클래스는 abstract 메서드, open 를 활용할수있습니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    abstract class Animal {

    // 추상 메서드는 반드시 override 해야 함
    abstract fun bark()

    // 이 메서드는 하위 클래스에서 선택적으로 override 할 수 있다. (하거나 안하거나 자유)
    open fun running() {
    println("animal running!")
    }
    }

    class Dog() : Animal() {

    override fun bark() {
    println("멍멍")
    }

    // 이 메서드는 override 하거나 하지 않거나 자유.
    override fun running() {
    println("dog's running!")
    }
    }

공개 상태 수정자[public(=default), internal, protected, private]

  • 자바와 다른개념

  • 변경자 클래스 멤버 최상위 선언
    public(기본값) 모든 곳에서 볼 수 있다 모든 곳에서 볼 수 있다
    internal 같은 모듈 안에서만 볼 수 있다 같은 모듈 안에서만 볼 수 있다
    protected 하위 클래스 안에서만 볼 수 있다 (최상위 선언에 적용할 수 없음)
    private 같은 클래스 안에서만 볼 수 있다 같은 파일 안에서만 볼 수 있다

data class

  • 자바에서 대게 equals, hashCode, toString을 오버라이드 해야하는 경우가 많았습니다. 코틀린은 이런 불편함을 줄이기 위해 data라는 변경자를 클래스 앞에 붙이면 필요한 메서드를 컴파일러가 자동으로 만들어줍니다. data 변경자가 붙은 클래스를 데이터 클래스라 부릅니다.

  • 데이터 클래스의 프로퍼티가 꼭 val일 필요는 없지만, 데이터 클래스의 모든 프로퍼티를 읽기 전용으로 만들어서 데이터 클래스를 불변(immutable) 클래스로 만들라고 권장합니다.

    데이터 클래스 인스턴스를 불변 객체로 더 쉽게 활용할 수 있게 코틀린 컴파일러는 copy 메서드를 제공합니다. copy 메서드는 객체를 복사하면서 일부 프로퍼티를 바꿀 수 있게 해줍니다. 복사본은 원본과 다른 생명주기를 가지며, 복사를 하면서 일부 프로퍼티 값을 바꾸거나 복사본을 제거해도 프로그램에서 원본을 참조하는 다른 부분에 전혀 영향을 끼치지 않습니다.(p3, p4참조)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    data class Person(val name:String, val age:Int)

    fun main() {
    val p1 = Person("conatuseus",30)
    val p2 = Person("conatuseus",30)

    // == 은 동등성 검사
    println(p1==p2)

    // === 은 같은 인스턴스인지 검사(동일성 확인)
    println(p1===p2)

    println(p1.toString())

    val p3 = p1.copy() //clone처럼 copy가능
    val p4 = p1.copy(name = "newName",age = 20)
    }

    //출력
    // true, false, Person(name=conatuseus, age=30)

코틀린 기본 문법

코틀린 출처

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143

fun main() {
//kotlin print
println("hello World!")
print("11")
println("12")

//val -> immutable
val valInt = 1
val valInt2: Int
valInt2 = 2

//var -> mutable
var varInt: Int
varInt = 2
varInt = 3

//System input
val yourWord = readln()

val sum = sum(1, 2)
println(sum)
printSum(2, 3)

var rectangle: Rectangle = Rectangle(5.0, 4.0)
println(rectangle.perimeter)

//String expressing
var a = 1
val s1 = "a is $a"
a = 2
val s3 = "${s1.replace("is", "was")}, but now is $a"
println(s3)

//function expressing1
val maxOf = maxOf(1, 2)
println(maxOf)
//function expressing2
fun maxOf2(a: Int, b: Int) = if (a > b) a else b
val maxOf2 = maxOf2(1, 2)
println(maxOf2)


//nullable function return
getStringLength("ss")


//array and for
val items = listOf("apple", "banana", "kiwifruit")
for (index in items.indices) {
println("item at $index is ${items[index]}")
}

// when
fun describe(obj: Any): String =
when (obj) {
1 -> "One"
"Hello" -> "Greeting"
is Long -> "Long"
!is String -> "Not a string"
else -> "Unknown"
}

// when function usecase
val describe = describe("Hello")
println("뭐냐? $describe 이거다")


val x = 10
val y = 9
if (x in 1..y + 1) {
println("fint in range")
}


val list = listOf("a", "b", "c")
if (2 !in 0..list.lastIndex) {
println("-1 is out of range")
}
if (list.size - 1 !in list.indices) {
println("list size is out of valid list indices range, too")
}

// when
when {
"orange" in items -> print("오랜지")
"apple" in items -> print("사과")
}

//stream filter
val fruits = listOf("banana", "avocado", "apple", "kiwifruit")
fruits.filter { it.startsWith("a") }
.sortedBy { it }
.map { it.uppercase() }
.forEach { println(it) }


// y = 12
// print(y)
printProduct("a", "b")
}

//string to int and nullable function
fun parseInt(str: String): Int? {
return str.toIntOrNull()
}


fun printProduct(arg1: String, arg2: String) {
val x = parseInt(arg1)
val y = parseInt(arg2)

// Using `x * y` yields error because they may hold nulls.
if (x != null && y != null) {
// x and y are automatically cast to non-nullable after null check
println(x * y)
} else {
println("'$arg1' or '$arg2' is not a number")
}
}

fun sum(a: Int, b: Int): Int {
return a + b
}

fun printSum(a: Int, b: Int) {
println("sum of $a and $b is ${a + b}")
}


fun maxOf(a: Int, b: Int): Int {
if (a > b) {
return a
} else {
return b
}
}

fun getStringLength(obj: Any): Int? {
if (obj !is String)
return null
return obj.length
}

Advanced

속성 위임 정의(by)

  • 구현을 위임할 수 있는 클래스를 구현하려면 먼저 인터페이스를 잘 알아야 합니다. 인터페이스는 구현하는 클래스가 준수해야 하는 계약으로서, 작업을 하는 방법이 아닌 해야 할 작업에 중점을 둡니다. 간단히 말해 인터페이스는 추상화하는 데 유용합니다.

  • (작성중)

엘비스 연산자

  • ?: (엘비스 연산자)의 동작 방식:

  • 왼쪽의 값이 null이 아니면 그 값을 반환하고,

  • 만약 왼쪽 값이 null이면 오른쪽의 값을 반환하거나, 오른쪽에 특정 연산(예: 예외 던지기)을 수행합니다.

1
2
val value: String? = null
val result = value ?: "default value"

인라인 (inline)

인라인 함수(inline function)는 함수 호출 오버헤드를 줄이기 위한 최적화 기법입니다. 함수가 호출될 때, 보통은 호출된 함수로 제어가 이동하고, 함수가 끝난 후 다시 호출 지점으로 돌아가는 과정이 필요합니다. 이 과정은 특히 작은 함수일 때도 오버헤드가 발생할 수 있습니다.

인라인 함수는 이 문제를 해결하기 위해 함수 호출을 제거하고, 함수의 본문을 호출한 곳에 직접 복사하는 방식으로 처리합니다. 이를 통해 성능을 최적화할 수 있습니다.

Kotlin에서는 inline 키워드를 사용하여 인라인 함수를 정의할 수 있습니다. 인라인 함수는 특히 람다를 인자로 받는 고차 함수에서 성능 최적화 효과가 큽니다.

인라인 함수의 특징

  1. 함수 호출 오버헤드 제거: 호출된 함수로 제어가 이동하지 않고, 코드가 호출된 곳에 복사되므로 성능이 향상됩니다.
  2. 람다식의 성능 최적화: 인라인 함수에 전달되는 람다식은 객체로 변환되지 않고 인라인 처리됩니다.
  3. 재귀 함수는 인라인 불가능: 재귀 함수는 반복적으로 자기 자신을 호출하므로 인라인으로 변환할 수 없습니다.

예시

1
inline fun <reified T, ID> CrudRepository<T, ID>.findByIdOrThrow(id: ID, message: String = "해당 ID의 데이터가 존재하지 않습니다."): T = findByIdOrNull(id) ?: throw NoSuchElementException(message)
1
2
3
4
5
6
7
8
9
10
11
inline fun runIfTrue(condition: Boolean, block: () -> Unit) {
if (condition) {
block() // block()은 여기서 호출되기 전에 인라인 처리됨
}
}

fun main() {
runIfTrue(true) {
println("Condition is true!")
}
}

companion object

  • java static 객체라고 생각하면됨.
  • 클래스에 정의된 static class
1
2
3
4
5
6
7
class SignUtils(private val signService: SignService) {
companion object {
fun numberOperation(message:String,a:Int, b:Int, operation: (Int, Int)->Int):Int {
println(message)
return operation(a, b)
}
}
  • 호출시 다음과같이 가능
1
SignUtils.numberOperation("실행쌉가능", 1,2,{x,y->x*y+x})
CATALOG
  1. 1. 참조
  2. 2. 코틀린 특성
    1. 2.1. JAVA와 비슷한점
    2. 2.2. JAVA와 다른점
      1. 2.2.1. getter, setter
      2. 2.2.2. 기본생성자, 보조생성자
      3. 2.2.3. 기본적으로 닫혀있는 상속
      4. 2.2.4. 공개 상태 수정자[public(=default), internal, protected, private]
      5. 2.2.5. data class
  3. 3. 코틀린 기본 문법
  4. 4. Advanced
    1. 4.1. 속성 위임 정의(by)
    2. 4.2. 엘비스 연산자
    3. 4.3. 인라인 (inline)
      1. 4.3.1. 인라인 함수의 특징
      2. 4.3.2. 예시
    4. 4.4. companion object