[kotlin] 함수 정의하기

maxxyoung·2023년 5월 11일

함수

  • 자바 메서드 파라미터는 디폴트가 가변이여서 함수 내부에서 변경하지 못하게 하려면 final을 지정해 불변 값으로 바꾸어야함. 코틀린 함수 파라미터는 무조건 불변. 함수 본문에서 파라미터 값을 변경하면 컴파일 오류 발생
fun increment(n: Int): Int {
	return n++ // 컴파일 오류 발생
}
  • 파라미터 앞에 val, var를 표시할 수 없음

    • 강제하는 이유는 파라미터에 대입하는 중에 실수할 가능성이 높을 뿐 아니라 파라미터를 불변 값으로 강제하는 편이 더 깔끔하고 이해햐기 편한 코드를 만들어내기 때문
  • 코틀린은 값에 의한 호출(call by value) 의미론을 사용

    • 파라미터 값에 호출하는 쪽의 인자를 복사
    • 호출 인자로 전달한 변수를 변경해도 호출된 함수 내부의 파라미터 값에는 영향이 없음
    • 파라미터가 참조라면 호출한 쪽의 데이터는 그대로 남아 있고 이 데이터에 대한 참조만 복사됨
  • 타입 지정을 생략해도 되는 변수와 달리 파라미터에는 항상 타입을 지정해야함

    • 컴파일러는 함수 정의에서 파라미터 타입을 추론하지 못함
  • 반환 타입은 함수 파라미터에서 추론이 가능한데도 명시해야함

    • return하는 지점이 여러 곳일 수 있는데, 함수 본문의 모든 반환 지점을 살펴보고 반환 타입을 알아내기 어려울 수 있기 때문
    • 함수 정의에 있는 반환 타입은 일종의 문서화 역할을 하며, 함수 정의의 첫 줄만 보고도 함수가 만들어내는 값이 무엇인지 바로 알 수 있게 됨
    • 경우에 따라 반환 타입 생략 가능
      • 유닛 타입을 반환하는 경우
        • 유닛은 자바 void에 해당하는 코틀린 타입으로 , 함수가 의미 있는 반환값을 돌려주지 않는다는 뜻
        • 이런 함수가 반환하는 값은 Unit이라는 내장 타입에 속하는 Unit이라는 상수
        • 함수 정의에서 반환값 타입을 지정하지 않으면 코틀린은 여러분이 Unit 함수를 정의한다고 가정
        • 함수 본문의 끝에 도달하기 전에 함수 실행을 마치려면 return 문을 사용해 함수를 끝내야함
      • 식이 본문인 함수(expression-body)
        • 어떤 함수가 단일식으로 구성될 수 있다면 return 키워드와 블록을 만드는 중괄호를 생략하고 함수 본문 식 사이에 '='가 들어있음
        • 복잡하게 표현된 식이 본문인 함수는 일반적인 블록 구문을 사용해 가독성을 높여주는게 나음
        • 블록이 본문인 함수를 정의할 때 {} 앞에 =를 넣으면 이 블록이 익명 함수를 기술하는 람다로 해석되기 때문에 원하는 결과를 얻을 수 없음
        • 식이 본문인 함수 안에서 return문 금지(5장 참조)
  • 위치 기반 인자와 이름 붙은 인자

    • 위치 기반 인자: 순서대로 파라미터에 전달
    • 코틀린은 이름 붙은 인자라고 불리는 방식도 제공
      • 위치가 아니라 파라미터의 이름을 명시함으로써 인자를 전달하는 방식
      • ex) rectangleArea(width = w, height = h)
      • 이름 붙은 인자를 사용하면 인자의 실제 순서는 중요하지 않음
    • 위치 기반 인자, 이름 붙은 인자 함께 사용 가능, 단 순서는 지켜야함
      • 코틀린 1.4 이후 부터 이름 붙은 인자를 섞어 쓸 수 있음
  • 오버로딩과 디폴드 값

    • 코틀린 이름이 같은 함수 여럿 작성할 수 있게 오버로딩 가능
    • 컴파일러가 어떤 함수를 호출해야 할지 구분할 수 있도록 오버로딩한 함수의 파라미터 타입이 모두 달라야함
    • 코틀린 컴파일러 오버로딩 해소 규칙
      1. 파라미터의 개수와 타입을 기준으로 호출할 수 있는 모든 함수를 찾는다
      2. 덜 구체적인 함수를 제외시킨다. 규칙 1에서 선택한 후보 목록에서 어떤 함수의 파라미터 타입이 다른 함수의 파라미터 타입의 상위 타입인 경우 이 함수는 다른 함수보다 덜 구체적인 함수다. 덜 구체적인 함수가 모두 제외될 때까지 이 단계를 반복한다.
      3. 후보가 하나로 압축되면 이 함수가 호출할 함수다. 후보가 둘 이상이면 컴파일 오류가 발생한다.
    • 디폴트 파라미터
      • 파라미터 뒤에 변수 초기화 식을 추가하면 원하는 파라미터에 디폴트 값을 제공할 수 있음
      fun readInt(radix: Int = 10) = readLine()!!.toInt(radix)
      • 디폴트 값이 있는 파라미터를 함수 인자 목록 뒤쪽에 몰아두는 쪽이 더 좋은 코딩 스타일
  • vararg

    fun printSorted(vararg items: Int) {
        items.sort() //items는 IntArray
        println(items.sontentToString())
    }
    
    fun main() {
        printSorted(6, 2, 10, 1) //[1, 2, 6, 10]
    }
    • 함수 내부에서 파라미터를 적절한 배열 타입으로 사용할 수 있음
    • 스프레드 연산자인 *를 사용하면 배열을 가변 인자 대신 넘길 수 있음
      val numbers = intArraysOf(6, 2, 10, 1)
        printSorted(*numbers)
        printSorted(numbers) //ERROR
    • 스프레드는 배열을 복사함, 이때 복사는 참조값이 들어있다면 참조값이 복사되는 얕은 복사가 이루어짐
    • 둘 이상의 vararg 파라미터로 선언하는 것 금지. 단, vararg 파라미터에 콤마로 분리한 여러 인자와 스프레드를 섞어서 전달하는 것은 괜찮음
      printSorted(6, 1, *intArrayOf(3, 8), 2)
  • 함수 영역과 가시성

    • 위치에 따른 코틀린 함수 구분
      • 파일에 직접 선언된 최상위 함수
      • 어떤 타입 내부에 선언된 멤버 함수
      • 다른 함수 안에 선언된 지역 함수
    • 최상위 함수
      • 디폴트로 최상위 함수는 public 함수
      • 디폴트로 선언된 최상위 함수는 함수가 정의된 파일 내부뿐 아니라 프로젝트 어디에서나 쓰일 수 있음
    • 함수 가시성
      - public : 프로젝트 어디서나 사용 가능
      - internal : 함수가 적용된 모듈 내부에서만 함수를 사용할 수 있게 제한(intelliJ 모듈 마법사를 통해 모듈 생성 가능, 같은 프로젝트 내에서 여러 개의 모듈 만들 수 있음)
      - private : 함수가 정의된 파일 안에서만 해당 함수를 볼 수 있음
      - 지역 함수와 변수에는 가시성 변경자를 붙일 수 없음

      코틀린 최상위 함수나 지역 함수를 JVM 플랫폼에서 컴파일 할 수 있는 이유.
      JVM이 볼 때 최상위 main() 함수는 코틀린 파일마다 자동으로 만들어지는 특별한 파사드 클래스의 정적 멤버라는 사실을 설명했다. 지역 함수의 경우에도 코틀린 컴파일러는 (최상위 함수와) 비슷한 트릭을 써서 지역 함수를 정의하며, 지역 함수를 둘러싼 영역의 변수나 파라미터 목록 등의 문맥을 포획해주는 특별한 클래스(이를 자바의 지역 클래스와 비교할 수 있다)를 선언한다. 이 말은 지역 함수를 호출할 때마다 이런 특별한 클래스 객체를 생성하는 부가 비용이 든다는 뜻이다.

