타입 선언 생략, 불변 데이터 사용을 권장하는 이유, 왜 불변 데이터가 더 좋은지 살펴본다.
fun main() {
println("Hello World!")
}
함수 선언 → fun 키워드 사용
함수를 모든 코틀린 파일의 최상위에 정의할 수 있음 → 클래스 안에 함수를 넣어야 할 필요는 없음
최상위 main 함수를 애플리케이션의 진입점으로 지정할 수 있음, 이 때 main에 인자가 없어도 됨
자바 표준 라이브러리를 더 쉽게 사용할 수 있는 래퍼(wrapper)를 제공함
줄 끝에 세미콜론을 필요하지 않다면 붙이지 않는 것을 권장한다.
fun max(a: Int, b: Int): Int {
return if(a > b) a else b
}
함수 명 옆의 괄호 → 파라미터 목록
파라미터 목록 다음으론 함수의 반환 타입을 지정한다.
코틀린엔서 if는 결과를 만드는 식임을 유의한다.
함수를 호출하려면 함수 이름 뒤에 파라미터를 입력한다
main함수는 파라미터가 없는 함수로 선언될 수도 있고, 문자열 배열을 파라미터로 받는 함수로 선언할 수도 있다. 후자의 경우 배열의 각 원소에는 애플리케이션에게 전달될 각각의 커멘드라인 인자에 대응한다.
main함수는 어떤 경우에도 아무 값도 반환하지 않는다.
코틀린에서 if는 식이지 문이 아니다.
식은 값을 만들어내며 다른 식의 하위 요소로 계산에 참여할 수 있는 반면, 문은 자신을 둘러싸고 있는 가장 안쪽 블록의 최상위 요소로 존재하며 아무런 값을 만들어내지 않는다.
코틀린에서는 루프(for, while, do/while)를 제외한 대부분의 제어 구조가 식이라는 점이 자바와 같은 다른 언어와 다른 점이다.
val x = if(myBoolean) 3 else 5
val direction = when (inputString) {
"u" -> UP
"d" -> DOWN
else -> UNKNOWN
}
val number = try {
inputString.toInt()
} catch (nfe: NumberFormatException) {
-1
}
위의 코드는 올바를 코틀린 코드이다.
코틀린에서 대입은 항상 문으로 취급받는다. 값을 변수에 대입해도 그 대입 연산 자체는 아무 값도 돌려주지 않기 때문이다.
코틀린은 중괄호 제거를 통해 더 간결하게 함수를 정의할 수 있다.
fun max(a: Int, b: Int): Int = if(a > b) a else b
본문이 중괄호로 둘러싸인 함수를 블록 본문 함수(block body function)라 부르고
등호와 식으로 이뤄진 함수를 식 본문 함수(expression body function)라 부른다.
코틀린에서는 식 본문 함수가 자주 쓰인다.
식 본문 함수에서 반환 타입을 제거할 수도 있다. 정적 타입 지정 언어인 코틀린에서 이 일이 가능한 이유는 코틀린의 강력한 타임 추론이 있기에 가능하다.
fun max(a: Int, b: Int) = if(a > b) a else b
함수의 반환 타입 생략은 식 본문 함수에만 가능하다는 점을 유의하자.
변수 또한 기본적인 선언 방법은 함수와 동일하다. 변수 선언 키워드와 변수 명, 변수 타입을 지정해주면 된다.
val question: String = "삶, 우주 그리고 모든 것에 대한 궁극적인 질문"
val answer: Int = 42
여기서 타입 선언을 생략해 더 간결하게 할 수 있다. 이 또한 코틀린의 타입 추론으로 인해 가능하다.
val question = "삶, 우주 그리고 모든 것에 대한 궁극적인 질문"
val answer = 42
부동소수점 상수를 사용한다면 변수 타입이 Double이 된다.
val yearsToCompute = 7.5e6 // 7.5 * 10^6 = 7,500,000.0
타입 추론은 변수를 선언하고 바로 값을 초기화 해주어야 작동한다. 따라서 변수 선언만 해놓고 나중에 초기화 하고 싶을 때는 변수 타입을 명시적으로 지정해줘야 한다.
fun main() {
val answer: Int
answer = 42
}
코틀린은 val과 var 키워드를 사용하여 변수를 선언한다.
val(value) : 읽기 전용 참조를 선언한다. val로 선언된 변수는 단 한 번만 값을 대입할 수 있으며, 일단 초기화 하고 나면 다른 값을 대입할 수 없다.
var(variable) : 재대입 가능함 참조를 선언한다. var로 선언한 변수는 초기화가 이뤄진 다음이라도 다른 값을 대입할 수 있다.
기본적으로 코틀린은 모든 변수를 val 키워드를 사용해 선언하는 방식을 지켜야 한다. 필요할 때만 var로 변경한다. 읽기 전용 참조와 변경 불가능한 객체를 부수 효과가 없는 함수와 조합해 사용하면 함수형 프로그래밍 스타일이 제공하는 이점을 살릴 수 있다.
val 변수는 그 변수가 정의된 블록을 실행할 때 정확히 한 번만 초기화 되어야 한다. 하지만 초기화 문장이 한번만 실행됨을 컴파일러가 확인할 수 있다면 조건에 따라 val 값을 다른 여러 값으로 초기화 할 수 있다.
fun canPerfromOperation(): Boolean {
retrun true
}
fun main() {
val result: String
if(canPerformOperation()) {
result = "Success"
} else {
result = "Can't perform operation"
}
}
val 참조 객체가 읽기 전용이어서 한 번 대입된 다음에 그 값을 바꿀 수 없더라도 그 참조가 가리키는 객체 내부의 값은 변경할 수 있다.
fun main() {
val languages = mutableListOf("Java")
languages.add("Kotlin")
}
var 키워드를 사용하면 변수의 값을 변경할 수 있지만 변수의 타입은 고정된다.
fun main() {
var answer = 42
answer = "no answer" // Error: type missmatch 컴파일 오류 발생
}
위의 경우 변환 함수를 사용해 변수의 타입을 변경하거나 값을 변수에 대입할 수 있는 타입으로 강제 형 변환해야 한다.
fun main() {
val iuput = readln()
val name = if(input.isNotBlank()) input else "Kotlin"
println("Hello $name!")
}
여러 스크립트 언어와 비슷하게 코틀린도 변수명 앞에 $를 덧붙이면 변수를 문자열 안에 참조할 수 있다.
자바의 문자열 접합 연산과 똑같은 기능을 하지만 좀 더 간결하며 효율적이다.
컴파일러는 이 식을 정적으로 검사하기 때문에 존재하지 않는 변수를 문자열 탬플릿 안에 넣으면 컴파일 오류가 발생한다.
$를 문자열에 그대로 넣고 싶으면 백슬래시를 사용해 이스케이프 시켜야 한다.
println("\$x")
// $x 출력
문자열 템플릿 안에서 복잡한 식을 사용할 수도 있다.
fun main() {
val name = readln()
if(name.isNotBlank()){
println("Hello ${name.length}-letter person!")
}
}
문자열 탬플릿 내부에 조건을 직접 입력할 수도 있다.
fun main() {
val name = readln()
println("Hello, ${if(name.isBlank()) "someone" else name}!")
}
코틀린은 글자(letter)로 분류되는 모든 유니코드 문자를 식별자에 사용할 수 있다.
따라서 한글을 변수명으로 사용할 수도 있다.
이 경우 문자열 탬플릿을 작성할 때 문제가 발생할 수 있다.
println(”Hello $name님 반가워요!) 라는 출력문이 있다고 가정해보자. 이 출력문을 실행하면 unresolved reference 오류가 발생할 것이다. 컴파일러가 변수명을 ‘name님’ 으로 인식했기 때문이다.
‘name님’ 이라는 변수는 선언되어있지 않으니 오류가 발생할 수 밖에 없다.
이를 해결하기 위해선 그냥 변수명을 중괄호로 감싸면 된다.
println(”Hello ${name}님 반가워요!)
코틀린도 자바와 마찬가지로 클래스라는 추상화를 제공한다. 코틀린은 다른 객체지향 언어에 비해 훨씬 간단하게 클래스를 선언할 수 있다.
public class Person{
private final String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
class Person(val name: String)
위의 두 클래스는 똑같은 기능을 한다.
코틀린은 클래스를 간결하게 정의할 수 있는 문법을 제공하며,
특히 데이터만 저장하는 클래스에대해 엄청나게 간결한 문법을 제공한다.
코틀린의 기본 가시성은 public이다.
클래스 = 데이터를 캡슐화하고 캡슐화한 데이터를 다루는 코드를 한 주체에 가두는 것이다.
자바에서는 필드와 접근자를 하나로 묶어 프로퍼티라고 부르며 프로퍼티라는 개념을 활용하는 프레임워크가 많다.
코틀린은 프로퍼티를 언어 기본 기능으로 제공하며 자바의 필드와 접근자를 완전히 대신한다.
프로퍼티를 선언할 때는 변수 선언과 마찬가지로 val 또는 var을 사용한다.
class Person(
val name: String, // 읽기 전용
var isStudent: Boolean
)
이렇게 선언한 클래스에 대해서 코틀린은 여러 기본 기능을 제공한다.
val로 선언된 프로퍼티는 비공개 필드와 공개 게터를 제공한다.
var로 선언된 프로퍼티에 경우 비공개 필드, 공개 게터/세터를 제공한다.
fun main() {
val person = Person("Bob", true)
println(person.name)
println(person.isStudent)
person.isStudent = false
println(person.isStudent)
}
코틀린에서 클래스를 사용하는 방식이다.
게터를 명시적으로 호출하지 않고 프로퍼티를 직접 사용해도 코틀린이 자동으로 게터를 호출한다.
변경 가능한 프로퍼티의 세터도 프로퍼티를 직접 사용한다. 그럼 자동으로 세터가 호출될 것이다.
대부분의 프로퍼티는 그 프로퍼티에 값을 저장하기 위한 필드인 background field가 있다.
하지만 원한다면 프로퍼티 값을 그때그때 계산할 수도 있다.
커스텀 게터를 작성하면 그런 프로퍼티를 만들 수 있다.
커스텀 구현이 필요한 일반적인 경우 → 어떤 프로퍼티가 다른 프로퍼티에서 계산된 값을 저장하는 경우
package geometry.shapes
class Rectangle(val height: Int, val width: Int) {
val isSquare: Boolean
get() { // 커스텀 접근자
return height == width
}
}
fun createUnitSquare(): Rectangle {
return Rectangle(1, 1)
}
같은 패키지에 있는 선언이라면 다른 파일에 있어도 직접 호출이 가능하다.
하지만 다른 패키지에 있다면 import문으로 명시적으로 호출해주어야 한다.
package geometry.example
import geometry.shapes.Rectangle
import geometry.shapes.createUnitSquare
fun main() {
println(Rectangle(3,4).isSquare)
println(createUnitSquare)
}
코틀린은 여러 클래스를 같은 파일에 넣을 수 있다.
파일의 이름도 마음대로 정할 수 있으며 디스크상의 어느 디렉터리 소스코드를 위치시키든 관계없다.
하지만 대부분의 경우 자바와 같이 패키지를 구성하는 것이 좋다.
만약 자바와 함께 코틀린을 사용하는 프로젝트의 경우 자바 소스 코드를 코틀린으로 마이그레이션 할 때 문제가 생길 수도 있다.
// enum 클래스
package ch02.colors
enum class Color {
RED, ORANGE, YELLOW, GREEN, INDIGO, VIOLET
}
// enum 상수
package ch02.colors
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
fun printColor() = println("$this is $rgb)
}
fun main() {
println(Color.BLUE.rgb) // 255
Color.GREEN.printColor() // GREEN is 65280
}
자바의 switch 식에 해당하는 코틀린의 식(expression)이다.
fun getMnemonic(color: Color) =
when(color){
Color.RED -> "Richard"
Color.ORANGE -> "Of"
Color.YELLOW -> "York"
Color.GREEN -> "Gave"
Color.BLUE -> "Battle"
Color.INDIGO -> "In"
Color.VIOLET -> "Vain"
}
fun main() {
println(getMnemonic(Color.BLUE))
}
자바의 switch문과 달리 break를 넣지 않아도 된다.
한 분기에서 값 사이를 콤마( , )로 구분하면 여러 값을 패턴으로 사용할 수 있다.
fun measureColor() = Color.ORANGE
fun getWarmthFromSensor(): String{
val color = measureColor()
return when(color){
Color.RED, Color.ORANGE, Color.YELLOW -> "warm (red = ${color.r})"
Color.GREEN -> "neutral (green = $(color.g))"
Color.BLUE, Color.INDIGO -> "cold (blue = ${color.b})"
}
}
fun main() {
println(getWarmthFromSensor())
}
상수 값들을 임포트하면 enum 클래스 이름을 반복해서 입력하는 것을 피할 수 있다.
import ch02.colors.Color.*
fun measureColor() = ORANGE
fun getWarmthFromSensor(): String {
val color = measureColor()
return when (color) {
RED, ORANGE, YELLOW -> "warm ("red = ${color.r}")"
GREEN -> "neutral (green = $(color.g))"
BLUE, INDIGO, VIOLET -> "cold (blue = ${color.b})"
}
}
when 식의 대상 값을 변수에 넣을 수도 있다.
import ch02.colors.Color.*
fun measureColor() = ORANGE
fun getWarmthFromSensor(): String {
return when (val color = measureColor()) {
RED, ORANGE, YELLOW -> "warm ("red = ${color.r}")"
GREEN -> "neutral (green = $(color.g))"
BLUE, INDIGO, VIOLET -> "cold (blue = ${color.b})"
}
}
앞의 예시들은 when식이 모든 enum 상수를 처리하기 때문에 디폴트 케이스을 선언하지 않았지만 그렇지 않은 경우에는 else 키워드를 사용해 디폴트 케이스를 정의해줘야 한다.
코틀린 when의 분기 조건에는 임의의 객체를 사용할 수 있다.
fun mix(c1: Color, c2: Color) =
when(setOf(c1, c2)) {
setOf(RED, YELLOW) -> ORANGE
setOf(YELLOW, BLUE) -> GREEN
setOf(BLUE, VIOLET) -> INDIGO
else -> throw Excrption("Dirty Color")
}
fun main(){
println(mix(BLUE, YELLOW))
}
위의 예제는 함수가 호출될 때마다 분기 조건에 있는 두 색과 같은지 비교하기 위해 여러 Set 인스턴스를 생성하기 때문에 효율적이진 않다.
자주 사용하지 않는다면 큰 문제는 없겠지만 정말 자주 호출되는 함수라면 가비지 컬렉터가 수거해야 하는 객체가 늘어나는 샘이다.
인자가 없는 when을 사용하며 불필요한 객체의 생성을 방지할 수 있다. 코드의 가독성은 조금 떨어지지만 성능 향상 효과를 볼 수 있다.
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")
}
fun main() {
println(mixOptimized(BLUE, YELLOW))
}
when에 아무 인자도 없으려면 각 분기의 조건이 불리언 결과를 계산하는 식이어야 한다.
추가 객체를 생성하지 않는다는 장점은 있지만 가독성이 하락한 모습이다.
interface Expr
class Num(val value: Int) : Expr
class Sum(val left: Expr, val right: Expr) : Expr
fun eval(e: Expr): Int {
if(e is Num) {
val n = e as Num
return n.value
}
if(e is Sum) {
return eval(e.right) + eval(e.left)
}
throw IllegalArgumentException("Unknown expression")
}
fun main() {
println(eval(Sum(Sum(Num(1), Num(2)), Num(4))))
}
코틀린의 is 검사는 약간의 편의를 제공한다.
어떤 변수의 타입을 확인한 다음에 그 타입에 속한 맴버에 접근하기 위해
명시적으로 변수 타입을 변환하지 않아도 된다는 점이다.
타입을 검사한 변수는 코틀린 컴파일러가 타입을 변환해준다.
eval 함수에서 e의 타입을 검사한 이후에는 num 또는 sum의 형태로 해석한다.
따라서 각 객체의 프로퍼티를 명시적 캐스팅 없이 사용할 수 있다.
스마트 캐스트는 is로 변수에 든 값의 타입을 검사한 다음 그 값이 바뀔 수 없는 경우에만 작동한다.
따라서 클래스의 프로퍼티에 대해 스마트 캐스트를 사용한다면 그 프로퍼티는 반드시 val로 선언되어야 한다.
또한 커스텀 접근자를 통한 프로퍼티 접근도 안된다.
val이 아니거나 커스텀 접근자를 사용하는 경우 그 값이 항산 같은 값을 내놓는다고 확신할 수 없기 때문이다.
원하는 타입으로 명시적 타입 캐스팅을 하려면 as를 사용하면 된다.
eval 함수의 return과 중괄호를 없애는 대신 if 식을 함수 본문으로 하는 본문 구문을 작성할 수 있다.
fun eval(e: Expr): Int =
if(e is Num) {
e. value
} else if (e is Sum) {
eval(e.right) + eval(e.left)
} else {
throw IllegalArgumentException("Unknown expression")
}
이 코드를 더 간단하게 만들 수 있다.
if문의 분기에 식이 하나밖에 없다면 중괄호를 생략해도 된다.
if분기에 블록을 사용하는 경우 그 블록의 마지막 식이 그 분기의 결괏값이다.
fun eval(e: Expr): Int =
if(e is Num) e.value
else if (e is Sum) eval(e.left) + eval(e.right)
else throw IllegalArgumentException("Unknown expression")
이제 when을 사용해 더 나은 구성으로 리펙터링 해보자.
when 식은 동등성 검사 뿐만 아니라 다른 기능에도 사용할 수 있다.
fun eval(e: Expr): Int =
when (e) {
is Num -> e.value
is Sum -> eval(e.left) + eval(e.right)
else -> throw IllegalArgumentException("Unknown expression")
}
위 코드는 when이 받은 값의 타입을 검사하는 분기를 보여준다.
타입을 검사하고 나면 스마트 캐스트가 이뤄지기 때문에 Num이나 Sum 맴버에 접근할 때
강제로 캐스팅 할 필요가 없다.
if나 when 모두 분기에 코드 블록을 사용할 수 있다.
fun evalWithLogging(e: Expr): Int =
when (e) {
is Num -> {
println("num: ${e.value}")
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과 do-while 루프가 있다.
//while 문
while(condition){
/*...*/
if(shouldExit) break
}
//do-while 문
do{
if (shouldSkip) continue
} while (condition)
내포된 루프의 경우 레이블을 지정할 수 있다. break나 continue를 사용할 때 레이블을 참조할 수 있다.
outer@ while (outerCondition){
while(innerCondition){
if(shouldExitInner) break
if(shouldSkipInner) continue
if(shouldExit) break@outer
if(shouldSkip) continue@outer
}
}
범위를 사용할 때는 .. 연산자를 사용한다.
val oneToTen = 1..10
코틀린의 범위는 폐구간, 즉 양끝을 포함하는 구간이다.
정수 범위를 갖고 수행할 수 있는 가장 단순한 작업은 범위에 속한 모든 값에 대해 루프를 도는 것이다.
어떤 범위에 속한 값을 일정한 순서로 이터레이션 하는 경우를 순열(progression)이라고 한다.
fun fizzbuzz(i: Int) = when {
i % 15 == 0 -> "FizzBuzz"
i % 3 == 0 -> "Fizz"
i % 5 == 0 -> "Buzz"
else -> "$i"
}
fun main() {
for (i in 1..100) {
println(fizzbuzz(i))
}
}
fun main() {
for (i in 100 downTo 0 step 2) {
println(fizzbuzz(i))
}
}
이렇게 작성하면 100부터 0까지 짝수만 셀 수 있다.
downTo 키워드를 통해 역방향 순열을 만들고 step 키워드를 통해 증가 값을 지정해 수를 건너 뛸 수 있다.
끝 값을 포함하지 않는 반만 닫힌 범위에 대해 이터레이션 할 수 있다. ( ..< )
for(x in 1 ..< size)
fun main() {
val collection = listOf("red", "green", "blue")
for (color in collection){
println("$color")
}
}
위의 예시는 가장 대표적인 예시이다.
다음은 맵에 대해 이터레이션 하는 방법이다.
fun main(){
val binaryReps = mutableMapOf<Char, String>()
for(char in 'A'..'F') {
val binary = char.code.toString(2)
binaryReps[char] = binary
}
for((letter, binary) in binaryReps) {
println("$letter = $binary")
}
}
맵의 값을 가져오거나 키에 해당하는 값을 설정할 때 put, get같은 함수를 사용하는 대신
map[key]로 값을 가져오고, map[key] = value로 값을 설정할 수 있다.
컬렉션의 값을 받아올 때 인덱스도 함께 받아올 수 있다.
fun main(){
val list = listOf("10", "11", "1001")
for((index, value) in list.withIndex()){
println("$index: $value")
}
}
in 연산자를 사용해 어떤 값이 범위에 속하는지 검사할 수 있다.
반대로 !in을 사용하면 어떤 값이 범위에 속하지 않는지 검사할 수 있다.
fun isLetter(c: Char) = c in 'a'..'z' || c in 'A'..'Z'
fun isNotDigit(c: Char) = c !in '0'..'9'
fun main(){
println(isLetter('q'))
println(isNotDigit('x'))
}
fun recognize(c: Char) = when (c) {
in '0'..'9' -> "it's a digit!"
in 'a'..'z', in 'A' .. 'Z' -> "it's a letter!'"
else -> "I don't know"
}
fun main(){
println(recognize('8'))
}
범위는 문자뿐만 아니라 클래스에도 적용할 수 있다.
비교가 가능한 클래스라면 그 클래스의 인스턴스 객체를 사용해 범위를 만들 수 있다.
이렇게 만든 범위의 경우 그 범위 내의 모든 객체를 항상 이터레이션하지는 못한다.
하지만 in 연산자를 사용하면 값의 범위 안에 속하는지 항상 결정할 수 있다.
fun main(){
println("Kotlin" in "Java".."Scala")
}
// true
// String 클래스에 있는 Comparable 인터페이스가 두 문자열을 알파벳 순서로 비교한다.
컬랙션에도 in을 사용할 수 있다.
fun main(){
println("Kotlin" in setOf("Java", "Scala)"))
}
throw 키워드를 통해 예외를 던질 수 있다.
코틀린의 throw는 식이므로 다른 식에 포함될 수 있다.
val percentage =
if(number in 0 .. 100)
number
else
throw IllegalArgumentException ("A percentage value must be between 0 and 100: $number")
자바와 마찬자기로 try, catch, finally를 사용해 예외를 처리한다.
자바에서 명시적으로 처리해야만 하는 예외를 체크 예외라고 한다.
코틀린은 체크 예외(check exception)와 언체크 예외(uncheck exception)를 구별하지 않는다.
함수가 던지는 예외를 지정하지 않고 발생한 예외를 잡아내도 되고 잡아내지 않아도 된다.
코틀린에서는 컴파일러가 예외 처리를 강제하지 않는다.
fun readNumber(reader: BufferReader): Int {
val line = reader.readline()
reader.close()
return Integer.parseInt(line)
}
fun readNumber(reader: BufferedReader) {
val number = try {
Integer.parseInt(reader.readLine())
} catch (e: NumberFormatException) {
return
}
println(number)
}
fun main(){
val reader = BufferedReader(StringReader("not a number"))
readNumber(reader)
}
if와 달리 try의 본문은 반드시 중괄호로 감싸야 한다.
try의 본문 또한 내부에 여러 문장이 있으면 마지막 식의 값이 전체 결과값이다.
fun readNumber(reader: BufferedReader) {
val number = try {
Integer.parseInt(reader.readLine())
} catch (e: NumberFormatException) {
null
}
println(number)
}
fun main(){
val reader = BufferedReader(StringReader("not a number"))
readNumber(reader)
}
try 코드 블록의 실행이 정상적으로 끝나면 그 블록의 마지막 식의 값이 결과다.
에외가 발생해 잡히면 그 예외에 해당하는 catch 블록의 값이 결과다.
try를 식으로 사용하면 중간 변수를 도입하는 것을 피함으로써 코드를 좀 더 간결하게 만들고,
더 쉽게 예외에 대비한 값을 대입하거나 try를 둘러싼 함수를 반환시킬 수 있다.