일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- Python
- 자바 ORM 표준 JPA 프로그래밍 7장
- 13460 구슬탈출 2
- 코틀린
- Kotlin
- 코틀린인액션
- Kotlin In Action
- Kotlin in action 5장
- 기능개발 python
- 스프링 컨테이너와 스프링 빈
- 20055 컨베이어 벨트 위의 로봇
- 컨베이어 벨트 위의 로봇 Python
- 객체 지향 설계와 스프링
- Kotlin in action 10장
- 스프링 핵심 원리 - 기본편
- Kotlin in action 3장
- 백준 13460 Python
- 코틸린인액션
- 20055
- 스프링 핵심 원리 이해
- 백준 20055 컨베이어 벨트 위의 로봇
- kotlin in action 정리
- KotlinInAction
- 싱글톤 컨테이너
- Kotlin in action 6장
- 백준
- spring
- 고급매핑
- 스프링 핵심 원리
- 7장 고급매핑
- Today
- Total
기록하는 습관
[Kotlin In Action]5장 - 람다로 프로그래밍 본문
** 이 글은 Kotlin In Action을 읽고 정리한 글입니다. **

1. 람다 식과 멤버 참조
1-3. 람다 식의 문법
{ x:Int, y:Int -> x + y }
- 코틀린 람다 식은 항상 중괄호로 둘러싸여 있다.
- 화살표(->)가 인자 목록과 람다 본문을 구분한다.
실행 시점에 코틀린 람다 호출에는 부가 비용이 들지 않으며, 프로그램의 기본 구성 요소와 비슷한 성능을 낸다.
1) 함수 호출 시 맨 마지막 인자가 람다식이라면 이를 괄호 밖으로 빼낼 수 있다.
people.maxBy() { p -> p.age }
2) 람다가 어떤 함수의 유일한 인자이고 괄호 뒤에 람다를 썼다면 호출 시 빈 괄호를 없애도 된다.
people.maxBy { p -> p.age }
3) 람다의 파라미터가 하나뿐이고 컴파일러가 타입을 추론할 수 있다면 it을 바로 쓸 수 있다.
people.maxBy { it.age }
- 람다를 변수에 저장할 때는 파라미터 타입을 추론할 문맥이 존재하지 않으므로, 파라미터 타입을 명시해야 한다.
1-4. 현재 영역에 있는 변수에 접근
람다가 포획한 변수
: 람다 안에서 사용하는 외부 변수를 람다가 포획(capture)한 변수라고 부른다.
closure
: 닫힌 객체 그래프와 람다 코드를 저장하는 데이터 구조. 람다를 실행 시점에 표현하는 데이터 구조는 람다에서 시작하는 모든 참조가 포함된 닫힌 객체 그래프를 람다 코드와 함께 저장해야 한다.
- 람다를 함수 안에서 정의하면 함수의 파라미터뿐 아니라 람다 정의의 앞에 선언된 로컬 변수까지 람다에서 모두 사용 가능
- final 변수 포획한 경우: 람다 코드를 변수 값과 함께 저장
- final이 아닌 변수를 포획한 경우: 특별한 래퍼로 감싸고 래퍼에 대한 참조를 람다 코드와 함께 저장한다.
자바와 다른 점
- final 변수가 아닌 변수에 접근할 수 있다.
- 람다 안에서 바깥의 변수를 변경해도 된다.
1-5. 멤버 참조
멤버 참조는 프로퍼티나 메서드를 단 하나만 호출하는 함수 값을 만들어준다.
::를 사용하는 식을 멤버 참조라고 부른다.
people.maxBy(Person::age)
최상위에 선언된 함수나 프로퍼티를 참조할 수도 있다. 이 때, 클래스를 생략하고 ::로 참조를 바로 시작한다.
fun salute() = println("Salute!")
fun main(args: Array<String>) {
run(::salute)
}
생성자 참조를 사용하면 클래스 생성 작업을 연기하거나 저장해둘 수 있다.
:: 뒤에[ 클래스 이름을 넣으면 생성자 참조를 만들 수 있다.
val createPerson = ::Person
val p = createPerson("Alice", 29)
println(p)
바운드 멤버 참조를 사용하면 멤버 참조를 생성할 때 클래스 인스턴스를 함께 저장한 다음 나중에 그 인스턴스에 대해 멤버를 호출해준다. 따라서 호출 시 수신 대상 객체를 별도로 지정해 줄 필요가 없다.
val p = Person("name", 34)
val personAgeFunction = Person::age
println(personAgeFunction(p))
val dmitrysAgeFunction = p::age // 바운드 멤버 참조
println(dmitrysAgeFunction())
2. 컬렉션 함수형 API
2-1. 필수적인 함수: filter와 map
- filter: 컬렉션을 이터레이션하면서 주어진 람다에 각 원소를 넘겨서 람다가 true를 반환하는 원소만 모은다.
- map: 주어진 람다를 컬렉션의 각 원소에 적용한 결과를 모아서 새 컬렉션을 만든다.
val list = listOf(1, 2, 3, 4)
println(list.filter { it % 2 == 0 })
val people = listOf(Person("Alice", 29), Person("Bob", 31))
println(people.map { it.name }) // people.map(Person::name)
필요 없는 반복 계산 줄이기
// 매 번 최댓값 연산을 실행
people.filter { it.age == people.maxBy(Person::age)!!.age }
// 한 번만 최댓값 연산 실행
val maxAge = people.maxBy(Person::age)!!.age
people.filter{ it.age == maxAge }
2-2. all, any, count, find: 컬렉션에 술어 적용
- all, any: 특정 조건이 모두 일치하는지 또는 일부 일치하는지 판단
- count: 조건을 만족하는 원소의 개수를 반환
- size를 사용하게 되면 조건을 만족하는 모든 원소가 들어가는 중간 컬렉션이 생기므로 count 사용 권장.
- find: 조건을 만족하는 첫 번째 원소를 반환. 없으면 null 반환 (firstOrNull 함수와 동일)
2-3. groupBy: 리스트를 여러 그룹으로 이뤄진 맵으로 변경
- 컬렉션의 원소를 구분하는 특성이 KEY이고, 키 value에 따른 각 그룹이 값인 map이다.
- 각 그룹은 List이다.
2-4. flatMap과 flatten: 중첩된 컬렉션 안의 원소 처리
flatMap 함수는 먼저 인자로 주어진 람다를 컬렉션의 모든 객체에 적용하고, 람다를 적용한 결과 얻어지는 여러 리스트를 한 리스트로 모은다.
val strings = listOf("abc", "def")
println(strings.flatMap { it.toList() }) // 1. toList 2. flatMap
3. 지연 계산(lazy) 컬렉션 연산
3-1. 시퀀스 연산 실행: 중간 연산과 최종 연산
시퀀스는 중간 임시 컬렉션을 사용하지 않고 컬렉션 연산을 연쇄할 수 있다.
- Sequence라는 인터페이스를 사용한다.
- 중간 컬렉션을 생성하지 않으므로 원소가 많은 경우 성능이 좋다.
- 지연 계산: 최종 연산이 호출될 때 계산이 수행된다.
- asSequence 확장함수를 호출하면 어떤 컬렉션이든 시퀀스로 바꿀 수 있다.
listOf(1, 2, 3, 4).asSequence() // 시퀀스로 변환
.map { print("map($it) "); it * it }
.filter { print("filter($it) "); it % 2 == 0 }
.toList() // 다시 리스트로 변환
시퀀스는 모든 연산은 각 원소에 대해 순차적으로 적용된다. 즉시 계산은 전체 컬렉션에 연산을 적용하지만 지연 계산은 원소를 한 번에 하나씩 처리한다. filter와 map 순서가 성능에 영향을 미친다.
참고) 자바 8에 stream과 개념이 같다. 병렬 기능을 사용하고 싶다면 스트림 연산을 사용해라.
3-2. 시퀀스 만들기
generateSequence 함수 사용
val naturalNumbers = generateSequence(0) { it + 1 }
val numbersTo100 = naturalNumbers.takeWhile { it <= 100 }
println(numbersTo100.sum())
sum() 최종 연산이 호출될 때 실행된다.
4. 자바 함수형 인터페이스 활용
코틀린에서 함수형 인터페이스를 인자로 받는 Java함수를 호출 할 경우 인터페이스 객체 대신 람다를 넘길 수 있다.
4-1. 자바 메서드에 람다를 인자로 전달
자바에서 메서드에 람다를 넘기기 위해 무명 클래스의 인스턴스를 만들어 넘기는 방식으로 사용했었지만 코틀린에서는 무명 클래스 인스턴스 대신에 람다를 넘기는 것이 가능하다.
4-2. SAM 생성자: 람다를 함수형 인터페이스로 명시적으로 변경
- SAM이란? Single abstract method - 단일 추상 메소드
- SAM 인터페이스란, 추상 메소드가 단 1개 있는 인터페이스로 '함수형 인터페이스' 라고도 한다.
- SAM 생성자란, 람다를 함수형 인터페이스의 인스턴스로 변환할 수 있게 컴파일러가 자동으로 생성한 함수.
// 1. 자바에서 interface 정의 후 kotlin에서 sam 생성자 사용
public interface SAM{
void a();
}
val test = SAM { println("Test") }
// 2. SAM interface에 b 함수 추가하면 컴파일 에러 발생
public interface SAM{
void a();
void b();
}
val test = SAM { println("Test") }
SAM 생성자 사용 예시
// 1. SAM 생성자 사용 X
val obejctInstance = object : View.OnClickListener{
override fun onClick(v: View?) {
println("onClick")
}
}
// 2. SAM 생성자 사용 O
val lambdaInstance = View.OnClickListener { println("onClick") }
5. 수신 객체 지정 람다: with와 apply
수신 객체 지정 람다란 수신 객체를 명시하지 않고 람다의 본문 안에서 다른 객체의 메서드를 호출하는 것을 말한다.
5-1. with 함수
with는 첫 번째 인자로 받은 객체를 두 번째 인자로 받은 람다의 수신 객체로 만든다.
fun <T, R> with(receiver: T, block: T.() -> R): R
T.()를 람다 리시버라고 하는데, 입력을 받으면 함수 내에서 this를 사용하지 않고도 입력받은 객체(receiver)의 속성을 변경할 수 있다.
val person = Person("Bob", 20)
with(person) {
println(name)
println(age)
}
- with(T) 타입으로 Person을 받으면 {} 내의 블럭 안에서 곧바로 name 이나 age 프로퍼티에 접근할 수 있다.
- with는 non-null의 객체를 사용하고 블럭의 return 값이 필요하지 않을 때 사용한다.
- 주로 객체의 함수를 여러 개 호출할 때 그룹화하는 용도로 활용됨.
5-2. apply 함수
apply는 객체를 만들면서 인스턴스를 초기화 하고 싶을 때 사용한다.
fun <T> T.apply(block: T.() -> Unit): T
T의 확장 함수이고, 블럭 함수의 입력을 람다 리시버로 받았기 때문에 블럭 안에서 객체의 프로퍼티를 호출할 때 it이나 this를 사용할 필요가 없다.
val person = Person("", 0)
val result = person.apply {
name = "Bob"
age = 20
}
println("$person")
- apply는 확장 함수로 정의 되어 있다.
- 항상 자신에게 전달된 객체를 반환하는 점이 with와 다르다.
'스터디 > Kotlin In Action' 카테고리의 다른 글
[Kotlin In Action]10장 - 애노테이션과 리플렉션 (0) | 2023.05.30 |
---|---|
[Kotlin In Action]6장 - 코틀린 타입 시스템 (0) | 2023.03.13 |
[Kotlin In Action]4장 - 클래스, 객체, 인터페이스 (0) | 2023.02.20 |
[Kotlin In Action]3장 - 함수 정의와 호출 (0) | 2023.02.06 |
[Kotlin In Action]2장 - 코틀린 기초 (0) | 2023.01.24 |