[Kotlin] 코틀린 심화 문법

donghyeok·2023년 4월 9일

Kotlin

목록 보기
3/3

1. 컬렉션

1. 컬렉션 타입

  • 코틀린 표준 라이브러리는 기본 컬렉션 타입인 List, Set, Map을 제공한다.
  • 컬렉션은 두종류로 나뉜다.
    - 불변 컬렉션(Immutable) : 읽기 전용
    - 가변 컬렉션(Mutable) : 쓰기 가능한 컬렉션

2. 컬렉션 생성

  • 가장 일반적인 방법은 표준 라이브러리 함수를 사용하는 것이다.
    //컬렉션 생성 
     var immutableList : List<String> = listOf("사과", "딸기") //immutable List
     var mutableList = mutableListOf<String>() //mutable List
     var immutableSet = setOf(1,2,3,4) //immutable list
     var mutableList = mutableSetOf<Int>() //mutable set
     var immutableMap = mapOf("one" to 1, "two" to 2) //immutable map
     var mutableMap = mutableMapOf<String, Int>() // mutable map
     // 값 추가 
     mutableList.add("수박")
     mutableSet.add(5)
     mutableMap["three"] = 3
  • apply 함수를 이용해 가독성 좋은 값 추가 가능하다.
    mutableList.apply{
    	add("배")
    	add("참외")
    }
    
  • 컬렉션 빌더를 사용하여 컬렉션 생성 가능
  • 빌더 내부에선 Mutable이고 반환시에는 Immutable 컬렉션 반환
    val numberList: List<Int> = buildList {
    	add(1)
    	add(2)
    	add(3)
    }	
    

3. 컬렉션 반복하기

  • 코틀린의 컬렉션은 Iterable의 구현체이므로 순차적 반복이 가능하다.

  • 앞서 학습한 for loop를 사용하면 암시적으로 이터레이터 사용한다.

    val iterator = list.iterator()
    while(iterator.hasNext()) {
    	println(iterator.next())
    }
    
    for (current in list) {
    	println(current)
    }
    
  • 또한 코틀린 표준 라이브러리는 컬렌션 사용시 자주 사용되는 패턴인 forEach, map, filter와 같은 유용한 인라인 함수를 제공한다.

    tempList.forEach { println(it) }   //forEach
    var upperList = tempList.map {it.uppercase() } //map
    var filterList = tempList.filter { it == 'A' || it == 'C' } //filter
    
  • 위에서 처럼 자바8은 중간 연산자(map,filter 등)만 사용했을때 아무 동작도 하지 않고 최종 연산자(terminal operator)를 사용해야 값을 얻을 수 있다.

  • 코틀린에서는 인라인 함수가 동작할때마다 조건에 맞는 컬렉션을 생성한다.

  • 하지만 대량의 데이터를 처리할 때는 컬렉션이 새로 생성되는 것이 부담이다.

  • 이를 위해 코틀린에서는 아래와 같이 시퀀스 API를 지원하는데 벤치마크상 일반적으로 인라인 함수가 빠르고 대량일 경우 시퀀스 API를 사용해야 한다.

    var filteredList = upperList
    			.asSequence()
             	.filter {it == 'A' || it == 'C' } 
             	.toList()

3. 데이터 클래스

1. 데이터 클래스란

  • 데이터를 보관하거나 전달하는 목적을 가진 객체를 만들때 사용한다. ex) DTO
    data class Person(val name: String, val age: Int)
  • 데이터 클래스를 사용하면 컴파일러가 자동으로 다음 함수들을 만들어준다.
    - equals()
    - hashCode()
    - toString()
    - conponentN(), copy()
  • 자바에선 주로 Lombok을 사용
  • JDK15에서는 record라는 이름으로 추가됨

2. 데이터 클래스가 필요한 이유

  • 데이터 클래스는 자동으로 toString, equals, hashCode, copy, componentN을 정의한다.
  • equals의 경우 재정의를 하지 않을 경우 객체 동등 비교 불가하다.
  • hashCode의 경우 재정의를 하지 않을 경우 Hash 자료구조에서 정상 동작하지 않는다.
  • copy의 경우 재정의 하지 않을 경우 객체의 불변성을 유지하기 힘들다 (객체를 수정하는 것보다 새로운 객체로 복사해서 사용하는 것이 불변성 유지에 좋음)
    - 불변성이 깨지면 Hash 자료구조에서 버그 발생 가능
    - 멀티 스레드 환경에서 동기화처리를 줄여주고 안정성 높여줌
    - 유지보수 관점에서 불변성이 유지되는 것이 코드 파악 쉽게 해줌
  • compnentN의 경우 데이터 클래스에서 정의된 프로퍼티를 순서대로 가져올 수 있다.
  • 구조분해할당을 사용해 좀 더 쉽고 안전하게 변수를 선언할 수 있다.
    val person = Person(name="tony", age=12)
    println("이름=${person.component1()}, 나이=${person.component2()}")
    val (name, age) = person
    println("이름=${name}, 나이=${age}")
    

