🌆
Sunset Archive
GitHubLinkedInBlog
  • litsynp
  • Archives
    • Projects
      • Let's Parking!
      • 맛집몬스터 (JMT Monster)
      • spring-x-demo Projects
    • Articles
      • My Thoughts on GitBook
      • Wake-on-LAN & RDP Setup
    • Learning
      • Languages
        • Rust
          • Useful Links
          • Syntax
          • Pointers
          • Dependencies
          • Logging
          • Phantom Types
          • Iterable
            • Optional Skip
        • Go
          • Useful Links
          • Structures
          • Zero Values
          • Error Handling
          • Dependency Injection
          • Project Layout
        • JavaScript/TypeScript
          • Basics
            • Types
              • [JS] Falsy Values
              • [TS] Types - unknown, any, never
            • Rest Parameters (...)
            • Spread Syntax (...)
            • Destructuing Assignment
            • CJS, AMD, UMD, ESM
          • Advanced
            • Conditional Destructuring Assignment
            • Type Guards
          • Miscellaneous
            • Dependency Injection in JS?
            • ESLint, Prettier, TypeScript
          • Node
            • Useful Links
            • General
              • V8 Engine
              • Version Management: NVM
              • Environment Variables
            • Database
              • Knex
        • C
          • Dynamic Multi-dimensional Arrays
        • Spring
          • General
            • @Import v.s. @ContextConfiguration
            • MessageSource
          • Kotlin+Spring
            • Kotlin Annotations (Use-Site Targets)
            • Handling Null in Request DTO
            • Handling Null in URL
          • Reactive Stack
            • Reactive API with Spring WebFlux
          • Spring Security
            • Google OAuth Setup
          • Spring Batch
            • Bulk Insert
        • Kotlin
          • val/var in Constructor
          • Initializer Blocks
          • Inheritance Modifiers (final, open, abstract)
          • Delegate Pattern
        • Java
          • Serialization
          • Random Number Generation
            • (1) Math.random() v.s. Random
            • (2) Random v.s. ThreadLocalRandom
        • Python
          • Version Management
        • Ruby
          • Installation
          • Getters & Setters
        • Elixir
        • Erlang
        • Flutter
        • AWS
          • AWS CLI
        • Terraform
          • Installation
          • Basics
      • Code
        • OOP
          • The 4 Basic Concepts of OOP
          • The SOLID Principles
          • GRASP
          • Spring POJO
          • Others
        • Functional Programming
          • Currying
          • Higher-Order Function
          • Closure
          • Monad
        • 공변성, 반공변성, 무공변성
        • Others
          • UUID
          • GraphQL
          • Multimedia
            • Streaming
          • Geography
            • 위도 (Latitude), 경도 (Longitude)
      • Tools
        • Nix
        • Mermaid
          • Flowchart
          • Sequence Diagram
          • Class Diagram
          • Entity Relationship Diagrams
        • VSCode
          • VSCode CLI
          • VSCode Extensions
        • JetBrains
          • IntelliJ - Open Projects in Tabs
          • Delete Leftover IDE Directories
        • vim
          • Commands
      • Books
        • 다재다능 코틀린 프로그래밍 (Programming Kotlin)
          • Pt 01. 코틀린으로 스크립팅 하기
            • Ch 01. 코틀린 시작하기
            • Ch 02. Java 개발자를 위한 코틀린 필수 사항
            • Ch 03. 함수를 사용하자
            • Ch 05. 콜렉션 사용하기
        • 오브젝트 (Object)
          • Ch.0 들어가며 - 프로그래밍 패러다임
          • Ch.1 객체, 설계
          • Ch.2 객체지향 프로그래밍
          • Ch.3 역할, 책임, 협력
          • Ch.4 설계 품질과 트레이드오프
          • Ch.5 책임 할당하기
          • Ch.6 메시지와 인터페이스
          • Ch.7 유연한 설계
          • Ch.8 의존성 관리하기
          • Ch.9 유연한 설계
          • Ch.10 상속과 코드 재사용
          • Ch.11 합성과 유연한 설계
          • Ch.12 다형성
          • Ch.13 서브클래싱과 서브타이핑
          • Ch.14 일관성 있는 협력
          • Ch.15 디자인 패턴과 프레임워크
          • End. 마치며 - 나아가기
          • 후기
        • 헤드 퍼스트 디자인 패턴 (Head First Design Patterns)
          • Ch.1 디자인 패턴 소개와 전략 패턴
          • Ch.2 옵저버 패턴
          • Ch.3 데코레이터 패턴
          • Ch.4 팩토리 패턴
          • Ch.5 싱글턴 패턴
          • Ch.6 커맨드 패턴
          • Ch.7 어댑터 패턴과 퍼사드 패턴
          • Ch.8 템플릿 메소드 패턴
          • Ch.9 반복자 패턴과 컴포지트 패턴
          • Ch.10 상태 패턴
          • Ch.11 프록시 패턴
          • Ch.12 복합 패턴
        • 이펙티브 타입스크립트 (Effective TypeScript)
          • Ch.1 타입스크립트 알아보기
            • Item 1 타입스크립트와 자바스크립트의 관계 이해하기
            • Item 2 타입스크립트 설정 이해하기
            • Item 3 코드 생성과 타입이 관계없음을 이해하기
            • Item 4 구조적 타이핑에 익숙해지기
            • Item 5 any 타입 지양하기
          • Ch.2 타입스크립트의 타입 시스템
            • Item 6 편집기를 사용하여 타입 시스템 탐색하기
            • Item 7 타입이 값들의 집합이라고 생각하기
            • Item 1-7 Study Summary
            • Item 8 타입 공간과 값 공간의 심벌 구분하기
            • Item 9 타입 단언보다는 타입 선언을 사용하기
            • Item 10 객체 래퍼 타입 피하기
            • Item 11 잉여 속성 체크의 한계 인지하기
            • Item 12 함수 표현식에 타입 적용하기
            • Item 13 타입과 인터페이스의 차이점 알기
            • Item 14 타입 연산과 제너릭 사용으로 반복 줄이기
            • Item 15 동적 데이터에 인덱스 시그니처 사용하기
            • Item 16 number 인덱스 시그니처보다는 Array, 튜플, ArrayLike를 사용하기
            • Item 17 변경 관련된 오류 방지를 위해 readonly 사용하기
            • Item 18 매핑된 타입을 사용하여 값을 동기화하기
          • Ch.3 타입 추론
            • Item 19 추론 가능한 타입을 사용해 장황한 코드 방지하기
            • Item 20 다른 타입에는 다른 변수 사용하기
            • Item 21 타입 넓히기
            • Item 22 타입 좁히기
            • Item 23 한꺼번에 객체 생성하기
            • Item 24 일관성 있는 별칭 사용하기
            • Item 25 비동기 코드에는 콜백 대신 async 함수 사용하기
            • Item 26 타입 추론에 문맥이 어떻게 사용되는지 이해하기
            • Item 27 함수형 기법과 라이브러리로 타입 흐름 유지하기
          • Ch.4 타입 설계
            • Item 28 유효한 상태만 표현하는 타입을 지향하기
            • Item 29 사용할 때는 너그럽게, 생성할 때는 엄격하게
            • Item 30 문서에 타입 정보를 쓰지 않기
            • Item 31 타입 주변에 null 값 배치하기
            • Item 32 유니온의 인터페이스보다는 인터페이스의 유니온을 사용하기
            • Item 33 string 타입보다 더 구체적인 타입 사용하기
            • Item 34 부정확한 타입보다는 미완성 타입을 사용하기
            • Item 35 데이터가 아닌, API와 명세를 보고 타입 만들기
            • Item 36 해당 분야의 용어로 타입 이름 짓기
            • Item 37 공식 명칭에는 상표를 붙이기
          • Ch.5 any 다루기
            • Item 38 any 타입은 가능한 한 좁은 범위에서만 사용하기
            • Item 39 any를 구체적으로 변형해서 사용하기
            • Item 40 함수 안으로 타입 단언문 감추기
            • Item 41 any의 진화를 이해하기
            • Item 42 모르는 타입의 값에는 any 대신 unknown을 사용하기
            • Item 43 몽키 패치보다는 안전한 타입을 사용하기
            • Item 44 타입 커버리지를 추적하여 타입 안전성 유지하기
          • Ch.6 타입 선언과 @types
            • Item 45 devDependencies에 typescript와 @types 추가하기
            • Item 46 타입 선언과 관련된 세 가지 버전 이해하기
            • Item 47 공개 API에 등장하는 모든 타입을 익스포트하기
            • Item 48 API 주석에 TSDoc 사용하기
            • Item 49 콜백에서 this에 대한 타입 제공하기
            • Item 50 오버로딩 타입보다는 조건부 타입을 사용하기
            • Item 51 의존성 분리를 위해 미러 타입 사용하기
            • Item 52 테스팅 타입의 함정에 주의하기
          • Ch.7 코드를 작성하고 실행하기
            • Item 53 타입스크립트 기능보다는 ECMAScript 기능을 사용하기
            • Item 54 객체를 순회하는 노하우
            • Item 55 DOM 계층 구조 이해하기
            • Item 56 정보를 감추는 목적으로 private 사용하지 않기
            • Item 57 소스맵을 사용하여 타입스크립트 디버깅하기
          • Ch.8 타입스크립트로 마이그레이션하기
            • Item 58 모던 자바스크립트로 작성하기
            • Item 59 타입스크립트 도입 전에 @ts-check와 JSDoc으로 시험해 보기
            • Item 60 allowJS로 타입스크립트와 자바스크립트 같이 사용하기
            • Item 61 의존성 관계에 따라 모듈 단위로 전환하기
            • Item 62 마이그레이션의 완성을 위해 noImplicitAny 설정하기
        • Dive Into Design Patterns
          • 디자인 패턴 소개
          • 소프트웨어 디자인 원칙들
          • 디자인 패턴 목록
          • 유용한 링크
        • 가상 면접 사례로 배우는 대규모 시스템 설계 기초 (System Design Interview)
          • Key Points
          • Real Life Systems
          • Engineering Blogs
        • Node.js 디자인 패턴 바이블 (Node.js Design Patterns 3rd Edition)
        • 리팩터링 2판 (Refactoring: 2nd Ed.)
          • 1장 리팩터링: 첫 번째 예시
          • 2장 리팩터링 원칙
          • 3장 코드에서 나는 악취
          • 4장 테스트 구축하기
