if
코틀린에서 if는 expression으로 값을 리턴한다. 그래서 삼항 연산자(condition? then : else)가 없다. 일반 if로 동일하게 할 수 있기 때문이다.
표현(expression)과 상태(statement)의 차이를 아주 간단하게 설명하면 값의 여부이다. 이 둘의 예시는 자바의 if문과 코틀린의 if문을 생각하면 편하다.
자바의 if문은 상태(statement)로 결과를 리턴하지 않고 끝이 난다.
하지만 코틀린의 if문은 표현(expression)으로 대입 연산자를 통해 리턴 값을 받을 수 있다.
//전통
var max = a
if( a < b ) max = b
//else사용var max: Int
if(a > b)
max = a
else
max = b
//expression으로 사용
var max = if(a > b) a else b
//if 브랜치는 블록일 수 있으며 마지막 식이 블록의 값이 된다.
max = if(a > b) {
print("choose a")
a
} else {
print("choose b")
b
}
when
c의 switch와 같은 명령문과 유사한 형태를 가지고 있다.
when(x){
1 -> print("x==1")
2 -> print("x==2")
else -> {
print("x 는 1, 2가 아닌 다른 수")
}
}
when은 특정 브랜치의 조건을 충족할 때까지 순차적으로 모든 브랜치의 인자가 일치하는지 확인한다.
식(expression)으로 사용하면 충족한 브랜치의 값이 식의 값이 된다. (if처럼 블록이 될 수 있으며, 블록의 마지막 값이 식의 값이 된다)
문장(statement)으로 사용되면 값은 무시된다.
만약 when이 식(expression)으로 쓰이는 경우, 나올 수 있는 모든 경우가 컴파일러 선에서 보장되지 않을 경우 else문은 필수적으로 써야 한다.
enum class Bit{
ZERO, ONE
}
val numericValue = when(getRandomBit()){
Bit.ZERO -> 0
Bit.ONE -> 1
//모든 케이스가 보장되있기 때문에 'else' 는 불필요함
}
when에서, 다음의 경우 else는 필수적이다.
- when의 조건 분기의 타입이 Boolean, enum, sealed 타입이거나, null 입력이 가능하거나(nullable)
- when의 조건 분기가 모든 경우의 수를 다루고 있지 않은 경우
enum class Color {
RED, GREEN, BLUE
}
when (getColor()) {
Color.RED -> println("red")
Color.GREEN -> println("green")
Color.BLUE -> println("blue")
//모든 케이스가 보장되있기 때문에 'else' 는 불필요함
}
when (getColor()) {
Color.RED -> println("red") // GREEN, BLUE의 분기가 없다.
else -> println("not red") // 'else'가 필요함
}
같은 결과를 가지는 조건일 경우 ‘ , ’로 묶을 수 있다. 또한 조건에 임의의 식을 사용할 수 있다.
when(x){
//콤마로 다중 조건
0, 1 -> print("t")
//함수 사용
parseInt(s) -> print("x s encodes x")
}
마찬가지로 in, 범위, 컬렉션을 사용해서 값을 검사할 수 있고 is로 특정 타입 여부를 확인할 수 있다.
//expression으로써 when 사용
val test = when(x){
in 1..10 -> print("x is in the range")
in validNumbers -> print("x is valid")
!in 10..20 -> print("x is outside the range")
is String -> x.startsWith("prefix")
}
when절은 if-else if 체인을 대신해서 사용할 수 있다. 인수가 제공되지 않으면 분기 조건은 단순히 Boolean 식이며 조건이 참일 때 분기가 실행된다.
when{
x.isOdd() -> print("x is odd")
y.isEven() -> print("y is even")
else -> print("x+y is odd")
}
다음 구문을 사용하여 변수의 when주제를 캡처할 수 있다
fun Request.getBody() =
when (val response = executeRequest()) {
is Success -> response.body
is HttpError -> throw HttpException(response.status)
}
when에 사용된 변수의 범위는 this when 본문으로 제한된다.
for
for(item in collection) print(item)
단일구문으로 사용해도 되지만 블록을 이용하여 사용할 수도 있다
for(item: Int in ints){
//...
}
for는 이터레이터를 제공하는 모든 것에 동작한다.
- iterator() 멤버 함수나 확장 함수를 갖고 있고
- 함수의 리턴 타입이 next() 멤버 함수나 확장 함수를 갖고
- 리턴 타입이 Boolean인 hasNext() 멤버 함수나 확장 함수를 가짐
이 세 함수는 모두 operator로 지정해야 한다.
여기서 질문이 하나 생긴다.
배열은 어떻게 하는 거지??
배열에 대한 for 루프는 Iterator 객체를 생성하지 않는 인덱스 기반 루프로 컴파일된다. 따라서 인덱스 범위로 조건을 설정하면 된다.
for(i in 1..3){
print(array[i])
}
for(i in 6 downTo 0 step 2){
println(i) //6 에서 0까지 2씩 차감하면서 출력
}
for(i in array.indices){ //0부터 array.size - 1 == array.indices
print(array[i])
}
만약 인덱스와 값 둘 다 필요할 경우 루프에서 배열을 참조해야 하는 indices대신 withIndex 라이브러리 함수를 써도 된다.
for((index, value) in array.withIndex()){
println("the element at $index is $value")
}
while
자바랑 똑같다
while(x > 0)
x--
do{
val y = retriveData()
} while( y != null)
리턴과 점프
코틀린에선 전통적인 break와 continue를 지원한다. 두 가지를 포함해서 3 가지 구조의 점프 식이 있다.
- return : 근본 중 근본, 가장 가깝게 둘러싼 함수나 익명 함수에서 리턴한다.
- break : 가장 가깝게 둘러싼 루프를 끝낸다.
- continue : 가장 가깝게 둘러싼 루프의 다음 단계를 진행한다.
세 식 모두 더 큰 식의 안에서 사용할 수 있다.
val s = person.name ?: return // 좌항이 null이면 우항을 처리(리턴)
이 세 식의 타입은 Nothing이다.
break and continue 라벨
코틀린은 모든 식에 라벨을 붙일 수 있다.
라벨은 식별자 뒤에 @(At Sign) 부호가 붙는 형식으로 “abc@, fooBar@” 형태로 쓸 수 있다. 라벨의 위치는 식 앞이다
loop@ for ( i in 1..100) {
for (j in 1..100){
if (...) break@loop
}
}
라벨을 써주게 되면 가까운 루프의 흐름을 제어하는 것이 아닌 원하는 루프의 흐름을 제어할 수 있다. 다음 예제로 차이를 알아보자.
for ( i in 1..3) {
for (j in 1..5){
if (j >= 3) break
print("$j ")
}
}
1 2 1 2 1 2
위 루프의 결괏값은 1 2 1 2 1 2이다. 여기서 라벨을 붙이면 어떻게 될까?
loop@ for ( i in 1..3) {
for (j in 1..5){
if (j >= 3) break@loop
print("$j ")
}
}
1 2
1 2가 나온다. 라벨을 통해 가까운 j루프를 중단하는 것이 아니라 i루프의 흐름을 중단했다.
Return to labels
이처럼 라벨의 활용도는 무궁무진한다. 코틀린에선 함수 리터럴, 객체 표현식, 로컬 함수를 통해 함수를 중첩시킬 수 있다. 만약 label을 이용한 return을 사용한다면 외부함수에서 반환할 수 있다. 가장 중요한 사례는 람다식에서 반환하는 것이다. 아래 예제를 통해 살펴보자
fun foo(){
val ints = arrayListOf(1, 2, 3, 0, 3, 4, 5, 7)
val m = ints.forEach{
if(it == 0) return//foo()에서 리턴 -> foo()를 종료시킴
println("it == $it ")
}
print("return : $m")
}
이러한 로컬이 아닌 반환은 인라인 함수에 전달된 람다 식에 대해서만 지원된다. (위 예제에선 안된다는 소리)
it == 1 it == 2 it == 3
만약 람다의 결과로 리턴 하고 싶다면? 라벨을 붙여주면 된다.
fun main() {
val ints = arrayListOf(1, 2, 3, 0, 3, 4, 5, 7)
val m = ints.forEach lit@ {
if(it == 0) return@lit// for loop의 continue와 동일하게 작동 -> 현지 진행중인 루프를 라벨에 반환
println("it == $it")
}
print("return : $m")
}
이렇게 하면 forEach문에 현재 진행 중인 루프를 리턴한다. (continue 하는 것처럼)
it == 1 it == 2 it == 3 // i == 0 은 return 했기 때문에 출력되지 않는다.
it == 3 it == 4 it == 5 it == 7
return : kotlin.Uni
때론 직접 라벨을 지정하는 것보다 암시적 라벨을 사용하는 것이 더 효율적일 때도 있다. 왜냐면 이러한 암시적 라벨은 함수의 이름과 같기 때문이다.
fun foo(){
ints.foreach{
if(it == 0) return@foreach
print(it)
}
}
라벨 대신 익명 함수를 써도 된다
fun foo(){
ints.forEach(fun(value: Int){
if (value == 0) return
print(value)
})
}
앞에서 소개했던 라벨의 사용법은 일반 루프의 continue를 사용하는 것과 같은 기능을 한다
break문과 같진 않지만 다른 람다로 둘러싸면서 비로컬 리턴을 통해 비슷하게 구현해 볼 수 있다.
fun foo() {
run loop@{
listOf(1, 2, 3, 4, 5).forEach {
if (it == 3) return@loop // 실행하기위해 람다로 부터 전달된 비-로컬 리턴
print(it)
}
}
print(" done with nested loop")
}
12 done with nested loop
값을 리턴할 때는 아래와 같이 쓰면 된다.
return@a 1
이 코드는 @a라벨에 1을 리턴하는 것을 의미한다.
'코틀린' 카테고리의 다른 글
프로퍼티와 필드 (0) | 2023.01.31 |
---|---|
클래스와 상속 (0) | 2023.01.30 |
타입 체크와 캐스트(is, as) (1) | 2023.01.30 |
기본타입 (1) | 2023.01.30 |
기초구문, 이디엄(관용어) (0) | 2023.01.30 |