고차 함수
- 함수를 마치 클래스에서 만들어낸 인스턴스처럼 취급하는 방법
- 함수를 파라미터로 넘겨줄 수 있음
- 결과값으로 반환 받을 수 있음
- 코틀린은 모든 함수를 고차함수로 사용 가능
fun main(){
b(::a) // 고차함수 형태로 넘겨주기 위해서는 콜론을 두개 붙여줘서 넘겨줘야함
}
fun a (str: String){
println("$str 함수 a")
}
fun b(function: (String) -> Unit){
function("b가 호출한")
}
- fun b의 파라미터로 함수 a를 받고싶음. 이때 a는 타입이 아니라 함수명임. 어떻게 함수 a의 타입을 전달할 수 있을 까?
-
입력 파라미터 타입 -> 반환형 타입
- 즉, (String) -> Unit) 이 하나의
타입
임
- 즉, (String) -> Unit) 이 하나의
-
파라미터로 넘겨줄 함수의 이름을 굳이 정해줘야 할까? 그렇지 않다. 람다함수를 쓰자!
람다식
-
람다식은 코드조각이다
-
기본형
{ argumentList -> codeBody}
- 화살표앞은 인자와 그 타입, 화살표뒤는
코드바디
- 화살표 뒤는 함수처럼 어떤 의도한 기능들을 수행할 수 있는 코드가 올 수 있다!
코드바디
니까 여러줄이 올 수 있는것임!!!
-
람다식에선 항상 마지막에 오는 코드가 return 값
val square = {number:Int -> number * number} val nameAge = {name:String, age:Int -> "my name is $name and $age years old" 3+5 } fun main(){ println(square(15)) println(nameAge("hojun", 27)) }
- my name is hojun and 27 years old 는 출력 되지 않는것을 확인할 수 있다
- 마지막 값이 중요하며 이것이 타입추론에 쓰인다!
- 화살표앞은 인자와 그 타입, 화살표뒤는
람다함수
- 람다 함수는 그 자체가 고차 함수여서 별도의 연산자 없이 변수에 담을 수 있음
fun main(){
b(::a)
val c:(String) -> Unit = {str -> println("$str lambda")}
b(c)
}
fun a (str: String){
println("$str function a")
}
fun b(function: (String) -> Unit){
function("b call")
}
-
여러 표현 방법
// 생략 하나도 없는 전체 표현 val sum: (Int, Int) -> Int = {x:Int, y:Int -> x + y} // 항상 중괄호 사이에 존재 // x:Int, y:Int가 파라미터, -> 뒤에가 본문 // 이렇게 변수에 할당하면 그 변수에서 인자 받아들임 // 선언 자료형 생략 val sum = {x: Int, y: Int -> x + y} // 람다식 매개변수 자료형 생략 val sum: (Int, Int) -> Int = {x, y -> x + y} // 에러 val sum: {x, y -> x + y} // 주의사항!! // 아래와같이 선언하면 에러남 val sum: Int -> Int = {x:Int -> x} // 따라서 람다의 입력인자 타입을 지정해줄 땐 항상 ( ) 로 해줘야함 val sum: (Int) -> Int = {x:Int -> x} // 인자로 아무것도 안 받으며 반환값 없는 람다식 선언 val out: () -> Unit = {println("Hello world!")} // 인자가 하나인 경우 it 키워드로 인자값을 사용할 수 있음 val c:(String) -> Unit = {println("$it 람다함수")}
안드로이드에서 많이 쓰는 람다식 표현법
fun main(){
val lambda:(Double) -> Boolean = {number:Double ->
number == 4.3213
}
println(invokelambda(lambda))
println(invokelambda({it > 5})) // invokelambda 함수의 인자를 람다식 형태로 구성
// invokelambda 함수인자가 요구하는 람다식의 입력인자가 1개 이므로 it으로 대체 가능
// 즉 여기서의 it은 invokelambda 내의 lambda(4.3213)에 의해 4.3213이 됨
// 람다식 내의 입력인자가 하나 혹은 없을 경우 `->` 생략 가능하므로 it > 5은 코드바디 이면서 boolean 형식
}
fun invokelambda(lambda: (Double) -> Boolean): Boolean{
return lambda(4.3213)
}
-
위 코드의 주석과 더불어서 몇가지 조건에 의해 람다식을 더 간단하게 만들 수 있음
-
클래스 혹은 함수의 마지막 파라미터가 람다식이라면 소괄호 바깥으로 뺄 수 있음
invokelambda(){lambda} invokelambda(){it > 5}
-
입력 파라미터가 람다식뿐이라면 소괄호 생략 가능
invokelambda{lambda} invokelambda{it > 5}
-
버튼 리스너를 통한 이해
-
object 키워드를 활용한 익명 객체 방식
button.setOnClickListener{object : View.OnClickListener{ override fun onClick(p0: View?){ // to do.. } }
-
람다식을 활용한 방식 (요구조건 2가지 있음)
- Kotlin interface가 아닌 Java interface 이어야함
- 그 interface는 딱 하나의 메소드만 가져야함
button.setOnClickListener{ // to do... }
- 위 익명 객체 방식 코드와 완벽히 똑같은 코드임
- 1, 2 조건을 모두 만족하며 onClick 메소드가 void 타입이므로 람다식의 반환형이 Unit? 처리되어있는것을 알 수 있다
- 이런식으로 오직 하나의 추상 메소드를 가진 인터페이스를 함수형 인터페이스 또는 SAM 인터페이스(Single Abstract Method)라고 부름
-
-
확장함수
fun main(){
println(pizzaIsGreat("Wow!"))
println(expandString("HHHH", 25))
}
val pizzaIsGreat:String.() -> String ={ // 입력 인자가 하나거나 아예 없을경우 -> 생략가능
this + "Pizza is the best!"
}
fun expandString(name: String, age: Int): String{ // 함수 선언부이므로 : String은 함수 반환 타입
val introduceMyself:String.(Int) -> String = {"I am ${this} and ${it} years old"}
return name.introduceMyself(age)
}
- :String.(Int) -> String
- 나는 String object의 확장함수로 사용될 것이며 사용되는 순간에 내가 받을 인자는 Int형 이야
- 그리고 나의 반환형은 String이야
object, companion object
Singleton Patten(클래스의 인스턴스를 단 하나만 만들어 사용하도록 하는 코딩 아키텍쳐 패턴)을 언어차원에서 지원함
- object
- 생성자가 필요없음
- 최초 호출 시 그 자체가 객체가 되기 때문
- 생성자가 필요없음
fun main(){
println(Counter.count)
Counter.countUp()
Counter.countUp()
println(Counter.count)
Counter.clear()
println(Counter.count)
}
object Counter{
var count = 0
fun countUp(){
count++
}
fun clear(){
count = 0
}
}
// 출력결과
0
2
0
- companion object
- Class 안에 object가 들어가 있는 것
- 즉 클래스들간의 공용 속성 및 함수를 사용하겠다!
- Class 안에 object가 들어가 있는 것
fun main(){
var A = FoodVote()
var B = FoodVote()
A.vote()
A.vote()
A.vote()
B.vote()
B.vote()
println("${FoodVote.total}") // companion object에 접근할 땐 클래스명으로 접근
}
class FoodVote(){
var count = 0
companion object{
var total = 0
}
fun vote():Unit{
count ++
total ++
}
}
// 출력결과
5
옵저버 패턴, 콜백 메서드
- 옵저버는 이벤트 발생하는것을 감시하는것을 의미함
- 이벤트란?
- 함수로 직접 요청하지 않았지만, 시스템이나 루틴에의해 발생하는 동작
- 바로 위 문구에서 언급한 동작이 발생할 때 마다 즉각 처리 가능하게 만드는것이 옵저버 패턴
개념 이해
- 옵저버 패턴 구현을 위해 필요한 클래스는 2개
- 이벤트 수신하는 클래스(A)
- 이벤트를 발생 및 전달하는 클래스(B)
- 안드로이드에서는 Button에 리스너 객체를 다는것을 예시로 들 수 있음. 즉 통상적으로 A 클래스에서 B 클래스의 인스턴스를 만들어서 구현함
- 문제 발생
- A 클래스 내에서 인스턴스를 만들기 때문에 A 클래스는 B 클래스를 참조 가능하지만, B 클래스가 A 클래스를 참조할 수는 없음(상속관계 아님). 즉, 통신 불가. 이를 해결하기 위한 수단이 인터페이스
- 문제 발생
인터페이스를 활용한 통신 구현
- B 클래스가 Interface를 어떻게 활용할 것인지를 선언함
- 나에게 종이 주어진다면 나는 그 종을 자진모리 장단으로 흔들다가 한번씩 추임새를 올리겠노라. 그 종은 색깔이 푸른색이도다
- A 클래스가 Interface를
구현
해서 B에게 전달함(A 클래스 내에 B 클래스의 인스턴스가 있으므로 참조 가능)- 푸른색 빛이 은은한 종을 만들었오. 그대(B)에게 드리겠소.
- 코드 작동이 시작되고 A 클래스가 B 클래스의 인스턴스 메서드를 호출하면 B 클래스가 사전에 계획했던 Interface 활용이 시작됨
- 헤이, 내가(B) 자진모리 장단을 지금부터 출터이다. 내가 추임새를 올리는 순간 너가 만든 종의 청량함 (구현된 기능)을 맛보겠도다
fun main(){
val button = EventPrinter()
button.start()
}
interface EventListener{
fun onEvent(count: Int)
}
class Counter(var listener: EventListener){
// 상속해서 쓰는게 아니라 속성으로 사용한다는것에 주목
// 어떤 의미가 있는가?
// 직접 인터페이스의 구현된 기능을 사용하겠다! 로 보임
// 상속을 받으면 직접 구현을 해줘야 하는 것이고
fun count(){
for(i in 1..100){
if(i%5 == 0) listener.onEvent(i)
}
}
}
class EventPrinter:EventListener{
override fun onEvent(count:Int){
print("${count}-")
}
fun start(){
val counter = Counter(this) // 다형성 개념에서 추가설명 할것이긴 하겠지만 약간만 설명하자면
// this는 EventPrinter의 인스턴스임
// Counter가 받는 파라미터는 EventListener이므로 EventPrinter 인스턴스에서
// onEnvet 함수의 구현부만 가져온다. (이건 코틀린 고유 문법으로 보임)
counter.count() // 이 코드가 두 클래스간의 약속으로 보면 될 듯
// Counter가 말하길.. 종이 나한테 온다면 내가 그걸 흔들께! 대신 그 종은 너가 만들어서 나한테 줘!
// EventPrinter가 말하길.. 알겠어 내가 종을 만들어서(override) 너한테 줄게! (counter.count())
}
}
// 출력결과
5-10-15-20-25-30-35-40-45-50-55-60-65-70-75-80-85-90-95-100-
PREVIOUS경제