코틀린 개발자들은 세미콜론은 굳이 권장하지 않는다고 한다. 세미콜론은 한줄에 여러 문장을 적는 경우를 빼고 사용하지 않아도 된다.
val sum = 6 + 2
코틀린은 정적 타입 지정 언어다. 컴파일 시점에서 모든 객체나 메소드의 타입을 알 수 있다. 즉, 컴파일러가 타입을 확정하고 검증해준다. 코틀린은 문맥을 통해 변수의 타입을 추론하는 타입 추론(type inference) 기능을 지원한다.
val greet = "hello"
println(greet)
println(greet::class)
println(greet.javaClass)
결과
hello
class kotlin.String
class java.lang.String
::class call은 해당 객체가 참조하는 Kotlin class를 반환한다. .javaClass는 해당되는 Java class를 반환해준다. greet 변수에 대입되는 값이 문자열이므로 코틀린 컴파일러는 변수의 타입을 String형임을 추론하는것을 확인할 수 있다.
따라서 아래처럼 미리 greet이라는 변수에 문자열 할당한 다음 정수형 변수를 재할당(reassgine)하게 되면 코틀린은 컴파일 레벨에서 변수에 따른 type error를 미리 감지할 수 있다.
val greet = "hello"
println(greet)
greet = 0 //error
결과
error: val cannot be reassigned
greet = 0
^
error: the integer literal does not conform to the expected type String
greet = 0
결론은 변수명을 지을때 한번에 제대로 지어줘야 한다. 또한 변수명을 지을때 타입에 대한 명시는 하지 않는게 좋다. ex) taxRateDouble = 0.08
자바와 다르게 코틀린은 선언, 표현식 없이 클래스와 함수 작성이 가능하다. 코틀린 컴파일러는 코드가 컴파일되거나 스크립트로 실행될 시점에 JVM expectation에 맞는 기준의 wrapper 클래스나 메소드를 생성해 처리해주기 때문이다.
fun nofluff() {
println("nofluff called...")
throw RuntimeException("oops")
}
println("not in a function, calling nofluff()")
try {
nofluff()
} catch(ex: Exception) {
val stackTrace = ex.getStackTrace()
println(stackTrace[0])
println(stackTrace[1])
}
결과
not in a function, calling nofluff()
nofluff called...
Standalone.nofluff(standalone.kts:4)
Standalone.<init>(standalone.kts:10)
실행 결과를 확인해보면 작성한 함수를 코틀린 컴파일러가 Standalone이라는 클래스로 wrapping 해버린 모습을 확인할 수 있다. 이런 간단한 구조의 코드는 클래스나 함수부 선언 없이 작성해도 되지만 코드의 구조가 복잡해질수록 클래스와 메소드를 적절히 조합해 구조상의 설계를 잘 잡아줘야 한다.
코틀린은 try-catch문을 강요하지 않는다. 코틀린에서 만약 try-catch로 처리되지 않는 코드가 오류를 낸다면, 해당 exception은 자동적으로 호출부로 넘어가게 되고 만약 exception handling 처리를 하지 못한다면 프로그램이 종료된다.
불변(immutable) 타입 변수를 선언할때는 val을 사용한다 (코틀린에서는 변수명 뒤에 콜론(:)를 붙혀 변수의 타입을 hinting 해줄 수 있다. val은 자바의 final에 해당한다고 볼 수 있다.
val pi: Double = 3.141592
pi = 3.1555 // error
가변(mutable) 타입 변수를 선언할때는 var를 사용해준다.
var score = 10
score += 1
println(score) // 결과 : 11
코틀린에서는 코드의 가변성이 높을수록 오류의 확률 또한 비례한다고 한다. 또한 같은 코드를 병렬로 실행하기도 어려워서 함수형 프로그래밍 기법에서는 거의 금기(taboo)라고 한다. 따라서 가능하면 val을 사용하도록 권장하고 있다.
하지만 val은 값에 의한 불변성만을 보장한다. 만약 변수가 객체에 대한 참조값만 저장하고 있다면 객체의 내부값은 변경이 가능하다. 다음 예시를 확인해보자
val message = StringBuilder("hello ")
message.append("there")
println(message) // 결과 : hello there
코틀린에서 동일성 확인(equality check)는 다음과 같이 사용해준다
1) 값에 대한 equality check
val num1 = 18
val num2 = 18
println(num1 == num2) // true
2) 참조(reference)에 대한 equality check. 같은 객체(같은 주소를 참조하는지)인지 비교할때
val num1 = 18
val num2 = 18
println(num1 === num2) // 같은 kotlin.Int = 18을 참조하기 때문에 True
코틀린에서 == 연산을 사용해 값을 비교할때, 코틀린은 null check를 먼저 수행하고 equals() 메소드를 수행한다. 따라서 어느 한쪽의 값이 null이면 자동적으로 NullPointerException을 핸들링 해준다. 만약 둘중 하나가 null이면, 결과는 false이다.
println("hi" == "hi") // true
println("hi" == "Hi") // false
println(null == "hi") // false
println("hi" == null) // false
println(null == null) // true
코틀린에서 문자열 안에서 외부에 있는 변수를 가져올 때 $를 사용해준다. 외부 변수에 대한 연산이 필요할때는 ${}를 사용해준다
val name = "sungjun"
var age = 27
println("Hello my name is $name") // Hello my name is sungjun
println("My Age is ${age + 1}") // My age is 28
코틀린의 string template은 문자열이 생성된 시점에 초기화된다 따라서 다음과 같은 코드는 컴파일상의 오류는 없지만 문맥상 문제를 일으킬 수 있다.
var factor = 2
fun doubleIt(n: Int) = n * factor
var message = "The factor is $factor" // The factor is 2로 초기화
factor = 0
println(doubleIt(2)) // 0
println(message) // The factor is 2
코틀린에서 raw string은 new line을 포함하는 임의 텍스트이다, string template도 가능하다. """을 사용해준다
val name = "Eve"
val memo = """Dear $name, a quick reminder about the
party we have scheduled next Tuesday at
the 'Low Ceremony Cafe' at Noon. | Please plan to..."""
println(memo)
// 결과
Dear Eve, a quick reminder about the
party we have scheduled next Tuesday at
the 'Low Ceremony Cafe' at Noon. | Please plan to...
만약 텍스트가 if문 안에 위치해 들여쓰기 문제가 생긴다면 다음과 같이 처리해주면 된다.
fun createMemoFor(name: String): String {
if (name == "Eve") {
val memo = """Dear $name, a quick reminder about the
|party we have scheduled next Tuesday at
|the 'Low Ceremony Cafe' at Noon. | Please plan to..."""
return memo.trimMargin()
}
return ""
}
println(createMemoFor("Eve"))
//결과
Dear Eve, a quick reminder about the
party we have scheduled next Tuesday at
the 'Low Ceremony Cafe' at Noon. | Please plan to...
문장의 시작에 |를 사용하면 된다. 이후 trimMargin() 메소드를 사용해주면 깔끔하게 들여쓰기 공백이 사라진다.
우선 Expression(표현식)과 Statement(선언식)의 차이점 부터 알아보자.
Expression은 수식이라는 뜻으로 하나 이상의 값을 만들어내는 코드를 말한다. 예를들어 사칙연산이 있다 (1+1, 2+2, "hello" 등)
프로그래밍에서 Statement(선언식)이란 실행가능(executable)한 최소의 독립적인 코드 조각을 일컫는다. 흔히 한개 이상의 expression과 프로그래밍 키워드를 포함하는 경우가 많다. (if, switch, for, while, age = 7, name = "sungjun" 등)
다음 예시를 보자
fun canVote(name: String, age: Int): String {
var status: String
if (age > 17) {
status = "yes, please vote"
} else {
status = "nope, please come back"
}
return "$name, $status"
}
println(canVote("Eve", 12))
canVote() 메소드는 if 선언식을 사용한다. if문 안을 보면 status라는 변수에 대한 값만 변경할 뿐이지 무언가를 return 하지 않는다. 이 if 선언식을 사용한 함수 안에서 유의미한 값을 얻어낼려면 status라는 변수를 선언한 후 조건 처리를 통해 값을 할당해 마지막에 선언식 밖에서 return 해줘야 한다.
반면에 코틀린에서 if는 표현식으로 처리할 수 있다.
val status = if (age > 17) "yes, please vote" else "nope, please come back"
return "$name, $status"
아까와는 다르게 status 변수를 val로 선언할 수 있다. 왜냐하면 if문을 표현식으로 선언함으로써 실행이후 값에 대한 변화가 없기 때문이다. 또한 status 변수에 대한 타입 추론(type inference)도 사용해주고 있다. 코드가 훨씬 더 간결해졌다.
코틀린에서는 try-catch 또한 표현식으로 선언할 수 있다.
fun tryExpr(blowup: Boolean): Int {
return try {
if (blowup) {
throw RuntimeException("fail")
}
2
} catch(ex: Exception) {
4
} finally {
//...
}
}
println(tryExpr(false)) //2
println(tryExpr(true)) //4