최근 수정 시각 : 2026-07-03 10:24:41

Kotlin/문법


#!if top = 문서명1 != null ? 문서명1 : calleeTitle != null ? (i = calleeTitle.lastIndexOf("/")) != -1 ? calleeTitle.substr(0, i) : '상위 문서' : '상위 문서'
[[파일:상위 문서 아이콘.svg|align=left&width=21.6px]] {{{#!html  }}} 상위 문서: [[]]


#!style
.table > div {
    margin: 5px 0;
}
.table td {
    border-style: solid none;
}
.sub-title {
    margin: 5px;
    padding: 3px;
    border-radius: 5px;
    background: #154c79;
    color: #fff;
    font-weight: bold;
}
프로그래밍 언어 문법
{{{#!folding [ 펼치기 · 접기 ]
{{{#!wiki class="table"
#!wiki class="sub-title"
프로그래밍 언어 문법
C(포인터 · 구조체 · 공용체 · size_t) · C++(이름공간 · 클래스 · 특성 · 상수 표현식 · 람다 표현식 · 템플릿/제약조건/메타 프로그래밍 · 코루틴) · C# · Forth · Java · Python(함수 · 모듈) · Kotlin · MATLAB · SQL · PHP · JavaScript(표준 내장 객체, this) · Haskell(모나드) ·
#!wiki class="sub-title"
마크업 언어 문법
HTML · CSS
#!wiki class="sub-title"
개념과 용어
자료형 · 함수(인라인 함수 · 고차 함수 · 콜백 함수 · 람다식) · 리터럴 · 문자열 · 식별자(예약어) · 네임스페이스 · 조건문 · 반복문 · 비트 연산 · 참조에 의한 호출 · 상속 · 예외 · eval · 호이스팅
#!wiki class="sub-title"
기타
#! · == · === · deprecated · GOTO · NaN · null · undefined · S-표현식 · 배커스-나우르 표기법
}}}}}}
프로그래밍 언어 목록 · 분류 · 문법 · 예제


1. 개요2. 기본3. 파일 구조4. 패키지5. 모듈6. import7. 타입
7.1. 리터럴7.2. String7.3. Unit7.4. Any7.5. Nothing7.6. Array7.7. 컬렉션
8. 변수 선언
8.1. Nullable
9. 출력10. 입력11. 연산자
11.1. 오버로딩11.2. 산술11.3. 증감11.4. 부호11.5. 비교11.6. 논리11.7. 비트11.8. 시프트11.9. 대입11.10. 형변환11.11. 범위11.12. null 관련11.13. 호출11.14. 기타 특수 연산자11.15. 우선순위
12. 조건문
12.1. if12.2. when
13. 반복문
13.1. while13.2. for13.3. 분기
14. 라벨15. 메인 함수16. 제어자
16.1. 접근 제어자16.2. 기타 제어자
17. 함수
17.1. 오버로딩17.2. 확장 함수17.3. infix 함수
18. 스코프 함수
18.1. let18.2. run18.3. also18.4. with18.5. apply18.6. takeIf18.7. takeUnless
19. 람다식
19.1. 기본19.2. 고차함수
20. 클래스
20.1. Getter, Setter20.2. 상속20.3. 추상 클래스20.4. 데이터 클래스20.5. 봉인 클래스20.6. 위임
21. 인터페이스22. 여러 객체
22.1. 오브젝트22.2. 동반 객체
23. 제네릭
23.1. where23.2. reified
24. 예외

1. 개요

프로그래밍 언어 Kotlin의 문법을 정리한 문서이다.

2. 기본

코틀린의 주목적은 현재 자바가 사용되고 있는 모든 용도에 적합하면서도 더 간결하고 생산적이며 안전한 대체 언어를 제공하는 것이다.
1장 코틀린이란 무엇이며, 왜 필요한가?

코틀린은 개발자를 더 행복하게 하기 위해 고안된 모던하면서도 성숙한 프로그램언어 입니다. 간결하고 안전하며 Java 및 기타 언어와 상호 운용 가능하며 생산적인 프로그래밍을 위해 여러 플랫폼 간에 코드를 재사용할 수 있는 다양한 방법을 제공합니다.
원문: Kotlin is a modern but already mature programming language designed to make developers happier. It's concise, safe, interoperable with Java and other languages, and provides many ways to reuse code between multiple platforms for productive programming.
Get started with Kotlin

코틀린은 플랫폼에 따라 동작이 달라질 수 있다. 여기서는 JVM에서 동작을 기본으로 서술한다.

코틀린의 코드는 객체 지향을 원칙으로 하며, 자바100% 연계되는 문법을 사용하고 있다.[1]

일반 파일은 .kt, 스크립트의 경우 .kts의 확장자를 가진다.

상술했듯 자바와 굉장히 비슷한 문법 구조를 가지고 있어 이 문서를 읽기 전에 Java/문법 문서를 읽으면 큰 도움이 된다.

3. 파일 구조

[package 이름 명시]
[import할 패키지 명시]
[클래스 구현, 변수 및 함수 선언]

package는 생략할 수 있다. 자세한 건 후술한다.

Java와 다른점은 다음과 같다.
  • 패키지를 실제 파일의 경로와 동일하게 명시할 필요가 없다. 다만, 일관성을 위해 동일하게 명시하는 것을 추천한다.
  • 하나의 파일이 여러개의 public class를 가질 수 있다. 비슷하게 파일 이름에 제한이 없다.[2]
  • top-level에 변수와 함수를 선언할 수 있다.

4. 패키지

#!syntax kotlin
// Main.kt
package com.example

fun main() {
    println(MainKt::class) // class com.example.MainKt
}
자바와 동일하게 사용한다. 파일 최상단에 표기한다. 보통 해당 파일의 경로를 뜻한다.[3] 이 경우에서 파일의 위치는 com/example/Main.kt를 권장한다. 클래스 중복을 방지하기 위해 존재하며 클래스 이름이 같더라도 패키지가 다르면 구분할 수 있다.
패키지 이름은 절대 겹치지 않도록 하는 것이 좋다. 때문에 도메인 기반 패키지를 사용하는 편이다. 예시로 com.google..., org.apache... 등이 있다. 그 외에도 비슷한 클래스끼리 묶어서 보기 편하게 하기 위해 사용한다.[4]

#!syntax kotlin
fun main() {
    println(MainKt::class) // class MainKt
}
자바와 마찬가지로 패키지를 쓰지 않을 경우 default package가 된다. 다른 패키지에서 접근할 수 없으므로 추천하지 않는다.

5. 모듈

internal 접근 제어자에서 사용되는 한번에 컴파일되는 단위이다.

IntelliJ IDEA 프로젝트에서는 IDE의 모듈이, Gradle과 Maven에서는 하나의 프로젝트(또는 gradle에서의 서브프로젝트)가, kotlinc에서는 한번에 컴파일 시 동일한 모듈로 취급된다.

6. import

다른 패키지의 클래스에 접근하기 위해서는 패키지명. 클래스명인 완전 수식 이름을 사용하거나 import를 해야 한다.

#!syntax kotlin
// Main.kt
package com.example

import net.sample.foo

fun main() {
    foo()
}

// Foo.kt
package net.sample

fun foo() {
    println("foo")
}
대체로 자바와 비슷하다. 차이는 import static이 구분되지 않고 import 하나로만 다룬다.
컴파일 시 import는 제거되고 모두 완전 수식 이름으로 교체되므로 import만으로 클래스가 로딩되거나 하지 않는다.
기본적으로 import되는 패키지는 다음과 같다.
kotlin.*
kotlin.annotation.*
kotlin.collections.*
kotlin.comparisons.*
kotlin.io.*
kotlin.ranges.*
kotlin.sequences.*
kotlin.text.*
플랫폼별 추가 기본 import도 있다. 예시로 JVM은 java.lang.*과 kotlin.jvm.*가 추가로 import 된다.
같은 이름의 클래스가 있을 때 우선순위는 현재 스코프 > 현재 파일 > 명시적 import > 현재 패키지 > * import > 기본 import이다.

#!syntax kotlin
import kotlin.io.println as log

fun main() {
    log("Hello, World!")
}
자바와 달리 별칭을 쓸 수 있다.

7. 타입

Kotlin에는 원시 타입[5]과 그 래퍼 클래스[6]가 구분되지 않고 모든 타입이 객체로 취급되고, 컴파일 시 가능한 경우 원시 타입으로 변환된다.

아래 타입들은 Java의 원시 타입과 대응된다. 소괄호는 대응되는 자바 타입이다.
  • 부호 있는 정수: Long(long) > Int(int) > Short(short) > Byte(byte)
  • 실수: Double(double) > Float(float)
  • 논리: Boolean(boolean)
  • 문자: Char(char)

다음 타입들은 Java에는 없는 타입이다. 코틀린에서는 내부적으로 각 사이즈에 맞는 부호있는 정수로 저장하고, toString() 메서드가 부호 없는 정수로 반환한다.
ULong[7] > UInt[8] > UShort[9] > UByte[10]

7.1. 리터럴

#!syntax kotlin
val a = 12 // Int
val b = 10000000000 // Long
val c = 1L // Long
val d: Byte = 2 // Byte
val e = 3.14 // Double
val f = 3.14f // Float
val g = 3u // UInt
val h = 3uL // ULong
정수는 문맥에 따라 타입을 결정하며, 실수는 무조건 Double로 취급한다. 부호 없는 정수는 뒤에 u를 붙인다.

7.2. String

#!syntax kotlin
val a = "abc"
val b = String("abc")
자바와 동일하게 생성자 없이 쓸 수 있다.

7.3. Unit

단 하나의 인스턴스를 가진 싱글톤 객체로, 모든 함수가 값을 반환하는 코틀린의 특징을 유지하기 위해 존재하는 의미없는 값이다. 때문에 Unit을 직접 다루는 경우는 없고[11] 자바의 void를 대체하기 위해 사용된다.
객체 타입이기에 제네릭에서 쓸 수 있다.

7.4. Any

최상위 클래스로 자바이 Object에 대응된다. 단, 몇몇 함수가 없다.

7.5. Nothing

최하위 클래스로 값을 가지지 않는 타입이다.
throw의 반환 타입이며, 문맥에 따라 return, continue, break의 반환 타입이기도 한다.[12]
직접 쓰는 경우는 함수가 반환은 하지 않음을 알릴 때 사용된다. 여기서 반환하지 않는 함수는 종료되지 않거나[13] 예외를 던지는 경우를 말한다. Nothing 함수 이후의 코드는 IDE에서 죽은 코드임을 알 수 있다.

7.6. Array

JVM에서 특별하게 관리되는 객체인 Java와 달리, Kotlin은 제네릭을 사용하여 일반적인 객체로 다룬다.

단, 원시 타입의 배열은 따로 존재한다.[14]
  • 원시 타입 배열[15]
    • 부호 있는 정수의 배열: LongArray, IntArray, ShortArray, ByteArray
    • 부호 없는 정수의 배열: ULongArray, UIntArray, UShortArray, UByteArray
    • 실수 배열: DoubleArray, FloatArray
    • 논리 배열: BooleanArray
    • 문자 배열: CharArray
  • 참조 타입 배열: Array<T>[16]

#!syntax kotlin
val intArr = intArrayOf(1, 2, 3, 4) // { 1, 2, 3, 4 }가 저장된다.
val uintArr = uintArrayOf(1, 3, 7, 1)
val boolArr1 = BooleanArray(3) // { false, false, false }가 저장된다.
val boolArr2 = BooleanArray(5) { it % 2 == 0 } // { true, false, true, false, true }가 저장된다.
val strArr1: Array<String> = arrayOf("a", "b", "c")
val strArr2: Array<String?> = arrayOfNulls<String>(2) // { null, null }이 저장된다.
val anyArr: Array<Any> = arrayOf(1, "a", 3.4, false)

7.7. 컬렉션

코틀린이 자바에서 비롯됐기 때문에 이러한 부분은 자바에 의존한다 볼 수 있다. 자바의 컬렉션을 보고 오자.
컬렉션은 변경 불가한 Collection와 변경 가능한 MutableCollection으로 나누어져 있으며, 컬렉션을 List, Set, Map처럼 인터페이스로 다룬다.
Array와 비슷하게 listOf(), mutableSetOf(), mapOf() 와 같은 함수로 객체를 만들 수 있으며, 이때 만들어지는 객체의 타입은 각각 ArrayList, LinkedHashSet, LinkedHashMap이며[17], 함수의 반환 타입은 각 객체의 상위 인터페이스인 (Mutable)List, (Mutable)Set, (Mutable)Map이다.
Java의 클래스를 직접 다루지 않기 때문에 임포트가 필요 없다.

#!syntax kotlin
val list = listOf<String>()
list += "a"
list += "b"
println(list[0]) // a
println(list.getOrNull(3)) // null

8. 변수 선언

기본적으로 '(이름): (타입)'의 구조를 가지며 이는 함수 선언과 매개변수, catch 문에서 받을 변수 등에서도 해당한다.

#!syntax kotlin
var namu: Int
Int 타입의 namu라는 변수를 선언한다. var는 variable의 약자다.

#!syntax kotlin
namu = 3
namu 변수에 값을 대입할 수 있다. 여기서 namu가 어떤 값도 가지고 있지 않은 경우[18] 초기화한다고 부른다.

#!syntax kotlin
var namu: Int = 3;
선언과 동시에 대입할 수도 있다.

#!syntax kotlin
val namu: Int = 3;
val을 사용하여 불변 변수로 만들 수 있다. 재대입이 불가능하다.[19] val은 value의 약자다.
val을 주로 사용하는 것을 추천한다. 실제로 IntelliJ IDEA에서는 val을 사용할 수 있는 경우 경고를 한다.

#!syntax kotlin
val namu = 3;
타입을 직접 적지 않아도 된다. 이 경우 컴파일러가 타입추론을 한다.

#!syntax kotlin
println(namu);
변수의 값을 참조할 수도 있다.

#!syntax kotlin
namu = namuwiki;
변수의 값을 이렇게 복사할 수도 있다.[20]

8.1. Nullable

#!syntax kotlin
val a: Int? = null
//val b: Int = null // error

val c: String? = " Hello, World! "
// trim()은 앞 뒤 공백을 제거하는 함수다.
//c.trim() // error
c?.trim() // 만약 c가 null이라면 호출 대신 null을 반환한다.

val d: Int? = getIntOrNull() // 정수 또는 null을 반환하는 임의의 함수
println(d!!) // d가 null이라면 NPE를 발생시키며, 아니면 그대로 사용한다. 이후에 사용되는 d는 자동으로 non-nullable로 항변환된다. 스마트 항변환이라고 한다.

val e: Int = getIntOrNull() ?: 0 // null이 반환되면 뒤에 있는 0을 사용한다.

val f = getIntOrNull()
if (f != null) println(f + 1) // 스마트 항변환. if문 내에서는 non-nullable로 항변환한다.

기본적으로 모든 타입은 non-nullable로 null을 사용할 수 없으며, 타입 뒤에 ?를 붙여 nullable 타입으로 선언해 사용해야 한다. 하지만 nullable의 경우, NullPointerExceptionNPE가 발생할 수 있어 바로 메서드를 호출할 수 없다.[21]

nullable 변수의 메서드를 호출하려면 safe call 연산자인 ?.를 사용한다. 이 연산자는 이름 그대로 "안전한 호출"이며, null이 아니면 메서드를 호출, null이면 호출하지 않고 대신에 null을 반환한다.

만약 null이 아님을 확신했거나 의도적으로 NPE를 발생시키려면 변수 뒤에 Non-null Assertion 연산자인 !!를 붙이면 된다. null이라면 NPE로 실행이 끊기므로 불변 변수에 한에 스마트 캐스팅이 된다.[22]

null일 경우 기본값 개념으로 대체할 수 있다. Elvis 연산자인 ?:는 앞의 값이 null이라면 뒤의 값을 사용한다.

!!, if문, 스코프 함수 등 null이 아님을 검사했다면 그 이후에서는 non-nullable로 스마트 캐스팅된다.
스마트 캐스팅은 자동으로 항변환해 주는 기능으로, nullable 변수가 null일 경우 도달할 수 없는 위치에서는 null이 아님이 확정되어서 non-nullable 변수 처럼 다룰 수 있다. 단, var로 선언한 가변 변수는 다른 스레드에서 변경 가능성이 있어 스마트 캐스팅이 안 되는 경우도 있다.
여담으로 스마트 캐스팅은 형변환이 가능한지 검사하는 is 연산자에 의해서도 발생할 수 있다.

9. 출력

#!syntax kotlin
println("Hello, World!")
print("Hello, World!\n")
각각 자바의 IO.println(), IO.print()에 대응된다.

#!syntax kotlin
System.err.println("error message")
val str = "World!"
System.out.printf("Hello, %s", str)
표준 에러 스트림이나 printf를 사용하려면 자바의 방법을 써야 한다.

10. 입력

#!syntax kotlin
val str = readln()
표준 입력 스트림에서 한 줄 입력받는다. 작동 방식은 BufferedReader이다. EOF을 만나면 예외를 던진다.

#!syntax kotlin
val str = readlnOrNull()
readln과 동일하나, EOF을 만나면 null을 반환한다.

그 외에도 Scanner 같은 기능을 쓸 수 있다.

11. 연산자

11.1. 오버로딩

일부 연산자는 오버로딩을 하여 특정한 기능을 하도록 할 수 있다. 예시로 컬렉션에서의 +=는 add()의 기능을 하도록 오버로딩되어 있다.

11.2. 산술

연산자 대응 함수
a + b plus
a - b minus
a * b times
a / b div
a % b rem
자바와 동일하다.

11.3. 증감

++a inc
a++
--a dec
a--
자바와 동일하나, a = a + 1 또는 a = a - 1과 항상 대응되지 않는다.[23]

11.4. 부호

+a unaryPlus
-a unaryMinus

11.5. 비교

a == b equals[24]
a != b
a > b compareTo[25]
a \< b
a >= b
a \<= b
a === b -
a !== b
a in b contains
a !in b
==의 경우 내부적으로 equals를 호출하도록 되어 있다. a가 null이더라도 NPE가 발생하지 않는다. 반면 부등호 연산자는 발생할 수 있다.
===과 !==은 자바의 ==, != 처럼 주소를 비교한다. 따라서 오버로딩할 수 없다.
in은 a가 b에 포함되었는지를 뜻한다. 예를 들어 a in list는 a가 list에 포함되어 있다면 true이다.

11.6. 논리

a && b -
a \|\| b
!a not

11.7. 비트

a and b
a or b
a xor b
a.inv()
자바와 달리 연산자가 아닌 함수로 제공한다.

11.8. 시프트

a shl b
a shr b
a ushr b
마찬가지로 함수로 제공한다.

11.9. 대입

x = a -
x += a plusAssign
x -= a minusAssign
x *= a timesAssign
x /= a divAssign
x %= a remAssign
자바와 달리 값을 반환하지 않는다.

11.10. 형변환

obj is Class
obj !is Class
obj as Class
obj as? Class
is는 자바의 instanceof에 대응된다.
as는 obj를 Class로 형변환하며, 실패시 예외를 던진다. as?는 실패 시 null을 반환한다.

11.11. 범위

a..b rangeTo
a..\<b until
a downTo b downTo
a step b step
..은 a부터 b까지의 범위, ..<은 a부터, b-1까지의 범위를 반환한다. 예를 들어 1..4는 1, 2, 3, 4가 포함된 범위를 반환한다.
downTo는 점점 감소하며, step은 한번에 여러번 증가/감소한다.

11.12. null 관련

exp1 ?: exp2
a!!
?:는 엘비스 연산자로 exp1이 null이면 대신 exp2를 사용한다.
!!는 non-null 단언 연산자로 a가 null이라면 NPE를 발생시킨다.[26]

11.13. 호출

a[i] get
a[i] = x set
a.b -
a?.b
a() invoke
각각 인덱싱, 인덱스 대입, 멤버 접근, 안전한 호출, 함수 호출이다.
안전한 호출은 a가 null이 아니면 그대로 b에 접근하며, null이라면 null을 반환한다.

11.14. 기타 특수 연산자

componentN[27]
getValue
setValue
operator 함수이긴 하나 연산자로 사용되지 않는다.

#!syntax kotlin
class Point(val x: Int, val y: Int) {
    operator fun component1() = x
    operator fun component2() = y
}

val p = Point(1, 2)
val (a, b) = p
//val a = p.component1()
//val b = p.component2()
componentN는 구조 분해에서 사용되며 개수 제한은 없다.[28]


getValue, setValue는 위임에서 사용된다.

11.15. 우선순위

우선순위 연산자 역방향 결합
1 (), [], ., ?., !!, 후위 증감 X
2 전위 증감, 부호, ! O
3 is, !is, as, as? X
4 *, /, %
5 +, -
6 .., ..\<, downTo, step
7 in, !in
8 <, >, <=, >=
9 ==, != , ===, !==
10 &&
11 \|\|
12 ?:
13 대입 O

12. 조건문

12.1. if

#!syntax kotlin
if (exp1) {
    stmt1
} else if (exp2) {
    stmt2
}
...
} else {
    stmtN
}
자바와 동일하게 소괄호 내의 식(exp1)이 true면 stmt1을, exp1이 거짓인데 exp2가 true면 stmt2를, ... 전부 false면 stmtN을 실행한다.
else if 이하는 생략 가능하다.

#!syntax kotlin
val x = if (a) b else c
자바와 달리 표현식으로 쓸 수 있으며 삼항 연산자를 대체한다. 이 경우 a가 true라면 b를, false라면 c를 x에 대입힌다. else if를 이용할 수도 있다.

12.2. when

#!syntax kotlin
when (exp) {
    value1 -> stmt1
    value2 -> {
        stmt2
    }
    value3, value4 -> stmt3
    in list -> stmt4
    is Char -> stmt5
    else -> stmt6
}
자바개선된 switch 문과 비슷하다.

#!syntax kotlin
val a = when (exp1) {
    value1 -> exp2
    value2 -> {
        stmt
        exp3
    }
    else -> exp4
}
마찬가지로 표현식으로 쓸 수 있다. 중괄호가 있는 경우 마지막 표현식의 반환값을 반환한다.
sealed class나 enum처럼 컴파일 단계에서 모두 평가가 가능할 경우 마지막 else를 생략할 수 있다.

#!syntax kotlin
val a = 12
val b = "hello"
when {
    a == 12 -> stmt1
    "h" in b -> stmt2
}
별도의 소괄호 없이 쓸 수 있다.

13. 반복문

13.1. while

#!syntax kotlin
while (exp1) {
    stmt1
}

do {
    stmt2
} while (exp2)
자바와 동일하다.

13.2. for

#!syntax kotlin
for (i in iterable) {
    stmt
}
자바의 for-each와 비슷하다. Iterable을 구현한 객체에 대해 각 데이터를 순환하며 반복한다. 여기서 i는 자바와 달리 불변 변수다.

#!syntax kotlin
for (i in 0..9) {
    println("안녕하세요")
}
for (i in 0 until 10) {
    println("안녕하세요")
}
for (i in 9 downTo 0) {
    println("안녕하세요")
}
for (i in 0 until 10 step 2) {
    println("안녕하세요")
}
for (i in 10 downTo 1 step 3) {
    println("안녕하세요")
}
for (i in arrayOf("0", "1", "2", "3", "4", "5", "6", "7", "8", "9")) {
    println("안녕하세요")
}
해당 for문들은 모두 같은 역할을 한다.

13.3. 분기

#!syntax kotlin
for (e in arr) {
    if (e == null) continue
    println(e)
}
해당 코드는 배열 arr의 각 원소에 대하여 null이 아닐경우 출력하는 코드이다.
continue는 현재 반복문에서 다음 반복으로 넘어간다. while인 경우 다음 반복 전에 조건을 검사한다.

#!syntax kotlin
while (true) {
    val str = readln("비밀번호:");
    if (str == "pass") break;
}
해당 코드는 pass를 입력받을 때까지 계속 입력을 받는 코드이다.
break는 현재 반복문에서 탈출한다.

14. 라벨

#!syntax kotlin
outer@for (arr : arr2d) for (e : arr) {
    if (...) continue@outer
}
자바와 동일하게 쓸 수 있다.
자바와 마찬가지로, if, try, 중괄호 블록 등 문이면 다 붙일 수 있으나, break, continue, return을 사용할 수 없어 의미없다.

#!syntax kotlin
list.forEach {
    if (it == null) return@forEach
    println(it)
}

run {
    if (...) return@run
    stmt
}
자바와 달리 람다에서 쓸 수 있다. 여기서 return은 람다를 종료하는 역할을 한다.[29]

15. 메인 함수

#!syntax kotlin
fun main(args: Array<String>) {
    println("Hello, World!")
}
Java와 비교하면 메인 함수의 길이가 상당히 짧다.

#!syntax kotlin
fun main() {
    println("Hello, World!")
}
Kotlin 1.3 버전부터는 매개변수를 생략할 수 있다.

16. 제어자

16.1. 접근 제어자

자바와 다르게 기본값이 public이다.
  • public: 공개. 어디에서나 접근 가능하다.
  • Internal: 내부. 코틀린에 새로 추가된 제어자로, 동일 모듈[30]에서만 접근 가능하다.
  • protected: 보호. 해당 클래스나 파일에서만 접근 가능하지만, 상속 관계라면 외부에서도 접근 가능하다.
  • private: 비공개. 해당 클래스나 파일에서만 접근 가능하다.

자바의 접근 제어자와 차이가 있어, 자바와 코틀린을 함께 사용할 때 접근 범위에 차이가 생긴다.
  • 코틀린에서 자바 코드 사용
    • default: 코틀린에서는 package-private 개념이 없어. private로 취급된다.
    • protected: 코틀린의 접근 범위가 적용되어 상속 관계가 아니면 접근할 수 없다.
  • 자바에서 코틀린 코드 사용
    • internal: public으로 취급하지만 권장하지 않는다. 경고를 하는 IDE도 있다.
    • protected: 자바의 접근 범위가 적용되어 동일 패키지라면 사용 가능하지만, 권장하지 않는다. 이 역시 경고를 하는 IDE도 있다.

16.2. 기타 제어자

  • final, open: 수정 가능. 기본값은 final이며 기본값이 다르다는 걸 제외하면 자바와 동일하다.
  • abstract: 추상. 자바와 동일하다. 추상 클래스는 open이 자동으로 적용된다.
  • override: 재정의. 자바에서는 동일한 이름, 매개변수를 가지면 재정의되었고, 어노테이션을 사용해 명시했다면 코틀린에서는 제어자를 사용해야 한다.
  • lateinit: 초기화 지연. 변수의 초기화 시점을 지연한다. 객체 타입인 가변 변수에서만 사용할 수 있다.
  • const: 상수. 원시 타입 또는 String 타입인 val 키워드로 선언된 상수에 사용 가능하며, 자바의 static final로 컴파일된다.
  • data: 데이터. 클래스에 사용 가능하며, 필드에 대해 equals, hashCode, toString 등의 메서드를 자동으로 생성한다.
  • infix: 객체 a에 대해 하나의 매개 변수 m을 받는 메서드에 사용 가능하다. 메서드 이름을 method라고 할 때 호출을 a.method(b) 대신 a method b로 호출할 수 있다.
  • operator: 해당 메서드가 연산자를 정의하는 메서드임을 알린다. 이는 자바에서 원시 타입과 String에서만 사용 가능한 연산자를 다른 객체에서 사용할 수 있게 된다. 예를 들어 + 연산자는 plus()메서드를 호출하고, 이 메서드를 정의하면 다른 객체에서 + 연산자를 쓸 수 있다
  • external: 해당 메서드의 구현이 현재 언어 밖에 있음을 선언한다. JVM에서는 자바의 native와 동일하다.

17. 함수

자바와 달리 모든 함수가 반환값을 가진다.

#!syntax kotlin
// fun 키워드와 함수이름을 쓰고 뒤에 소괄호를 쓴다. 반환 타입은 마지막에 쓴다.
fun name(): Unit {
    // 함수 내용
}
함수를 선언하려면 다음과 같이 작성한다.

#!syntax kotlin
private fun myFunction(i: Int): Unit {
    // 함수 내용
}
앞에 제어자를 쓰거나, 매개변수를 지정할 수 있다.

#!syntax kotlin
// 두 정수를 받고 더한 결과를 반환하는 함수
fun sum(a: Int, b: Int): Int {
    return a + b
}
함수를 종료하고 값을 반환하려면 return을 사용한다.

#!syntax kotlin
// 문자열을 출력하는 함수
fun printHelloWorld() { // 반환타입 Unit이 생략되었다.
    println("Hello, World!")
    // 자바와 마찬가지로 return 또는 return Unit이 생략되었다.
}
Unit을 반환하는 함수는 반환 타입을 생략할 수 있으며 return Unit 대신 그냥 return으로 쓸 수 있다. 마지막 return은 생략 가능하다.

#!syntax kotlin
fun sum(a: Int, b: Int) = a + b // 이 경우 타입 추론이 가능하다.

fun printHelloWorld() = println("Hello, World!")
함수 내용이 한줄이라면 더 간단히 표현할 수 있다.

#!syntax kotlin
fun print(a: Any, newLine: Boolean = false) {
    print(a)
    if (newLine) println()
}
매개변수가 기본값을 가지도록 할 수 있다. 이 경우 print(a)만으로 출력 가능하다. 기본값을 사용할 매개변수는 호출 시점에서 마지막에 있어야 한다.[31]

#!syntax kotlin
// 문자열들을 받아 하나의 문자열로 결합 후 출력하는 함수
fun printJoinedText(vararg args: String) {// 가변 매개변수이다. 배열로 취급된다.
    if (args.length == 0) return // return은 값 반환 뿐만 아니라 함수 종료도 하므로 args가 비어있을 경우 아래코드가 실행되기 전에 함수가 종료된다.
    println(args.joinToString(", ")) // 문자열 배열을 하나의 문자열로 합친다. ", "로 원소가 이어진다.
}

17.1. 오버로딩

같은 위치에 같은 이름의 함수를 만들 수 없지만, 매개변수가 다른 경우 호출할 함수를 구분할 수 있기에 가능하다.
매개변수 개수, 타입, 순서가 달라야 한다.

#!syntax kotlin
fun println() = println()
fun println(i: Int) = println(i)
fun println(s: String) = println(s)
fun println(i: Int, s: String) = println(i + s)
fun println(s: String, i: Int) = println(s + i)
해당 메서드들은 호출 시점에서 호출할 메서드를 구분할 수 있다.

#!syntax kotlin
fun print(a: Any, newLine: Boolean) {
    print(a)
    if (newLine) println()
}
fun print(a: Any) = print(a, false)
매개변수가 기본값을 가진 것처럼 동작하도록 할 수 있다. 코틀린에서는 이미 매개변수에 기본값을 지정할 수 있기 때문에 매개변수 순서를 바꿔야하는 경우가 아니라면 잘 쓰이지 않는다.

#!syntax kotlin
fun print(i: Int) = println(i)
fun print(vararg args: Int) {
    for (i in args) print(i)
    println()
}
fun print(vararg args: String) {
    for (i in args) print(i)
    println()
}
매개변수를 Int 하나만 주면 1번째가, 2개 이상 주면 2번째가 호출된다. 하나도 안 줄 경우 2번째와 3번째를 구분할 수 없어서 호출할 수 없다.

17.2. 확장 함수

이미 선언되어 있는 클래스의 메서드를 재정의하거나 새로 선언할 수 있다.

#!syntax kotlin
fun String.sayHello() {
    println("Hello, $this") // this는 객체 자기 자신을 가리킴
}

fun main() {
    "NamuWiki".sayHello() // "Hello, NamuWiki" 출력
}

17.3. infix 함수

infix 키워드를 이용해서 .과 ()를 쓰지 않아도 되는 함수를 만들 수 있다.

infix 함수의 조건으로는
  • 확장 함수 또는 메서드여야 한다
  • 매개 변수 1개여야 한다
  • 매개 변수는 기본 값이 없으면서 vararg 매개변수가 없어야 한다

#!syntax kotlin
class Human(var name: String, var age: Int, var location: String) {
    fun travel(location: String) {
        this.location = location
    }
    override fun toString(): String {
        return "${name}, ${age}세, ${location} 거주"
    }
    infix fun eat(food: String) = println("${this.name}님이 ${food}를 먹었습니다.")
}

fun main() {
    Human("홍길동", 30, "서울") eat "피자" // 홍길동님이 피자를 먹었습니다.
    Human("홍길동", 30, "서울") browse URL("https://namu.wiki/") // 홍길동님이 https://namu.wiki/를 검색했습니다.
}

infix fun Human.browse(url: URL) = println("${this.name}님이 ${url}를 검색했습니다.")

18. 스코프 함수

18.1. let

객체에 대해서 람다식 수행하고, 블록의 마지막 문장 실행 결과를 반환한다. 자기 자신을 참조할 때에는 it을 기본으로 사용하며, 이는 변경이 가능하다. (이는 it을 사용하는 다른 스코프 함수에도 동일하다.)

let이 있는 버전
#!syntax kotlin
package HelloWorld

class Human(var name: String, var age: Int, var location: String) {
    fun travel(location: String) {
        this.location = location
    }
    override fun toString(): String {
        return "${name}, ${age}세, ${location} 거주"
    }
}

fun main() {
    Human("홍길동", 30, "서울").let {
        println(it)
        it.travel("부산")
        println(it)
    } // 마지막 println()의 결과인 Unit이 반환됨
    /*
        실행결과
        홍길동, 30세, 서울 거주
        홍길동, 30세, 부산 거주
    */
}

let이 없는 버전
#!syntax kotlin
package HelloWorld

class Human(var name: String, var age: Int, var location: String) {
    fun travel(location: String) {
        this.location = location
    }
    override fun toString(): String {
        return "${name}, ${age}세, ${location} 거주"
    }
}

fun main() {
    val human = Human("홍길동", 30, "서울")
    println(human)
    human.travel("부산")
    println(human)
}

let을 사용하는 경우
  • null이 가능한 오브젝트가 null이 아닐 때 코드를 실행하게 할 때
    {{{#!folding [ 예시 ]
#!syntax kotlin
package HelloWorld
val humans = ArrayList<Human>()


class Human(var name: String, var age: Int, var location: String) {
    fun travel(location: String) {
        this.location = location
    }
    override fun toString(): String {
        return "${name}, ${age}세, ${location} 거주"
    }
}

fun main() {
    
    Human("홍길동", 30, "서울").let {
        humans.add(it)
    }
    // ?. 연산자를 사용, getHuman()의 결과가 null이라면 람다식이 무시되고 null이 반환된다.
    getHuman("홍길동")?.let {
        println(it.name)
    }
}

fun getHuman(name: String): Human? {
    return humans.firstOrNull {
        it.name == name
    }
}

}}}
  • 특정 변수를 제한적인 블록에서만 접근하게 만들 때

18.2. run

runlet과 비슷하나 자신을 참조할 때 it 대신 this를 쓴다는 점이 다르다. let과 마찬가지로 마지막 실행 결과가 반환된다.

#!syntax kotlin
fun main() {
    Human("홍길동", 30, "서울").run {
        println(this)
        this.travel("부산")
        println(this)
    } // 마지막 println()의 결과인 Unit이 반환됨
}


run는 Top-level에서 사용할 수도 있다. 객체 내에서 run을 사용할 때 객체 자신을 가리키지 않은 것과 동일하다. 객체 안에 있지 않으므로 객체 자신을 가리키는 this는 사용할 수 없다.

#!syntax kotlin
fun main() {
    run { // Top-level이므로 this 키워드 사용 불가
        val human = Human("홍길동", 30, "서울")
        println(human)
        human.travel("부산")
        println(human)
    } // 마지막 println()의 결과인 Unit이 반환됨
}

18.3. also

alsolet과 달리 자기 자신을 반환한다. let의 마지막 문장으로 it을 쓴 것과 동일하다.

#!syntax kotlin
fun main() {
    val type = Human("홍길동", 30, "서울").also {
        println(it)
        it.travel("부산")
        println(it)
    } // Human 객체가 그대로 반환됨

    println(type::class.simpleName) //Human 출력
}



해당 코드는 아래와 동치다. let 사용에 유의하자.


#!syntax kotlin
fun main() {
    val type = Human("홍길동", 30, "서울").let {
        println(it)
        it.travel("부산")
        println(it)
        it
    } // Human 객체가 그대로 반환됨

    println(type::class.simpleName) //Human 출력
}

18.4. with

withrun의 일반 함수 버전으로, with(receiver, transform)receiver.run(transform)과 동일한 효력을 갖는다. Top-level에서 사용할 수 있으며, 자기 자신 참조는 this를 사용한다.

#!syntax kotlin
fun main() {
    with(Human("홍길동", 30, "서울")) {
        println(this)
        this.travel("부산")
        println(this)
    } // 마지막 println()의 결과인 가 그대로 반환됨
}

18.5. apply

applyrun과 달리 자기 자신을 반환한다. run의 마지막 문장으로 this를 쓴 것과 동일하다.

#!syntax kotlin
fun main() {
    Human("홍길동", 30, "서울").also {
        println(this)
        this.travel("부산")
        println(this)
    } // Human 객체가 그대로 반환됨
}

18.6. takeIf

람다식에 명세된 조건을 만족하는 경우 자기 자신을, 그렇지 않으면 null을 반환한다. 자기 자신은 it으로 가리킨다.

#!syntax kotlin
fun main() {
    val human = Human("홍길동", 30, "서울").also {
        this.travel("부산")
    }.takeIf {
        it.location == "서울"
    }
    println(human) // it.location == "서울"이 false이므로 null 출력
}

18.7. takeUnless

람다식에 명세된 조건을 만족하는 경우 null을, 그렇지 않으면 자기 자신을 반환한다. 자기 자신은 it으로 가리킨다.

#!syntax kotlin
fun main() {
    val human = Human("홍길동", 30, "서울").also {
        this.travel("부산")
    }.takeUnless {
        it.location == "서울"
    }
    println(human) // it.location == "서울"이 false이므로 "홍길동, 30세, 부산 거주" 출력
}


19. 람다식

19.1. 기본

다음과 같이 활용한다.
  • 함수형
    {{{#!syntax kotlin

    • package Kotlin
fun main(){
fun fn(a:Int, b:Int):Int {return a+b}
println(fn(1,2))
}
}}}
  • 람다식
    {{{#!syntax kotlin

    • package Kotlin
fun main(){
val fn = {a:Int, b:Int -> a+b}
println(fn(1,2))
}
}}}

19.2. 고차함수

코틀린은 인터페이스를 통해서 함수를 인자로 넘겨주던 Java와 다르게 아주 간편하게 함수를 주고받거나 반환할 수 있다.
  • 함수를 인자로 받기
    {{{#!syntax kotlin

    • package Kotlin
fun printer(a: Int, b: Int, f: (Int, Int) -> Int) {
println("$a 와 $b 를 함수에 -> ${f(a, b)}")
}
}}}
* 함수를 반환하기
{{{#!syntax kotlin
package Kotlin

fun getPrintingFunction(str: String): () -> Unit {
return { println(str) }
}

}}}

또한, 함수(람다식)을 인자로 넘겨주는 함수를 사용할 경우, 함수의 내용을 소괄호 밖에 표시하는 것이 권장된다.

#!syntax kotlin

printer(3, 5, {i, j -> i + j}) // 작동
printer(3, 5) {i, j ->
    i + j
} // 작동



이와 앞서 언급한 클래스 내 함수 삽입을 이용하면 context를 만들 수 있다.

#!syntax kotlin
package HelloWorld

object NamuWiki {
    fun getDocument(name: String): String {
        // TODO
        return document
    }
}

fun <T> namuWiki(lambda: NamuWiki.() -> T) : T { // lambda는 일종의 확장 함수
    return NamuWiki.lambda()
}

fun main() {
    val kotlin: String = namuWiki { // namuWiki 함수 호출 및 결과
        getDocument("Kotlin/문법") // 문서 얻기 (확장 함수로 가능한 this 생략)
    }

    getDocument("Kotlin/문법") // ERROR: function getDocument doesn't exist

    println(kotlin)
}


20. 클래스

자바의 정적 메서드를 모아둔 유틸리티 클래스는 코틀린에서는 객체[32]라고 따로 있다.

"사람"이라는 클래스는 다음과 같다.[33]
#!syntax kotlin
class Person(name: String) {
    companion object { // 이 블록 안의 함수와 변수는 static이 붙은 것처럼 호출해야 한다.
        const val SPECIES = "Homo sapiens"
    }

    var name = name
        private set
}

fun main() {
    val myPerson = Person("John")
    println(myPerson.name)
    println(myPerson.SPECIES)
}

20.1. Getter, Setter

자바와 달리, 단순히 변수 선언 만으로 getter와 setter가 자동 생성되며, object.field처럼 직접 field에 접근하는 것처럼 보여도 내부적으로 getter를 호출한다.

예시
#!syntax kotlin
class Car {
    var speed = 0
        set(speed: Int) {
            if (speed < 0) throw IllegalArgumentException("속력은 음수가 될 수 없음")
            field = speed // field는 필드 자기 자신을 가리킨다.
        }
    private var gear = 0
    val trunk = mutableListOf<Any>()
        get() = field.toMutableList()
    var licensePlate = "12가 3456"
        protected set
}

20.2. 상속

한 객체가 다른 객체를 상속할 때에는 다음과 같은 형태를 띤다.
#!syntax kotlin
open class ParentClass()

class ChildClass: ParentClass()


코틀린은 다른 프로그래밍 언어나 자바와 달리 더 이상 상속하지 못하는 final이 클래스의 기본값이다

따라서 이 클래스를 상속가능하게 하고 싶다면 open(상속 선택)이나 abstract(상속 필수)를 붙여서 다른 클래스가 해당 클래스를 상속할 수 있도록 해주어야 한다

한 객체가 인터페이스를 구현할 때에는 다음과 같은 형태를 띤다.
#!syntax kotlin
interface SampleInterface()

class ChildClass: SampleInterface

20.3. 추상 클래스

#!syntax kotlin 
abstract class Animal(val name: String) {  // 생성자 가능
    abstract fun makeSound()               // 추상 메서드 — 반드시 override
    abstract val legs: Int                 // 추상 프로퍼티

    fun breathe() = println("$name 호흡") // 구체 메서드 — 그대로 상속
}


추상 클래스는 객체 지향 프로그래밍(OOP)에서, 다형성에 관한 부분이다.

추상 클래스는 하위 구현 클래스가 없다면 인스턴스화 될 수 없다. 즉, 하위 클래스를 필요로 한다. 그리고 abstract 키워드를 통해서 하위 클래스들에게 구현해야 할 것들을 명시한다.

20.4. 데이터 클래스

#!syntax kotlin
data class Point(val x: Int, val y: Int)

val p1 = Point(1, 2)
val p2 = p1.copy(y = 10)  // Point(1, 10)
val (x, y) = p1  // 구조 분해


클래스를 만들 때, 자동적으로 equals, hashCode, toString, copy 함수가 자동으로 생성된다.

20.5. 봉인 클래스


#!syntax kotlin
sealed class Shape

class Circle(val radius: Double) : Shape()
class Rectangle(val w: Double, val h: Double) : Shape()
class Triangle(val base: Double, val height: Double) : Shape()


봉인 클래스는 컴파일 시, 컴파일러에게 "이 클래스의 하위 클래스는 이게 전부야"하고 명시하는 것이다.

과거에는 봉인 클래스를 사용할 때, 그 하위 클래스들이 같은 파일에 있어야 했었다. 하지만 지금은 같은 패키지에 있다면 괜찮다.

#!syntax kotlin
fun area(shape: Shape): Double = when (shape) {
    is Circle ->    Math.PI * shape.radius * shape.radius
    is Rectangle -> shape.w * shape.h
    is Triangle ->  0.5 * shape.base * shape.height
    // else 없어도 됨 — 컴파일러가 모든 경우를 알기 때문
}


위 주석처럼, when과의 결합에서 강력하게 쓰인다. 원래 봉인 클래스가 아닌 다른 클래스에서는 else가 들어갔어야 했다.[34] 그러나 봉인 클래스로 하위 클래스 목록을 지정한다면, else가 없어도 된다. 써져있는 대로, 컴파일러는 모든 경우를 알기 때문이다.

#!syntax kotlin
sealed class Shape

class Circle(val radius: Double) : Shape()
class Rectangle(val w: Double, val h: Double) : Shape()
class Triangle(val base: Double, val height: Double) : Shape()
class Square(val side: Double) : Shape()


그런데, 앞선 when을 쓰는 코드를 쓰다가, Square 객체처럼 다른 클래스를 새로 만든다면, 그때에는 에러가 발생한다. 모든 when에서 에러가 발생하는 것은 아니지만, 위 경우처럼 when을 값으로 사용한다면, 모든 경우를 지정하지 않았기에 발생한다.[35]

#!syntax kotlin
sealed class Expr

// 일반 클래스
class Num(val value: Int) : Expr()

// 데이터 클래스 (가장 흔하게 쓰임)
data class Add(val left: Expr, val right: Expr) : Expr()

// 오브젝트 (싱글톤, 상태 없음)
object Zero : Expr()

// 봉인 클래스가 봉인 클래스를 상속 (계층 구조)
sealed class BinaryOp : Expr()
class Mul(val left: Expr, val right: Expr) : BinaryOp()
class Div(val left: Expr, val right: Expr) : BinaryOp()


하위 클래스가 반드시 class 키워드로 만들어져 있지 않아도 된다. 위 예시에서는 data class, object[36], sealed class이 쓰였다.

#!syntax kotlin
fun eval(expr: Expr): Int = when (expr) {
    is Num -> expr.value
    is Add -> eval(expr.left) + eval(expr.right)
    is Zero -> 0
    is BinaryOp -> when (expr) {
        is Mul -> eval(expr.left) * eval(expr.right)
        is Div -> eval(expr.left) / eval(expr.right)
    }
}


봉인 클래스는 위 예시처럼 when과 같이 있을 때, 훨씬 더 강력해진다.

20.6. 위임

#!syntax kotlin
interface Printer { fun print() }
class RealPrinter : Printer { override fun print() = println("print") }

class SmartPrinter(p: Printer) : Printer by p

인터페이스의 구현을 다른 객체에 위임한다.

#!syntax kotlin
val config: String by lazy { loadFromFile() }  // 최초 접근 시 초기화

var name: String by Delegates.observable("init") { _, old, new ->
    println("$old → $new")
}

또한, 이처럼 클래스를 직접 만들지 않고 by를 통해서 오버라이딩 할 수 있다.

21. 인터페이스


인터페이스는 자바의 인터페이스 이 문서를 보는 것이 더 효과적일 수 있다. 다만, 차이점은 코틀린의 상속처럼 :로 extendsimplements를 대신 가능하다는 것이다.

22. 여러 객체

22.1. 오브젝트

#!syntax kotlin
object Object1 {
    var v = 0
}

fun main() {
    val a = Object1
    val b = Object1
    println("a=${a.v}, b=${b.v}")  // a=0, b=0
    a.v = 10
    println("a=${a.v}, b=${b.v}")  // a=10, b=10
}


object 키워드는 일명 오브젝트 객체를 만들어주는 예약어이다. 오브젝트 객체는 싱글톤이다. 그리고 사용할 때, 괄호 없이 사용한다. 다른 것은 클래스와 유사하다. 다만, C언어의 공용체처럼 공유된다는 것만 다르다.

22.2. 동반 객체

#!syntax kotlin
class clazz {
    companion object {
        var v = 0
        fun f() { println("v") }
    }
}

fun main() {
    clazz.v = 10
    clazz.f()
}

동반 객체(companion object)는 자바에서 static 키워드와 매칭되는 예약어이다. 코들린에는 static 키워드가 없다. 대신 companion object 키워드가 있다. 위 코드처럼 객체 없이도 참조 또는 호출할 수 있다. [37]

23. 제네릭

자바에서도 제네릭이라 부르고, C++에서는 템플릿이라 부르는 문법이다. 제네릭은 많은 곳에서 존재한다. 예를 들어 반복 가능한 객체의 타입인 Iterable 클래스가 제네릭을 명시하고 있으므로 제네릭을 없앤 일부 클래스를 제외한 나머니 컬렉션을 쓸 수 없다. 다시 말해서 List, Set, Map 등을 쓸 수 없다는 것이다.

#!syntax kotlin
package HelloWorld

fun <T> a(args: Array<T>) {      // 기본 제네릭
    // TODO
}
fun <T> b(args: Array<out T>) {  // 공변성 제네릭
    // TODO
}
fun <T> c(args: Array<in T>) {   // 반공변성 제네릭
    // TODO
}
fun d(args: Array<*>) {          // star-projections
    // TODO
}


기본 제네릭은 자바와 거의 동일하므로 자바의 제네릭을 보도록 하자.

공변성 제네릭은 읽기 전용이다. b 함수 안에서는 args의 값을 바꿀 수 없다는 소리다. 그리고 T를 상속한 클래스도 인자[38]의 값으로 들어갈 수 있다.
예를 들어서, 매개변수의 타입이 Array<Any>이라고 하면 인자로 들어갈 수 있는 값의 타입은 Any를 상속한 모든 클래스, Array<Int>, Array<Double>, Array<String> 등은 불가능하다. 오직 Array<Any>만 들어갈 수 있다.
하지만 매개변수의 타입이 Array<out Any>라고 한다면 들어갈 수 있는 인자의 값으로는 Array<Any>와 더불어서 Array<Int>, Array<Double>, Array<String>도 같이 들어갈 수 있다.

반공변성 제네릭은 쓰기 전용이다.[39] c 함수 안에서는 args의 값을 바꿀 수 있다는 소리다. 그리고 이름부터 공변성의 반대로, 인자의 값으로 들어갈 수 있는 값도 T가 상속을 하는 것이다.
예를 들어서, 매개변수의 타입이 Array<Int>이라고 하면 인자로 들어갈 수 있는 값의 타입은 Any를 상속한 모든 클래스, Array<Any>, Array<Number> 등은 불가능하다. 오직 Array<Int>만 들어갈 수 있다.
하지만 매개변수의 타입이 Array<in Any>라고 한다면 들어갈 수 있는 인자의 값으로는 Array<Int>와 더불어서 Array<Any>, Array<Number>도 같이 들어갈 수 있다.[40]

star-projections(스타 프로젝션)은 그냥 <out Any>와 같은 제네릭이다. 다시 말해서, 인자로 들어온 값은 변경이 불가하다는 것이다.

23.1. where

where은 뒤에 인터페이스 등이 붙어서 인터페이스를 구현한 타입이 필요하다는 것을 말해주는 제어자이다. 함수 제일 뒤, 중괄호 앞에 붙어서 이러한 인터페이스를 구현한 타입이 필요하다는 것을 말해준다.

#!syntax kotlin
package HelloWorld

fun <T> a(arg: T) where T: AutoCloseable {
    // TODO
}
fun <T> b(arg: T): Any where T: AutoCloseable {
    // TODO
}
fun <T> c(arg: T) where T: AutoCloseable, T: Iterable<*> {
    // TODO
}
fun <T> d(arg: T): Any where T: AutoCloseable, T: Iterable<*> {
    // TODO
}

23.2. reified

만약에 inline 함수에서 제네릭 타입 검사 또는 강제 형변환이 필요하다면 어떻게 해야 할까?

#!syntax kotlin
package HelloWorld

inline fun <T, E> a(args: List<E>, clazz: Class<T>): T {
    // TODO
    if (clazz.isInstance(args[0])) {
        // TODO
    }
}


보통은 클래스를 따로 인자에 넣을 것이다. 하지만 오직 타입 하나만을 위해서 매개변수를 추가해야 되는가? 이를 해결하기 위해서 reified 키워드가 등장했다.
#!syntax kotlin
package HelloWorld

inline fun <reified T, E> a(args: List<E>): T {
    // TODO
    if (args[0] is T) {
        // TODO
    }
}


inline 함수에서만 reified 키워드를 쓰면은 T를 그저 제네릭이 아닌 그냥 일반 타입으로 취급할 수 있게 한다.

24. 예외


예외도 자바와 같다. 다음을 보자. 자바의 예외.


[1] 실제로 IntelliJ IDEA안드로이드 스튜디오 등에서 자바 코드를 넣으면 자동으로 코틀린으로 변환해 준다.[2] Java에서는 파일 이름과 주요 클래스의 이름이 동일해야 한다.[3] 강제하진 않는다.[4] 파일을 하나의 폴더에 저장하지 않고 이미지, 동영상 등으로 나눠서 저장하는 것과 같은 방식.[5] Java의 int, double, char 등 소문자로 시작하는 타입[6] Integer, Double, Character 등 원시 타입을 참조 타입으로 변환한 클래스[7] 8바이트[8] 4바이트[9] 2바이트[10] 1바이트[11] 어차피 가질 수 있는 값이 단 하나밖에 존재하지 않는다.[12] 때문에 변수에 대입하는 상황에서 쓸 수 있다.[13] while (true) 같은 무한루프[14] IntArray와 Array<Int>는 각각 int\[\]와 Integer\[\]에 대응된다.[15] 부호 없는 정수는 내부적으로 부호 있는 정수 타입을 사용하므로 UIntArray, Array<UInt>는 각각 int\[\]을 활용, 그 값 자체를 배열로 처리한다.[16] 후술할 제네릭이라는 것이다.[17] 단, 읽기 전용 컬렉션은 원소가 없을 때, 1개일 때 각각 EmptyList, SingletonList 객체가 사용된다.[18] null이 아니다.[19] 선언 따로, 초기화 따로 해서 선언도 가능하다.[20] 코틀린에서는 변수에는 주소가 저장되어 있으므로 주소가 복사된다. 해당 객체가 불변 객체인 경우에는 안전하다. 예시로 원시 타입, String 등이 있다.[21] 연산자도 메서드로 정의되므로 사용할 수 없다. 단, equals 메서드는 연산자로 호출하면 컴파일 시 null 체크를 하므로 사용 가능하다.[22] 스마트 캐스팅에 관한 건 후술한다.[23] + 또는 - 연산자를 정의하지 않고 사용할 수 있으며, 해당 연산자만 정의하면 증감 연산자를 쓸 수 없다.[24] 정확히는 equals의 축약형으로 연산자 함수가 아니다.[25] 정확히는 Compareable 인터페이스 메서드의 축약형으로 연산자 함수가 아니다.[26] 이후 a는 non-null 타입으로 스마트 형변환된다.[27] N은 자연수[28] 5개 이상은 비권장한다.[29] forEach의 경우 람다가 종료되어도 내부 구조 상 다음 반복으로 넘어가기에 continue와 비슷하다고 볼 수 있으나, run은 종료되면 그대로 끝이라 break와 비슷하다.[30] 한번에 컴파일되는 단위.[31] 선언 시에는 중간에 있어도 상관 없다는 뜻이다.[32] 인스턴스가 아니다.[33] Java/문법의 코드와 동일하다.[34] 왜냐하면 또 어디서 더 상속할지 모르기 때문이다.[35] whenexpression, statement 이 두 경우로 쓸 수 있다. 전자는 값을 나타내는 것이고 후자는 그냥 일반 제어문이다. 전자는 조건부 표현식을 확장한 것이라 봐도 무방하다.[36] 후술할 내용이다. 그냥 공유 대상이다.[37] 굳이 일반 함수, 변수 대신에 동반 객체를 쓰는 이유는 단순히 소속을 강조하기 위해서다. 일반 함수, 일반 변수는 어떨 때에는 소속을 찾기 어려운 경우가 많다. 하지만, 동반 객체로는 소속을 명시할 수 있다.[38] 함수 호출할 때 들어가는 값이다. 이와 비슷하게 함수 선언할 때 들어가는 값은 매개변수라고 부른다.[39] 읽기도 되더라[40] Any -> Number -> Int이다.