4. 싱글톤과 동반객체

1. 싱글톤 패턴

  • 싱글톤 패턴은 클래스의 인스턴스를 하나의 단일 인스턴스로 제한하는 디자인 패턴이다.

  • 싱글톤 패턴을 구현할때는 몇가지 제약사항을 통해 구현한다.
    - 직접 인스턴스화 하지 못하도록 생성자를 private로 숨긴다.
    - getInstance()라는 클래스의 단일 인스턴스를 반환하는 static 메서드를 제공한다.
    - 멀티-스레드 환경에서도 안전하게유일한 인스턴스를 반환해야한다.

  • 자바에서 많이 쓰이는 구현 방식

    //이른 초기화 
    public class Java_Singleton {
    	private static final Java_SingleTon INSTANCE = new Java_Singleton();
       
       private Java_Singleton() {
       	//do nothing
       }
       
       public Java_SingleTon getInstance() {
       	return INSTANCE;
       }
    }
    
    //지연 초기화 
    public class Java_Singleton {
    	private Java_Singleton() {
       	//do nothing
       }
       
       public Java_Singleton getInstance() {
       	return LazyHolder.INSTANCE;
       }
       
       private static class LazyHolder {
       	private static final Java_Singleton INSTANCE = new Java_Singleton();
       }
    }	

2. 코틀린의 싱글톤

  • 코틀린은 언어에서 객체 선언을 통해 싱글톤을 기본 지원한다.

  • 객체 선언은 object 키워드를 사용한다.

    object Singleton { }
  • 함수나 변수를 사용할때는 클래스 한정자를 사용한다. (클래스명.함수명)

    object Singleton {
    	val a = 1234
        fun printA() = println(a)
    }
    
    fun main() {
    	println(Singleton.a)
        Singleton.printA()
    }
  • 객체 선언을 사용하면 자바의 static 유틸리티를 대신해 쉽게 싱글톤 기반의 유틸리티를 만들 수 있다.

3. 동반객체

  • companion 키워드를 사용해 클래스 내부에 객체 선언을 사용할 수 있다.

    class MyClass {
    	companion object { }
    }
  • 동반객체의 멤버는 object로 선언한 객체와 마찬가지로 클래스 한정자를 사용해 호출할 수 있다.

    class MyClass {
    	private constructor()
       
       companion object {
       	val a = 1234
           fun newInstance() = MyClass()
       }
    }
    
    fun main() {
    	println(MyClass.a)
       println(MyClass.newInstance())
       
       //이렇게도 호출가능 
       println(MyClass.Companion.a)
       println(MyClass.Companion.newInstance())
    }

5. 실드 클래스 (봉인)

