본문 바로가기
Study/다재다능 코틀린 프로그래밍

Day 3. 함수를 사용하자

반응형
본 포스트는 지인들과 스터디한 내용을 정리한 포스트입니다

 

http://www.kyobobook.co.kr/product/detailViewKor.laf?mallGb=KOR&ejkGb=KOR&barcode=9788931463422 

 

다재다능 코틀린 프로그래밍 - 교보문고

다양한 프로그래밍 패러다임으로 살펴보는 코틀린 기본서 | 코틀린은 멀티패러다임 프로그래밍 언어다. 코틀린은 스크립트로 사용할 수도 있고, 객체지향 코드나 함수형 코드, 비동기 프로그램

www.kyobobook.co.kr


1. 함수를 사용하자

  • 재사용 가능한 가장 작은 단위가 클래스인 Java와 달리, 코틀린에서는 클래스는 물론 단독함수까지 모두 재사용이 가능하다
  • 함수를 호출할때 모든 파라미터를 전달하지 않고 기본 파라미터를 전달할 수 있다 ( 함수와 메서드를 쉽게 확장할 수 있다 )
  • 메소드 사용을 효율적으로 만들기 위해서 코틀린은 아규먼트에 이름을 만들 수 있도록 허용

 

2. 함수 생성

  • 키스 함수(KISS, Keep It Simple, Stupid. "단순하게 해, 멍청아!")
    • 예제 코드: 가장 단순하고 짧은 함수 정의
      // kiss.kts
      fun greet() = "Hello"
      println(greet())​
    • 함수의 정의는 fun키워드로 시작한다
    • 함수 이름("greet") 다음에는 파라미터 리스트가 나온다
    • 함수가 매우 짧은 단일표현함수라면 위 예제처럼 함수 바디를 {}로 만드는 대신 =을 이용하여 만들 수 있다 ( 리턴타입 추론까지 가능, return 키워드를 사용할 수 없음 )
  • 리턴타입과 타입추론
    • 코틀린은 {} 블록 바디가 없는 함수(단일표현함수)에 대해 타입 추론을 해준다, 즉 return 할 필요도, 리턴타입을 명시해줄 필요도 없다
    • 전에 설명했던 대로 타입 추론은 컴파일 시점에서 일어난다, 미스 매치된 타입을 할당하려고하면 컴파일 오류가 난다
    • 하지만 함수가 외부에서 사용되거나 복잡하다면 리턴타입을 지정해주도록 하자!! ( 명확하게 설명할 수 있으며, 코드 구현부에 의해 결정되는 타입추론이 의도치 않은 다른 타입으로 변경되는 상황을 방지할 수 있다 )
    • 리턴타입은 파라미터 리스트 뒤에 ':' 키워드를 붙여서 작성한다
    • return 키워드는 단일표현식 함수이고, 바디가 블록이 아니라면 사용할 수 없다 ( 리턴타입이 명확하다면 타입추론 사용, 명확하지 않다면 리턴타입을 명시해주자 )
  • 모든 함수는 표현식이다
    • 코틀린은 Unit이라는 특별한 타입을 사용, Unit은 Java의 void형과 대응된다
    • 리턴할 게 아무것도 없는 경우 Unit을 사용할 수 있다
    • 코틀린은 함수에 리턴이 없으면 Unit타입을 리턴 타입으로 추론한다
    • 예제 코드
      // inferunit.kts
      fun sayHello() = println("Well, hello")
      val message: String = sayHello() // ERROR, Return Type이 Unit
      
      //error: type mismatch: inferred type is Unit but String was expected (inferunit.kts:2:23)
      //inferunit.kts:2:23: error: type mismatch: inferred type is Unit but String was expected
      //val message: String = sayHello() // ERROR, Return Type이 Unit​

      • 타입 추론을 명시적으로 Unit으로 줘도 가능하다
      • 심지어 Unit 타입 변수에 할당도 가능
    • Unit 타입은 toString(), equals(), hashCode() 메소드를 가지고 있다
    • 모든 함수는 유용한 리턴을 준다, 혹은 리턴이 없다면 Unit을 리턴한다. 따라서 모든 함수는 표현식으로 취급될 수 있고 그 결과들은 변수에 할당되거나 추후 프로세스에 사용될 수 있다
  • 파라미터 정의하기
    • 코틀린은 함수나 메소드에 파라미터의 타입을 명시하도록 한다
    • 파라미터의 타입을 파라미터 이름 바로 뒤에 ':'으로 구분해서 명시해준다
    • 예제 코드
      // passingarguments.kts
      fun greet(name: String): String = "Hello $name"
      println(greet("Eve"))​
    • 코틀린은 파라미터를 val나 var로 명시적인 지정을 할 수 없게 만들었고 ( 전부다 val, 이뮤터블 ) 함수나 메소드에서 파라미터의 값을 변화시키려고 하면 컴파일 오류를 발생시킨다
  • 블록바디로 만든 함수
    • 함수가 복잡하다면 {} 블록을 사용해서 바디를 만들면 된다
    • {} 블록 바디를 이용하면 반드시 리턴타입을 정의해줘야한다 ( 정의하지 않으면 Unit으로 타입 추론된다 )
    • 예제 코드
      // multilinefunction.kts
      fun max(numbers: IntArray): Int {
          var large = Int.MIN_VALUE
          for (number in numbers) {
              large = if (number > large) number else large
          }
          return large
      }
      
      println(max(intArrayOf(1,5,2,12,7,3))) // 12​
    • 코틀린은 코드 블록 안으로 들어가서 리턴타입을 추론하지 않는다
      • 특정 리턴타입을 명시하고 =을 사용한뒤 {} 블록 바디를 사용하면 컴파일 오류가 난다 
      • 반드시 단일 표현식에만 '='을 사용하고 복잡한 함수 + 리턴타입 명시를 할때만 {} 블록을 사용하자
      • 예제 코드
        // caveat.kts
        fun f1() = 2
        fun f2() = { 2 }
        
        println(f1()) // 2
        println(f2()) // () -> kotlin.Int​

        • f1은 {} 블록없이 단일표현식으로 타입추론이 되어 의도한 대로 리턴값이 나왔다
        • f2는 =에 {} 블록을 감싼뒤 2를 적었다, 코틀린은 해당 코드를 람다표현식으로 추론한다 ( () -> kotlin.Int )

 