Powered by GitBook
On this page
  • About
  • 3-1. 함수 생성
  • 3-2. 기본 인자와 명시적 인자
  • 3-3. 다중인자와 스프레드
  • 3-4. 구조분해
  • 정리

Was this helpful?

Edit on GitHub
  1. Archives
  2. Learning
  3. Books
  4. 다재다능 코틀린 프로그래밍 (Programming Kotlin)
  5. Pt 01. 코틀린으로 스크립팅 하기

Ch 03. 함수를 사용하자

About

코틀린은 개발자에게 무조건 클래스를 사용하도록 강요하지 않는다. 재사용 가능한 가장 작은 단위가 클래스인 Java와 달리, 코틀린에서는 클래스는 물론 단독 함수(standalone function)까지 모두 재사용 가능하다.

코틀린에서 우리는 함수가 특정 타입의 파라미터를 사용하도록 정해야 한다. 하지만 단일 표현식 함수(single-expression function)에 대한 리턴 유형을 유추하도록 요청할 수 있다. 함수를 호출할 때, 모든 파라미터를 전달하지 않고 기본 파라미터를 전달할 수 있다. 이런 특징을 이용해서 함수와 메소드를 쉽게 확장할 수 있다.

메소드 사용을 효율적으로 만들기 위해서 아규먼트에 이름을 넣을 수 있도록 허용했다. 이런 발전으로 인해 코드의 가독성이 올라갔다.

