코틀린 인액션 2장 - 1
함수와 변수
- 코틀린에서는 왜 타입 선언 생략과 변경 가능한 데이터보다 변경할 수 없는 불변 데이터 사용을 장려하는지 알아보자.
Hello, World!
// Test.kt
fun main(args: Array<String>) {
println("Hello, World!")
}
위의 간단한 코드에서 여러가지 코틀린 문법의 특성을 발견할 수 있다.
간략하게 위의 코드를 컴파일 후 디컴파일 해보면 아래와 같이 코틀린 파일이름 클래스의 static 메소드로 변경됨을 확인할 수 있다.
public final class TestKt {
public static final void main(@org.jetbrains.annotations.NotNull String[] args) {
System.out.println("Hello, World!");
}
}
- 함수를 선언할 때는 fun 키워드를 사용하고, 함수는 최상위 수준에 정의될 수 있다. (자바와 달리, 파이썬 처럼) 꼭 클래스 안에 함수를 넣어야 할 필요가 없다.
- 파라미터 이름 뒤에 그 파라미터의 타입을 쓴다. (변수를 선언할 때도 마찬가지)
- 배열도 일반적인 클래스와 마찬가지로, 자바와 달리 배열 처리를 위한 문법이 따로 존재하지 않는다.
- System.out.println 대신에 println을 사용한다. 코틀린 표준 라이브러리는 여러 가지 표준 자바 라이브러리를 감싼 래퍼를 제공한다.
- 세미콜론(;)을 붙이지 않아도 된다.
- 코틀린의 기본 철학은 폐쇄이므로 기본적으로 상속을 허용하지 않는다. 상속을 허용하고 싶으면 클래스 앞에 open 키워드를 붙여야 한다.
함수
위의 Hello World를 출력하는 함수는 아무런 값도 반환하지 않는다.
하지만 결과를 반환하는 함수의 경우 반환 값의 타입을 파라미터 목록의 닫는 괄호 다음에
콜론(:)으로 구분하여 작성한다.
fun max(a: Int, b: Int): Int {
return if (a > b) a else b // 블록이 본문인 함수 형태
}
문(statement)과 식(expression)의 구분
코틀린에서 if는 “식”이지 “문”이 아니다. 식은 값을 만들어 내며 다른 식의 하위 요소로 계산에 참여할 수 있는 반면 문은 자신을 둘러싸고 있는 가장 안쪽 블록의 최상위 요소로 존재하며 아무런 값을 만들어내지 않는다.
자바에서는 모든 제어 구조가 “문”인 반면 코틀린에서는 루프를 제외한 대부분의 제어 구조가 “식”이다.
위의 함수를 조금 더 간결하게 표현할 수 있다.
앞의 함수 본문은 if 식 하나로만 이루어져 있는데, 이런 경우 아래와 같이 중괄호를 없애고
return을 제거하면서 등호(=)를 식 앞에 붙이면 더 간결하게 함수를 표현할 수 있다.
// 식이 본문인 함수 형태
1. fun max(a: Int, b: Int): Int = if (a > b) a else b
2. fun sayHello(): Unit = println("Hello")
// 반환 타입을 생략한 식이 본문인 함수 형태
1. fun max(a: Int, b: Int) = if (a > b) a else b
2. fun sayHello() = println("Hello")
이렇게 등호(=)와 식으로만 이루어진 함수를 "식이 본문인 함수"라 하고 본문이 중괄호로 둘러싸인 함수를
"블록이 본문인 함수"라고 부른다. 식이 본문인 함수는 굳이 사용자가 반환 타입을 적지 않아도 컴파일러가
함수 본문 식을 분석해서 식의 결과 타입을 함수 반환 타입으로 정해준다.
블록이 본문인 함수가 "값을 반환한다면" 반드시 반환 타입을 지정하고, return 문을 사용해 반환 값을 명시해야 한다.
하지만 만약 아래와 같이 "값을 반환하지 않는다면" 반환 타입을 지정할 필요가 없다.
fun sayHello() {
val hello = "hello"
println("안녕하세요 $hello")
}
- 이렇게 컴파일러가 타입을 분석해 구성 요소의 타입을 정해주는 기능을 타입 추론이라 부른다.
변수
- 코틀린에서는 타입으로 변수 선언을 시작하는 대신 변수 이름 뒤에 타입을 명시하거나 생략하게 허용한다.
val question = "삶, 우주, 그리고 모든 것에 대한 궁극적인 질문"
// 타입 지정
val answer: Int = 42
val yearsToCompute: Double = 7.5e6
// 타입 생략 가능
val answer = 42
val yearsToCompute = 7.5e6
식이 본문인 함수와 마찬가지로 타입을 지정하지 않으면 컴파일러가 초기화 식을 분석해서 초기화 식의 타입을 변수 타입으로 지정한다.
하지만 초기화 식을 사용하지 않고 변수를 선언하면 컴파일러가 타입을 추론할 수 없기 때문에 변수 타입을 반드시 명시해야 한다.
val question: String
// 선언과 동시에 초기화하지 않았으므로 타입 명시
question = "삶, 우주, 그리고 모든 것에 대한 궁극적인 질문"
변경 가능한 변수와 변경 불가능한 변수
- 변수 선언시 사용하는 키워드는 다음과 같은 2가지가 있다.
val - 변경 불가능한 참조를 저장하는 변수, 초기화 되고 나면 재대입이 불가하다. (자바의 final)
var - 변경가능한 참조를 저장하는 변수, 변수의 값은 바뀔 수 있다.
변경 불가능한 참조와 변경 불가능한 객체를 부수 효과가 없는 함수와 조합해 사용하면 코드가 함수형에 가까워지므로 기본적으로 모든 변수를 val 키워드를 사용해 선언하고, 나중에 필요한 경우에 var로 변경하는 것을 권장한다.
val 변수는 블록을 실행할 때 한 번만 초기화돼야 한다.
val message: String
if (canPerformOperation()) {
message = "Success"
} else {
message = "Failed"
}
val 참조 자체는 불변일지라도 그 참조가 가리키는 객체의 내부 값은 당연히 변경될 수 있다.
val languages = arrayListOf("JAVA")
languages.add("KOTLIN")
컴파일러는 변수 선언 시점의 초기화 식으로부터 변수의 타입을 추론하며, 변수 선언 이후 변수 재대입이 이뤄질 때는
이미 추론한 변수의 타입을 염두에 두고 대입문의 타입을 검사한다.
따라서 var 키워드를 사용하면 변수의 값을 변경할 수 있지만, 변수의 타입은 고정돼 바뀌지 않는다.(정적언어이기 때문에 파이썬과는 다르다)
var answer = 42
// answer = "STRING" -> 컴파일 오류
문자열 템플릿
- 여러 스크립트 언어와 비슷하게 코틀린에서도 문자열 리터럴의 필요한 곳에 $+{변수}를 넣어 변수를 문자열 안에 사용할 수 있다.
fun main(args: Array<String>) {
val name = if (args.isNotEmpty()) args[0] else "kotlin"
println("Hello ${name}") // 자바의 System.out.println("Hello " + name); 과 동일한 결과다.
println("Hello, ${if (args.size > 0) args[0] else "kotlin"}") // 복잡한 식도 중괄호({})로 둘러싸서 문자열 템플릿 안에 넣을 수 있다.
println("Hello, ${if (args.size == 0) "zero" else "no zero ${args[0]}"}") // 중괄호로 둘러싼 식 안에서 문자열 템플릿 사용도 가능하다.
}
자바와 달리 + 연산으로 문자열과 변수를 붙여도 StringBuilder를 사용해 문자열 상수와 변수의 값을 append로 문자열 빌더 뒤에 추가한다.
Comments