<퀴즈 >
열거형 CIRCLE, TRIANGLE, RECT, POLYGON 을 만들고
탑레벨의 열거형 데이터 5개를 가지는 immutable 리스트(initDataList란 이름의 리스트) 1개를 생성하고,
draw 와 printInfo 라는 인터페이스 메소드를 가진 IShape 인터페이스를 만들고, IShape 을 상속한 Circle, Triangle, Rect, Polygon를 x, y, w, h 를 가지는 데이터 클래스로 만들고 인터페이스 메소드를 오버로딩한다.
해당 클래스는 생성자를 private 으로 하고 동반객체를 통해 팩토리 메소드 제공한다.
오버로딩하는 메소드에서는 어느클래스에서 어떤 함수가 호출되었는지를 출력하게 한다.
initDataList 에 들어있는 열거형 타입에 맞는 클래스를 생성해서 도형 리스트(shapeList)에 넣는다. (when 을 사용한다)
shapeList를 반복하면서 draw 와 printInfo 를 호출한다. (이때 서로 다른 객체이지만, 하나의 코드로 동작해야한다)
# Kotlin - 함수형 프로그래밍
- 부작용(공용 변수에 따라 in/output이 다를 수 있는 것)을 없애기 위한 (위해 노력하는) 프로그래밍
* 순수함수를 지향 : 부작용이 없고 입력이 같으면 항상 출력이 보장되는 함수
* 순수함수의 조합으로 프로그래밍 지향
* mutable 데이터를 지양하고 immutable 지향
* 1급객체인 함수를 이용, 고차함수를 통한 재사용성 지향
- 공유 상태(Shared state)와 부작용(Side effects) 대신 순수 함수(Pure function) 사용
- 변경 가능한 데이터보다는 불변성(Immutability)
- 명령형(Imperative) 흐름 제어보다는 합성 함수(Function composition) 사용
- 많은 데이터 유형에 대해 작업할 수 있도록 고차 함수(Higher order functions)를 사용
- 명령적(Imperative)인 코드보다는 선언적으로(Declarative, 어떻게 하는지보다는 무엇을 해야하는지)
- 구문(statement)보다는 표현식(expression)을 사용
- ad-hoc polymorphism(가장 단순한 형태의 다형성)보다는 컨테이너와 고차 함수를 사용
# Kotlin - 1급 객체
- Kotlin의 함수는 1급 객체 함수가 데이터처럼 사용
* 1급 객체 조건 : 변수나 데이터에 할당 가능, 인자로 사용 가능, 리턴값으로 사용 가능
* 함수 타입 필요
(paramType1, paramType2, … , paramTypeN) -> returnType
- 함수를 1급객체로 다룰려면 함수타입이 있어야함
- 함수 타입은 변수, 파라메터, 리턴 모두 사용할 수 있음.
- typealias 으로 정의할 수 있음
typealias mytype = (Int, Int)->Int
// Int 하나를 인자로 받고 Boolean 리턴하는 함수타입
var l5: ((Int) -> Boolean)? = null
l5 = { it > 0 }
println("람다5 : ${l5(2)}, ${l5.invoke(2)}")
// 인자 없고 String 리턴하는 함수타입
var l6: (() -> String)? = null
l6 = { "인자 없고 String 리턴" }
println("람다6 : ${l6()}, ${l6.invoke()}")
var l7: (Int, Int) -> Int = { x, y -> x * y }
println("람다7 : ${l7(1, 2)}, ${l7.invoke(3, 4)}")
var l8: (Int, Int) -> Unit = { x, y -> println("l8 : $x, $y") }
println("람다8 : ${l7(1, 2)}, ${l8.invoke(3, 4)}")
var l9: ((Int, Int) -> Int, Int, Int, Int) -> Int = { x, y, z, k -> x(x(y, z), k) }
println("람다9 : ${l9({ x, y -> println("$this : $x, $y"); if (x > y) x else y }, 1, 3, 2)}")
// () -> (...) 인자가 없는 함수
// x()(x,y) : x, y 둘중에 큰 값 리턴
var l10: (() -> (Int, Int) -> Int, Int, Int, Int) -> Int = { x, y, z, k -> x()(x()(y, z), k) }
println("람다10 : ${l10({ { x, y -> println("$this : $x, $y"); if (x > y) x else y } }, 1, 3, 2)}")
- 출력
람다5 : true, true
람다6 : 인자 없고 String 리턴, 인자 없고 String 리턴
람다7 : 2, 12
l8 : 3, 4
람다8 : 2, kotlin.Unit
com.inu.kotlinlecture1.Lecture3@368102c8 : 1, 3
com.inu.kotlinlecture1.Lecture3@368102c8 : 3, 2
람다9 : 3
com.inu.kotlinlecture1.Lecture3@368102c8 : 1, 3
com.inu.kotlinlecture1.Lecture3@368102c8 : 3, 2
람다10 : 3
# Kotlin - 고차함수(Higher order function)
- 함수를 인자로 받거나 함수를 리턴하는 함수
* 이전엔 함수 포인터, 익명 객체로 처리
* 함수가 1급 객체이기 때문에 고차 함수 작성 가능
- 다른 함수를 이용해서 새로운 함수를 조립하는 방법으로 프로그램 가능 ex) 합성함수
# Kotlin - Lambda 함수
- 이름없는 함수(익명함수)를 표현식으로 기술한 것
- 함수를 선언하지 않고 곧바로 식으로 전달돼서 표현
* 함수가 1급객체이기 때문에 람다함수도 변수에 할당하거나 파라메터, 리턴으로 사용 가능
- 중괄호 { } 로 시작하고 끝난다.
{ p1:type, p2:type -> statement1; statement2 }
* 람다 body 가 여러 표현식이면 ; 으로 구분
* 리턴값은 return 을 하지 않고, 마지막 표현식의 값이 리턴. 값이 없으면 void 가 아닌 Unit 타입
* 표현식에서 생략 가능한것은 생략 가능
+ 타입을 유추할 수 있고, 파라메터가 1개면 생략 가능 (생략된 파라미터는 it 으로 참조 가능)
+ 파라메터가 (생략되어서라도) 없으면 -> 연산자 생략가능
* 함수의 마지막 인자가 람다라면 () 에서 빼서 밖에서 표현가능
+ 인자가 하나면 () 생략 가능
- 클로저 생성(외부 변수 사용 가능)
- 실제로는 내부적으로 별개의 익명 클래스로 생성되어 처리된다.
* 실제 함수 호출은 익명 클래스의 Invoke 함수 (코틀린에서는() 연산자) 에 의해 호출된다.
- 장점으로 코드를 간결하게 만들 수 있는 여지가 있지만 디버깅이 어려울 수 있다.
fun f1(a: Int, b: Int): Int {
return a + b
}
println("일반 함수 : ${f1(1, 2)}")
fun f2(a: Int, b: Int) = a + b
println("표현식 함수 : ${f2(1, 2)}")
val f3: (Int, Int) -> Int = fun(x, y) = x + y
println("익명 함수 : ${f3(1, 2)}, $f3")
val f4 = fun(x: Int, y: Int) = x + y
println("익명 함수2 : ${f4(1, 2)}, $f4")
var l1 = { a: Int, b: Int -> println("l1");a + b }
var l1_1 = { a: Int, b: Int -> a + b; println("l1_1") }
println("람다1: ${l1(1, 2)}")
println("람다1 마지막 식의 값 : ${l1_1(1, 2)}")
var l2: (Int) -> Boolean = { it > 0 }
println("람다2 : ${l2(2)}, ${l2.invoke(2)}")
// 파라미터 없고, 리턴은 Unit
var l3 = { println("l3") }
println("람다3 : $l3 : ${l3()}")
val tmp = 10
var l4 = { println("$tmp") }
println("람다4 : $l4 : ${l4()}")
// 마지막 파라미터가 람다식이면 밖으로 뺄 수 있음
fun f5(a: Int, b: (Int, Int) -> Int): Int = b(a, a)
var f5r = f5(3) { x, y -> x * y } // var f5r = f5(3, { x, y -> x * y })
println("람다11: : $f5r")
fun f6(b: (Int, Int) -> Int): Int = b(5, 5)
var f6r = f6 { x, y -> x * y }
println("람다12: $f6r")
- 출력
일반 함수 : 3
표현식 함수 : 3
익명 함수 : 3, Function2<java.lang.Integer, java.lang.Integer, java.lang.Integer>
익명 함수2 : 3, Function2<java.lang.Integer, java.lang.Integer, java.lang.Integer>
l1
람다1: 3
l1_1
람다1 마지막 식의 값 : kotlin.Unit
람다2 : true, true
l3
람다3 : Function0<kotlin.Unit> : kotlin.Unit
10
람다4 : Function0<kotlin.Unit> : kotlin.Unit
람다11: : 9
람다12: 25
# Kotlin - Closure(클로저)
- 람다식이나 익명함수의 경우 함수외부 범위에서 선언된 변수에 접근할 수 있다.
- Java는 final로 선언된 변수만 접근할 수 있지만 코틀린은 모두 접근 가능하다.
- 캡쳐된 변수는 수정이 가능하다.
var tmp = 10
var l4 = {println("$tmp")}
println("람다4 : $l4 : ${l4()}")
var l4_2 = {tmp += 1; tmp}
println("람다4_1 : ${l4_2()}")
println("람다4 다시 : ${l4()}")
- 출력
10
람다4 : Function0<kotlin.Unit> : kotlin.Unit
람다4_1 : 11
11
람다4 다시 : kotlin.Unit
# Kotlin - it, _
- it : 단일 매개변수의 암시적 이름
* 람다식에서 인자가 하나일 경우 생략 가능
* 그래서 생략되었을 경우에 it 키워드로 참조
- _(언더바) : 사용되지 않는 변수. 파라미터를 비우고 함수를 호출하고 싶을 때
ex) map.forEach { _, value -> println("$value") } // key를 _로 아무것도 안넘김
# Kotlin - inline
- 고차 함수를 이용할때 익명함수 -> 익명클래스 생성과 같은 런타임 오버헤드가 발생할 때, 이를 줄이는 방법
- 전달된 함수 파라메터가 inline 된다.
- 고차 함수 선언 앞에 inline 키워드 사용
- 특정 함수 파라메터를 inline 시키지 않으려면 파라메터 앞에 noinline 키워드 사용
- Inline 이 불가능한 경우는 무시
- 전달된 함수 파라메터를 가공(저장) 하려면 오류
- Inline이 되면 람다에서 return 사용 가능 : 람다함수 리턴이 아니고 outer 함수(고차함수. 그 람다함수를 호출한 외부 함수) 리턴이 됨. (non-local return). local return을 위해 라벨(return@label), 익명함수 가능
- 그냥 간단하게 컴파일시에 inline 키워드가 붙은 함수들은 모두 복붙느낌. 람다식을 그 함수 안에 코드를 넣어버림
- 단점은 컴파일 시간이 오래걸린다는 것(여러 함수에서 동일한 함수를 inline 했을 경우에 중복되는 부분이 많아지므로)
- 하지만 성능이 더 좋아짐(빨라지겠지?)
data class Person(val name: String, val age: Int)
val people = listOf(Person("Alice",29), Person("Bob", 31))
fun lookForAlice(people: List<Person>){
people.forEach{
if(it.name == "Alice"){
println("Found!")
return
}
println("if문 바깥")
}
println("Alice is not found!")
}
fun lookForAlice2(people: List<Person>){
people.forEach{
if(it.name == "Alice"){
println("Found!")
return@forEach
}
println("if문 바깥")
}
println("Alice is not found!")
}
fun lookForAlice3(people: List<Person>){
people.forEach(fun (person){
if(person.name == "Alice"){
println("Found!")
return
}
println("if문 바깥")
})
println("Alice is not found!")
}
lookForAlice(people)
lookForAlice2(people)
lookForAlice3(people)
- 출력
Found!
Found!
if문 바깥
Alice is not found!
Found!
if문 바깥
Alice is not found!
- 좀더 자세히 공부 필요. 인라인 개념을 잘 모르겠군
# Kotlin - SAM(Single Abstract Method)
- 추상 메소드가 하나만 있는 인터페이스를 파라메터로 받을때 람다로 표현할 수 있게 변환
- 선언부(interface, setOnClickListener)가 Java에 있고 코틀린에서 setOnClickListener을 호출하였을 때만 SAM변환이 동작
- 코틀린에서 선언부가 있을때는 객체 표현 표현식으로 처리해야함.
class JavaSAMSample {
static SAMInterface ss = null;
public static void setSAM(SAMInterface sam) {
ss = sam;
}
public static void doFire(int pos) {
if (ss != null)
ss.onClick(pos);
else
System.out.println("ss is null");
}
interface SAMInterface {
void onClick(int position);
}
}
fun T03() {
JavaSAMSample.setSAM { println("sam-onclick : $it") }
JavaSAMSample.doFire(100)
}
- setOnClickListener(new ...) 가 위와 같이 단순화됨
# Kotlin - Stream
- Collection 의 멤버함수 대부분은 고차함수. 대표적으로 filter, map, reduce, all, any, count, find, flatMap 등이 있음
* filter : 조건에 맞는 컬렉션을 뽑아 리스트를 만들어 주는 함수. Ex) 짝수만 걸러내기 : 짝수를 판단하는 람다를 필터에 인자로 넣어주면 리턴값으로 2,4,6,8,… 리스트가 리턴됨
* map : filter는 골라주기만 하지만 map은 변환까지 리턴시켜줌. 각 요소를 변환시켜 새로운 타입의 리스트를 만드는 함수. 변환하는 방법은 람다로 받음
* reduce : 조건에 맞게 합쳐주는 함수. 각 요소를 더하게끔 람다를 만들어 주면 리턴값은 요소, 각 리스트를 더하는 것을 람다로 넘기면 된다.
- 필터로 걸러진 값들은 같지만 실제로 객체는 다르다(주소가 다르겠지?). 이때 객체 생성/소멸이 계속 이뤄지기 때문에 오버헤드가 발생
- 그래서 중간 과정을 새로운 객체 생성 없이 진행할 수 없을까 라는 생각에서 stream이 만들어졌고, 그래서 중간에 객체를 생성하는 오버헤드를 줄일 수 있게 되었다.
- 연속해서 사용할 때 분리되어 실행.
- Stream 을 만들어 연속 실행하면 연속되어 실행
* 중간값을 생성하는 부하 감소
* 인라인되지 않음
+ 인라인 vs 중간값부하 (???? 설명 못들었음)
- 자바의 stream 와 다르게 병렬 (parallel) 지원 안함(아직은…)
* - PararellStream 은 스레드로 병행 처리 해주지만(Java 8) 코틀린은 아직 안됨. 일반 스트림만 됨
- 종단함수를 호출해야 스트림이 실행되며, 종단함수 호출 이후에는 스트림이 종료됨(Stream close)
* 종단함수 : forEach, reduce
var list = listOf(1,2,3,4,5,6,7,8,9,0)
var list2 = list.filter { println("filter"); (it % 2 ) == 0 }.map { println("map"); it * 100 }
list2.forEach { println(it) }
var list3 = list.stream().filter { println("filter"); (it % 2 ) == 0 }.map { println("map"); it * 100 }
list3.forEach { println(it) }
// list3.forEach { println(it) } // 런타임 에러. Stream operation 실행 불가. 스트림이 닫혔습니다.
var avg = list.asSequence().filter { println("filter"); (it % 2 ) == 0 }.map { println("map"); it * 100 }.reduce { acc, i -> acc + i }
println(avg)
- 출력값 귀찮
# Kotlin - Standard function : run
- 인자가 없고 리턴은 있는 확장 멤버함수 람다를 인자로 받음
- 람다가 리턴하는 값을 리턴함
- 람다를 실행하여 결과를 리턴함
* 람다는 마지막 식이 결과임
* 멤버함수이기 때문에 this 가 호출한 객체임
- 제너릭으로 모든타입에 대해 확장함수로 구현
val user = User("importre").run {
email = "importre@example.com"
profile = "http://path/to"
this // 얘가 리턴
}
println("run : $user")
/*
User 타입의 멤버함수를 파라미터로 받을 것인데, 그 함수는 파라미터가 없는 User의 멤버함수를 파라미터로 받는 run 함수.
리턴은 user
User.email
User.profile 도 같음.
초기화할 때 run이 없었으면 user. user. 다 붙여야함!
*/
# Kotlin - Standard function : let
- 인자가 자기자신 타입 한 개를 넘기고 리턴이 있는 람다를 인자로 받음
- 람다가 리턴하는 타입을 리턴함
- 람다를 this 인자로 실행하여 결과를 리턴함
* this 인자를 넘기기 때문에 this 를 it 으로 사용할 수 있음
* 람다는 마지막 식이 결과임
- 제너릭으로 모든타입에 대해 확장함수로 구현
- Safe call 로 if 검사 대신 많이 쓰임 obj?.let {} // obj가 null이 아니면 {} 모두 실행
public inline fun <T, R> T.let(f: (T) -> R): R = f(this)
val user2 = User("importre").let {
it.email = "importre@example.com"
it.profile = "http://path/to"
this
}
println("let : $user2")
# Kotlin - Standard function : apply
- 인자가 없고 리턴도 없는 확장 멤버함수 람다를 인자로 받음
- 자신의 타입을 리턴함
- 람다를 실행하고 this 를 리턴함
* inline 이기 때문에 람다지만 return 가능
* 멤버함수이기 때문에 this 가 호출한 객체임
- 제너릭으로 모든타입에 대해 확장함수로 구현
val user3 = User("importre").apply {
email = "importre@example.com"
profile = "http://path/to"
}
println("apply : $user3")
Functions |
블록내 argument |
블록내 return |
Function Type |
비고 |
run() |
this |
블록내 마지막 객체 |
normal |
|
with(T) |
this |
Unit |
normal |
|
T.apply() |
this |
T |
extension |
|
T.run() |
this |
블록내 마지막 객체 |
extension |
|
T.let() |
it |
블록내 마지막 객체 |
extension |
it을 블록 내에서 rename 가능함. |
T.also() |
it |
T |
extension |
|
# Kotlin - Etc.
- Currying – 커링
* 다중인자를 받는 함수를 하나의 인자를 받는 함수의 합성으로 처리하는 방법
* 인자를 고정시켜 제외한 함수를 만들 수 있다.
- Memoization – 메모이제이션
* 고차함수를 이용한 함수형 기법으로 캐쉬기능 제공하는 기법
# 출처 : 모베란 백지훈 대표이사님
'Programming > Kotlin' 카테고리의 다른 글
[Kotlin]코틀린을 이용한 안드로이드 프로그래밍 실습 04 (0) | 2018.07.05 |
---|---|
[Kotlin]코틀린을 이용한 안드로이드 프로그래밍 실습 02 (0) | 2018.07.03 |
[Kotlin] 코틀린을 이용한 안드로이드 프로그래밍 실습 01-2 (0) | 2018.07.02 |
[Kotlin] 코틀린을 이용한 안드로이드 프로그래밍 실습 01-1 (0) | 2018.07.02 |