게다가 함수에 가변 아규먼트를 컴파일 안정성 저해 없이 넘길 수 있다.

코틀린은 구조분해(destructuring) 기능을 가지고 있어서 객체에서 속성을 독립 변수(standalone variable)로 뽑아낼 수 있다.

이 챕터에서 우리는 전역 함수와 단독(standalone) 함수를 사용하는 법을 배운다. 코틀린의 함수를 정의하는 규칙을 시작으로, 어떻게 함수가 표현식으로 취급되는지를 살펴보며, 기본 아규먼트, 명시적(named) 아규먼트, 가변 아규먼트 정의, 스프레드 연산자, 구조분해 등 많은 유용한 특징들에 대해서도 알아볼 것이다. 이런 특징을 이용해 읽기 쉽고, 유연하고, 유지보수하기 좋은 고품질 코드를 만들 수 있다. 객체지향 코드를 작성할 때에도 여기에서 배운 컨셉들이 클래스으 ㅣ메소드를 만드는데 적용될 것이다.

3-1. 함수 생성

코틀린에서 함수와 메소드를 만드는 방법은 Java의 메소드 만드는 방법과 차이가 있다. 코틀린은 함수를 만들 때, 불필요한 관행적인 코드들을 없애 버려서 훌륭한 유연성을 제공한다. 짧은 함수부터 시작해서 타입추론, 파라미터 정의와 멀티라인 함수를 알아보자.

