b) a else b // java -> (a > b) ? a : b } fun main(args: Array) { println(max(1, 2)) } ``` - 코틀린에서 if는 식이지 문이 아니다. - (문(statement) 과 식(expression)) - 자바에서는 모든 제어구조가 문인 반면 코틀린에서는 루프를 제외한 대부분의 제어 구조가 식이다. ```kotlin // 식이 본문인 함수 fun max(a: Int, b: Int): Int = if"> b) a else b // java -> (a > b) ? a : b } fun main(args: Array) { println(max(1, 2)) } ``` - 코틀린에서 if는 식이지 문이 아니다. - (문(statement) 과 식(expression)) - 자바에서는 모든 제어구조가 문인 반면 코틀린에서는 루프를 제외한 대부분의 제어 구조가 식이다. ```kotlin // 식이 본문인 함수 fun max(a: Int, b: Int): Int = if"> b) a else b // java -> (a > b) ? a : b } fun main(args: Array) { println(max(1, 2)) } ``` - 코틀린에서 if는 식이지 문이 아니다. - (문(statement) 과 식(expression)) - 자바에서는 모든 제어구조가 문인 반면 코틀린에서는 루프를 제외한 대부분의 제어 구조가 식이다. ```kotlin // 식이 본문인 함수 fun max(a: Int, b: Int): Int = if">
# 2장 코틀린기초

### 이번장에서는...

#### 배운점, 느낀점

1. 변수
2. 함수
3. 클래스
4. 프로퍼티
5. 스마트 캐스트 -> 타입 검사와 타입 캐스트, 타입 강제 변환을 하나로 엮은 기능
6. 예외처리

1. 자바에서는 변수를 선언할 때 타입이 맨 앞에 온다. 코틀린에서는 타입 지정을 생략하는 경우가 흔하다는데 실무에서 정말 그렇게 사용할 지?

---

## 2장 시작

### 함수와 변수

- 코틀린에서 타입 선언을 생략해도 된다는 사실을 보고, 코틀린이 어떻게 변경 가능한 데이터 보다 변경할 수 없는 불변 데이터 사용을 장려함

