* Contents
Part1: Introducing Kotlin
Chapter1: Kotlin: what and why
Chapter2: Kotlin basics ✔
Chapter3: Defining and calling functions
Chapter4: Classes, objects, and interfaces
Chapter5: Programming with lambdas
Chapter6: The Kotlin type system
Part2: Embracing Kotlin
Chapter7: Operator overloading and other conventions
Chapter8: Higher-order functions
Chapter9: Generics
Chapter10: Annotations and reflection
Chapter11: DSL construction
코틀린에서 함수는 아래와 같은 형태로 정의한다.
함수가 expression을 반환할 경우, 함수의 중괄호 및 return 문을 생략해 표현할 수 있다.
fun main(a: Int, b: Int) = if (a > b) a else b
중괄호로 감싼 함수의 경우 block body를, expression을 바로 반환하는 함수의 경우 expression body를 갖는다고 한다.
statements vs expressions
1) statements: 값을 반환하지 않는다.
2) expressions: 값을 반환한다. 따라서 return 값으로 활용 가능하다. (코틀린에서 if는 expression이다.)
변수를 나타내는 키워드는 다음과 같다.
val
: value의 약자. 한번 변수를 할당하면 재할당할 수 없다(immutable reference).var
: variable의 약자. 변수의 재할당이 가능하다(mutable reference).이 때, val
이 참조하는 객체 자체가 불변(immutable)한 것은 아니다.
해당 객체가 담는 값은 변할 수 있다.
val languages = listOf("Java") // languages 자체는 immutable reference이다.
languages.add("Kotlin") // 하지만 languages가 가리키는 객체는 mutable 하다.
$
, ${ }
를 사용해 문자열 보간(string interpolation)에 활용할 수 있다.
해당 기능을 코틀린에선 문자열 템플릿(string templates)이라고 부른다.
fun main(args: Array<String>) {
val name = if (args.isNotEmpty()) args[0] else "kotlin"
println("Hello $name!")
}
// 변수 뿐만 아니라 expression 자체도 문자열 템플릿으로 활용 가능하다.
fun main(args: Array<String>) {
println("Hello ${if (args.isNotEmpty()) args[0] else "kotlin"}!")
}
클래스 선언시 생성자, getter, setter 등이 필요한 자바와 달리, 코틀린의 클래스 선언은 간단하다.
class Person(val name: String)
위와 같이 본문 코드 없이 data만 갖는 클래스를 value objects
라고 부른다.
/* Java */
public class Person {
// field
private final String name;
// 생성자
public Person(String name) {
this.name = name;
}
// getter(accessor)
public String getName() {
return name;
}
}
자바에서는 값을 필드(field)에 저장하고, 해당 필드에 접근할 수 있는 getter 및 setter를 제공한다.
그리고 이러한 필드 및 접근자를 모두 합쳐 프로퍼티라 지칭한다.
하지만 코틀린은 변수 선언시 이러한 필드 및 접근자가 한번에 생성된다.
class Person(
val name: String, // field + getter
var isMarried: Boolean, // field + getter + setter
)
따라서 코틀린에선 변수 자체를 프로퍼티라 지칭한다.
변수를 선언하면 필드 및 접근자가 자동 생성된다.
val
: read-only 프로퍼티. 필드 및 getter를 자동 내포한다.var
: 수정 가능한 프로퍼티. 필드, getter 및 setter를 자동 내포한다.custom 프로퍼티 접근자를 선언할 수도 있다.
아래는 custom getter의 예시이며, 프로퍼티에 접근할 때마다 getter를 호출하면서 값을 새로 계산한다.
class Rectangle(val height: Int, val width: Int) {
val isSquare: Boolean
get() {
return height == width
}
}
enum class Color {
RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET
}
자바와 마찬가지로 코틀린에서도 enum class를 선언할 수 있다.
enum
은 soft keyword로 예약어이지만,class
앞에 쓰이는 게 아니면 변수명, 함수명 등으로 활용 가능하다.
반면class
는 변수명으로 활용이 불가해clazz
,aClass
와 같은 변수명으로 대체한다.
이러한 열거형 클래스는 단순 값의 목록이 아니다.
자바처럼 프로퍼티 및 함수도 내부에 선언 가능하다.
enum class Color(val r: Int, val g: Int, val b: Int) {
RED(255, 0, 0), ORANGE(255, 165, 0), YELLOW(255, 255, 0), GREEN(0, 255, 0), BLUE(0, 0, 255), INDIGO(75, 0, 130), VIOLET(238, 130, 238);
fun rgb() = (r * 256 + g) * 256 + b
}
>>> println(Color.BLUE.rgb())
255
enum class와 많이 쓰이는 식은 when
이다.
자바의 switch
와 기능적으로 유사하지만, switch
는 statement인 반면 when
은 expression이다.
fun getWarmth(color: Color) = when(color) {
Color.RED, Color.ORANGE, Color.YELLOW -> "warm"
Color.GREEN -> "neutral"
Color.BLUE, Color.INDIGO, Color.VIOLET -> "cold"
}
// 인자값 없이도 사용 가능하다.
fun mixOptimized(c1: Color, c2: Color) =
when {
(c1 == RED && c2 == YELLOW) ||
(c1 == YELLOW && c2 == RED) -> ORANGE
(c1 == YELLOW && c2 == BLUE) ||
(c1 == BLUE && c2 == YELLOW) -> GREEN
(c1 == BLUE && c2 == VIOLET) ||
(c1 == VIOLET && c2 == BLUE) -> INDIGO
else -> throw Exception("Dirty color")
}
when
사용시 smart casts
가 적용되어 명시적인 타입 캐스팅이 불필요하다.
interface Expr
class Num(val value: Int) : Expr
class Sum(val left: Expr, val right: Expr) : Expr
fun eval(e: Expr): Int =
when (e) {
is Num -> e.value // e가 Num으로 smart cast 된다.
is Sum -> eval(e.right) + eval(e.left) // e가 Sum으로 smart cast 된다.
else -> throw IllegalArgumentException("Unknown expression")
}
여타 언어와 마찬가지로 코틀린에서 반복문은 while
, do-while
, for
을 사용한다.
그 중 for
문을 활용해 반복문을 더 편하게 다룰 수 있다.
코틀린에선 구간을 다루는 여러 문법(..
, until
, downTo
)을 제공한다.
// 아래 구간은 inclusive range, 즉 0과 10을 포함한다.
val oneToTen = 1..10
for (i in oneToTen) {
println(i)
}
// 변수 할당 없이도 활용 가능하다.
for (i in 1..10) {
println(i)
}
val list = listOf(1, 2, 3) // size: 3
// 마지막 값을 포함하지 않는다. (0부터 1씩 증가해 2까지 도달.)
for (i in 0 until list.size) {
println(list[i])
}
// 10부터 1씩 감소해 0까지 도달한다.
for (i in 10 downTo 0) {
println(i)
}
구간을 1이 아닌 특정 단위의 값을 기준으로 돌 수 있을 때, 해당 구간을 progression
이라고 부른다.
이 때 함께 쓰이는 문법이 step
이다.
// 0, 2, 4, 6, 8, 10을 방문한다.
for (i in 0..10 step 2) {
println(i)
}
반복문은 collection 내부 아이템들을 체크할 때 흔히 사용된다.
이 때 활용되는 키워드가 in
이다.
val list = listOf("1", "2", "3")
// item은 "1", "2", "3"을 순차적으로 가리킨다.
for (item in list) {
println(item)
}
// withIndex()를 통해 index를 함께 반환받을 수 있다.
for ((index, element) in list.withIndex()) {
println("$index" $element")
}
특정 범위 혹은 collection 내에 아이템 포함 여부를 확인할 때도 in
을 사용한다.
println(c in 'a'..'z') // true, a <= c && c <= z와 동일
println("1" !in listOf("1", "2")) // false
코틀린의 기본 exception handling은 자바와 유사하다.
다만 throw
가 expression이라는 점에서 자바와 차이가 있다.
if (percentage !in 0..100) {
throw IllegalArgumentException("A percentage value must be between 0 and 100: $percentage")
}
// 조건에 맞을 경우 percentage를 숫자로 초기화하며,
// 그렇지 않을 경우 예외를 던진다.
val percentage = if (number in 0..100) {
number
} else {
throw IllegalArgumentException("A percentage value must be between 0 and 100: $number")
}
fun readNumber(reader: BufferedReader): Int? {
try {
val line = reader.readLine()
return Integer.parseInt(line)
} catch (e: NumberFormatException) {
return null
} finally {
reader.close()
}
}
자바와 유사하게 try-catch-finally
구문을 통해 예외 처리를 한다.
하지만 자바와 달리 코틀린은 checked exceptions과 unchecked exceptions를 구분짓지 않는다.
따라서 어떤 exception이 해당 함수에서 발생할지 명시적으로 지정하지 않는다.
자바의 checked exceptions 및 unchecked exceptions
- checked exceptions: 컴파일 타임에 확인되어 반드시 에러 처리를 해줘야 하는 exceptions.
- unchecked exceptions: 런타임에 발생 가능하며 에러 처리가 강제되지 않는 exceptions.
try
또한 expression이며 리턴값으로 지정할 수 있다.
fun readNumber(reader: BufferedReader) {
val number = try {
Integer.parseInt(reader.readLine()) // 예외가 발생하지 않으면 해당 값을 반환한다.
} catch (e: NumberFormatException) {
null // exception 발생시 null을 반환한다.
}
println(number)
}