키스 (KISS) 함수

코틀린은 함수를 정의할 때 "단순하게 해, 멍청아!(Keep It Simple, Stupid - KISS 원칙)"을 준수한다.

작은 함수들은 단순하게 작성하고, 방해요소가 없고, 실수가 없어야 한다.

// functions/kiss.kts
fun greet() = "Hello"
println(greet())

함수 정의는 fun 키워드로 시작한다. 함수 이름 다음엔 파라미터 리스트가 나온다. 만약 함수가 매우 짧은 단일표현함수(single-expression function)라면 함수 바디를 {} 로 만드는 대신 함수 정의 부분과 함수 바디를 =로 구분할 수 있다. 그리고 {} 블록 바디가 없는 단일표현함수에서는 return 키워드를 사용할 수 없다.

결과는 다음과 같다.

Hello

리턴타입과 타입 추론

greet() 함수는 문자열을 리턴한다. 그러나 리턴타입(String)을 명시하지 않았다. 이것이 가능한 이유는 코틀린이 {} 블록 바디가 없는 함수에 대해 타입 추론을 해주기 때문이다. 리턴 타입 추론은 컴파일할 때 진행된다.

코드에 일부러 오류를 넣어보자.

fun greet() = "Hello"
val message: Int = greet() // ERROR
// type mismatch: inferred type is String but Int was expected

코틀린은 컨텍스트에 기반해 greet()의 리턴을 String이라고 결정했다. 그리고 우리는 Int형 변수 message에 greet()의 결과를 할당하려고 시도했다. 코드는 컴파일 오류가 났다.

타입 추론은 안전하게 사용할 수 있고, 컴파일 시간에 타입 체크를 한다. 내부 API에서 사용가능하고, 단일표현 함수가 =로 정의된 경우 사용할 수 있다. 하지만, 함수가 외부에서 사용되거나 복잡하다면 리턴타입을 지정해주자. 리턴타입을 지정해줘야 코드를 개발한 사람과 사용하는 사람 모두에게 리턴타입을 명확하게 알려줄 수 있다. 또한, 리턴 타입 추론이 구현에 의해서 다른 타입으로 변경되는 것을 막을 수 있다.

코틀린의 함수 리턴타입 추론은 함수의 바디가 단일표현식이고, {} 블록이 아닐 때만 가능하다. 리턴타입을 명시해보자.

fun greet(): String = "Hello"

