Post

(Kotlin) Tip2 + 문자열 템플릿

가변 인수

가변 인수를 받을 때는 앞에 vararg 키워드를 붙인다.

1
2
fun listOf<T>(vararg values: T): List<T> { ... }

이미 배열에 들어있는 원소를 가변 길이 인자로 넘길 때는 spread 연산자 \*를 붙인다.

1
2
val list = listOf("args: ", \*args)

자바 코드로 변환해보면 호출하는 측에서 SpreadBuilder라는 객체에 \*로 지정된 인자를 모두 풀어서 추가하고, 이를 배열로 만들어 넘기게 된다.

infix(중위) 함수 호출 구문과 딕셔너리( 맵 )

함수 선언 시에 infix를 붙이면 3.mul(10)3 mul 10으로 쓸 수 있도록 하는 건데, 가독성에 이점이 있는 듯. 맵 만들 때 쓰면 편하다. * 파이썬의 딕셔너리가 자바에서는 맵이다.

1
2
3
4
>>> val numbers = mapOf("zero" to 0, "one" to 1)
>>> println(numbers)


삼항 연산자가 없는 대신 if로 처리할 수 있다.

코틀린에서 if는 단순한 문장(statement)이 아니라 결과를 반환하는 식(expression)이다. Note ) if when try 모두 식(expression)이다.

1
2
var k = if (a > b) a else b

스마트 캐스트 : 코틀린에서는 타입 검사와 캐스트가 한 연산자에 의해 이루어진다.

어떤 객체의 타입을 검사했고 객체가 그 타입에 속한다면 해당 타입의 메소드나 필드 등의 멤버를 별도의 캐스트 없이 사용할 수 있다.

1
2
3
if (value is String)
println(value.toUpperCase())

Note) 단, 스마트 캐스트는 변수의 타입을 검사한 다음 변경되지 않음을 보장할 수 있는 경우에만 동작한다. 따라서 val이면서 커스텀 접근자를 사용하지 않는 경우에만 동작한다. var이면 null check/type check 이후 바로 변수를 사용하더라도 스마트 캐스트가 동작하지 않는데, 이는 바로 다음 코드에서 이 변수를 사용하더라도, 멀티 스레딩 환경에서는 변수가 변경되지 않음을 보장할 수 없기 때문이다.

원래 널이 될 수 있는 타입(bar?)은 .으로 메소드를 호출할 수 없고(널 체크 포함으로 정의된 확장 함수 제외), 널이 될 수 없는 타입의 변수에 대입할 수 없는 등 제약 조건을 가지고 있다. 그러나 스마트 캐스트가 작동할 수 있는 조건을 만족하는 변수에 대해서 널 체크를 수행하고 나면컴파일러가 그 사실을 기억해두고 null이 아님이 확실한 영역에서는 해당 값을 널이 될 수 없는 타입의 값처럼 사용할 수 있다. 한 예로 if또는 kt ?:return이 포함되어 있으면 이후의 코드는 실행되지 않는 것이 확실하기 때문에 스마트 캐스트가 동작한다. 그러나 return이 포함되어 있지 않으면 프로그래머의 처리에 따라 null인 상태로 이후 코드가 실행될 수도 있기 때문에 스마트 캐스트가 동작하지 않는다.

명시적 형변환
1
2
val n = e as Num

이런 식으로 사용하면 쓸데 없는 변수를 선언하지 않아도 돼서 편리한 경우가 있다.

1
2
(mainView as RelativeLayout).gravity

try-catch-finally

try도 식이기 때문에 try 실행 결과를 변수에 대입할 수 있다.

1
2
3
4
5
6
7
8
9
10
fun readNumber(reader: BufferedReader) {
val number = try {
Integer.parseInt(reader.readLine())
} catch (e: NumberFormatException) {
// return  여기서 return해버리면 val number 이후 문장이 실행이 안된다.
null
}
println(number)
}

문자열 템플릿 ( string template )

자바의 문자열 접합 연산( +) 와 동일한 기능이다.

1
2
"Hello, $"

{} 내부는 문장이 아니라 식(expression)이다. 그래서 {} 내부에 다른 식과 문, ", 또 다른 문자열 템플릿을 적어줄 수 있다.

1
2
"Hello, $!"

변수 이름만 사용하는 경우 {}로 감싸지 않고 바로 $name으로 사용할 수 있지만, 이런 경우에도 {}로 감싸주는게 좋다. 정규식 패턴 매칭이 용이하며 가독성에도 도움이 되기 때문.

  • 유니코드 입력은 "\u2103"

expression body : 한 줄 함수

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

이렇게 한 줄로 작성된 함수를 식이 본문인 함수(expression body)라고 부른다. 반대는 블록이 본문인 함수(block body). expression body에 =를 사용하는 것은 함수형 개념을 잘 표현하기 때문인 것 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fun expressionBody(i: Int) = when (i%2) {
0 -> "i is $"
else -> {
println("else")
"i is odd"
}
}

  
fun blockBody(i: Int): String {
when (i%2) {
0 -> return "i is $"
else -> {
println("else")
return "i is odd"
}
}
}

  • expression body는 알아서 타입 추론이 들어가기 때문에 리턴 타입을 지정하지 않아도 된다.
  • expression body로 작성하는 경우 return을 적어서는 안된다.
  • expression body 내부에서 다시 {}를 사용하는 경우, 블록의 맨 마지막 식이 반환값이 된다.

Note ) 블록의 마지막 식이 블록의 결과라는 규칙은 블록이 값을 만들어내야 하는 경우 항상 성립한다.

  • block body로 작성하는 경우 반드시 return 문이 필요하다.
  • block body로 작성했는데 return을 빼먹는 경우 kotlin.Unit을 리턴하게 된다.
비트 연산자

Int, Long 타입에만 사용할 수 있다.

1
2
3
4
5
6
7
8
x.shl(y)    : <<
x.shr(y)    : >>
x.ushr(y)   : >>>
x.and(y)    : &
x.or(y)     : |
x.xor(y)    : ~
x.inv()     : !

This post is licensed under CC BY 4.0 by the author.