본문 바로가기

코틀린

코틀린 정리 - (2)

안드로이드 개발하는 kancho입니다

이번 포스팅에서는 코틀린의 함수에 대해 정리하려고 합니다. '코틀린 완벽 가이드' 책을 참고하였습니다

 

함수

  • 입력을 받아 출력값을 반환할 수 있는 재사용 가능한 코드 블록

 

구조

fun (function) : 함수의 시작
addNum : 함수의 이름
num1, num2 : 매개변수이며 지역변수로 취급된다. 어떤 데이터가 필요한지 알려준다
Int : 함수의 반환 타입 (return)

 

fun addNum(num1: Int, num2: Int): Int {
    return num1 + num2
}

fun main() {
    println("sum : ${addNum(5, 10)}")
}

 

특징

매개변수가 없어도 괄호는 필요
kotlin 매개변수는 immutable, java는 mutable
return문 다음 코드는 실행 X
코드 내부에서 지역변수, 지역 함수의 사용이 가능
call by value 방식
call by value
값으로 호출한다는 뜻으로, 인자로 전달되는 변수를 함수의 매개변수에 복사한다는 것이다. 값을 복사했기 때문에 전달한 변수를 변경해도 함수의 매개변수 값에는 영향이 없다.

 

 

패키지

  • 파일들을 분류해서 모아둔 것

구조

  • packge(키워드) + 패키지 이름(qualified name)
// util 패키지에 속하고, util은 kancho 패키지, kancho는 com, com은 루트 패키지에 속한다
package com.kancho.util

fun findFirstLetter(word: String) = word[0]

 

특징

파일의 최상단에 작성
파일의 디폴트는 최상위 패키지 (패키지 지정하지 않았을 시)
패키지 hierarchy는 소스파일 hierarchy와 별도의 구조

 

Import

  • 다른 패키지의 선언을 참조
package com.kancho.util

fun findFirstLetter(word: String) = word[0]

///////////////////////////////////////////

fun main() {
  com.kancho.util.findFirstLetter("kancho")
}

///////////////////////////////////////////

import com.kancho.util.findFirstLetter

fun main() {
  findFirstLetter("kancho")
}

/////

import java.lang.* // lang에 속한 모든 것을 임포트 (*)

import java.lang.Math // jdk 클래스
import com.kancho.util.findFirstLetter // 최상위 함수


// 이름이 같을 때 alias(별명) 사용, 새 이름으로 사용 가능 
import com.kancho.util.findFirstLetter as utilFirst
import com.kancho.util2.findFirstLetter as utilSecond

 

특징

코드가 간결해짐
nested class, enum 상수도 가능
코틀린이 제공하는 여러 가지 import
https://kotlinlang.org/docs/packages.html#default-imports
 

Packages and imports | Kotlin

 

kotlinlang.org

 

 

조건문

  • 조건에 따라 동작을 수행할 수 있게 만드는 문
// 조건은 항상 Boolean 타입의 식

fun max(a: Int, b: Int): Int {	// a > b 이면 a를 리턴하고 아닐 시 b 리턴
    if (a > b) return a
    else return b
}

fun max4(a: Int, b: Int): Int { // a > b 가 아닐 시의 결과가 필요한 것이 아니라면 else 생략 가능
    if (a > b) return a
    return b
}

fun max3(a: Int, b: Int): Int {
    return if (a > b) a
    else b
}

fun max2(a: Int, b: Int) = if (a > b) a else b	// 식으로 사용, if else 모두 필요

fun main() {
    val name = "kancho"
    
    val result = if (name.length > 10) {
        name
    } else ""
}

// return은 함수의 종료를 의미, Nothing 타입
// return은 함수 내부 어디서나 사용 가능 -> Nothing은 모든 타입의 하위 타입이므로

 

Nothing vs Unit
Unit : 값이 존재하지만 유용한 값이 없다는 것을 표현
Nothing : 값이 없다는 것을 표현

 

 

범위, 진행, 연산

 

range(범위)

// Range는 동적으로 할당되는 객체이지만 약간의 부가 비용이 든다
fun main() {
    val name = "kancho"

    val result = if (name.length > 10) {
        name
    } else ""

    // 비교 가능한 타입에 대해 .. 연산 사용 가능
    // ..연산은 닫혀있다(closed). 시작,끝값 포함
    val range = 0..9 // 0 ~ 9

    val a = 5
    println(a in 0..99) --> a >= 0 && a <= 99 // in 연산자 -> 범위 안의 값의 유무
    println(a in 10..99)

    println(a !in 10..99)

    // 끝값이 시작 값보다 더 작으면 빈 범위
    println(5 in 10..1) // false


    // until은 끝 값이 제외(semi-closed) -> 정수 타입만 사용가능
    val semiClosedRange = 0 until 10 // 0 ~ 9

    // downTo -> descending 하향 진행
    // step -> 간격을 지정
    15 downTo 9 step 2 // 15부터 9까지 내려오는데 간격을 2로 해라

    for (i in 20 downTo 3 step 3) {	// 20 17 14 11 8 5
        print("$i ")
    }

    println("name is Kancho".substring(1..6)) // ame is
    println("name is Kancho".substring(1 until 6)) // ame i
    println("name is Kancho".substring(1, 6)) // ame i , until과 같다
}

 

 

 

when

when을 사용하면 더 많은 조건을 더 간결하게 표현할 수 있다

 

fun checkSign(num: Int): String {	// 조건이 많아질수록 if else가 중첩되어
    if (num > 0) return "양의 정수"	// 가독성이 떨어진다
    else if (num == 0) return "0"
    else return "음의 정수"
}

fun checkSign(num: Int): String {
    return if (num > 0) "양의 정수"
    else if (num == 0) "0"
    else "음의 정수"
}