리턴타입은 앞에는 : 를 붙이고, 파라미터 리스트 뒤에 작성한다. return 키워드는 단일표현식 함수이고, 바디가 블록이 아니라면 허용되지 않는다. 리턴타입이 명확하면 타입추론을, 아니면 리턴타입을 명시하자.

모든 함수는 표현식이다

코틀린은 명령문보다는 표현식을 좋아한다. 그 원칙에 기반해 함수는 명령문보다는 표현식으로 취급되어야 한다.

코틀린은 Unit이라는 특별한 타입을 사용하는데, Java의 void와 대응된다. Unit이라는 이름은 타입 이론에서 아무런 정보를 갖지 않는 싱글톤인 Unit에서 유래했다. 리턴할 게 없는 경우 Unit을 사용할 수 있다. 코틀린은 함수에 리턴이 없으면 Unit 타입을 리턴타입으로 추론한다.

// functions/inferunit.kts
fun sayHello() = println("Well, hello")
val message: String = sayHello() // ERROR
// type mismatch: inferred type is Unit but String was expected

sayHello() 함수는 println() 함수를 이용해 일반 출력하는데, Java에서 println()의 리턴값은 알다시피 void이다. 코틀린에서는 Unit을 리턴한다.

타입 추론 사용 대신에 리턴 타입으로 Unit을 지정할 수도 있다.

// functions/specifyunit.kts
fun sayHello(): Unit = println("Well, hello")
val message: Unit = sayHello()
println("The result of sayHello is $message")

코틀린에서는 void 함수도 Unit을 리턴해주기 때문에 모든 함수가 표현식으로 취급될 수 있다. 그리고 모든 함수 결과에 대해서 메소드를 호출할 수도 있다.

Unit 타입은 toString(), equals(), hashCode() 메소드를 가지고 있다. 물론 엄청나게 유용하진 않겠지만, 위의 함수들을 실행시킬 수 있다. 이전 예제에서도 println()에서 내부적으로 Unit 의 toString() 메소드를 호출했다. 출력 결과이다.

Well, hello
The result of sayHello is kotlin.Unit

모든 함수들은 유용한 리턴을 준다. 혹은 리턴이 없다면 Unit을 리턴해 준다. 그래서 모든 함수는 표현식으로 취급될 수 있고, 그 결과들은 변수에 할당되거나 추후 프로세스를 위해서 사용될 수 있다.

파라미터 정의하기

Haskell, F# 같은 일부 언어는 함수 안으로 들어가서 파라미터의 타입을 추론할 수 있다. 저자는 이 방식을 마음에 들어하지는 않는다고 한다. 함수의 구현을 바꾸는 것은 파라미터의 타입을 바꾸는 결과를 초래할 수 있어 불안하게 만든다.

코틀린은 함수나 메소드에 파라미터의 타입을 명시하도록 했다. 파라미터의 타입을 파라미터 이름 바로 뒤에 :로 구분해서 명시해 주는 것이다.

// functions/passingarguments.kts
fun greet(name: String): String = "Hello $name"
println(greet("Eve")) // Hello Eve

코틀린에서 함수 파라미터 타입을 지정할 때 "candidate(후보): Type" 형태의 문법을 사용한다. 이 문법은 함수 파라미터 지정 외에도 var, val을 이용한 변수 선언, 함수의 리턴 타입 선언, 함수 파라미터, 캐치블록에 전달될 아규먼트 타입 지정에도 사용된다.

greet() 함수를 정의할 때 파라미터에 var이나 val을 사용하지 않는다. 여기엔 합리적인 이유가 있다.

블록바디로 만 함수

함수가 작을 때(단일 표현식일 때) 우리는 =를 통해 바디와 함수 선언부를 나눴고, 리턴타입은 생략할 수 있었다.

함수가 복잡하다면 {} 블록을 사용하여 바디를 만든다. {} 블록 바디를 이용해 함수를 정의하면 항상 리턴타입을 정의해줘야 하며, 정의하지 않는다면 리턴타입은 Unit으로 추론된다.

