[Kotlin 기초문법] 총정리
- Coding/Kotlin
- 2022. 10. 29.
https://github.com/seohaem/kotlin-step1
변수선언
1) var 변수 선언
- 값 변경이 가능하다.
var number1 = 10L
2) val 변수 선언
- 값 변경이 불가능하다.
val number2 = 10L
- 초기화되지 않은 val 변수는 처음 딱 1번만 값 할당이 가능하다.
val number5: Long
number5 = 2
print(number5)
3) 타입 선언
- Long 타입 변수를 선언해보자.
var number3: Long = 10L
- 선언과 동시에 초기화하는 경우에는 타입 선언은 생략 가능하다.
var number3 = 10L
- 초기화하지 않은 선언은 타입을 넣어줘야한다.
var number4: Long
number4 = 1
print(number4)
4) Primitive Type
- 코틀린은 null을 허용하지 않는 Int, Long 등의 자료형을 Primitive Type으로 자동 변환하여 JVM에서 실행된다.
- null을 허용하는 Int?, Long? 등의 자료형은 Primitive Type으로 자동 변환하지 않고 Heap 공간에서 관리한다.
var number1: Long = 10L // 상황에 따라서 Long -> long으로 변환해준다.
var number2: Long? = 10L
5) Null 가능
- 코틀린에서 모든 변수는 null이 들어갈 수 없다. null 허용은 타입?(Int?, Long? 등)를 사용해야한다.
var number2 = 10L
// number2 = null // 기본적으로 모든 변수는 null이 들어갈 수 없게끔 설계되었다.
var number3: Long? = 10L
number3 = null // Long? 타입?을 사용해야 null 이 들어갈 수 있다.
6) 인스턴스 생성
- 코틀린은 JAVA와 다르게, 객체 인스턴스화때 new 키워드를 사용하지 않는다.
var person = Person("최태현")
null 변수
1) null이 아닌 경우 수행
- str 변수가 null이 아닌 경우 length 함수를 호출한다.
- str 변수가 null이라 length 함수가 호출되지 않았을 경우, 결과는 null을 반환한다.
str = null
print(str?.length) // 결과는 null
2) Elvis 연산자 (null 일 경우 결과값 default 설정)
str?.length 의 결과가 null이면 전체 식의 결과가 0이다.
print(str?.length ?: 0)
3) !! : null 이 아님을 명시
fun startsWith(str: String?): Boolean {
return str!!.startsWith("A")
}
- 만약 null인 경우 컴파일 에러가 발생한다.
println(startsWith("AAA"))
println(startsWith(null)) // 컴파일 에러 발생
Type
1) 값으로 타입을 추론한다.
val number1 = 3 // Int
val number2 = 3L // Long
val number3 = 3.0f // Float
val number4 = 3.2 // Double
2) 명시적 선언으로 타입 변환할 수 있다.
- toLong(), toString() 등과 같은 toXX() 메서드를 사용해야한다.
// val number6 :Long = number5 // Type mismatch
val number6 = 3
val number7: Long = number6.toLong() // 명시적 선언
3) is 키워드
- obj가 Person 타입인가? (JAVA에서 instanceof)
fun printAgeIfPerson(obj: Any) {
if (obj is Person) { // instanceof -> is
val person = obj as Person // (Person) obj (생략도 가능)
println(person.name)
println(obj.name) // smart cast
}
}
- is 부정
if (!(obj is Person)) { // instanceof not -> is
println(obj)
}
// 위와 결과 동일
if (obj !is Person) { // instanceof not -> is
println(obj)
}
4) as 키워드
- 변수 obj가 null 이 아닌 경우 Person을 반환하고 null이면 null을 반환한다.
val person = obj as? Person
- person이 null인 경우 결과는 null이다.
println(person?.name)
5) Any 타입
- Java의 Object 역할 (모든 객체의 최상위 타입)
- 모든 Primitive Type의 최상위 타입도 Any이다.
- Any 자체로는 null 을 포함할 수 없다. null로 표현하려면 Any?로 표현
- Any에 equals, hashCode, toString도 존재한다.
6) Unit
- Java의 void와 동일한 역할
- void와 다르게 Unit은 그 자체로 타입 인자로 사용 가능하다.
- 함수형 프로그래밍에서 Unit은 단 하나의 인스턴스만 갖는 타입을 의미한다.
- 즉, 코틀린의 Unit은 실제 존재하는 타입이라는 것을 표현한다.
7) Nothing
- 함수가 정상적으로 끝나지 않았다는 사실을 표현하는 역할이다.
- 무조건 예외를 반환하는 함수나 무한루프 함수 등에 사용한다.
String
1) String 변수 출력 방법
val person = Person("KIM")
val age = 22
println("이름 : ${person.name}")
println("나이 : $age")
2) """ 키워드
val str = """
ABCDE
EFG
AJWJKQLWK
""".trimIndent()
3) String에서 특정 인덱스에 해당하는 문자열 가져오기
val str2 = "ABC"
print(str2[0])
print(str2[1])
결과
ABCDE
EFG
AJWJKQLWKAB
연산자
1) 코틀린은 비교 연산자(>, <, >=, <=)를 사용해서 compareTo() 함수를 자동 호출한다.
val money1 = JavaMoney(2000L)
val money2 = JavaMoney(1000L)
if (money1 > money2) { // compareTo() 메서드를 자동으로 호출해준다.
println("Money1이 Money2보다 금액이 큽니다.")
}
2) 코틀린은 == 를 사용해서 equals()를 자동 호출한다.
val money3 = JavaMoney(1000L)
val money4 = money3; // 주소 같음
val money5 = JavaMoney(1000L)
println(money3 == money4) // equals() true
println(money3 == money5) // equals() true
3) 주소가 같은지는 === 를 사용한다.
val money3 = JavaMoney(1000L)
val money4 = money3; // 주소 같음
val money5 = JavaMoney(1000L)
println(money3 === money4) // true
println(money3 === money5) // false
4) 범위
연산자 | 설명 |
in | 컬렉션이나 범위에 포함되어있다. |
!in | 컬렉션이나 범위에 포함되어있지 않다. |
a..b | a부터 b까지의 범위 객체를 생성한다. |
a[i] | a에서 특정 index i로 값을 가져온다. |
a[i] = b | a의 특정 index i에 b를 넣는다. |
5) operator : 코틀린의 연산자를 직접 정의하기
연산자 | 함수 |
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) |
- operator 키워드를 사용하여 메서드를 재정의한다.
data class Money(
val amount: Long
) {
operator fun plus(other: Money): Money {
return Money(this.amount + other.amount)
}
}
main()
fun main() {
val money1 = Money(1000L)
val money2 = Money(2000L)
println(money1.plus(money2)) // 가능하긴함
println(money1 + money2) // + 연산자로 직접 정의 가능
}
제어문 if
1) if
- score 가 0과 100 사이에 포함되어있지 않을 경우
if (score !in 0..100) {
throw IllegalArgumentException("score의 범위는 0부터 100입니다.")
}
2) if~else 에서 즉시 return문
fun getPassOrFail(score: Int): String {
return if (score >= 50) {
return "P"
} else {
return "F"
}
}
제어문 when
- when 값에 따라서 해당하는 값을 리턴한다.
fun getGradeWithSwitch(score: Int): String {
return when (score / 10) {
in 90..99 -> "A" // 어떠한 expression이라도 들어갈 수 있다.
8 -> "B"
7 -> "C"
else -> "D"
}
}
fun judgeNumber(number: Int) {
when (number) {
1, 0, -1 -> println("어디서 많이 본 숫자입니다")
else -> println("1, 0, -1이 아닙니다")
}
}
fun judgeNumber2(number: Int) {
when {
number == 0 -> println("주어진 숫잔는 0입니다.")
number % 2 == 0 -> println("주어진 숫자는 짝수입니다.")
else -> println("주어지는 숫자는 홀수입니다.")
}
}
반복문
1) 리스트 numbers 반복하여 값 출력
fun main() {
val numbers = listOf(1L, 2L, 3L)
for (number in numbers) {
println(number)
}
}
2) 1부터 3까지 반복
for (i in 1..3) {
println(i)
}
3) 3부터 1까지 -1씩 하며 반복
// 3부터 1까지 내려간다.
for (i in 3 downTo 1) {
println(i)
}
4) 1부터 5까지 +2씩 하며 반복
// 2칸씩 올라간다.
for (i in 1..5 step 2) {
println(i)
}
5) while문 사용
fun main() {
var i = 1
while (i <= 3) {
println(i)
i++
}
}
예외처리
1) try~catch~finally문
try {
return str.toInt()
} catch (e: NumberFormatException) {
throw IllegalArgumentException("주어진 ${str}는 숫자가 아닙니다.")
}
- 코틀린에서는 try~catch문을 return하여 처리 가능하다.
fun parseIntOrThrowV2(str2: String): Int? {
return try {
return str2.toInt()
} catch (e: NumberFormatException) {
null
}
}
2) CheckedException
Kotlin 에서는 CatchException, UncheckedExcpetion 을 구분하지 않는다.
모두 UncheckedException이기 때문에, 에러를 던지지 않아도 된다.
3) use 키워드
- JAVA의 try~catch~resource 구문을 대체한다.
fun readFile(path: String) {
// try~catch~resource 구문 자체는 사라지고 use를 사용한다.
BufferedReader(FileReader(path)).use {
reader -> println(reader.readLine())
}
}
함수 선언
1) 기본 함수 선언 방법
- 기본적으로 public 함수다. (public 접근제어자 생략이 가능하다.)
- fun : 함수를 의미하는 키워드
- max : 함수 이름
- a, b : 함수의 매개변수
- Int {...} : 함수의 반환 타입
- block {} 을 사용하는 경우, 반환 타입이 Unit 이 아닌 경우에는 반환 타입을 명시해줘야한다.
fun max(a: Int, b: Int): Int {
// 하나의 expression
return if (a > b) {
return a
} else {
return b
}
}
2) 코틀린의 함수는 하나의 expression으로 표현 가능하다.
fun max2(a: Int, b: Int): Int =
// 하나의 expression
if (a > b) {
a
} else {
b
}
3) block {} 생략이 가능하다.
- 반환 타입 생략 가능 : 하나의 expression으로 선언된 함수(=인 경우)는 반환 타입 추론이 가능하다.
fun max3(a: Int, b: Int) = if (a > b) a else b
함수 파라미터(parameter)
1) default 값 설정이 가능하다.
fun repeat(
str: String,
num: Int = 3, // default 3
useNewLine: Boolean = true // default true
) { ... }
main() - 호출문 예제
repeat("Hello World")
repeat("Hello World", 5, false)
repeat("Hello World", 7)
repeat("Hello World", 1, true)
2) 함수 호출시, 매개변수 이름을 명시해줄 수 있다.
repeat2("Hello World", useNewLine = true)
num은 default 값인 3이 셋팅된다.
fun repeat2(
str: String,
num: Int = 3, // default 3
useNewLine: Boolean = true // default true
) { ... }
3) 매개변수에 이름을 명시해서 budiler와 비슷하게 사용 가능하다.
fun main() {
// 이름 명시하여 builder 비슷하게 사용 가능
printNameAndGender2(name = "KIM", gender = "F")
}
fun printNameAndGender2(name: String, gender: String) {
println(name)
println(gender)
}
4) vararg 키워드 : 가변인자
fun printAll(vararg strArr: String) { // JAVA의 ...을 쓰 는대신 vararg
for (str in strArr) {
println(str)
}
}
- , 로 구분하여 호출
printAll("A", "B", "C") // ,로 구분
- 변수 array 를 * 사용하여 호출
val array = arrayOf("A", "B", "C") // 배열
printAll(*array) // 가변인자 넣어줄때 배열을 바로 넣는 대신, *를 사용해야한다.
클래스
1) 기본 클래스 선언
class Person constructor(name: String, age: Int) {
// Kotlin은 필드를 만들면 getter, setter을 자동으로 만들어준다.
}
2) constructor 키워드 생략 가능
- Kotlin은 기본 생성자를 자동으로 생성해준다. constructor 키워드 생략이 가능하다.
class Person (name: String, age: Int) { // 기본 생성자 작성 (constructor 키워드는 생략 가능)
// Kotlin은 필드를 만들면 getter, setter을 자동으로 만들어준다.
// 불변 필드
val name = name
// 변경가능 필드
var age = age
}
- 위 코드를 아래와같이, 생성자로 옮길 수 있다.
class Person2 (val name: String, var age: Int) { // 생성자 작성 (constructor 키워드는 생략 가능)
// Kotlin은 필드를 만들면 getter, setter을 자동으로 만들어준다.
}
3) 필드만 존재하는 경우 중괄호 {} 생략이 가능하다.
class Person3 (
val name: String,
var age: Int)
getter/setter 사용 방법
fun main() {
// JAVA 클래스를 코틀린에서 사용할때도 아래와 같은 방법으로 사용할 수 있다.
val person = Person("KIM", 20)
println(person.name) // getter
person.age = 10 // setter
}
생성자
1) 기본 생성자(주 생성자)는 반드시 존재해야한다.
class Person5 (
// 기본생성자 (=주생성자)는 반드시 존재해야한다.
val name: String,
var age: Int) { ... }
2) init {...}를 사용하여 초기화 블록 사용이 가능하다.
- 생성자가 호출되는 시점에 수행된다.
init {
if (age <= 0) {
throw IllegalArgumentException("나이는 ${age}일 수 없습니다.")
}
println("초기화 블록")
}
3) 부 생성자 생성
constructor(name: String): this(name, 1) { // 위의 기본 생성자를 호출
println("첫번째 부 생성자") // body add 가능
}
프로퍼티
class Person (
name: String = "KIM",
var age: Int = 10) {
init {
if (age <= 0) {
throw IllegalArgumentException("나이는 ${age}일 수 없습니다.")
}
println("초기화 블록")
}
val name = name // 주 생성자에서 받은 name을 담는다.
get() = field.uppercase() // field 키워드
...
}
1) field.uppercase()
field 키워드 사용 이유
- field 키워드 : 자신을 가리킨다.
- name.uppercase()로 한다면, name.getter 을 계속 호출하기 때문에 field 키워드를 사용해야한다.
- ex) name -> get() -> name -> get()
2) 프로퍼티 get() 선언시 return문 사용도 가능하다.
val isAdult: Boolean
get() = this.age >= 20
val isAdult2: Boolean
get() {
return this.age >= 20
}
3) set() 선언도 가능하다.
var name = name
set(value) { // setter
field = value.uppercase()
}
상속
- 코틀린은 클래스와 메서드는 기본적으로 final 이라 상속이 불가하다.
- 인터페이스와 abstract 클래스에서는 final 이 없는 메서드나 프로퍼티 기본적으로 open 이다.
키워드 | 설명 |
final | override 할 수 없게 한다. default이다. |
open | override를 열어준다. |
abstract | 반드시 override 해야한다. |
override | 상위 타입을 오버라이드 하고있다. |
1) 상위 클래스
- open 키워드를 선언해야만 자식클래스에서 상속을 받을 수 있다.
- 상속을 받을 경우 자식 클래스는 부모 클래스의 프로퍼티와 함수를 사용할 수 있다.
open class Base(
// 상위 클래스를 설계할때 생성자 또는 초기화 블록에 사용되는 프로퍼티에는 open을 피해야한다.
open val number: Int = 100
) {
init {
println("Base Class")
// 0 출력 - 상위클래스 생성자가 생성되는 동안, 하위 클래스의 number 는 아직 초기화가 안되었다.
println(number)
}
}
2) 하위 클래스
- 하위 클래스의 객체를 생성할대 상위 클래스의 생성자가 호출된다. 따라서 하위 클래스에서 상위 클래스의 메서드나 프로퍼티를 사용할 수 있다.
class Derived(
override val number: Int
) : Base(number) {
init {
println("Derived Class")
}
}
main()
fun main() {
/*
Base Class
0 --> 상위클래스 생성자가 생성되는 동안, 하위 클래스의 number 는 아직 초기화가 안되었다.
Derived Class
*/
Derived()
}
접근제어
- 생성자 : constructor 키워드 생략이 가능했는데, 접근지시어를 적으려면 constructor 키워드를 명시해야한다.
- 프로퍼티 : getter, setter 에 동일하게 적용된다.
public | protected | internal | private | |
기본 | 모든 곳에서 접근 가능 | 선언된 클래스 또는 하위 클래스에서만 접근 가능 | 같은 모듈에서만 접근 가능 | 선언된 클래스 내에서만 접근 가능 |
파일 | 모든 곳에서 접근 가능 | 파일 최상단에 사용 불가능 | 같은 모듈에서만 접근 가능 | 같은 파일 내에서만 접근 가능 |
클래스 | 모든 곳에서 접근 가능 | 선언된 클래스 또는 하위 클래스에서만 접근 가능 | 같은 모듈에서만 접근 가능 | 선언된 클래스 내에서만 접근 가능 |
생성자 | 모든 곳에서 접근 가능 | 선언된 클래스 또는 하위 클래스에서만 접근 가능 | 같은 모듈에서만 접근 가능 | 선언된 클래스 내에서만 접근 가능 |
프로퍼티 | 모든 곳에서 접근 가능 | 선언된 클래스 또는 하위 클래스에서만 접근 가능 | 같은 모듈에서만 접근 가능 | 선언된 클래스 내에서만 접근 가능 |
1) 프로퍼티 제어
- setter에만 추가로 가시성 부여 가능하다.
class Car(
internal val name: String, // getter, setter 부여
private val owner: String, // getter, setter 부여
_price: Int
) {
// default public
var price = _price
// private set, public get
var price2 = _price
private set
}
object 키워드
1) companion object
- 해당 블록 안에는 모두 static으로 선언된다.
- const : 컴파일시에 변수가 할당된다. 상수에 사용하기 위한 용도로, 기본타입과 String에 붙일 수 있다.
- 코틀린에서는 static 키워드가 없고 companion object 를 사용한다.
class Person private constructor(
var name: String,
var age: Int
) {
// static : 클래스가 인스턴스화 될때 새로운 값이 복제되는것이 아니라 정저긍로 인스턴스끼리의 값을 공유한다.
// companion objedct : 클래스와 동행하는 유일한 오브젝트 (동반객체도 하나의 객체로 간주된다. 이름을 붙일 수 있고, interface를 구현할수도 있다.)
// kotlin 에는 static이 없다. companion object 라고 해야한다.
companion object Factory : Log { // 해당 블록 안에는 모두 static 으로 선언된다.
// const : 컴파일시에 변수가 할당된다. (상수에 붙이기 위한 용도, 기본 타입과 String에 붙일 수 있음)
// 기본 val : 런타임시에 변수가 할당된다.
const val MIN_AGE = 1
fun newBaby(name: String): Person {
return Person(name, MIN_AGE)
}
override fun log() {
println("나는 Person 클래스의 동행 객체입니다.")
}
// 유틸성 함수들을 넣어도 되지만, 최상단 파일을 활용하는 것을 추천한다.
}
}
2) object 키워드
- object : 단 하나의 인스턴스만 갖는 싱글톤 클래스를 생성한다.
fun main() {
// 유일한 객체이므로 바로 접근 가능하다.
Singleton.a
Test.a
}
object Singleton {
var a: Int = 0
}
object Test {
var a: Int = 0
}
3) 익명클래스 : object : Class명
fun main() {
moveSomething(object : Movable { // object : 타입이름 으로 익명클래스 표현
override fun move() {
println("움직인다")
}
override fun fly() {
println("난다")
}
})
}
중첩 클래스
1) 클래스 안의 클래스 | 2) 클래스 안의 inner 클래스 |
바깥 클래스 참조가 없다. (권장) | 바깥 클래스 참조가 존재한다. 이 경우, 참조를 해지하지 못하게되어 메모리 누수가 생길 수 있다. |
1) 클래스 안의 클래스
class JavaHouse(
private val adress: String,
private val livingRoom: LivingRoom
) {
class LivingRoom( // 기본적으로 바깥 클래스에 대한 연결이 없는 중첩 클래스
private var area: Double
)
}
2) 클래스 안의 inner 클래스
- inner : 바깥 클래스에 대한 참조가 존재하는 중첩클래스
class JavaHouse2(
private val address: String,
private val livingRoom: LivingRoom
) {
inner class LivingRoom( // 기본적으로 바깥 클래스에 대한 연결이 없는 중첩 클래스
private var area: Double
) {
val address: String
get() = this@JavaHouse2.address
}
}
다양한 클래스
1) data class : equals, hashCode, toString 자동으로 만들어준다.
data class PersonDto ( // data 키워드 : equals, hashCode, toString 자동으로 만들어준다.
// name arguments 사용하면 builder 와 같은 효과도 얻을 수 있다.
val name:String,
val age: Int
) {
}
2) enum class
enum class Country(
private val code: String
) {
KOREA("KO"),
AMERICA("US")
;
}
3) sealed class
- 컴파일러는 부모 클래스를 상속받은 자식 클래스들의 존재를 알지 못하는데, sealed class를 사용하면 자식 클래스의 존재를 알게되어 제한하는 특성을 가진다.
sealed class HyundaiCar(
val name: String,
val price: Long
)
// 추상화가 필요한 Entity or DTO에 sealed Class 를 활용한다.
private fun handleCar(car: HyundaiCar) {
when (car) {
is Avante -> TODO()
is Grandeur -> TODO()
is Sonata -> TODO()
// else 를 사용하지 않아도 된다. 컴파일러가 자식클래스를 알고있기 때문이다.
}
}
배열
1) 배열 생성
val array = arrayOf(100, 200) // 배열 생성
2) 배열 index for문
// index
for (i in array.indices) {
println("${i} ${array[i]}")
}
3) 배열 index, value
// index, value
for ((idx, value) in array.withIndex()) {
println("$idx $value")
}
4) 배열에 새로운 원소 추가 : push
array.plus(300) // 배열에 새로운 element 추가
Collection
Collection | 설명 |
가변 컬렉션 (Mutable) | 컬렉션에 element를 추가, 삭제할 수 있다. |
불변 컬렉션 | 컬렉션에 element를 추가, 삭제할 수 없다. 불변리스트의 1번째 원소에 접근해서 그 안의 필드(예) price)를 바꿀 수는 있다. |
▶ Null 처리
null 처리 | 설명 |
List<Int?> | 리스트에 null이 들어갈 수 있다. 리스트 자체는 절대 null이 아니다. |
List<Int>? | 리스트에 null이 들어갈 수 없다. 리스트 자체는 null일 수 있다. |
List<Int?>? | 리스트에 null이 들어갈 수도 있다. 리스트 자체도 null일 수 있다. |
List
1) 불변리스트 생성 : listOf()
val numbers = listOf(100, 200) // 불변 리스트
2) 빈 리스트 생성 : emptyList<Type>()
- 타입을 명시해야한다. 만약 타입을 추론할 수 있는 경우에는 타입 생략 가능하다.
val emptyList = emptyList<Int>() // 빈 리스트는 타입을 명시해야한다.
3) 0번째 index 원소 출력
println(numbers[0])
4) 리스트 for~in
for (number in numbers) {
println(number)
}
5) 리스트 index, value
for ((idx, value) in numbers.withIndex()) {
println("$idx $value")
}
6) 가변 리스트 생성 : mutableListOf()
val numbers2 = mutableListOf(100, 200)
numbers2.add(300)
Set
1) 가변 Set 생성 : mutableSetOf()
val numbers = mutableSetOf(100L)
Map
1) 가변 Map 생성 : mutableMapOf()
val oldMap = mutableMapOf<Int, String>()
2) index 번째의 원소 값 변경
// oldMap.put(1, "A")
oldMap[1] = "MONDAY"
oldMap[2] = "TUESDAY"
3) to : key to value
// 중위 호출 (Pair 클래스를 만들어주고 전달)
mapOf(1 to "MONDAY", 2 to "TUESDAY")
4) for문 - keys
for (key in oldMap.keys) {
println(key)
println(oldMap.get(key))
println(oldMap[key])
}
5) for문 - entries
for ((key, value) in oldMap.entries) {
println(key)
println(value)
}
확장함수
1) 확장함수 lastChar() : String 클래스를 확장한다는 의미다.
- fun 확장하려는 클래스.함수이름(파라미터) : 리턴타입 { ... }
- this를 통해서 불려진 인스턴스 접근이 가능하다.
- this : 수신객체
- 확장하려는 클래스 : 수신객체 타입
fun String.lastChar(): Char {
return this[this.length - 1] // 마지막 문자
}
infix (중위 함수)
infix fun Int.add2(other: Int): Int {
return this + other
}
main()
fun main() {
3.add(4)
3.add2(4)
3 add2 4 // 중위 함수(infix) 사용
}
inline 함수
- 인라인 함수로 정의된 함수는 컴파일 단계에서 호출하는 방식이 아닌, 코드 자체가 복사되는 방식으로 컴파일된다.
- 함수를 호출한 지점에 함수 본문을 그대로 복붙하고 싶은 경우 사용한다.
- 함수를 파라미터로 전달할때의 오버헤드를 줄일 수 있다.
- 인라인 함수의 사용은 성능 측정과 함께 신중하게 사용되어야한다.
inline fun Int.add3(other: Int): Int {
return this + other
}
main()
- add3() 함수에 바이트 코드가 메모리에 할당되어있고, main()의 내용은 sum() 함수의 바이트 코드가 저장된 주소를 호출하는 바이트 코드를 포함하는데, inline 함수이기 때문에 직접 a + b로 복붙한다.
fun main() {
3.add3(4) // int var10000 = $this$add$iv + other$iv
}
지역함수
fun createPerson(firstName: String, lastName: String): Person {
// depth가 깊어지고 코드가 간결하지는 않다.
fun validateName(name: String, fieldName: String) {
if (name.isEmpty()) {
TODO()
}
}
validateName(firstName, "firstName")
validateName(lastName, "lastName")
return Person(firstName, lastName, 1)
}
람다
- 코틀린에서는 람다가 시작하는 지점에 참조하고있는 변수들을 모두 포획해서 정보를 가지고있기 때문에, final인 변수만 사용 가능한 JAVA와 다르게 아무런 제약이 없다.
- 함수가 불려지는 시점에 존재하는 변수들을 모두 포획한다. (Closure)
private fun filterFruits(fruits: List<Fruit>, filter: (Fruit) -> Boolean): List<Fruit> {
val results = mutableListOf<Fruit>()
for (fruit in fruits) {
if (filter(fruit)) {
results.add(fruit)
}
}
return results
}
main()
fun main() {
var a = "aa"
a = "bb"
val fruits = listOf(
Fruit("사과", 1000),
Fruit("사과", 2000),
Fruit("사과", 3000)
)
filterFruits(fruits) { it.name == a }
}
1) use
- Closeable 구현체에 대한 확장함수
Closeable.kt
@InlineOnly
@RequireKotlin("1.2", versionKind = RequireKotlinVersionKind.COMPILER_VERSION, message = "Requires newer compiler version to be inlined correctly.")
public inline fun <T : Closeable?, R> T.use(block: (T) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
var exception: Throwable? = null
try {
return block(this)
} catch (e: Throwable) {
exception = e
throw e
} finally {
when {
apiVersionIsAtLeast(1, 1, 0) -> this.closeFinally(exception)
this == null -> {}
exception == null -> close()
else ->
try {
close()
} catch (closeException: Throwable) {
// cause.addSuppressed(closeException) // ignored here
}
}
}
}
.use { ... } 사용 예제
- 인라인 함수
- 람다를 받게 만들어진 함수
class FilePrinter2 {
fun readFile(path: String) {
// try~catch~resource 구문 자체는 사라지고 use를 사용한다.
BufferedReader(FileReader(path)).use { // lambda
reader -> println(reader.readLine())
}
}
}
'Coding > Kotlin' 카테고리의 다른 글
함수형 프로그래밍이란? (with 코틀린 예제) (1) | 2023.11.22 |
---|---|
[Kotlin in Action] 26. 수신 객체 지정 람다 (with, apply) (0) | 2022.06.19 |
[Kotlin in Action] 25. SAM 생성자 (0) | 2022.06.19 |
[Kotlin in Action] 24. 지연 계산(lazy) 컬렉션 연산 - 시퀀스(sequence) 사용 (0) | 2022.06.13 |
[Kotlin in Action] 23. 컬렉션 함수형 API (filter, map, all, any, count, find, groupBy, flatMap, flatten) (0) | 2022.06.01 |