본문 바로가기

Kotlin

코틀린, 코틀린답게 사용하기 - 1. Expression

Java 에는 Effective Java 라는 유명한 지침서가 있습니다. 코틀린을 주로 다뤄야하는 환경으로 오면서 코틀린을 코틀린답게 사용하기위해 

Kotlin계의 이펙티브자바인 Kotlin in action 을 공부하고있습니다.

 

Kotlin in action 에서 다루는 내용들을 바탕으로 기억해야 할 것들을 정리하려고 합니다.

해당코드는 github에서 확인할 수 있습니다.

 

 

 

코틀린에서 문(statement) 과 식(expression) 의 구분

코틀린에서 if, when 등 루프를 제외한 대부분의 제어 구조 등은 자바와 달리 expression 입니다. 이로인한 효과에 대해서 알아보려고합니다.

 

statementexpression 는 쉽게 혼동하기 쉬운 개념입니다.

코틀린에서 statementexpression 를 구분하는 방법은 값을 만들어 내는 차이에 있습니다. expression 은 값을 만들어내고 또 다른 expression 의 하위 요소로 계산에 참여할 수 있습니다. 반면에 statement 는 최상위 요소로 존재하며 어떠한 값도 만들어내지 않습니다. 

 

 

자바에서 if 문은 statement 입니다. 따라서 아래와 같이 조건에 따라 값을 할당해야 할경우에는 삼항연산자를 사용하거나 분기를 통한 대입이 필연적입니다.

 

public int max(int a, int b) {
  int max;
        
  // statement
  if( a > b ) {
    max = a;
  } else {
    max = b;
  }

  max = a > b ? a : b;
  return max;
}

 

코틀린에서 if 문은 값을 만들어낼 수 있기때문에 expression 이며 다음과 같이 간결하게 표현할 수 있습니다.

 

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

 

이는 자바의 3항 연산자를 완전하게 대체할 수 있기때문에 코틀린에서는 3항 연산자를 지원하지 않습니다. 사실 위의 코드는 3항연산자에 비해 간결하게 표현했다고 말하기 힘든데, 조금 더 살펴보겠습니다.

 

위의 max 함는 if 식 하나로만 이뤄져있습니다. 이러한 경우에는 중괄호와 return 을 제거하고 등호(=) 를 식 앞에 붙이면,

 

fun max(a: Int, b: Int) : Int = if(a > b) a else b // expression

 

이처럼 나타낼 수 있고 이렇게 등호와 식으로만 이뤄진 함수를 expression body function 이라고 합니다.

또한, 값을 만들어내는 expression 은 컴파일시점에 컴파일러가 타입을 분석하여 타입을 추론하는게 가능합니다. 

 

fun max(a: Int, b: Int) = if(a > b) a else b // expression

 

따라서, 반환타입을 명시하지 않아도 됩니다.

 

중괄호로 둘러싸인 함수인 block body function 은 반드시 반환 타입을 지정하고 return 문을 통해 반환 값을 명시해야 합니다. 

실제 코드에서는, return 문이 여러개일 수 있고 함수가 어떤 타입의 값을 반환하고 어디서 그런 값을 반환하는지 더 쉽게 알아볼수 있기 때문에 반환타입과 return 문을 강제 했다고 합니다.

 

코틀린에서는 타입 지정을 생략하는 경우가 흔한데, expression 의 경우에도 타입을 지정해야 읽기 쉽다면 명시해줘야 한다고 생각합니다. 코드는 짧아질 수 있지만, 읽는데에 오래걸린다면 간결한 코드라고 말하기 힘들겠죠?

 

try-catch 문도 코틀린에서는 값을 만들어낼 수 있기때문에 expression 입니다. 그리고 중괄호를 강제합니다.

즉, 블럭 안에서 값을 만들어내야 하는 경우가 생기는데 이러한 경우에는 블럭 의 마지막 문장이 값에 해당합니다.

 

// 자바와 비슷하게 사용
fun readNumber(reader: BufferedReader): Int? {
    try {
        val line = reader.readLine()
        return Integer.parseInt(line)
    }
    catch (e:NumberFormatException) {
        return null
    }
    finally {
        reader.close()
    }
}

 

// expression
fun readNumberExpression(reader: BufferedReader) {

    val number = try {
        Integer.parseInt(reader.readLine())
    } catch (e: NumberFormatException) {
        null
    }
    ...
}

 

예외가 발생하지 않는다면 number 변수에는 Integer.parseInt(reader.readLine()) 의 결과가 할당됩니다. 

예외가 발생한경우에도 마찬가지로 number 에는 값이 할당되어야만 합니다. 따라서 catch 블럭 안의 마지막 문장도 반드시 값을 나타내는 문장이어야 합니다! (return 을 통해 종료하는 경우가아니라면)

 

이처럼 코틀린에서는 블럭의 마지막 식이 블록의 결과 라는 규칙은 블럭이 값을 만들어 내야 하는 경우 항상 성립합니다.

 

 

참고