// functions/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

max() 함수는 파라미터를 배열로 받고 리턴으로 Int를 반환한다고 정의되어 있다. 함수의 바디는 {} 블록으로 둘러싸여있으므로 리턴타입은 선택사항이 아니다. return 키워드도 필수이다.

한 가지 주의사항은, =을 {} 블록 바디 대신 사용하면 안 된다. 만약 특정 리턴타입을 명시하고 =을 사용한 뒤 {} 블록 바디를 사용한다면 컴파일러가 오류를 발생시킨다.

fun notReally() = {2}

코틀린은 코드 블록 안으로 들어가서 리턴타입을 추론하지 않는다. 이 경우 코틀린은 블록을 람다표현식이나 익명함수로 취급할 것이다. 코틀린은 notReally() 함수를 람다표현식을 리턴하는 함수라고 판단한다.

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

오버로딩은 함수가 기존과는 다른 타입과 다른 수의 아규먼트를 받을 수 있도록 만들 수 있다. 코틀린 또한 오버로딩이 가능하다.

기본 아규먼트 기능은 단순하고, 함수를 변경하는 좋은 방법이다. 하지만 기본 아규먼트 기능을 쓰면 바이너리가 변경되므로 컴파일을 다시 해야 한다. 그럼에도 기본 아규먼트 기능은 매우 좋다.

그리고 명시적 아규먼트(named argument)는 읽기 좋은 코드를 만드는 아주 유용한 방법이다.

기본 아규먼트를 통한 함수 변경

fun greet(name: String): String = "Hello $name"
println(greet("Eve")) // Hello Eve

지금은 "Hello"라는 내용이 하드코딩되어있다. 함수를 호출하는 사람이 선택할 수 있도록 유연성을 제공한다면 어떨까?

만약 함수에 새로운 파라미터를 추가한다면 이전에 함수를 호출하던 코드는 추가된 파라미터를 전달하고 있지 않기 때문에 모두 오류를 만들어낼 것이다. Java에서는 이런 목적을 달성하기 위해 오버로딩을 사용했지만, 그렇게 하면 코드의 중복이 발생한다. 코틀린은 기본 아규먼트를 이용해 이 문제를 해결한다.

// functions/defaultarguments.kts
fun greet(name: String, msg: String = "Hello"): String = "$msg $name"
println(greet("Eve")) // Hello Eve
println(greet("Eve", "Howdy")) // Howdy Eve

기본 아규먼트를 효과적으로 사용하고 싶다면 맨 마지막에 사용하는 것이 좋다. 람다표현식이 파라미터로 들어오는 경우엔 람다표현식 앞에서 사용하면 된다.

명시적 아규먼트(Named Argument)를 이용한 가독성 향상

// functions/namedarguments.kts
createPerson("Jake", 12, 152, 43)

이 숫자들이 무엇을 의미하는 것일까? 추론하기는 쉽지 않다. 의미를 파악하기 위해 흐름을 끊고 함수에 대한 문서나 함수 정의 부분을 봐야 한다.

// functions/namedarguments.kts
fun createPerson(name: String, age: Int = 1, height: Int, weight: Int) {
    println("$name $age $height $weight")
}

여기서 명시적 아규먼트가 나올 차례이다.

// functions/namedarguments.kts
createPerson(name = "Jake", age = 12, weight = 152, height = 43)

훨씬 낫다. 파라미터들이 어떤 의미인지 추측할 필요가 없다. 또한, 순서와 상관 없이 사용할 수 있다.

위치에 기반해서 값만 전달한 아규먼트를 일부 사용할 수도 있다.

// functions/namedarguments.kts
createPerson("Jake", age = 12, weight = 152, height = 43)

3-3. 다중인자와 스프레드

