002. 코틀린의 함수와 변수

코틀린의 함수와 변수

Hello, world!

프로그래밍의 가장 전통적이고 고전적인 예시인 “Hello, world!”를 작성해보자.

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

위 코드에서 코틀린의 특정적인 부분을 찾아보면 아래와 같다.

  • fun 키워드는 funtion을 뜻한다. 즉 함수 선언에 사용된다.
  • 파라미터의 타입은 이름 뒤에 작성된다.
  • Java와는 달리 함수는 최상위 수준에서 선언될 수 있으므로, 클래스 안에 작성될 필요가 없다.
  • Array도 클래스일 뿐이다. Java처럼 특정 구문으로 선언되지 않는다.
  • 코틀린의 표준 라이브러리는 Java보다 더 많은 기능과 간결한 구문을 제공한다. 대표적으로 System.out.println 대신 println을 사용할 수 있다.
  • 세미콜론 (;)을 생략할 수 있다.

함수

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

위의 코드를 다시 보면 main함수에서 반환하는 것이 없다는 것을 알 수 있다.

만약 함수에서 특정 값을 반환하고 싶은 경우 함수 뒤에 타입을 선언한다.

아래 코드를 보자.

1
2
3
4
5
6
7
fun max(a: Int, b: Int): Int {
return if (a > b) a else b
}

/* 출력 결과 */
>>> println(max(1, 2))
2

함수를 선언할 땐 fun 키워드로 먼저 함수임을 표시한 뒤, 함수 이름과 호출에 필요한 파라미터들,

그리고 마지막에 반환할 타입을 선언한다.

따라서 위의 max 함수는 파라미터로 두 개의 Int를 입력 받아 Int를 반환하는 함수임을 알 수 있다.

max 함수는 EXPRESSION BODY 를 이용해 좀 더 간단하게 표현할 수 있다.

용어가 존재한다고해서 특정한 구문이라기 보단 간결하게 표현할 수 있는 함수 자체라고 이해하는 게 좋다.

함수의 코드블럭 영역을 나타내는 브라켓과 반환문을 제거할 수 있는데, max에 적용하면 아래와 같다.

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

코틀린에서는 위와 같이 식이 본문 그 자체인 경우를 종종 볼 수 있다.

만약 max 함수를 더 줄일 수 있다면 어떻게 될까?

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

실제로 모든 변수와 표현식에는 타입이 존재하고, 모든 함수도 마찬가지로 반환 타입을 가지고 있다.

다만 코틀린의 컴파일러가 추론을 통해 이를 파악할 수 있는 경우 생략이 가능하다.

변수

Java에선 타입 이름 순으로 변수를 선언한다.

하지만 위에서 언급한 타입 추론이 있으므로 많은 케이스에서 타입을 생략할 수 있다.

따라서 코틀린은 Java와는 반대로 이름 타입 순으로 변수를 선언하고, 후미에 오는 타입을 생략할 수 있게 된다.

아래 코드에서 두 개의 변수 선언을 살펴보자.

1
2
val question = "The Ultimate Question of Life, the Universe, and Everything"
val answer = 42

만약 answer 변수에 대해 타입을 명시적으로 선언하고 싶다면 아래와 같이 수정할 수 있다.

1
val answer: Int = 42

타입을 지정하지 않는 경우엔 초기화되는 값을 보고 해당 타입을 변수의 타입으로 판단한다.

따라서 Int가 없더라도 answer변수는 42를 통해 Int로 초기화된다.

Double과 같은 부동소수점 형태의 변수는 어떻게 처리할 수 있을까?

1
val yearsToCompute = 7.5.e6 // 7.5 * 10^6 = 7500000.0

위와 같이 부동소수점과 같은 실수도 마찬가지로 타입 추론이 가능하다.

참고 제일 뒤에 F를 붙이는 방식으로 Float를 명시하는 등의 방법이 가능하다. 추후 다루도록 하겠다.

1
2
val answer: Int
answer = 42

만약 변수의 타입에 해당하는 초기화 로직이 없는 경우 추론이 불가능하므로 타입을 명시적으로 선언해주어야 한다.

위의 코드를 보면 Int를 명시적으로 선언했기 때문에 초기화 없이 변수를 선언할 수 있다.

가변과 불변

변수는 valvar 두 개의 키워드로 선언할 수 있다.

val (value)

  • 불변이다. 즉, 한 번 초기화한 값을 변경할 수 없다.
  • Java의 final 키워드가 적용된 변수와 동일하다.

var (variable)

  • 가변이다. 즉, 초기화한 뒤 계속해서 값을 변경할 수 있다.

코틀린을 개발하면서 최대한 많은 변수에 대해 val로 선언하도록 노력해야 한다.

초기화 변경이 꼭 필요한 변수만 var로 선언한다면 부작용이 최소화된 프로그램을 개발할 수 있기 때문이다.

val로 선언하는 변수는 단 한 번 초기화되는 것이 일반적이지만 특정 조건에 따라 분기되는 경우 다른 값으로 초기화될 수 있다.

아래 코드가 그 예시이다.

1
2
3
4
5
6
7
8
9
val message: String

if (canPerformOperation()) {
message = "Success"
// ... perform the operation
}
else {
message = "Failed"
}

val로 선언하더라도, 참조하고 있는 객체 자체(아래 코드에서의 languages)가 아닌 참조한 객체의 내부 데이터는 변경이 가능하다.

아래의 코드가 그 예시이다.

1
2
val languages = arrayListOf("Java")
languages.add("Kotlin")

변경이 가능한 var로 선언하더라도 해당 변수의 값을 변경할 수 있는 것이지, 타입은 변경할 수 없다.

따라서 아래 코드는 컴파일 에러를 발생시킨다.

1
2
var answer = 42
answer = "no answer"

문자열 템플릿

위에서 작성한 “Hello, world!” 코드를 다시 살펴보자.

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

이 코드를 좀 더 개선해서, 실행시 입력받은 값이 있으면 그 값을 출력해주고, 없으면 대신 Kotlin이라는 문자열을 출력하도록 개선한다면 아래와 같이 작성된다.

1
2
3
4
fun main(args: Array<String>) {
val name = if (args.size > 0) args[0] else "Kotlin"
println("Hello, $name!")
}

위의 코드에서 눈여겨볼 만한 것은 $name이라는 코드이다.

이는 문자열 템플릿 이라는 기능으로, $를 이용해 지역 변수를 참조할 수 있다.

이를 통해 Java의 + 연산자 없이도 간단하게 코드를 작성할 수 있다.

만약 문자열 내부에 $ 문자를 포함하게 되는 경우 이스케이프(\)를 통해 정상적으로 출력할 수 있다.

문자열 템플릿 은 단순한 변수의 참조 뿐만 아니라 좀 더 복잡한 표현식도 사용할 수 있다.

1
2
3
4
5
fun main(args: Array<String>) {
if (args.size > 0) {
println("Hello, ${args[0]}!")
}
}

만약 큰 따옴표 안에 큰 따옴표가 있을 경우 중첩이 가능하다.

이를 이용해 별도의 변수 선언 없이도 문자열을 사용할 수가 있다.

1
2
3
4
5
6
fun main(args: Array<String>) {
println("Hello, ${if (args.size > 0) args[0] else "someone"}!")
}

/* 출력 결과 */
Hello, someone!