```kotlin
package part2

fun main(args: Array<String>) {
    println("Hello, world!")
}

  1. 함수를 만들 떄 fun 키워드를 사용한다. 파라미터 이름 뒤에 그 파리미터 타입을 쓴다
  2. 함수를 최상위 수준에 정의 할 수 있다 꼭 클래스 안에 함수를 넣어야 할 필요가 없다
  3. 배열도 일반적인 클래스와 마찬가지다. 코틀린은 자바와 달리 배열 처리를 위한 문법이 따로 존재하지 않는다.
  4. 여러가지 표준 자바 라이브러리 함 수를 간결하게 사용할 수 있게 감싼 Wrapper을 제공한다. ; 안붙여도 된다.

함수

fun max(a: Int, b: Int): Int {
    return if (a > b) a else b // java -> (a > b) ? a : b
}

fun main(args: Array<String>) {
    println(max(1, 2))
}
  • 코틀린에서 if는 식이지 문이 아니다.

  • (문(statement) 과 식(expression))

    • 자바에서는 모든 제어구조가 문인 반면 코틀린에서는 루프를 제외한 대부분의 제어 구조가 식이다.
      // 식이 본문인 함수
      fun max(a: Int, b: Int): Int = if (a > b) a else b
    
    • 중괄호를 없애고 return을 제거하면서 등호를 식 앞에 붙이면 더 간결하게 함수를 표현할 수 있다.
    • 본문이 중괄호로 둘러싸인 함수를 블록이 본문인 함수라 부르고, 등호와 식으로 이뤄진 함수를 식이 본문인 함수라고 부른다.
    • 코틀린에서는 식이 본문인 함수가 자주 쓰인다.
    fun max(a: Int, b: Int) = if (a > b) a else b
    
    • 반환타입 생략하면 더 간략하게 만들 수 있다.(타입 추론)
      • 실전 프로그램에는 아주 긴 함수에 return이 여럿 들어있는 경우가 자주 있다. 그런 경우 반환 타입을 꼭 명시하자
  • 반면 대입문은 자바에서는 식이었으나 코틀린에선 문이 됐다. 그로 인해 자바와 달리 대입식과 비교식을 잘못 바꿔 써서 버그가 생기는 경우가 없다.

변수

  • 자바에서는 변수를 선언할 때 타입이 맨 앞에 온다. 코틀린에서는 타입 지정을 생략하는 경우가 흔하다.
  • 타입으로 변수 선언을 시작하면 타입을 생략할 경우 식과 변수 선언을 구별할 수 없다. 그런 이유로 코틀린에서는 키워드로 변수 선언을 시작하는 대신 변수 이름 뒤에 타입을 명시하거나 생략하게 허용한다.
package part2

fun main() {
    val question = "헬로 월드"

    val answer = 42
    val answer2: Int = 42

    val answer3: Int
    answer3 = 42

    println(question)
    println(answer)
    println(answer2)
    println(answer3)
}
  • 타입을 지정하지 않으면 컴파일러가 초기화 식을 분석해서 초기화 식의 타입을 변수 타입으로 지정한다. 여기서 초기화 식은 42로 Int 타입이다. 따라서 변수도 Int 타입이 된다.
  • 부동소수점 상수를 사용한다면 변수 타입은 Double이 된다.
  • 초기화 식이 없다면 변수에 저장될 값에 대해 아무 정보가 없기 때문에 컴파일러가 타입을 추론할 수 없다. 그런경우 반드시 지정.
val answer: Int // val : Immutable : java(final) : 블록을 실행할 떄 정확히 한 번만 초기화
var answer2: Int // var: mutable
package part2

fun canPerForOperation(param: Boolean): Boolean {
    return param
}

fun main() {
    val ls: List<Boolean> = listOf(true, false)
    val message: String
//
//    for (l in ls) {
//        if (canPerForOperation(l)) {
//            message = "Success" // var 로 변경해야함
//        } else {
//            message = "Failed"
//        }
//    }

    if (canPerForOperation(true)) {
        message = "Success"
    } else {
        message = "Failed"
    }

    val languages = arrayListOf("JAVA")
    languages.add("KOTLIN")

    println(languages)

    var answer = 42
//    answer = "no answer" // type mismatch 오류

}

  • 기본적으로 모든 변수를 val 키워드를 사용해 불변 변수로 선언하고, 나중에 꼭 필요할 때에만 var로 변경하라.
  • 하지만 어떤 블록이 실행될 때 오직 한 초기화 문장만 실행됨을 컴파일러가 확인할 수 있다면 조건에 따라 val 값을 다른 여러 값으로 초기화할 수 도 있다
  • val 참조 자체는 불변일지라도 그 참조가 가리키는 객체의 내부 값은 변경될 수 있다.
  • var 키워드를 사용하면 변수의 값을 변경할 수 있지만 변수의 타입은 고정되어 바뀌지 않는다. 변환 하고 싶다면 변환 함수를 써서 변수의 타입으로 변환, 값을 변수에 대입할 수 있다는 타입으로 강제 형 변환 해야한다.

문자열 템플릿

package part2

fun main(args: Array<String>) {
    val name = if (args.size > 0) args[0] else "Kotlin"
    println("Hello, $name!")  // JAVA -> ("Hello," + name + "!")
    println("Hello, ${name}!") // 중괄호 쓰는 습관을 가져보자
    println("Hello, ${if (args.size > 0) args[0] else "Kotlin"} !") // 식도 가능
}

클래스와 프로퍼티

클래스

package part2;

public class PersonJava {

    private final String name;

    public PersonJava(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}
package part2

class Person(val name: String)
  • 이런 유형의 클래스(코드가 없이 데이터만 저장하는 클래스를 값 객체(value object)라 부른다.
  • public 가시성 변경자(visibility modifier)가 사라졌음을 확인
  • 코틀린의 기본 가시성은 public이므로 이런 경우 변경자를 생략해도 된다

프로퍼티

package part2

class Person(
    val name: String, // 읽기 전용 프로퍼티로, 코틀린은 비공개 필드와 필드를 읽는 단순한 공개 게터를 만든다
    var isMarried: Boolean // 쓸 수 있는 프로퍼티로, 코틀린은 비공개 필드, 게터, 공개 세터를 만들어낸다.
)
  • 코틀린은 값을 저장하기 위한 비공개 필드와 그 필드에 값을 저장하기 위한 세터, 필드의 값을 읽기 위한 게터로 이뤄진 간단한 디폴드 접근자 구현을 제공한다.
package part2;

import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;

@Metadata(
        mv = {1, 7, 1},
        k = 1,
        d1 = {"\\u0000\\u0018\\n\\u0002\\u0018\\u0002\\n\\u0002\\u0010\\u0000\\n\\u0000\\n\\u0002\\u0010\\u000e\\n\\u0000\\n\\u0002\\u0010\\u000b\\n\\u0002\\b\\u0007\\u0018\\u00002\\u00020\\u0001B\\u0015\\u0012\\u0006\\u0010\\u0002\\u001a\\u00020\\u0003\\u0012\\u0006\\u0010\\u0004\\u001a\\u00020\\u0005¢\\u0006\\u0002\\u0010\\u0006R\\u001a\\u0010\\u0004\\u001a\\u00020\\u0005X\\u0086\\u000e¢\\u0006\\u000e\\n\\u0000\\u001a\\u0004\\b\\u0004\\u0010\\u0007\\"\\u0004\\b\\b\\u0010\\tR\\u0011\\u0010\\u0002\\u001a\\u00020\\u0003¢\\u0006\\b\\n\\u0000\\u001a\\u0004\\b\\n\\u0010\\u000b¨\\u0006\\f"},
        d2 = {"Lpart2/Person;", "", "name", "", "isMarried", "", "(Ljava/lang/String;Z)V", "()Z", "setMarried", "(Z)V", "getName", "()Ljava/lang/String;", "playground"}
)
public final class Person {
    @NotNull
    private final String name;
    private boolean isMarried;

    @NotNull
    public final String getName() {
        return this.name;
    }

    public final boolean isMarried() {
        return this.isMarried;
    }

    public final void setMarried(boolean var1) {
        this.isMarried = var1;
    }

    public Person(@NotNull String name, boolean isMarried) {
        Intrinsics.checkNotNullParameter(name, "name");
        super();
        this.name = name;
        this.isMarried = isMarried;
    }
}
// Java에서 Person 클래스를 사용하는 방법
package part2;

public class PersonJava {

    private final String name;
    private Boolean isMarried;

    public PersonJava(String name, Boolean isMarried) {
        this.name = name;
        this.isMarried = isMarried;
    }

    public String getName() {
        return name;
    }

    public void setMarried(Boolean married) {
        isMarried = married;
    }

    public Boolean getMarried() {
        return isMarried;
    }

    public static void main(String[] args) {
        PersonJava person = new PersonJava("Bob", true);
        System.out.println(person.getName());
    }
}
//kotlin에서 Person 클래스를 사용하는 법
package part2

class Person(
    val name: String, // 읽기 전용 프로퍼티로, 코틀린은 비공개 필드와 필드를 읽는 단순한 공개 게터를 만든다
    var isMarried: Boolean // 쓸 수 있는 프로퍼티로, 코틀린은 비공개 필드, 게터, 공개 세터를 만들어낸다.
)

fun main() {
    val person = Person("Bob", true) // new 키워드를 사용하지 않고 생성자를 호출
    person.isMarried = false //setter
    println(person.name) // 프로퍼티 이름을 직접 사용해도 코틀린이 자동으로 게터를 호출한다.
    println(person.isMarried)
}
  • 자바에서 선언한 클래스에 대해 코틀린 문법이 사용가능
  • 자바 클래스의 게터를 val:읽기전용 프로퍼티 처럼 사용가능
  • 게터 세터 쌍이 있는 경우 var 프로퍼티 처럼 사용 가능
  • 대부분의 프로퍼티에는 그 프로퍼티의 값을 저장하기 위한 필드가 있다. 이를 프로퍼티를 뒷받침하는 필드라고 부른다.

커스텀 접근자

package part2

class Rectangle(val height: Int, val width: Int) {
    val isSquare: Boolean get() = height == width
//        get() { // 프로퍼티 게터 선언
//            return height == width
//        }
}

디렉터리와 패키지

  • 자바의 경우 모든 클래스를 패키지 단위로 관리
  • 코틀린도 자바와 비슷한 개념의 패키지가 존재. 코틀린에선 클래스 임포트와 함수 임포트의 차이가 없으며, 모든 선언을 import 키워드로 가져올 수 있다. 최상위 함수는 그 이름을 써서 임포트할 수 있다. 패키지 이름 뒤에 .*도 사용가능
  • 자바에서는 패키지 구조와 일치하는 디렉터리 계층 구조를 만들고 클래스의 소스코드를 그 클래스가 속한 패키지와 같은 디렉터리에 위치시켜야한지만, 코틀린에서는 여러 클래스를 한 파일에 넣을 수 있고, 파일의 이름도 마음대로 정할 수 있다. 코틀린에서는 디스크상의 어느 디렉터리에 소스코드 파일을 위치시키든 관계없다. 하지만 대부분의 경우 자바와 같이 패키지 별로 디렉터리를 구성하는 편이 낫다.

선택 표현과 처리: enum과 when

package part2

import part2.Color.*
import java.lang.Exception

enum class Color(val r: Int, val g: Int, val b: Int) {
    RED(255, 0, 0), ORANGE(255, 165, 0), YELLOW(255, 255, 0); // 반드시 상수 목록과 메서드 정의 사이에 세미콜론을 넣어야 한다

    fun rgb() = (r * 256 + g) * 256 + b
}

fun main() {
    println(RED.rgb())
    println(getMnemonic(RED))
    println(mix(RED, YELLOW))
    println(mixOptimized(RED, YELLOW))
}

fun getMnemonic(color: Color) =
    when (color) {
        RED, ORANGE -> "warm"
        YELLOW -> "York"
    }

fun mix(c1: Color, c2: Color) =
    when (setOf(c1, c2)) {
        setOf(RED, YELLOW) -> ORANGE
        else -> {
            throw Exception("Dirty color")
        }
    }

fun mixOptimized(c1: Color, c2: Color) =
    when {
        (c1 == RED && c2 == YELLOW || c1 == YELLOW && c2 == RED) -> ORANGE // 추가 객체를 만들지 않는 장점이 있지만 가독성은 떨어진다.
        else -> throw Exception("Dirty color")
    }
  • 코틀린에서는 enum class를 사용하지만 자바에서는 enum을 사용한다
  • 코틀린에서는 enum을 소프트 키워드라 부르는 존재다
  • enum은 class 앞에 있을 떄는 특별한 의미를 지니지만 다른 곳에서는 이름에 사용할 수 있다.
  • 자바와 마찬가지로 enum은 단순히 값만 열거하는 존재가 아니다. enum 클래스 안에 프로퍼티나 메서드를 정의할 수 있다.
  • when(switch) 은 자바와 달리 각 분기 끝에 break을 넣지 않아도 된다.
  • setOf(c1, c2)와 분기 조건에 있는 객체 사이를 매치할 때 동등성을 사용한다

####스마트 캐스트

package part2

interface Expr
class Num(val value: Int) : Expr // value라는 프로퍼티만 존재하는 단순한 클래스로 Expr 인터페이스를 구현한다
class Sum(val left: Expr, val right: Expr) :
    Expr //Expr 타입의 객체라면 어떤 것이나 Sum 연산의 인자가 될 수 있다. 따라서 Num이나 다른 Sum이 인자로 올 수 있다.

fun main() {
    println(eval(Sum(Sum(Num(1), Num(2)), Num(4))))
}

fun eval(e: Expr): Int {
    if (e is Num) {
        val n = e as Num // 여기서 Num으로 타입을 변환하는데, 이는 불필요한 중복이다.
        return n.value
    }
    if (e is Sum) {
        return eval(e.right) + eval(e.left) // 변수 e에 대해 스마트 캐스트를 사용한다.
    }
    throw IllegalArgumentException("Unknown expression")
}

  • 코틀린에서 is를 사용해 변수 타입을 검사한다.
  • is 검사는 자바의 instanceof와 비슷하다.
  • 자바에서는 어떤 변수 타입을 instanceof로 확인한 다음에 그 타입에 속한 멤버에 접근하기 위해서는 명시적으로 변수 타입을 캐스팅해야 한다. 이런 멤버 접근을 여러 번 수행해야 한다면 변수에 따로 캐스팅한 결과를 저장한 후 사용해야한다. 코틀린에서는 프로그래머 대신 컴파일러가 캐스팅을 해준다. 이를 스마트 캐스트라고 부른다.
  • 스마트 캐스는 is로 변수에 든 값의 타입을 검사한 다음에 그 값이 바뀔 수 없는 경우에만 작동한다.
  • 스마트 캐스트를 사용한다면 그 프로퍼티는 반드시 val이어야 하며 커스텀 접근자를 사용한 것이어도 안된다.
if 중첩 대신 when 사용하기
fun eval(e: Expr): Int =
    when (e) {
        is Num -> e.value // 인자 타입을 검사하는 when분기를
        is Sum -> eval(e.right) + eval(e.left) // 이부분에 스마트 캐스트가 쓰였다
        else -> throw IllegalArgumentException("Unknown expression")
    }
  • 블록의 마지막 식이 그 분기의 마지막 결과 값이다.
분기에 복잡한 동작이 들어가 있는 when 사용하기
fun evalWithLogging(e: Expr): Int =
    when (e) {
        is Num -> {
            println("num: ${e.value}")
            e.value // 이 식의 블록이 마지막 식이므로 e의 타입이 Num이면 e.value가 반환된다
        }
        is Sum -> {
            val left = evalWithLogging(e.left)
            val right = evalWithLogging(e.right)

            println("sum: $left + $right")
            left + right  //e의 타입이 Sum이면 이 식의 값이 반환 된다.
        }

        else -> throw IllegalArgumentException("Unknown expression")
    }
  • 블록의 마지막 식이 블록의 결과 라는 규칙은 블록이 값을 만들어내야 하는 경우 항상 성립한다.
  • 식이 본문인 함수는 블록을 본문으로 가질 수 없고 블록이 본문인 함수는 내부에 return문이 반드시 있어야 한다.

대상을 이터레이션: while과 for 루프

while 루프
while (condition) {
    /*...*/   // 조건이 참인 동안 본문을 반복 실행한다.
}

do {
    /*...*/  // 맨 처음에 무조건 본문을 한 번 실행한 다음, 조건이 참인 동안 본문을 반복 실행한다.
} while (condition)
수에 대한 이터레이션: 범위와 수열
  • 코틀린에서는 자바의 for(어떤 변수를 초기화하고 그 변수를 루프를 한 번 실행할 떄마다 갱신하고 루프 조건이 거짓일 될 떄 반복을 마치는 형태의 루프)에 해당하는 요소가 없다. 이런 루프의 가장 흔한 용례인 초깃값, 증가 값, 최종 값을 사용한 루프를 대신하기 위해 코틀린에서는 범위를 사용한다.
val oneToTen = 1..10 // 두 번째 값 10이 항상 범위에 포함된다는 뜻이다.
  • 범위는 기본적으로 두 값으로 이뤄진 구간이다. 보통은 그 두 값은 정수 등의 숫자 타입의 값이며, .. 연산자로 시작 값과 끝 값을 연결해서 범위를 만든다.
  • 코틀린의 범위는 폐구간(닫힌 구간) 또는 양끝을 포함하는 구간이다.
  • 정수 범위로 수행할 수 있는 가장 단순한 작업은 범위에 속한 모든 값에 대한 이터레이션이다. 이런식으로 어떤 범위에 속한 값을 일정한 순서로 이터레이션 하는 경우를 수열이라고 부른다.
when을 사용해 피즈버즈 게임 구현하기
package part2

fun fizzBuzz(i: Int) = when {
    i % 15 == 0 -> "FizzBuzz" // i가 15로 나워떨어지면 FizzBuzz를 반환한다. 자바와 바찬가지로 % 모듈로 연산자이다.
    i % 3 == 0 -> "Fizz" // i가 3으로 나눠떨어지면 Fizz를 반환한다.
    i % 5 == 0 -> "Buzz" // i가 5로 나눠떨어지면 Buzz를 반환한다.
    else -> "$i" // 다른 경우에는 그 수 자체를 반환한다
}

fun main() {
    for (i in 1..100) { //1..100 범위의 정수에 대해 이터레이션 한다.
        print(fizzBuzz(i))

    }
    println()

    for (i in 100 downTo 1 step 2) { // 증가 값 step을 갖는 수열에 대해 이터레이션 한다. 증가 값을 사용하면 수를 건너 띌 수 있다.
        print(fizzBuzz(i))
    }
}
  • 100 downTo 1은 역방향 수열을 만든다
  • ..는 항상 범위의 끝 값(.. 의 우항)을 포함한다. 하지만 끝 값을 포함하지 않는 반만 닫힌 범위(반폐구간 또는 반개 구간)에 대해 이터레이션하면 편할 때가 자주 있다. 그런 범위를 만들고 싶다면 until 함수를 사용하라
    • 예를 들어 for(x in 0 until size)라는 루프는 for(x in 0..size-1)과 같지만 좀 더 명확하게 개념을 표현한다.
맵을 초기화하고 이터레이션 하기
package part2

import java.util.TreeMap

val binaryReps = TreeMap<Char, String>() // 키에 대해 정렬하기 위해 Tree Map을 사용한다

fun main() {
    for (c in 'A'..'F') { // A부터 F까지의 문자의 범위를 사용해 이터레이션한다.
        val binary = Integer.toBinaryString(c.code) //아스키 코드를 2진 표현으로 바꾼다
        //binaryReps[c] = binary //c를 키로 c의 이진표현을 맵에 넣는다

        binaryReps.put(c, binary)
    }

    for ((letter, binary) in binaryReps) { // 맵에 대해 이터레이션한다. 맵의 키와 값을 두 변수에 각각 대입한다.
        println("$letter = $binary")
    }

    val list = arrayListOf("10", "11", "1001")
    for ((index, element) in list.withIndex()) { // 인덱스와 함께 컬렉션을 이터레이션 한다.
        println("$index: $element")
    }

}
  • 맵의 값을 가져오가나 키에 대한하는 값을 설정하는 기능인 get과 put을 사용하는 대신 map[key]나 map[key] = value를 사용해 값을 가져오고 설정할 수 있다.

in으로 컬렉션이나 범위의 원소 검사

in을 사용해 값이 범위에 속하는지 검사하기
package part2

fun isLetter(c: Char) = c in 'a'..'z' || c in 'A'..'Z'
fun isNotDigit(c: Char) = c !in '0'..'9'

fun recognize(c: Char) = when (c) {
    in '0'..'9' -> "It's a digit!" // c 값이 0부터 9사이에 있는지 검사한다
    in 'a'..'z', in 'A'..'Z' -> "It's a letter!" // 여러 범위 조건을 함께 사용해도 된다.
    else -> "I don't know.."
}

fun main() {
    println(isLetter('q'))
    println(isNotDigit('x'))
    println(recognize('8'))

    println("Kotlin" in "Java".."Scala") // Java <= Kotlin && Kotlin <= Scala 와 같다
    println("Kotlin" in setOf("Java", "Scala")) // 이 집합에는 Kotlin이 들어있지 않다
}

코틀린의 예외 처리

  • 코틀린의 예외 처리는 자바나 다른 언어의 예외 처리와 비슷하다.
  • 함수는 정상적으로 종료할 수 있지만 오류가 발생하면 예외를 던질 수 있다. 함수를 호출하는 쪽에서는 그 예외를 잡아 처리할 수 있다. 발생한 예외를 함수 호출 단에서 처리하지 않으면 함수 호출 스택을 거슬러 올라가면서 예외를 처리하는 부분이 나올 떄까지 예외를 다시 던진다.
package part2

import java.lang.IllegalArgumentException

fun main() {
    val number = 1001

    val percentage = if (number in 0..100)
        number
    else throw IllegalArgumentException("A percentage value must be between 0 and 100: $number") // new를 붙일 필요가 없다

    if (percentage !in 0..100) {
        throw IllegalArgumentException("A percentage value must be between 0 and 100: $percentage") // 자바와 달리 코틀린의 throw는 식이므로 다른 식에 포함 될 수 있다
    }
}

try, catch, finally

package part2

import java.io.BufferedReader
import java.io.StringReader
import java.lang.NumberFormatException

fun readNumber(reader: BufferedReader): Int? { // 함수가 던질 수 있는 예외를 명시할 필요가 없다
    try {
        val line = reader.readLine()
        return Integer.parseInt(line)
    } catch (e: NumberFormatException) { // 예외 타입을 :오른쪽에 쓴다
        return null
    } finally {   // finally는 자바와 똑같이 작동한다
        reader.close()
    }
}

fun main() {
    val reader = BufferedReader(StringReader("239"))
    println(readNumber(reader))
}
  • 자바 코드와 큰 차이는 throws(이 경우 s가 붙어있다) 절이 코드에 없다는 점이다.
  • 자바에서는 함수를 작성할 떄 함수 선언 뒤에 throws IOException을 붙여야 한다.
  • 이유는 IOException이 체크 예외 이기 때문이다. 자바에서는 체크 예외를 명시적으로 처리해야한다.
  • 어떤 함수가 던질 간능성이 있는 예외냐 그 함수가 호출한 다른 함수에서 발생할 수 있는 예외를 모두 catch로 처리해야 하며, 처리하지 않은 예외는 throws절에 명시해야한다.
  • 다른 최신 JVM 언어와 마찬가지로 코틀린도 체크 예외와 언체크 예외를 구별하지 않는다. 코틀린에서는 함수가 던지는 예외를 지정하지 않고 발생한 예외를 잡아내도 되고 잡아내지 않아도 된다.
  • 자바 7의 자원을 사용하는 try-with-resources는 어떨까? 코틀린은 그런 경우를 위한 특별한 문법을 제공하지 않는다. 하지만 라이브러리 함수로 같은 기능을 구현한다
try를 식으로 사용하기
package part2

import java.io.BufferedReader
import java.io.StringReader
import java.lang.NumberFormatException

fun readNumber(reader: BufferedReader) {// 함수가 던질 수 있는 예외를 명시할 필요가 없다
    val number = try {
        Integer.parseInt(reader.readLine()) // 이 식의 값이 try 식의 값이 된다.
    } catch (e: NumberFormatException) { // 예외 타입을 :오른쪽에 쓴다
        return
    }
    println(number)
}

fun main() {
//    val reader = BufferedReader(StringReader("239"))
    val reader = BufferedReader(StringReader("Not a number"))
    println(readNumber(reader))
}
  • 코틀린의 try 키워드느 if나 when과 마찬가지로 식이다. 따라서 try의 값을 변수에 대입할 수 있다. if와 달리 try의 본문을 반드시 중괄호로 둘러싸야한다.
  • 다른 문장과 마찬가지로 try 본문도 내부에 여러 문장이 있으면 마지막 식의 값이 전체 결과 값이다.
catch에서 값 반환하기
package part2

import java.io.BufferedReader
import java.io.StringReader
import java.lang.NumberFormatException

fun readNumber(reader: BufferedReader) {// 함수가 던질 수 있는 예외를 명시할 필요가 없다
    val number = try {
        Integer.parseInt(reader.readLine()) // 이 식의 값이 try 식의 값이 된다.
    } catch (e: NumberFormatException) { // 예외 타입을 :오른쪽에 쓴다
        null //예외가 발생하면 null 값을 사용한다.
    }
    println(number)
}

fun main() {
//    val reader = BufferedReader(StringReader("239"))
    val reader = BufferedReader(StringReader("Not a number"))
    println(readNumber(reader))
}

[출처] Kotlin IN ACTION