패키지와 임포트

  • 패키지와 디렉토리 구조
    • 패키지를 이루는 최상위 선언에는 타입, 함수, 프로퍼티가 있음
  • 임포트 디렉티브 사용하기
    • 클래스, 함수 등의 최상위 선언, 클래스 안에 내포된 클래스(nested class), enum constant 등도 임포트 가능
    • 자바와 달리 import static 없음. 코틀린의 모든 선언은 일반적인 임포트 디렉티브 구문을 사용함
    • 서로 다른 패키지에 있는 일부 선언의 이름이 똑같을 때 as 사용
import foo.readInt as fooReadInt
import bar.readInt as barReadInt
  • 모든 선언 import할 때 * 사용

조건문

  • if문으로 선택하기
    • 코틀린에서는 if문을 식으로 사용할 수 있음
    fun max(a: Int, b: Int) = if (a > b) a else b
    • 코틀린은 삼항 연산자 없음
    • if문 식에서 return을 쓸 경우 if문을 둘러싼 함수의 리턴값임
  • 범위, 진행, 연산
    • 코틀린은 순서가 정해진 값 사이의 수열을 표현하는 몇 가지 타입을 제공
    • ..연산자 사용
    val chars = 'a'..'h'
    val twoDigits = 10..99
    val zero2One = 0.0..1.0
    • in 연산자 사용 시 어떤 값이 범위 안에 들어갔는지 알 수 있음
    num in 10..99 // num >= 10 && num <= 99
    num !in 10..99 // == !(num in 10..99)
    • 수 타입, Char, Boolean, String 등 모든 비교 가능한 타입에 대해 ..연산 사용 가능
    • ..연산의 범위는 닫혀있음 즉 시작 값과 끝 값이 범위에 포함됨
    • until연산자는 반만 닫혀있음 즉 끝 값이 포함되지 않음
    val twoDigits = 10 until 100 // 10..99와 같음
    • 내장 범위 연산에서 끝 값이 시작 값보다 확실히 더 작으면 빈 범위가 됨
    • downTo연산을 사용하면 아래로 내려가는 진행을 만들 수 있음
    • step을 통해 간격 지정도 가능
    5 in 10 downTo 1
    5 in 1 downTo 10 // fasle 빈배열
    1..10 step 3 // 1, 4, 7, 10
    15 downTo 8 step 2 //15, 13, 11, 9
    • 범위를 사용하여 문자열이나 배열의 일부분을 뽑아낼 수 있음
    "Hello, World".substring(1..4) //ello
    "Hello, World".substring(1 until 4) //ell
    "Hello, World".substring(1, 4) //ell
    IntArray(10) { it*it }.sliceArray(2..5) //4, 9, 16, 25
    IntArray(10) { it*it }.sliceArray(2 until 5) // 4, 9, 16
    • kotlin.ranges 패키지에 대한 문서를 찾아보면 범위나 진행에 관계된 모든 타입과 함수, 프로퍼티 목록을 볼 수 있음
    • in/!in 연산을 지원하는 타입이 범위만 있는 것이 아니라 문자열이나 배열처럼 다른 타입의 원소를 담는 컨테이너 종류의 타입이라면 보통 이 두 연산을 지원함
    val numbers = intArrayOf(3, 7, 2, 1)
    val text = "Hello!"
    println(2 in numbers) //true
    println(9 !n numbers) //true
    println(4 in numbers) //false
    println('a' in text) //false
    println('H' in text) //true
  • when 문과 여럿 중에 하나 선택하기
  • 코틀린은 여러 대안 중 하나를 선택할 수 있는 더 간결한 대안인 when을 제공(if, else if, else대신 when 사용 가능)
    fun hexDigit(n: Int): Char {
    	when {
       	n in 0..9 -> return '0' + n
           n in 10..15 -> return 'A' + n -10
           else -> return '?'
       }
    }
    • when 문도 if처럼 '식'으로 쓸 수 있음
    • when(n) 사용 가능
    • when(val n = readLine()!!.toInt()) 사용 가능
      • 이 때 정의한 변수는 when 블록 내부에서만 사용할 수 있고 var로 선언할 수 없음
    • when은 fall throgh를 사용하지 않음. 해당 조건을 만족하면 해당 코드 실행하고 끝