// 순서대로 조건을 검사하고 만족하는 조건이 있으면 종료
// 모든 조건이 거짓이면 else 실행
fun checkSign(num: Int): String {
    when {
        num > 0 -> return "양의 정수"
        num == 0 -> return "0"
        else -> return "음의 정수"
    }
}

fun checkSign(num: Int): String {
    return when {
        num > 0 -> "양의 정수"
        num == 0 -> "0"
        else -> "음의 정수"
    }
}

// 식으로 쓰는 경우
fun checkSign(num: Int) =
    when {
        num > 0 -> "양의 정수"
        num == 0 -> "0"
        else -> "음의 정수"
    }
}

// num 이라는 대상이 있다. 대상이 있는 형태에서는 한가지 안에 여러 조건을 콤마로 분리해서 사용할 수 있다
fun numberToString(num: Int) =
    when (num) {
        0 -> "Zero"
        1 -> "One"
        2 -> "Two"
        else -> ""
    }
}
switch vs when
switch : 주어진 값들 중 하나만 선택 가능. break를 만나기 전까지 계속 실행(fall through)
when : 여러 조건을 검사할 수 있다. 만족하는 조건이 있으면 종료

 

 

반복문

주어진 데이터나 조건이 만족될 때까지 수행한다.

while, do-while, for의 3가지 제어구조

 

while, do-while

// do-while 구문
// do 블럭을 먼저 실행한 후 while의 조건을 실행한다
// 최소 한번 do 블록이 진행된다
var sum = 0
var a = 1
do {
    sum += a
    a++
} while (a != 6)	// while의 조건이 참이면 do로 이동

println(sum) // 15


// while 구문
var sum = 0
var a = 1
while (a != 6) {	// 조건이 참이면 실행, 조건을 먼저 검사
    sum += a
    a++
}

println(sum)

 

for

데이터의 여러 값들에 대해 반복적으로 실행한다

 

반복문을 실행할 데이터가 iterator() 함수를 지원하면 for 반복문이 사용 가능하다

iterator() 함수는 값을 뽑는 Iterator 타입을 반환한다

 

// 반복문을 위한 자료구조
val a = intArrayOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
var sum = 0
    
// a 데이터를 반복하며 변수 i에 순차적으로 새로운 값을 담는다
// 반복문의 변수는 해당 블록에서만 접근 가능하며 immutable
for (i in a) {
    sum += i
}
    
for (i in 0.. a.lastIndex) {
  if (i / 2 == 0) {
      a[i] *= 2
  }
}

 

break, continue

반복문을 제어
break는 즉시 반복문을 종료
continue는 현재 반복되는 구간을 끝내고 다음 반복 단계로 이동
when에서 break, continue 사용 시 가장 가까운 반복문으로 이동

 

Nested loop, Lable

 

중첩된 반복문에서 break와 continue는 가장 가까운 반복문에 적용된다

밖의 루프로 제어하고 싶을 때 Lable 사용

 

// break, continue는 반복문 앞에 붙은 Label만 사용가능
fun aa(): Int {
    outerLoop@for (i in b) {
        for (j in c) {
            continue@outerLoop
        }
        return i
    }
    return -1
}

 

예외처리

함수는 예기치 못한 오류의 경우 예외를 던질(throw) 수 있다

예외가 발생한 경우, 예외를 함수를 호출한 쪽에서 잡거나 (catch) 더 위의 단계로 예외를 전달한다

// Exception handler를 먼저 찾는다
// 예외와 일치하는 handler가 있다면 처리한다
// 일치하는 handler가 없다면 함수가 종료되고 함수를 호출한 곳으로 넘긴다
// 전파된 함수에서 해당하는 exception handler를 찾는다
// 반복되며 예외를 마지막까지 잡지 못하면 종료된다
fun parseIntNumber(s: String): Int {
    var num = 0
    if (s.length !in 1..31) throw NumberFormatException("Not a number")

    for (c in s) {
        if (c !in '0'..'1') throw NumberFormatException("Not a number")
        num = num * 2 + (c - '0')
    }
    
    return num
}

 

try문으로 예외 처리

// 예외발생 가능한 코드에 try를 사용
// 예외를 잡는 catch 사용
fun readInt(default: Int): Int {
    try {
        return readLine()!!.toInt()
    } catch (e: NumberFormatException) {	// e : 예외 변수
        return default
    }
}

// 여러 예외 핸들러가 있을 시 순차적으로 검사
// 예외처리 하위타입이 먼저 작성되어야 죽은코드가 나오지 않는다
fun readInt(default: Int): Int {
    try {
        return readLine()!!.toInt()
    } catch (e: Exception) {
        return 0
    } catch (e: NumberFormatException) {
        return default // 죽은 코드
    }
}

// 식으로 사용
fun readInt(default: Int) =
    try {
        readLine()!!.toInt()
    } catch (e: Exception) {
        0
    } catch (e: NumberFormatException) {
        default // 죽은 코드
    }
    
// finally는 try-catch 구문을 완료하기 전 마지막으로 실행
fun readInt(default: Int) =
    try {
        readLine()!!.toInt()
    } catch (e: Exception) {
        0
    } catch (e: NumberFormatException) {
        default // 죽은 코드
    } finally {
        println("kancho")
    }
코틀린은 checked Exception과 unchecked Exception을 구분하지 않는다

'코틀린' 카테고리의 다른 글

[코틀린] Scope function (영역 함수)  (0) 2022.10.10
[코틀린] 확장 (Extension)  (0) 2022.10.10
[코틀린] Lazy 초기화  (0) 2022.10.09
코틀린 정리 - (3)  (0) 2022.10.03
코틀린 정리 - (1)  (0) 2022.09.18