if문과 조건문
블록의 표현식이 길어질 때
람다식처럼 블록의 마지막 표현식이 변수에 반환되어 할당됩니다.
fun main() {
val a = 12
val b = 7
// 블록과 함께 사용
val max = if (a > b) {
println("a 선택")
a // 마지막 식인 a가 반환되어 max에 할당
} else {
println("b 선택")
b // 마지막 식인 b가 반환되어 max에 할당
}
println(max)
}
/*
결과 :
a 선택
12
*/
else if문
fun main() {
print("Enter the Score: ")
val score = readLine()!!.toDouble() // Input Console
var grade: Char = 'F'
if (score >= 90.0) {
grade = 'A'
} else if (score in 80.0..89.9) {
// score >= 80.0 && score <= 89.9
grade = 'B'
} else if (score in 70.0..79.9) {
grade = 'C'
}
println("Score: $score, Grade: $grade")
}
/*
결과 :
Enter the Score: 89.9
Score: 89.9, Grade: B
*/
(readLine()함수는 코틀린의 표준 라이브러리의 함수로 콘솔로부터 문자열을 입력받는 함수입니다.
또한 in 연산자와 범위 연산자로 조건식을 간략하게 할 수 있습니다.)
when문
when문은 인자가 있는 경우와 인자가 없는 경우의 2가지 방식으로 사용합니다.
fun main() {
print("Enter the Score: ")
val score = readLine()!!.toDouble()
var grade: Char = 'F'
when(score) {
in 90.0..100.0 -> grade = 'A'
in 80.0..89.9 -> grade = 'B'
in 70.0..79.9 -> grade = 'C'
!in 70.0..100.0 -> grade = 'F'
}
println("Score: $score, Grade: $grade")
}
/*
결과 :
Enter the Score: 85.7
Score: 85.7, Grade: B
*/
(추가로 is 키워드를 사용하면 해당 변수의 자료형을 검사할 수 있습니다.)
val str = "test"
val result = when(str) {
is String -> "String타입 입니다."
else -> false
}
/*
결과 :
String타입 입니다.
*/
인자가 없는 when문
fun main() {
print("Enter the Score: ")
val score = readLine()!!.toDouble()
var grade: Char = 'F'
when {
score >= 90.0 -> grade = 'A'
score >= 80.0 -> grade = 'B'
score >= 70.0 -> grade = 'C'
score < 70.0 -> grade = 'F'
}
println("Score: $score, Grade: $grade")
}
/*
결과 :
Enter the Score: 82.4
Score: 82.4, Grade: B
*/
다양한 자료형의 인자
fun main() {
cases("Hello") // String
cases(1) // Int
cases(System.currentTimeMillis()) // 현재 시각(ms) return Long Type
cases(MyClass()) // Instance\
}
fun cases(obj: Any) {
when(obj) {
1 -> println("Int: $obj")
"Hello" -> println("String: $obj")
is Long -> println("Long: $obj")
!is String -> println("Not a String")
else -> println("Unknown")
}
}
/*
결과 :
String: Hello
Int: 1
Long: 1619183627471
Not a String
*/
class MyClass() {}
반복문
for문
for문은 내부적으로 반복을 처리하는 인터페이스인 이터레이터(Iterator)에 의해 배열이나 특정 값의 범위, 컬렉션으로 불리는 요소등에서 사용할 수 있습니다.
컬렉션이란 컬렉션(Collection)이란 코틀린이 사용하는 Array, List, Map 등의 여러 데이터를 다루는 요소를 말합니다. 이것은 모두 이터레이터(Iterator)라는 반복을 위한 인터페이스를 구현합니다. |
(downTo, step키워드를 사용하여 하행 반복 또는 증가 설정(ex - 2씩 증가)를 할 수 있습니다.)
의사코드란 의사 코드(Pseudo-code)란 프로그래밍 언어가 아닌 이해하기 쉬운 우리말로 프로그램의 수행 내용을 간략히 서술해 놓은 것을 말합니다. |
반복문을 활용한 삼각형 출력하기
<의사코드>
n: 라인 수
반복 (line: 1 -> n) {
반복 (space: 1 -> n - line) { space 출력 }
반복 (star: 1 -> 2*line - 1) { star 출력 }
}
fun main() {
print("Enter the lines: ")
val n = readLine()!!.toInt()
for(line in 1..n) {
for(space in 1..n - line) { print(" ") }
for(star in 1..(2*line-1)) { print("*") }
println()
}
}
/*
결과 :
*
***
*****
*******
*********
*/
짝수의 합과 홀수의 합 구하기
fun main() {
var total : Int = 0
for (num in 1..100 step 2) total += num
println("Odd total: $total")
for(num in 0..99 step 2) total += num
println("Even total: $total")
}
/*
결과 :
Odd total: 2500
Even total: 4950
*/
while문
while문은 조건식을 항상 true로 해 무제한으로 반복하는 데몬 프로그램을 만들 때 사용합니다.
데몬이란 데몬(daemono)은 백그라운드에서 실행하면서 종료되지 않고 지속적으로 무엇인가 처리하는 프로그램입니다. 사용자가 직접 제어하지 않아도 특정한 작업을 지속적으로 처리할 수 있습니다. 예를 들어 보일러의 온도를 검사하고 한계 온도를 넘어가면 경고를 발생하는 데몬 프로그램을 만든다고 한다면 지속적으로 온도를 검사해야 하기 때문에 데몬 프로그램은 다음과 같이 구성합니다. |
while(true) {
temp = 온도 검사
if ( temp > 한계 온도) { 경고 발생 }
}
whlie문을 활용한 팩토리얼 계산
fun main() {
print("Enter the number: ")
var number = readLine()!!.toInt()
var factorial: Long = 1
while(number > 0) {
factorial *= number--
}
println("Factorial: $factorial")
}
/*
결과 :
Enter the number: 5
Factorial: 120
*/
do~while문
fun main() {
do {
print("Enter an integer: ")
val input = readLine()!!.toInt()
for(i in 0 until input) {
for(j in 0 until input) print((i + j) % input + 1)
println()
}
} while (input != 0)
}
/*
결과 :
3
123
231
312
*/
흐름의 중단과 반환
조건문이나 반복문을 사용할 때 수행 중이던 코드를 바로 중단하거나 조건식으로 되돌아가도록 프로그램을 작성해야 하는 경우가 있는데 여기에서는 return, break, continue문을 사용하여 프로그램 흐름을 제어합니다.
그리고 프로그램이 도중에 중단되는 예외를 처리하는 try~catch문도 있습니다.
흐름 제어문 - return: 함수에서 결괏값을 반환하거나 지정된 라벨로 이동한다. - break: for문이나 while문의 조건식에 상관없이 반복문을 끝낸다. - continue: for문이나 while문의 본문을 모두 수행하지 않고 다시 조건식으로 넘어간다. 예외 처리문 - try{...}catch{...}: try 블록의 본문을 수행하는 도중 예외가 발생하면 catch 블록의 본문을 실행한다. - try{...}catch{...}finally{...}: 예외가 발생해도 finally블록 본문은 항상 실행된다. |
return문
return은 함수에서 값을 반환할 때 사용하는데 값 없이 return만 사용할 때는 Unit자료형을 반환합니다. 다른 언어의 void와 비슷합니다. 코틀린에서 Unit이란 반환하는 값이 아예 없는 뜻이 아니라 Unit이라는 자료형 자체를 반환합니다. Unit을 return으로 반환할 때는 생략이 가능합니다.
fun hello(name: String) : Unit {
println(name)
return Unit
}
fun hello(name: String) : Unit {
println(name)
return
}
fun hello(name: String) {
println(name)
}
// 전부 같은 코드
람다식에서 return, break, continue를 사용할 때 람다식에서 return은 라벨 표기와 함께 사용해야 하고 break와 continue는 아직 지원하지 않습니다. |
람다식에서 return 사용
인라인(inline)으로 선언되지 않은 람다식에서는 return을 그냥 사용할 수 없습니다. return@label과 같이 라벨(label)표기와 함께 사용해야 합니다. @기호와 이름을 붙여서 사용하며, 인라인으로 선언된 함수에서 람다식을 매개변수로 사용하면 람다식에서 return을 사용할 수 있습니다.
fun main() {
retFunc()
}
inline fun inlineLambda(a: Int, b: Int, out: (Int, Int) -> Unit) {
out(a, b)
}
fun retFunc() {
println("start of retFunc")
inlineLambda(13, 3) {a, b ->
val result = a + b
// return문이 호출되면 람다식 바깥의 함수를 빠져나가서 end of retFunc()도 호출되지 않습니다.
if(result > 10) return
println("result: $result")
}
println("end of retFunc")
}
/*
결과 :
start of retFunc
*/
이런 반환을 비지역(Non-local)반환 이라고 합니다.
람다식에서 라벨과 함께 return 사용
fun main() {
retFuncLabel()
}
fun labelLambda(a: Int, b: Int, out: (Int, Int) -> Unit) {
out(a, b)
}
// inline을 지우면 람다 내부의 return부분에 오류가 표시됨 -> 라벨을 붙여주면 오류가 사라짐.
fun retFuncLabel() {
println("start of retFunc")
labelLambda(13, 3) lit@{a, b -> // 1. 람다식 블록의 시작 부분에 라벨을 지정
val result = a + b
if(result > 10) return@lit // 2. 라벨을 사용한 블록의 끝부분으로 반환
println("result: $result")
} // 3. 이부분으로 빠져나감
println("end of retFunc")
}
/*
결과 :
start of retFunc
end of retFunc
*/
암묵적 라벨
fun main() {
retFuncImplicitLabel()
}
fun retFuncImplicitLabel() {
println("start of retFunc")
labelLambda(13, 3) {a, b ->
val result = a + b
if(result > 10) return@labelLambda // 1. 암묵적 라벨을 사용한 블록의 끝부분으로 반환
println("result: $result")
} // 2. 이부분으로 빠져나감
println("end of retFunc")
}
/*
결과 :
start of retFunc
end of retFunc
*/
익명 함수를 사용한 반환
익명함수를 사용한 버전으로도 동일한 결과를 출력합니다.:)
fun main() {
retFuncAnonymous()
}
fun retFuncAnonymous() {
println("start of retFunc")
labelLambda(13, 3, fun(a, b) {
val result = a + b
if(result > 10) return
println("result: $result")
})
println("end of retFunc")
}
/*
결과 :
start of retFunc
end of retFunc
*/
익명 함수는 fun (...) {...}형태로 이름 없이 특정 함수의 인자로 넣을 수 있습니다. 이때는 일반 함수처럼 작동하기 때문에 return도 일반 함수에서 반환하는 것과 같이 사용할 수 있습니다.
이번에는 람다식 형태로 return을 사용하는 또다른 방법입니다.
// 람다식을 이용한 반환
fun returnLambdaLabel() {
val getMessage = lambda@ {num: Int->
if(num !in 1..100) {
return@lambda "Error"
}
"Success"
}
println(getMessage(100))
}
/*
결과 :
Success
*/
같은 방식의 익명 함수입니다.
// 익명 함수를 이용한 반환
fun returnAnonymousFunc() {
val getMessage = fun(num: Int): String {
if(num !in 1..100) {
return "Error"
}
return "Success"
}
println(getMessage(110))
}
/*
결과 :
Success
*/
익명 함수 방법을 사용하면 2개의 return이 확실히 구별됩니다. 해당 값의 범위에 따라 반환되는 문자열을 분명히 하고 있습니다. 따라서 보통의 경우에는 람다식을 사용하고 return과 같이 명시적으로 반환해야 할 것이 여러 개라면 익명 함수를 쓰는 것이 좋습니다.
람다식과 익명 함수를 함수에 할당할 때 주의점
람다식은 특정 함수에 할당할 때 주의하며 사용해야 합니다. 익명 함수와 람다식은 할당하는 방법에서 약간의 차이가 있는데 읽기에 따라 문제가 생길 수 있습니다.
fun greet() = {println("Hello")}
// 해당 greet()을 호출할 때 greet()가 아니라 greet()()으로 호출해야 합니다.
할당 연산자(=)에 의해 람다식 {println("Hello")}자체가 greet()함수에 할당되었기 때문에 greet()함수가 가지고 있는 함수를 사용하려면 다음과 같이 호출해야 합니다.
greet()()
함수가 할당됨을 명시적으로 표현하려면 다음과 같이 익명 함수를 써서 선언하는 것이 가독성이 좋습니다.
fun greet() = fun() {println("Hello")}
break문과 continue문
fun main() {
for(i in 1..5) {
if (i == 3) break
print(i)
}
println()
println("outside")
for(i in 1..5) {
if (i == 3) continue
print(i)
}
println()
println("outside")
}
/*
결과 :
12
outside
1245
outside
*/
break와 continue에 라벨 함께 사용
(block을 break하는 부분이 해당 라벨이 붙어있는 블록)
fun main() {
println("labelBreak")
for(i in 1..5) {
second@ for (j in 1..5) {
if (j == 3) break
println("i:$i, j:$j")
} // break실행 시 second@ 라벨이 있는 블록을 break
println("after for j")
}
println("after for i")
}
/*
결과 :
labelBreak
i:1, j:1
i:1, j:2
after for j
i:2, j:1
i:2, j:2
after for j
i:3, j:1
i:3, j:2
after for j
i:4, j:1
i:4, j:2
after for j
i:5, j:1
i:5, j:2
after for j
after for i
*/
(가장 바깥의 for문에 라벨이 붙어있어서 해당 for문을 break)
fun main() {
println("labelBreak")
first@ for(i in 1..5) {
second@ for (j in 1..5) {
if (j == 3) break@first
println("i:$i, j:$j")
}
println("after for j")
} // break실행 시 first@ 라벨이 있는 블록을 break
println("after for i")
}
/*
결과 :
labelBreak
i:1, j:1
i:1, j:2
after for i
*/
(continue문에 라벨이 붙음)
fun main() {
println("labelBreak")
first@ for(i in 1..5) {
second@ for (j in 1..5) {
if (j == 3) continue@first
println("i:$i, j:$j")
}
println("after for j")
} // continue실행 시 first@ 라벨이 있는 블록을 continue
println("after for i")
}
/*
결과 :
labelContinue
i:1, j:1
i:1, j:2
i:2, j:1
i:2, j:2
i:3, j:1
i:3, j:2
i:4, j:1
i:4, j:2
i:5, j:1
i:5, j:2
after for i
*/
이렇게 return, break, continue를 적절히 사용해서 기본적인 프로그램의 흐름을 제어하거나 라벨을 사용해 원하는 위치로 흐름을 바꿀 수 있습니다.
예외 처리
코틀린에서도 자바와 동일한 문법으로 예외 처리 블록을 사용할 수 있습니다.
try {
예외 발생 가능성 있는 문장
} catch (e: 예외 처리 클래스 문장) {
예외를 처리하기 위한 문장
} finally {
반드시 실행되어야 하는 문장
}
예를 들어 try문에서 '파일 열기' 작업을 했다면 finally문에서 '파일 닫기' 작업을 작성합니다. 반드시 실행해야 할 작업이 없다면 finally블록을 생략 하고 try~catch 블록만으로 코드를 구성할 수 있습니다.
0으로 나누었을 때 예외 처리하기
fun main() {
val a = 6
val b = 0
val c :Int
try {
c = a/b // Divded 0
} catch(e: Exception) {
println("Exception is handled :: $e")
} finally {
println("finally 블록은 반드시 항상 실행됨")
}
}
/*
결과 :
Exception is handled :: java.lang.ArithmeticException: / by zero
finally 블록은 반드시 항상 실행됨
*/
특정 예외 처리
산술 연산에 대한 예외를 따로 특정해서 잡으려면 ArithmeticException을 사용할 수 있습니다.
...
} catch(e : ArithmeticException) {
println("Exception is handled. ${e.message}\
}
스택의 추적
임시 메모리 영역인 스택을 추적할 수 있도록 합니다.
...
} catch(e : Exception) {
e.printStackTrace()
}
/*
결과 :
Exception is handled :: java.lang.ArithmeticException: / by zero
finally 블록은 반드시 항상 실행됨
Exception is handled. / by zero
java.lang.ArithmeticException: / by zero
at chapter_04._04_3_3__예외처리Kt.arithmeticException(04_3.3. 예외처리.kt:43)
at chapter_04._04_3_3__예외처리Kt.main(04_3.3. 예외처리.kt:12)
at chapter_04._04_3_3__예외처리Kt.main(04_3.3. 예외처리.kt)
*/
이렇게 오류의 원인이 되는 줄을 스택으로부터 추적할 수 있는 이유는 프로그램이 디버깅 정보를 유지하고 있기 때문입니다. 단, finally 블록이 먼저 실행된 것처럼 보이고 있는데 이것은 실행할 때마다 조금씩 달라질 수 있습니다. println()의 경우 일반 출력인 System.out을 사용하고 오류용 출력은 System.err를 사용하기 때문입니다.
예외 발생시키기
throw키워드를 사용하면 의도적으로 예외를 발생시킬 수 있습니다. 먼저 특정 함수를 만들면서 필요한 경우 예외를 발생하도록 하려면 다음과 같은 형태로 지정합니다.
throw Exception(message: String)
throw를 사용해 예외 발생
fun main() {
var amount = 600
try {
amount -= 100
checkAmount(amount)
} catch (e: Exception) {
println(e.message)
}
println("amount: $amount")
}
fun checkAmount(amount: Int) {
if (amount < 1000)
throw Exception("잔고가 $amount 으로 1000 이하입니다.")
}
/*
결과 :
잔고가 500 으로 1000 이하입니다.
amount: 500
*/
amount가 1000이하일 때 throw로 예외를 발생시키고 이것은 main() 함수의 catch가 잡아서 처리합니다.
사용자 정의 예외
코틀린에서는 ArithmeticException, IOException, ClassNotFoundException 등 많은 예외 클래스를 제공하고 있습니다. 이들 클래스는 Throwble클래스의 자식 클래스입니다.
기본 Exception 클래스로부터 새롭게 사용자가 정의한 예외 클래스를 만들어 낼 수 있습니다.
class <사용자 정의 클래스 이름> (message: String) : Exception(message)
사용자 예외 클래스 만들기
class InvalidNameException(message: String) : Exception(message) // 1. 사용자 예외 클래스
fun main() {
val name = "JamesPark_H123" // 2. 숫자가 포함된 이름
try {
validateName(name)
} catch (e: InvalidNameException) { // 3. 숫자가 포함된 예외 처리
println(e.message)
} catch (e: Exception) { // 기타 예외 처리
println(e.message)
}
}
private fun validateName(name: String) { // 4. 이름에 숫자가 포함되어 있으면 예외 발생시킴
if (name.matches(Regex(".*\\d+.*")))
throw InvalidNameException("Your name : $name :: contains number")
}
/*
결과 :
Your name : JamesPark_H123 :: contains number
*/
1. 기존의 Exception 클래스로부터 InvalidNameException 클래스를 콜론(;)을 통해 하위 클래스로 선언합니다.
2. 이름은 숫자가 포함되어 있어 예외를 유도합니다.
3. 사용자 예외 클래스를 선언해서 만일 이름에 숫자가 포함된 경우 예외를 발생시킵니다.
4. matches와 정규식 표현인 Regex를 통해 숫자가 포함되어 있는지를 검사하고 있습니다. 검사 시 숫자를 포함하고 있으면 throw로 정의한 클래스를 사용해 해당 메시지를 출력하고 catch에서 처리됩니다.
이상 마치겠습니다. :)
'책 요약하기 > Do it! 코틀린 프로그래밍' 카테고리의 다른 글
#05-2. super와 this의 참조, 정보 은닉 캡슐화, 클래스와 클래스의 관계 2021-04-27 (0) | 2021.04.27 |
---|---|
#05-1. 클래스와 객체, 생성자, 상속과 다형성 2021-04-25 (0) | 2021.04.25 |
#03-2. 함수와 함수형 프로그래밍 2021-04-20 (0) | 2021.04.20 |
#03-1. 함수와 함수형 프로그래밍 2021-03-17 (0) | 2021.03.17 |
#1일차. 1장, 2장 2021-03-11 (0) | 2021.03.11 |