루프

  • 코틀린의 모든 루프는 식이 아니고 문이기 때문에 어떤 값으로 평가되지 않으며 부수 효과를 발생시킬 수만 있음

  • while과 do-while 루프

    • do-while은 루프 몸통이 실행되고 조건 검사
    • while은 조건 먼저 검사
  • for 루프와 이러터블

    fun main() {
    	val a = IntArray(10) {it*it}
      var sum = 0
      
      for(x in a) {
      	sum += x
      }
    }
    • 일반 변수와 달리 루프 변수에는 val이나 var를 붙이지 않음
    • 루프 변수는 자동으로 불변 값이 됨
    • 원하면 루프 변수의 타입을 지정할 수도 있지만, 실전에서는 거의 쓸 일이 없음
    • 문자열 루프 가능(for (char in text))
    • 인덱스 범위를 제공하는 indices라는 프로퍼티 있음(for (i in 0..a.lastIndex step 2))
    • 어떤 타입이 iterator()라는 함수를 제공하기만 하면 for 루프 사용 가능
  • 루프 제어 흐름 변경하기: break와 continue

    • break는 루프를 종료시키고, 실행 흐름이 루프 바로 다음 문으로 이동하게 만듦
    • continue는 현재 루프 이터레이션을 마치고 조건 검사로 바로 진행하게 만듦
    • when절에서 break, continue를 사용하면 when절에서 가장 가까운 루프로 제어권 넘김(코틀린 1.4 미만은 오류 1.4 부터 정상)
  • 내포된 루프와 레이블

    fun indexOf(subarray: IntArray, array: IntArray): Int {
    	outerLoop@ for (i in array.indices) {
      	for (j in subarray.indices) {
          	if(subarray[j] != array [i + j]) contitnue@outerLoop
          }
          return i   
      }
      return -1
    }
  • 꼬리 재귀 함수

    • 코틀린은 꼬리 재귀 함수에 대한 최적화 컴파일을 지원
    • tailrec를 붙이면 컴파일러가 재귀 함수를 비재귀적인 코드로 자동 변환해줌