1. 실드 클래스를 쓰는 이유

  • 실드 클래스의 하위 클래스는 같은 파일 내에서만 정의가능

  • 하나의 상위 클래스 또는 인터페이스에서 하위 클래스의 정의를 제한할 수 있는 방법

  • 실드 클래스는 하위 클래스를 제한 조건에 따라 정의해야하고 이렇게 만들어진 실드 클래스의 하위 클래스는 컴파일 시점에 판단할 수 있다

    sealed class Developer {
    	abstract val name: String
       abstract fun code(language: String)
    }
    
    data class BackendDeveloper : Developer() { //구현 생략 }
    data class FrontendDeveloper : Developer() { //구현 생략 } 
    
    object DeveloperPool() {
    	val pool = mutableMapOf<String, Developer>() 
       
       //sealed 정의가 아니라 일반 클래스였다면 
       //아래 함수에서 when의 else 구문이 없어서 컴파일 에러!!
       //sealed 정의 덕분에 하위 클래스가 2종인 걸 컴파일러가 인지해서 컴파일 오류 발생X
       fun add(developer : Developer) = when (developer) {
       	   is BackendDeveloper -> pool[developer:name] = developer
           is frontendDeveloper -> pool[developer:name] = developer 
       }
    }

2. 활용 예시

  • 위 코드 예시에서 Developer 하위 클래스가 추가될 경우 sealed 클래스가 아니라면 else 구문으로 인해 컴파일 오류 발생X ->예상치 못한 버그 발생
  • sealed 클래스로 정의되었다면 하위 클래스가 추가될 경우 컴파일 오류가 발생하여 예상치 못한 버그를 인지하고 제거할 수 있다.

6. 확장 함수

1. 확장 함수란

  • 코틀린은 클래스를 상속하거나 데코레이터 패턴과 같은 디자인 패턴을 사용하지 않고도 클래스를 확장할 수 있는 기능을 제공한다.
  • 예를 들어 일반적으로 수정할 수 없는 코틀린의 표준 라이브러리에 기능을 추가하기 위해 확장을 사용할 수 있다.
  • 확장 함수 내부의 this는 확장의 대상이 되는 객체의 참조이다. (receiver혹은 수신자 객체)
    fun String.first(): Char { return this[0] }
    fun String.addFirst(char: Char): String { return char + this.substring(0) } 
    

2. 자바로 변환된 확장 함수

  • 확장 함수는 실제론 대상 객체를 수정하지 않고 자바 내부적으로 static 메서드를 만든다.
  • 첫번째 인자로 확장 대상 객체를 사용한다.
    public final class Example {
    	public static final char first(@NotNull String $this) {
       		return $this.charAt(0);
       }
    }

3. 멤버와 중복될 경우

  • 확장하려는 클래스에 동일한 명칭의 함수가 존재할 경우 클래스의 멤버 함수가 우선된다.
  • 함수의 시그니처(리턴값, 인자)가 다른 경우는 문제없이 확장 기능 사용 가능

7. 제네릭

1. 코틀린의 제네릭

  • 코틀린의 클래스는 자바와 마찬가지로 타입 파라미터를 가질 수 있다
  • 제네릭을 사용한 클래스의 인스턴스를 만들려면 타입 아규먼트를 제공하면 된다.
  • 또한 인자를 통해 타입 추론이 가능한 경우 타입 아규먼트 생략 가능
  • 어떤 타입이 들어올지 알 수 없지만 안전하게 사용하고 싶은 경우 스타 프로젝션 구문을 제공한다.
    class MyGenerics<T> (val t: T)
    val generic1 = MyGenerics<String>("test") //인스턴스 생성
    val generic2 = MyGenerics("test") //타입 아규먼트 생략
    val generic3: MyGeneric<*> = MyGenetrics<String>("test") //스타 프로젝션 
    

2. 변성

  • 제네릭에서 파라미터화된 타입이 서로 어떤 관계에 있는지 설명하는 개념

  • 변성은 크게 공변성, 반공변성, 무공변성으로 나뉜다.

  • 이펙티브 자바에선 공변성과 반공변성을 설명할때 PECS 규칙을 언급
    - Producer - Extends (코틀린 out)
    - Consumer - Super (코틀린 in)

  • 공변성 예시
    - 아래 예시에서 CharSequenceString의 상위 타입일 때 MyGenerics<CharSequence>MyGenerics<String>의 상위 타입으로 만들어 공변성이다.

    class MyGenerics<out T>(val t: T)  //공변성을 위해 out 사용 
    fun main() {
    	val generics = MyGenerics("test")
       val charGenerics : MyGenerics<CharSequence> = generics //out이 없으면 에러 발생 
    }
  • 반공변성 예시
    - 아래 예시에서 CharSequenceString의 상위 타입일 때 Bag<String>Bag<CharSequence>의 상위 타입이므로 반공변성이다.

    class Bag<T> {
    	fun saveAll (to: MutableList<in T>, from: MutableList<T>) { //반공변성을 위해 in 사용 
       		to.addAll(from) 
       }
    }
    
    fun main() {
    	val bag = Bag<String>()
       //in이 없으면 에러 발생 
       bag.savaAll(mutableListOf<CharSequence>(""), mutableListOf<String>(""))
    }
  • 위 예시들에서 in, out 어떠한 것도 지정하지 않은 경우 StringCharSequece의 하위 타입이지만 MyGenerics<String>MyGenerics<CharSequence>는 아무 관계도 아니므로 무공변성이다.

8. 지연 초기화

1. 지연초기화란

  • 지연초기화는 대상에 대한 초기화를 미뤘다가 실제 사용시점에 초기화하는 기법을 말한다.
  • 초기화 과정에서 자원을 많이 쓰거나 오버헤드가 발생할 경우 지연초기화를 사용하는게 유리하다
  • 지연초기화는 다음과 같은 상황에서 쓰일 수 있다
    - 웹페이지의 무한 스크롤
    - 싱글톤 패턴의 지연초기화
    - JPA 엔티티 LazyLoading rlsmd
  • 코틀린은 두가지 다른 방식의 지연초기화를 제공한다.

2. by Lazy

  • 변수를 선언한 시점엔 초기화하지 않다가 특정 시점에 초기화가 필요할때

  • 사용 시점에 1회만 초기화 로직이 동작한다.

  • 멀티 스레드 환경에서도 안전하게 동작한다. (by Lazy(LazyThreadSafetyMode.SYNCHRONIZED))

  • LazyThreadSafetyMode.NONE으로 설정하면 멀티 스레드 환경에서 동기화X

    class HelloBot {
    	val greeting: String by Lazy { getHello() } //지연 초기화
       fun sayHello() = println(greeting)
    }
    
    fun getHello() = "안녕하세요"
    
    fun main() {
    	val helloBot = HelloBot()
      	//..
       helloBot.sayHello()
    }

3. lateinit

  • 가변 프로퍼티에 대한 지연초기화가 필요한 경우

  • 특정 프레임워크나 라이브러리에서 지연초기화가 필요한 경우

  • lateinit을 사용할 경우에는 가변 변수이며 non-null이어야 한다.

  • 초기화 여부를 파악하고 사용하려면 isInitialized 프로퍼티 사용 (내부에서만 사용 가능)

    class lateinitTest {
    	lateinit var text: String 
       
       fun printText() {
       	if (this::text.isInitialized) {
           	println("초기화됨")
               println(text)
        } else {
           	text = "안녕하세요"
       		println(text)
        }
       }
    }
    
    fun main() {
    	val test = lateinitTest()
       test.printText()
    }
    

9. 페어와 구조분해할당

1. 페어, 트리플

  • 코틀린은 페어를 통해 2개의 요소가 있는 튜플을 기본 제공한다.
  • 첫번째인자는 first, 두번째인자는 second로 사용할 수 있다.
  • 페어는 불변이라 copy 함수를 통해 복사를 통해 사용 가능하다
    fun plus(pair: Pair<Int, Int>) = pair.first + pair.second
    val newPair = Pair("A", 1).copy(first = "B") 
  • 마찬가지로 트리플을 통해 3개의 요소가 있는 튜플을 기본 제공한다.
  • 각 인자는 first, second, third를 사용할 수 있다.

2. 구조분해할당

  • 구조분해할당을 사용하면 값을 분해해서 한번에 여러 변수를 초기화할 수 있다.
    val newTriple = Triple("A", "B", "C")
    val (a, b, c) = newTriple
    println("$a, $b, $c")     // A, B, C
  • 구조분해할당은 컴파일러 내부에서 componentN 함수를 사용한다.
  • 배열 또는 리스트에서도 구조분해할당을 사용할 수 있다.
  • 하지만 componentN은 앞선 5개 요소에 대해서만 제공한다.
  • map을 초기화할 때 사용하는 to는 내부적으로 페어를 사용한다.
    val map = mutableMapOf("test" to "개발자")
    for ( (key, value) in map) {
    	println("${key}의 직업은 ${value}")
    }

10. 스코프 함수

0. 스코프 함수란

  • 코틀린의 표준 라이브러리에는 객체의 컨텍스트 내에서 코드 블록을 실행하기 위해서만 존재하는 몇가지 함수가 포함되어 있는데 이를 스코프 함수라고 부른다.
  • 스코프 함수의 코드 블록 내부에서는 변수명을 사용하지 않고 객체에 접근할 수 있는데 그 이유는 수신자 객체(receiver)에 접근할 수 있기 때문이다.
  • 수신자 객체는 람다식 내부에서 사용할 수 있는 객체의 참조이다. (this, it)
  • 코틀린은 총 5개의 스코프 함수를 제공하며 각 스코프 함수들은 본질적으로 유사한 기능을 제공한다.

1. let

  • null이 아닌 경우 사용될 로직을 작성하고 새로운 결과를 반환하고 싶을 때
fun main() {
	val str: String? = "안녕" 
    val result = str?.let {
    	println(it)
    }
    println(result)           //let 함수 마지막 코드가 결과로 반환 
}

2. run

  • 수신 객체의 프로퍼티를 구성하거나 새로운 결과를 반환하고 싶을 때
class DatabaseClient {
	val url: String? = null
    var username: String? = null
    var pw: String? = null
    
    fun connect(): Boolean {
    	println("접속중...")
        println("접속 완료")
        return true
    }
}

fun main() {
	var connected = DatabaseClient().run {
    	url = "localhost:3306"
        username = "mysql"
        pw = "1234"
        connect()
    }
    println(connected)     //true
}

3. with

  • 결과 반환없이 내부에서 수신 객체를 이용해 다른 함수를 호출하고 싶을 때 사용
  • with는 확장함수가 아니다
val str = "안녕"
val length = with(str) {
	length
}
println(length)  //2

4. apply

  • 수신 객체의 프로퍼티를 구성하고 수신 객체를 그대로 결과로 반환하고 싶을 때 사용
  • 앞선 스코프 함수는 함수의 결과가 반환타입인데 apply는 수신 객체가 반환타입
val client: DatabaseClient = DatabaseClient().apply {
	url = "localhost:3306"
    username = "mysql"
    pw = "1234"
    connect()
}

5. alse

  • 부수 작업을 수행하고 전달받은 수신 객체를 그대로 결과로 반환하고 싶을 때
class User(val name: String, val pw: String) {
	fun validate() {
    	if (name.isNotEmpty() && pw.isNotEmpty()) {
        	println("검증 성공")
        } else {
        	println("검증 실패")
        }
    }
}

fun main() {
	User(name= "tony", pw= "1234).also {
    	it.validate()
    }
}

11. 고급 예외처리

1. use를 사용한 리소스 해제

  • Java7 부터 제공하는 try-with-resources 구문을 사용하면 자동으로 리소스를 close 처리해준다.
  • 정확히는 Closeable, Autocloseable 인터페이스의 구현체에 대해 자동으로 close 메서드를 호출한다.
    try (PrintWriter writer = new PrintWriter("test.txt")) {
    	writer.println("Hello world");
    } catch (FileNotFoundException e) {
    	System.out.println(e.getMessage());
    }
  • 코틀린에서는 이대신 use라는 확장 함수를 제공한다.
    FileWriter("test.txt")
    			.use { it.write("Hello Kotlin") }

2. runCatching을 사용해 예외처리하기

  • 코틀린은 try-catch를 통한 예외처리외에도 함수형 스타일의 Result 패턴을 구현한 runCatching을 제공한다.

  • Result 패턴이란 함수가 성공하면 캡슐화된 결과를 반환하거나 예외가 발생하면 지정한 작업을 수행하는 패턴이다.

  • runCatching 사용

    val result = runCatching { getStr() }
    					.getOrElse {
                       	println(it.message) 
                           "기본값"
                       }
    //getStr()에서 예외 발생하여 result = "기본값"으로 초기화 
  • runCatching은 결과에 따라 Result.success(), Result.failure()를 호출하는데 해당 메서드들은 Result 내부 동반 객체에서 성공 상태와 실패 상태에 따라 Result 객체를 생성함.

  • Result 객체에는 다양한 기능들이 존재한다.
    - getOrNull : 실패인 경우 null 반환
    - exceptionOrNull : 성공인 경우 null, 실패인 경우 Throwable 반환
    - getOrDefault : 성공시엔 성공값을 반환하고 실패시엔 지정한 기본값을 반환
    - getOrElse : 실패시 수신자 객체로 Throwable을 전달받고 let, run과 같이 함수의 결과를 반환
    - getOrThrow : 성공시엔 값을 반환 실패시엔 예외를 발생시킨다.
    - map : 성공인 경우 원하는 값으로 변경할 수 있다. (map 내부 예외 재처리 불가)
    - mapCatching : map처럼 성공인 경우 원하는 값으로 변경할 수 있다. 예외가 발생하면 재처리 가능
    - recover : map은 성공시 원하는 값으로 변경하지만 recover는 실패시에 원하는 값으로 변경할 수 있다. (recover 내부 예외 재처리 불가)
    - recoverCatching : recover처럼 실패시 원하는 값으로 변경할 수 있다. 예외가 발생하면 재처리 가능

    //1. getOrNull() 
    val result = runCatching { //에러 발생  }
    		.getOrNull()
    println(result)     //null 반환 
    
    //2. exceptionOrNull() 
    val result = runCatching { //에러 발생  }
    		.exceptionOrNull()
    println(result)     //Throwable 반환 
    
    //3. getOrDefault() 
    val result = runCatching { //에러 발생  }
    		.getOrDefault("기본값")
    println(result)     //"기본값" 반환 
    
    //4. getOrElse 
    val result = runCatching { //에러 발생  }
    		.getOrElse {
           	println(it.message)
               "기본값"
           }
    println(result)     //"기본값" 반환 
    
    //5. getOrThrow() 
    val result = runCatching { //에러 발생  }
    		.getOrThrow()
    println(result)     //예외 발생 
    
    //6. map  
    val result = runCatching { "안녕"  }
    		.map{ 
           	it + "하세요"
           }.getOrThrow()
    println(result)     //안녕하세요 
    
    //7. mapCatching 
    val result = runCatching { "안녕"  }
    		.map{ 
           	//예외 발생 
           }.getOrDefault("기본값")
    println(result)     //"기본값"  
    
    //8. recover 
    val result = runCatching { //예외발생 }
    		.recover{ 
           	"복구" 
           }.getOrNull()
    println(result)     //"복구" 
    
    //9. recoverCatching 
    val result = runCatching { //예외발생  }
    		.recoverCatching{ 
           	//예외 발생 
           }.getOrDefault("복구")
    println(result)     //"복구"  

12. 람다로 프로그래밍하기

1. 함수형 프로그래밍

  • 함수형 프로그래밍 (FP : Functional-Programming)은 수학의 함수적 개념을 참고해 만든 패러다임의 하나로 깔끔하고 유지보수가 용이한 소프트웨어를 만들기 위해 함수를 사용한다.
  • 함수형 패러다임은 부수효과가 없고 똑같은 Input이 들어오면 항상 동일한 output을 내놓는 순수함수의 개념을 기반으로 람다, 고차함수, 커리, 메모이제이션, 모나드 등의 개념을 포함한다.

2. 코틀린과 함수형 프로그래밍

함수를 값으로 사용하기
  • 함수형 프로그래밍에서 함수는 1급 객체로 분류된다.
  • 1급 객체란 함수를 인자에 넘기거나 변수에 대입하거나 함수를 반환하는 개념 등을 통틀어 말한다.
  • 코틀린에서는 함수도 타입이다. () -> Unit 화살표 포기법을 사용한다.
  • 함수는 값이 될 수 있고 역으로 값은 함수가 될 수 있다 그러므로 함수에 인자로 넘기거나 데이터 구조에 저장할 수 있다.
  • fun으로 선언된 함수는 값으로 다룰 수 없다.
    val printHello: () -> Unit = { println("Hello") } 
    val list = mutableListOf(printHello) //데이터 구조에 함수 저장
    list[0]()                            //함수 호출 
  • 고차 함수 (HOF : Higher-Order Function)은 함수를 인자로 받거나 결과로 리턴하는 함수를 말한다.
  • 컬렉션의 filter, map, forEach 등도 함수를 인자로 받아서 결과를 반환하므로 고차함수이다.
익명함수와 람다식
  • 함수형 프로그래밍에선 이름 없는 무명 함수를 익명함수라고 한다.
  • 익명함수를 람다 표현식으로 바꿀 수 있다.
    fun outerFunc(): () -> Unit {
    	return fun() {
       	println("이것은 익명함수")
       }
    }
    outerFunc()(    //"이것은 익명함수" 출력 
    //람다 표현식
    fun outerFunc(): () -> Unit = { println("이것은 람다함수") } 
  • 함수의 마지막 인자가 함수인 경우 후행 람다 전달을 사용할 수 있다. (filter, map, forEach 사용 방식)
    fun forEachStr(collection: Collection<String>, action: (String) -> Unit) {
    	for (item in collection) {
       	action(item)
       }
    }	
    val list = listOf("a", "b", "c")
    forEachStr(list) {         //후행 람다 전달 
    	println(it) 
    }	
람다 레퍼런스 사용하기
  • 람다 레퍼런스를 사용하면 좀 더 가독성 좋게 함수를 인자로 넘길 수 있다.

    //일반 함수값 스타일
    val callReference: () -> Unit = { printHello() } 
    callReference()
    
    val numberList = listOf("1", "2", "3")
    numberList.map{ it.toInt() }.forEach{ println(it) } 
    val callReference = ::printHello   //탑 레벨 함수는 ::함수명 사용
    callReference()
    
    val numberList = listOf("1", "2", "3")
    numberList.map(String::toInt).forEach(::println)

0개의 댓글