3. 기본 인자와 명시적 인자

  • 코틀린도 Java처럼 오버로딩이 가능하다
  • 기본 아규먼트 기능이 더 단순하고, 함수를 변경하는 좋은 방법이긴 하나 바이너리가 변경되므로 컴파일을 다시 해야한다
  • 명시적 아규먼트는 읽기 좋은 코드를 만드는 아주 유용한 방법이다
  • 기본 아규먼트를 통한 함수 변경
    • 기존의 Java에서는 원형 함수에 확장성이 필요하여 수정하려할 경우 원형 함수는 그대로 놔두고 아규먼트 갯수를 조절하여 오버로딩을 통해 해결했다 ( 하지만 이와 같은 방식은 코드의 중복이 발생한다 )
    • 코틀린은 위의 문제를 기본 아규먼트를 이용해서 해결했다
    • 예제 코드
      // defaultarguments.kts
      fun greet(name: String, msg: String = "Hello"): String = "$msg $name"
      println(greet("Eve")) // Hello Eve
      println(greet("Eve", "Howdy")) // Howdy Eve​

      • greet 함수는 String type의 "name" 파라미터와 String type의 "msg" 파라미터를 가지고 있다
      • 하지만 "msg" 파라미터가 선언과 동시에 초기화도 되어 있는 것을 확인할 수 있는데 이는 인자값이 들어오지 않았을 때 default 값으로 바인딩된다
      • 기본 아규먼트를 효과적으로 만들기 위해서는 맨 마지막에 사용하는 것이 좋고, 람다표현식을 파라미터로 사용할 경우 바로 그 앞까지 사용하면 된다
  • 명시적 아규먼트를 이용한 가독성 향상
    • 함수에 대한 파라미터들의 의미를 알려면 문서나 함수 정의 부분을 참고해야하는 어려움이 있다
    • 이를 명시적인 파라미터 전달을 통해 해결할 수 있다
    • 비교 코드
      // namedarguments.kts
      fun createPerson(name: String, height: Int, weight: Int, age: Int = 1) {
          println("$name $age $height $weight")
      }
      
      createPerson("jimin.joo", 173, 70, 31) // original
      createPerson(name = "jimin.joo", height = 173, weight = 70, age = 31) // named​

      • 물론.. intelliJ가 각 아규먼트에 대한 정보를 보여주긴 하지만 명시적으로 나타내어 가독성을 향상시킬 수 있다
      • 파라미터들이 어떤 의미인지 추측할 필요도 없다
      • 또한 명시적인 아규먼트는 파라미터 순서와 상관없이 사용할 수 있다 ( 직접 명시해주니까! )
      • 이런 방식은 함수에 발생할 수 있는 잠재적인 오류의 가능성들을 제거해준다

 