예외 처리

  • 예외 던지기
    • 예외를 던질 때 벌어지는 일
      • 프로그램은 예외를 잡아내는 핸들러를 찾는다. 예외와 일치하는 예외 핸들러가 있다면 예외 핸들러가 예외를 처리한다.
      • 현재 함수 내부에서 핸들러를 찾을 수 없으면 함수 실행이 종료되고 함수가 스택에서 제거(pop)된다. 그리고 호출한 쪽의 문맥 안에서 예외 핸들러 검색을 수행한다. 이런 경우 예외를 호출자에게 전파했다고 말한다.
      • 프로그램 진입점에 이를 때까지 예외를 잡아내지 못하면 현재 스레드가 종료된다.
  • try 문으로 예외 처리하기
    • catch문 순서대로 검사 따라서 앞 순서에 하위 타입을 체크해야함
    • 코틀린은 try가 식
    fun readInt(default: Int) = try {
    	readLine()!!.toInt()
    } catch (e: NumberFormatException) {
    	default
    }
    • 자바와 달리 체크드 익셉션과 언체크드 익셉션을 구분하지 않음
    • finally 블록에서 try 블록이 떠나기 전에 프로그램이 어떤 일을 수행하도록 만들어 줌(자원 할당 해제할 때 유용)
  • 출처 코틀린 완벽 가이드
profile
오직 나만을 위한 글. 틀린 부분 말씀해 주시면 감사드립니다.

0개의 댓글