람다 방식
람다라는 의미는 기본적으로 다른 함수에 넘길 수 있는 작은 코드 조각을 의미한다
람다는 약간 논리?를 코드로 표현하기 위해 일련의 동작을 변수에 저장하거나 다른 함수에 넘겨야 하는 경우가 자주 있다 그래서 원래는 내부에서 익명클래스을 사용해서 코드를 함수에 넘기거나 변수에 담거나 이러한 과정을 거쳤었다 근데 함수형 프로그래밍에서는 함수를 값처럼 다루는 접근 방법을 사용해서 귀찮은 단계를 모두 해결해버렸다 클래스를 선언하고, 그 클래스의 인스턴스를 함수에 넘기는 대신 함수형 언어에서는 함수를 직접 다른 함수에 전달할 수 있다 람다를 사용하면 간결하게 함수를 선언하지 않고, 코드블록을 함수의 인자로 전달하는 것이 가능하다
람다 식의 문법
람다는 값처럼 여기저기 전달할 수 있는 동작의 모음이다. 람다를 따로 선언해서 변수에 저장하는 것도 가능하긴 하지만 일단은 함수에 인자로 넘기면서 바로 람다를 정의하는 경우가 대부분이다 람다의 구조는 이러하다 => {x: Int, y: Int -> x+y} 화살표를 기준으로 파라미터부분과 람다부분으로 나뉘게 된다 람다 식은 변수에 저장하는 것이 이런식으로 가능하다
이렇게 람다를 변수에 직접 집어넣어서 사용하는 것이 아주 효율적이긴 하다 자바에서는 식을 만들고 그 식을 적용한 변수에 넣어주고 이렇게 2단계를 거쳐서 해야할 일을 이제는 단 한줄로 불필요한 단계들을 없앴다 그리고 람다를 만들고 바로 호출하는 것보다 람다 본문을 직접 실행하는 방법이 있다. 즉, 코드의 일부분을 블록으로 둘러싸서 실행할 필요가 있을 때는 run 이라는 키워드를 사용한다 run은 인자로 받은 람다를 실행해주는 라이브러리 함수이다 실행 시점에 코틀린 람다 호출에는 아무런 비용이들지 않는것도 특징이다 그래서 결과적으로 어떻게 사용하느냐, 방식은 간단하다 - 예를 들어서 콜렉션에서 최대값을 구하는 maxBy 라는 함수로 봐보자 람다로 어떤 값을 인자로 넘기는 케이스 people.maxBy({p: Person -> p.age}) 만약 어떤 함수의 파라미터가 하나인 경우에는 이렇게 중괄호를 빼서 사용한 케이스 people.maxBy() {p: Person -> p.age} 아니면 그냥 괄호는 없애도 가능한 케이스 people.maxBy {p: Person -> p.age} 사실 여기서 굳이 파라미터를 선언적으로 명시해줄 필요는 없다 people.maxBy {p -> p.age} 요렇게 파라미터의 타입을 생략해서 컴파일러가 알아서 추론하도록 하는 것도 가능하다 -> 뭐 당연하게 추론가능한 경우에는 이렇게 할 수 있다는 점은 상식적으로 이해할 수 있다 그리고 파라미터 중 일부의 타입은 저장하고 나머지 파라미터 타입을 지정하지 않고 이름만 남겨두는 것도 가능하다 파라미터 중 일부의 타입은 지정하고 나머지는 알아서 하도록 이름만 선언한 것도 가능하다는 점 그리고 람다의 파라미터 이름을 람다의 디폴트 이름인 it로 바꿔서 더욱 간단하게 할 수 있는데 요놈의 조건은 이하와 같다 우선 람다의 파라미터가 하나뿐이고, 그 타입을 컴파일러가 추론할 수 있는 경우에는 바로 it를 사용할 수 있다 사용방법은 이렇다 people.maxBy {it.age} 여기서 it란 자동으로 생성한 파라미터의 이름이다 근데 정말 간단하게 처리해주지만 값이 중첩되고 그러면 it가 어떤 파라미터를 의미하는지에 대한 애매모호함이 생길 수 있다고 생각하기 때문에 조심해야한다
자바에서 무명 클래스를 사용할때 메소드의 로컬변수를 내부 클래스 안에서 사용할 수 있었다. 람다에서도 같은 방식이 가능하다 즉, 람다를 함수 안에서 정의하면, 함수의 파라미터 뿐만아니라 람다 정의 앞에 선언된 로컬변수까지 람다에서 사용하는 것이 가능하다 자바에서는 람다내부에서만 있는 변수만 사용가능했었지만 코틀린에서는 람다 밖에서 선언한 바깥 변수를 변경하거나 접근하는 것이 가능하다 반대로 람다 내부에서 선언하고 사용한 변수는 기본적으로 변수의 생명주기가 함수가 종료될 때이다 근데 만약 내부에 있는 함수를 밖에서도 사용하고 싶다면 변수를 포획함을 통해서 변수의 생명주기를 변경하는 것이 가능하다 기본적으로 파이널인 변수를 포획한 경우에는 람다 코드를 변수 값과 함꼐 저장한다 파이널이 아닌 경우에는 변수를 특별한 래퍼로 감싸서 래퍼를 통해서 사용한다
멤버를 참조하는 방법은 자바에서의 메소드 레퍼런스와 같은 방식인 :: 을 통해서 참조한다 ::을 사용하는 식을 멤버 참조라고 부르는데, 멤버 참조는 프로퍼티나 메소드를 단 하나만 호출하는 함수 값을 만들어준다 val myAge = Person::age 요렇게 생긴 것인데 val myAge = {p: Person -> p.age} 이 람다 방식을 줄이는 방식이라고 볼 수 있다 추가로 굳이 클래스::멤버 이렇게 참조하지 않고 클래스 이름을 생략하고 ::으로 참조를 바로 시작하는 것도 가능하다 람다가 인자가 다양한 다른 함수에게 작업을 위임하는 경우 람다를 정의하지 않고 직접 위임함수에 대한 참조를 제공하면 편리하다
또한 생성자 참조에서 이렇게 ::을 사용해서 진행하는데, 이렇게 사용하면 클래스 생성 작업을 연기하거나 저장하는 것이 가능하다
컬렉션 함수형 메소드
함수형 프로그래밍을 사용하게 되면 컬렉션을 사용하는데 있어서 아주 편리하다 그 중에서도 필수적인 함수 filter, map에 대해서 간단하게 보자 우선 filter함수는 컬렉션을 일일이 돌면서 주어진 람다에 각 원소를 넘겨서 람다의 식을 확인하는 그러한 함수이다 그래서 filter를 통해서 컬렉션 내부에 존재하는 원소들 중에서 원하지 않은, 즉 람다식에 부합하지 않는 원소는 제거된다 그리고 map 함수는 원소를 조작하는 것이 가능하다, 즉 주어진 람다를 컬렉션에 있는 각각의 원소에 적용해서 새로운 컬렉션을 만드는 것이다 사실근데 람다를 인자로 받는 함수에 람다를 넘기면 간단해 보일 수도 있지만 실제로는 정말 쉽지 않은 계산식이 될 수동 있기 떄문에 요부분은 항상 알아서 눈치것 사용하는 것이다 이외에도 map 의 경우에는 키와 값을 처리하는 함수가 따로 존재한다 filterKey, mapKey는 키를 걸러내거나 변환해주고, filterValues, mapValues는 값을 걸러내거나 변환해준다
이외에도 all, any, count, find 들이 잇다 all이나 any는 컬렉션에 대해 자주 수행하는 연산으로 컬렉션의 모든 원소가 어떤 조건을 만족하는지 판단하는 연산이다 count는 해당 조건에 만족하는 원소의 숫자를 반환해주는 연산이다 자바에서는 count가 아니라 size을 사용하는 경우가 있었는데 코틀린에서는 count가 있으니 요걸 사용하라.. size을 사용해서 처리하면 조건에 만족하는 모든 원소가 들어가는 중간 컬렉션이 생기지만 count은 조건을 만족하는 원소갯수만 추적하기 떄문에 더욱 효율적이다 그리고 find는 조건을 만족하는 첫 번째 원소를 반환해주는 연산이고 없으면 null을 반환해주는데, 이건 명시적으로 findOrNull 이렇게도 사용할 수 있다
또한 컬렉션에서 groupBy를 통해서 리스트를 여러 그룹으로 이루어진 맵으로 변경하는 것도 가능하다
위에서의 println 의 결과는 {31=[Person(name=Alice, addr=, age=31)], 29=[Person(name=Bob, addr=, age=29), Person(name=Chailre, addr=, age=29)]} 이렇게 그룹화해서 보여준다 결과를 봐보면 Map<Int, List> 이 형태로 나오기 때문에 결과값을 가지고 mapKeys, mapValues 을 통해서 처리할 수 있다
그리고 중복된 컬렉션의 처리를 위한 flatMap, flatten도 존재한다 flatMap 함수는 리스트로 받은 놈들에 들어있던 모든 원소로 이루어진 단일 리스트를 반한다 "abc" + "def" => flatMap => (a,b,c,d,e,f) 이렇게 다양한 컬렉션에 관련된 함수가 있으니까 필요하면 사용하자
컬렉션 연산의 지연 계산
지금까지의 컬렉션 함수들은 즉시 다음 결과 컬렉션을 만들어서 생성한다 즉, 컬렉션 함수를 체이닝을 통해서 사용하면 매 단계마다 계산의 중간 결과를 새로운 컬렉션에 담는다는 의미이다 하지만 시퀀스를 사용하면 중간의 컬렉션을 통하는 단계를 건너띄고 컬렉션 연산이 가능하다 시퀀스의 사용 방법은 리스트.컬렉션함수~~ 이렇게 가 아니라 리스트.asSequence() 을 통해서 시퀀스 인터페이스를 시작으로 연산을 지정해야 한다 이렇게 지정하고 사용하면 중간 결과를 저장하는 컬렉션이 생성되지 않기 때문에 성능이 아주 좋아진다 코틀린 지연 계산 시퀀스는 시퀀스 인터페이스에서 시작한다. 이 인터페이스는 단지 한 번에 하나씩 열거될 수 있는 원소의 시퀀스를 표현하는 것이다 그리고 이 Sequence 안에는 iterator 라는 단 하나의 메소드를 통해 시퀀스로부터 원소 값을 얻는 것이 가능하다 Sequence의 강점은 그 인터페이스 위에 구현된 연산이 계산을 수행하는 방법 때문에 생긴다 그리고 시퀀스의 원소는 필요할 때 그때야 계산된다 근데 시퀀스를 사용하면 지연 계산하기 때문에 실제로 계산하는건 최종 시퀀스의 원소를 하나씩 iteration 하거나 최종 시퀀스를 리스트로 변환해야 한다
시퀀스의 연산에는 중간과 최종 연산이 있다 일단 중간 연산은 다른 시퀀스를 반환한다 그 시퀀스를 최조 시퀀스의 원소를 변환하는 방법을 안다 그리고 최종연산은 결과를 반환해준다 메소드 체이닝으로 연산을 막~~ 작업했을 때 일단 그냥 .filter.map~~ 이렇게만 해두면 계산작업이 진행되지 않고 아무런 작업이 없다가 toList을 붙혀주면 그제서야 최종결과를 반환해주는 것이다 시퀀스는 자바에서의 스트림과 같다는 점만 기억하고 있자 그리고 시퀀스를 만드는 방법은 asSequence()뿐만 아니라 generateSequence이라는 함수을 통해서도 만들 수 있다는 점
자바 함수형 인터페이스 활용
원래 자바에서 어떠한 인터페이스를 사용하고자 하면, 내부에서 새로 구현하도록 ide에서 자동으로 함수 내부에 인자만 들어가야하는데 내부에 주루루루룩 override 해야하는 함수들을 보여준다 코틀린에서는 무명 클래스 인스턴스 대신 람다를 넘길 수 있는데, 만약 인터페이스에서 내부 함수가 단 하나만 존재하는 경우에 그 함수에 넘기는 인자 -> 이러한 생김새로 람다방식으로 표현하는 것이 가능하다 이러한 인터페이스를 함수형 인터페이스라고 하고 SAM(Single Abstract Method) 인터페이스라고도 부른다 예시로 보면 이러하다
이렇게 실제로 컴파일러는 자동으로 그러한 무명 클래스와 인스턴스를 만들어준다 이때 그 무명 클래스에 있는 유일한 추상 메소드를 구현할 때 람다 본문을 메소드 본문으로써 활용한다 코틀린에서는 inline 키워드가 들어가 있는데 이 inline 함수는 람다식 내부의 실행문들을 컴파일 시, 람다를 호출하는 부분에 주입하는 방식이다 이렇게 자동으로 컴파일러가 변환을 진행해주는데, 수동으로 해야 하는 경우가 존재한다
SAM 생성자는 람다를 함수형 인터페이스의 인스턴스로 변환할 수 있게 컴파일러가 자동으로 생성한 함수이다 컴파일러가 자동으로 람다를 함수형 인터페이스 무명 클래스로 바꾸지 못하는 경우 SAM 생성자를 사용할 수 있다 람다로 생성한 함수형 인터페이스 인스턴스를 변수로 저장해야 하는 경우에도 SAM 생성자를 사용하는 것이 가능하다 만약에 여러가지 객체에 동일한 인터페이스를 적용하고 싶다면 함수형 인터페이스를 새롭게 객체에 구현해주고, 그 구현한 부분에서 각 객체에 대한 분기처리를 해주는 방식으로 적용할 수 있다
뭐 각각 onClickListener를 구현하는 객체 선언을 통해 리스너를 구현할 수도 있지만 SAM 생성자를 쓰는 게 더 좋다 뭐 이런식으로 수동으로도 작업하는 것도 가능하다는 점을 기억하자
수신 객체 지정 람다
apply, with 함수는 수신 객체를 명시하지 않고 람다의 본문 안에서 다른 객체의 메소드를 호출할 수 있게 해준다 그리고 이것을 수신 객체 지정 람다라고 부른다
with 함수는 어떠한 객체의 이름을 반복하지 않고도 그 객체에 대한 다양한 연산을 수행할 수 잇도록 해주는 라이브러리 함수이다
여기서 보면 result를 매번 반복해서 사용했다 그래서 with을 통해서 다시 작성할 수 있다
아니면 val result = with(stringBuilder, {...}) 이렇게도 사용하는 것이 가능하다 with 함수는 첫 번째 인자로 받은 객체를 두 번재 인자로 받은 람다의 수신객체로 만든다 그래서 인자로 받은 람다 에서는 this 키워드를 통해서 사용하는 것이 가능했다 그리고 this을 사용하지 않고 그냥 명시해서 사용하는 것도 가능하다 이렇게 with이 반환하는 값은 람다 코드를 실행한 결과이며 그 결과는 람다 식의 본문에 있는 마지막 식의 값이다
apply 함수는 람다의 결과가 결국 수신객체인 경우에 사용한다 즉 apply는 항상 자기에게 전달된 수신 객체를 반환한다 요놈은 apply는 확장 함수로 정의되어 있어서 apply 수신 객체가 전달받은 람다의 수신객체가 된다 위의 alphabat을 수정하면
이렇게 apply는 객체의 인스턴스를 만들면서 즉시 프로퍼티의 일부분을 초기화해야하는 경우에 유용하다 자바에서는 빌더를 통해서 진행했었지만 코틀린에서는 기본적으로 지원해주기 때문에 apply를 활용하는 것이 가능하다
Last updated
Was this helpful?