println() 같은 함수는 여러 개의 인자를 받는다. 코틀린의 다중인자(vararg) 기능은 함수가 한 번에 여러 개의 인자를 받을 때 타입 안정성을 제공하는 기능이다. 스프레드 연산자는 컬렉션의 값을 개별 값으로 분해하거나 나열할 때 유용한다.

여러 개의 인자

코틀린 함수들은 많은 수의 인자를 받을 수 있다.

// functions/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
}
  • 파라미터 numbers가 vararg라는 키워드로 선언되었다.

  • 파라미터 타입이 IntArray에서 Int로 변경되었다.

vararg 키워드가 파라미터로 특정 타입의 배열이 들어갈 수 있다는 것을 알려준 것이다.

// functions/vararg.kts
println(max(1, 5, 2)) // 5
println(max(1, 5, 2, 12, 7, 3)) // 12

단, vararg 키워드는 함수에서 하나의 파라미터에서만 사용할 수 있다.

// functions/mixvararg.kts
function greetMany(msg: String, vararg names: String) {
    println("$msg ${names.joinToString(", ")}")
}
greetMany("Hello", "Tom", "Jerry", "Spike") // Hello Tom, Jerry, Spike

첫 번째 인자만 첫 번째 파라미터에 할당되고, 나머지 인자들은 vararg 파라미터로 전달된다. 함수를 정의할 때 vararg 파라미터를 반드시 마지막 파라미터에 넣을 필요는 없지만 그렇게 하기를 강력하게 추천한다. vararg를 마지막에 사용하지 않는다면, 함수를 호출할 때 반드시 명시적 인자를 사용해야 한다.

함수에 전달할 인자가 많을 때 쉽게 처리할 수 있는 기능을 확인했다. 그런데 이미 배열이 있다면 어떻게 할까?

스프레드 연산자

때때로 우리는 배열이나 리스트에 있는 값들을 vararg 인자로 함수에 전달해야 하는 경우가 있다. 이럴 때 스프레드 연산자가 필요하다.

// functions/vararg.kts
val values = intArrayOf(1, 21, 3)

vararg는 하나의 파라미터에 여러 개의 인자를 넘길 수 있다는 뜻을 함축하고 있지만, 우리가 해당 파라미터에 배열을 넘기면 오류가 난다.

println(max(values)) // ERROR
// type mismatch: inferred type is IntArray but Int was expected

배열에 있는 값을 넘기기 위해 아래처럼 입력한다.

// functions/vararg.kts
println(max(values[0], values[1], values[2]))

하지만 이런 방법은 너무 장황하다.

파라미터가 vararg라고 작성되어있는 경우, 우리는 스프레드 연산자 *을 이용해서 배열을 넘길 수 있다(당연히 같은 타입이어야 한다).

// functions/vararg.kts
println(max(*values)) // 21

배열이 있으면 스프레드를 사용할 수 있지만 보통은 배열보다 리스트를 많이 사용한다. 그러나 리스트에 속한 값을 전달하고 싶을 때 리스트에 직접 스프레드 연산자를 적용할 수 없다. 대신 리스트를 배열로 변환하면 적용할 수 있다. 예제를 통해 확인해본다.

// functions/vararg.kts
println(max(*listOf(1, 4, 8, 12).toIntArray()))

리스트 안의 요소의 타입과 vararg의 타입이 Int가 아니라면 List<T>의 메소드인 to...Array() 메소드를 사용해서 적절한 타입의 배열로 변환한 후 사용하면 된다.

// litsynp/functions/myvararg.kts
data class Person(val name: String, val age: Int? = null)

fun printNames(vararg persons: Person) {
	persons.forEach { println(it.name) }
}

val persons = listOf(Person("Frank", 20), Person("Tom", 20), Person("Jerry", 20))
printNames(*persons.toTypedArray())

3-4. 구조분해

구조화란 다른 변수의 값으로 객체를 만드는 것이다. 구조분해(Destructuring)는 그 반대다. 이미 존재하는 객체에서 값을 추출해 변수로 넣는 작업이다. 이런 작업은 방해요소와 반복되는 코드를 제거하는 데 유용하다. JavaScript와 유사하지만, 코틀린의 구조분해는 속성의 이름이 아닌 속성의 위치를 기반으로 진행된다.