4. 다중인자와 스프레드

  • 코틀린의 다중인자(vararg,, 뭐라고 읽는거지..) 기능은 함수가 한 번에 여러 개의 인자를 받을 때 타입 안정성을 제공해주는 기능
  • 스프레드 연산자는 콜렉션의 값을 개별 값으로 분해하거나 나열할 때 유용하다
  • 여러개의 인자
    • 코틀린 함수들은 많은 수의 인자를 받을 수 있다
    • 예제 코드
      // vararg.kts
      fun max(vararg numbers: Int): Int {
          var large = Int.MIN_VALUE
          for (number in numbers) {
              large = if ( number > large ) number else large
          }
          return large
      }
      
      println(max(1,4,3,6,2))​

      • 기존에 IntArray 타입이 였던 파라미터가 vararg 키워드로 선언됨과 동시에 Int 타입으로 변경되었다
    • 즉 vararg 키워드가 파라미터로 특정 타입(Int)의 배열이 들어갈 수도 있다는 점을 알려준다
    • vararg는 마지막에 두어서 호출 시 명시적 인자를 필수적으로 사용할 필요가 없도록 만들자
    • 역시나 마지막 파라미터가 람다표현식일 경우 마지막 바로 전에 둔다
  • 스프레드 연산자
    • 함수가 다중 인자를 받을 수 있도록 정의되어 있어도 배열이나 리스트를 직접 받을 수는 없다
    • 이럴때 스프레드 연산자가 필요하다
    • 즉 파라미터가 vararg라고 작성되어있는 경우, 스프레드 연산자 *을 이용해서 배열을 넘길 수 있다 ( 당연히 같은 타입만 가능하다 )
    • 예제 코드
      fun max(vararg numbers: Int): Int {
          var large = Int.MIN_VALUE
          for (number in numbers) {
              large = if (number > large) number else large
          }
          return large
      }
      
      
      val values = intArrayOf(1, 2, 3, 4, 5)
      
      println(max(values)) // compile error
      println(max(*values))​
      • 배열이 있으면 스프레드를 사용할 수 있지만 리스트는 스프레드 연산자를 사용할 수 없다
      • 이럴 땐 리스트를 배열로 변환하여 적용하면 된다
        *listOf(1, 4, 8, 12).toIntArray()

 

5. 구조분해

  • 구조화란 다른 변수의 값으로 객체를 만드는 것, 구조 분해는 이미 존재하는 객체에 값을 추출해 변수로 넣는 작업이다
  • 코틀린의 구조분해는 속성의 이름이 아닌 속성의 위치를 기반으로 진행된다
  • 예제 코드 ( 구조분해 )
    val (first, middle, last) = getFullName()
    println("$first $middle $last")​

    • 객체의 소성들이 구조분해되는 순서는 객체의 컨스트럭터가 객체를 초기화할 때 속성을 만드는 순서와 동일하다
    • 필요없는 속성은 '_'를 이용하여 스킵할 수 있다
    • 특정 포지션에서 할당을 멈추고, 이후 값을 무시하고 싶으면 아무것도 적지 않으면 된다
728x90
반응형