해당 내용은 이지스 퍼블리싱 카페에서 공부하며 올렸던 글을 다듬었습니다.
< 람다 함수와 고차 함수 >
[ 람다 함수 ]
- 익명 함수(anonymous function) 정의 기법
- 주로 함수를 간단하게 정의할 때 사용
- 고차 함수를 이해하고 사용하려면 꼭 알아야 함
- fun 키워드를 이용하지 않고, 이름도 없음
- 중괄호 { }로 표현
// 함수 선언 형식
fun 함수명(매개변수) { 함수 본문}
// 람다 함수 선언 형식
{ 매개변수 -> 함수 본문 }
[ 람다 함수 - 사용 규칙 ]
- 람다 함수는 { }로 표현
- { } 안에 화살표(->)가 있으며 화살표 왼쪽은 매개변수, 오른쪽은 함수 본문
{ 매개변수 -> 함수 본문 }
- 함수의 반환 값은 함수 본문의 마지막 표현식 (return문 사용 불가)
- 람다 함수 선언 방법 예제
// 일반 함수 선언
fun sum(no1: Int, no2: Int): Int {
return no1 + no2
}
// 람다 함수 선언
val sum = { no1: Int, no2: Int -> no1 + no2 }
- 람다 함수는 이름이 없어 함수명으로 호출할 수 없음
* 보통은 람다 함수를 변수에 대입해서 사용
// 람다 함수 호출문
sum(10, 20)
// 람다 함수 선언과 호출
{ no3: Int, no4: Int -> no3 + no4 } (10, 20))
- 매개변수가 없는 람다 함수
* 화살표까지 생략 가능
// 매개변수가 없는 람다 함수
{-> println("function call 1")}
{println("function call 2")} // 화살표 생략 가능!
- 매개변수가 1개인 람다 함수
* it 키워드 사용 시 매개 변수의 타입을 식별할 수 있을 때만 가능
* 함수 타입이 무조건 있어야 함
예) (Int) -> Unit = { println(it) }
// 매개변수가 1개인 람다 함수 => 일반적인 방법
val some = { no: Int -> println(no) }
some(10)
----- < 실행 결과 > -----
10
------------------------
// 매개변수가 1인 람다 함수에 it 키워드 사용
// (Int) -> Unit 은 함수 타입이라고 함
val some1: (Int) -> Unit = { println(it) }
some1(15)
----- < 실행 결과 > -----
10
------------------------
- 람다 함수의 반환값은 마지막 줄의 실행 결과
* return문 사용 불가
// 람다 함수의 반환문
val some2 = {no1: Int, no2: Int ->
println("in lambda function")
no1 * no2 // 반환값
}
println("result: ${some2(10, 20)}")
----- < 실행 결과 > -----
in lambda function
result: 200
------------------------
- 함수 타입과 고차 함수
* 변수는 타입을 가지며 타입을 유추할 수 있을 때를 제외하고는 생략 불가!
=> 변수를 함수 타입으로 선언해야 함
[ 함수 타입 ]
- 함수 타입이란?
: 함수를 선언할 때 나타내는 매개변수와 반환 타입을 의미
// 함수 타입을 이용해 함수를 변수에 대입
// ┌--- 함수 타입 --┐ ┌----------- 함수 내용 -----------┐
val some4: (Int, Int) -> Int = { no1: Int, no2: Int -> no1 + no2 }
- 타입 별칭(typealias) : 별칭을 선언하는 키워드. 함수 타입뿐만 아니라 데이터 타입을 선언할 때도 사용
* 변수보다 함수 타입을 선언하는데 주로 사용
// 타입 별칭 선언
typealias MyInt = Int
fun main() {
// 타입 별칭 사용
val data1: Int = 10
val data2: MyInt = 10
}
// 함수 타입 별칭
typealias MyFunType = (Int, Int) -> Boolean
fun main() {
val someFun: MyFunType = {no1: Int, no2: Int ->
no1 > no2
}
println(someFun(10, 20))
println(someFun(20, 10))
----- < 실행 결과 > -----
false
true
------------------------
- 매개변수의 타입을 유추할 수 있다면 타입 선언 생략 가능
* 타입 유추에 따른 타입 생략 기법은 타입이 유추 가능한 상황이면 어디서든 통함!!
// 매개변수 타입을 생략한 함수 선언
typealias MyFunType = (Int, Int) -> Boolean
val someFun2: MyFunType = { no1, no2 ->
no1 > no2
}
// 매개변수 타입 선언 생략 예
// typealias를 사용하지 않았으나 someFun에 매개변수 타입을 Int로 선언했으므로
// 람다 함수에서 매개변수의 타입을 생략할 수 있음
// ┌--------------------┐
val someFun1: (Int, Int) -> Boolean = {no1, no2 ->
no1 > no2
}
// 변수 선언 시 타입 생략
val someFun2 = {no1: Int, no2: Int ->
no1 > no2
}
[ 고차 함수 ]
- high order function
- 함수를 매개변수로 전달받거나 반환하는 함수
- 함수를 변수에 대입할 수 있기 때문에 사용 가능
// 고차 함수 예제
fun hofFun(arg: (Int) -> Boolean): () -> String {
val result = if (arg(10)) { // arg(10) 실제 값이 입력됨
"valid"
} else {
"invalid"
}
return { "hofFun result: $result" }
}
fun main() {
val result = hofFun({ no -> no > 0 })
println(result())
}
----- < 실행 결과 > -----
hofFun result: valid
------------------------
< 널 안전성 (null Safety) >
- 널 포인트 예외가 발생하지 않도록 코드를 작성하는 것!!!
- null은 객체가 선언되었지만 초기화되지 않은 상태 (객체가 주소를 가지지 못한 상태)
- 객체는 참조 변수
- 객체에는 주소가 저장되며 이 주소로 메모리에 접근해서 데이터를 이용
// 객체에 널 대입
val dataOne: String = "hello" // 실제로는 hello가 저장된 주소가 대입되며 그 주소로 문자열 데이터를 이용
val dataTwo: String? = null // 선언되었지만 이용할 수 없는 상태 (주솟값을 가지지 못한 상태)
- null인 상태의 객체를 이용하면 널 포인트 예외(NullPointException)가 발생!!
// 널 안정성을 개발자가 고려한 코드 => 이러면 널 안전성이 오롯이 개발자의 몫임
var data: String? = null
var length = if(data == null) {
0
} else {
data.length
}
// 코틀린이 제공하는 널 안전성 연산자를 이용한 코드
var data1: String? = null
println("data1 length: ${data1?.length ?: 0}")
- 코틀린이 널 안전성을 제공하는 이유
: 객체가 널인 상황에서 널 포인터 예외가 발생하지 않도록 연산자를 비롯해 여러 기법을 제공한다는 의미
[ 널 안전성 연산자 ]
- 널 허용 ? 연산자
* 널 허용과 널 불허로 구분
* 변수에 null을 대입할 수 있는지를 선언할 때 타입으로 구분
var data1: String = "kkang"
data1 = null // 오류! - 널 불허
// ↓ 널 허용으로 선언
var data2: String? = "kkang"
data2 = null // 성공! - 널 허용
- 널 안전성 호출 ?. 연산자
* ?. 연산자는 변수가 null이 아니면 멤버에 접근하지만,
null이면 멤버에 접근하지 않고 null을 반환함
// 널 안전성 호출 연산자 사용
// ↓ 물음표(?): 널 허용(nullable) 연산자
var data3: String? = "kkang"
var length1 = data3?.length // 성공!
// ↑ ?. 연산자(널 안전성 호출 연산자)는
// 변수가 null이 아니면 멤버에 접근,
// null이면 멤버에 접근하지 않고 null 반환
println(length1) // null이 아니라서 멤버에 접근
// └> 출력 결과
// null이면 null 출력
// null이 아니면 length 출력
- 엘비스 ?: 연산자
* 변수가 null이면 null을 반환
* 변수가 null일 때 대입해야 하는 값이나 실행해야 하는 구문이 있을 때 사용
// 엘비스 연산자(?:) 사용 : 변수가 null일 때 대입해야 하는 값이나 실행해야 하는 구문이 있을 때 사용
var data4: String? = "kkang"
println("data4 = $data4 : ${data4?.length ?: -1}")
data4 = null
println("data4 = $data4 : ${data4?.length ?: -1}") // null이면 -1 출력
- 예외 발생 !! 연산자
* !!는 객체가 null일 때 예외(exception)를 일으키는 연산자
* 널 포인트 예외를 발생시켜야 할 때 사용
// 예외 발생 연산자(!!) : null 포인트 예외를 발생시켜야 할 때 사용
fun some(data: String?): Int {
return data!!.length
}
fun main() {
println(some("kkang"))
println(some(null))
}
----- < 실행 결과 > -----
5
Exception in thread "main" java.lang.NullPointerException (null exception 오류 발생)
------------------------
'Programming > Kotlin' 카테고리의 다른 글
[6일차] 깡샘의 안드로이드 앱 프로그래밍 with 코틀린(Do it!) / 06 뷰를 이용한 화면 구성 (0) | 2022.05.08 |
---|---|
[4일차] 깡샘의 안드로이드 앱 프로그래밍 with 코틀린(Do it!) / 04 코틀린 객체지향 프로그래밍 (0) | 2021.12.17 |
[3일차] 깡샘의 안드로이드 앱 프로그래밍 with 코틀린(Do it!) / 03 코틀린 시작하기 (0) | 2021.12.04 |
[2일차] 깡샘의 안드로이드 앱 프로그래밍 with 코틀린(Do it!) / 02 안드로이드 앱의 기본 구조 (0) | 2021.12.02 |
[1일차] 깡샘의 안드로이드 앱 프로그래밍 with 코틀린(Do it!) / 01 개발 환경 준비하기 (0) | 2021.12.01 |