// functions/destructuring.kts
fun getFullName() = Triple("John", "Quincy", "Adams")

val result = getFullName()
val first = result.first
val second = result.second
val last = result.third
println("$first $middle $last") // John Quincy Adams

함수의 리턴 타입이 Pair나 Triple, 다른 데이터 클래스인 경우 구조분해로 값을 추출해 우아하고 명확하게 값을 변수에 할당할 수 있다. 구조분해를 사용해보자.

// functions/destructuring.kts
val (first, second, last) = getFullName()
println("$first $middle $last") // John Quincy Adams

네 줄의 코드를 명확한 한 줄의 코드로 바꿨다. 이게 가능한 이유는 Triple 클래스가 구조분해를 위한 특별한 메소드를 지니고 있기 때문이다. 객체의 속성들이 구조분해되는 순서는 객체의 생성자가 객체를 초기화할 때 속성을 만드는 순서와 동일하다.

리턴된 객체 중 속성 하나가 필요없다면 언더스코어(_)를 사용해 스킵할 수 있다.

// functions/destructuring.kts
val (first, _, last) = getFullName()
println("$first $last") // John Adams

// val (_, _, last) = getFullName() 도 가능하다.
// val (_, middle) = getFullName() 도 가능하다.

리턴 타입이 데이터 클래스인 경우 구조분해를 사용할 때는 구조분해를 Map 자료구조의 key와 value를 추출해 내는 용도로 사용할 수도 있다.

정리

  • 코틀린은 사용자가 메소드를 만들도록 강요하지 않는다. 코틀린에서는 개발자가 최상위 함수(top-level functions)도 만들 수 있다. 이런 점이 코틀린으로 개발을 할 때 Java에 비해 좀 더 많은 디자인 선택사항을 제공한다.

  • 애플리케이션이 반드시 객체로 이루어질 필요가 없다. 애플리케이션을 함수로 구성 가능하다. 이런 점들은 개발자가 절차적, 객체지향적, 함수형 코드 중 아무거나 상황에 맞게 선택이 가능하도록 한다.

  • 컴파일러는 단일 표현식이면서 블록이 없는 함수의 경우 리턴타입을 추론해준다. 파라미터를 정의할 땐 항상 타입이 필요하다.

  • 코틀린의 기본인자 기능은 함수를 확장하기 쉽게 해준다. 그리고 함수를 오버로드하는 일을 줄여준다.

  • vararg는 타입 안정성을 제공하면서 여러 개의 인자를 넘기는 것을 아주 유연하게 해준다.

  • 스프레드 연산자는 vararg 파라미터에 배열을 넘기는 것을 쉽게 만들어준다.

  • 명시적 인자를 사용하는 것은 코드의 가독성을 높여준다. 명시적 인자를 사용한다면 코드 자체가 문서화된다.

  • 구조분해는 코드의 방해요소를 줄여주고, 코드를 매우 간결하게 만들어 준다.

PreviousCh 02. Java 개발자를 위한 코틀린 필수 사항NextCh 05. 콜렉션 사용하기

Last updated 2 years ago

Was this helpful?

에서 프로그래머는 final과 immutable을 가능한 많이 사용해야 한다고 조언한다. 코틀린은 우리가 함수나 메소드 파라미터를 만들 때 immutable, mutable을 선택하는 것을 원치 않는다. 코틀린은 함수로 전달된 파라미터를 변경하는 것은 나쁜 생각이란 결론을 내렸기 때문에, 우리는 파라미터를 val이나 var로 단정지을 수 없다(하지만 굳이 따지자면 val이라고 할 수 있겠다). 그리고 함수, 메소드에서 파라미터의 값을 변화시키려 하면 컴파일 오류를 발생시킨다.

<이펙티브 자바 3/E>(인사이트, 2018)