코틀린 & Java/코틀린인액션

[Kotlin] 람다에 대해 알아보자

코딩하는후운 2023. 3. 23. 17:08
반응형

람다 (Λ λ)

  • 11번째 그리스 알파벳 으로 대문자는 Λ, 소문자는 λ이다. 람다라고 읽으며 영어로는 lambda라고 적는다.
  • 프로그래밍 언어 에서 사용되는 개념으로, 익명 함수 Anonymous functions 를 지칭하는 용어이다.

고차 함수

  • 함수를 인자로 받거나 결과로 반환하는 함수를 고차함수(高次函數)라 한다.
  • 람다식은 주로 고차 함수에 인자(argument)로 전달되거나 고차 함수가 돌려주는 결과값으로 쓰인다

장점

  • 코드의 간결성 : 불필요한 루프문의 삭제가 가능하며, 동일한 함수를 재활용할 수 있는 여지가 커진다.
  • 인터페이스 없이 콜백구조를 간단하게 구현할 수 있다.
  • 지연 연산을 지원하는 방식 을 통하여 효율적인 퍼포먼스를 기대 → 5.3절

5.1.1) 람다 소개

// Java
btn.setOnClickListener(new View.OnClickListener(){
      @Override
      public void onClick(View view)
      {
          textView.setTextColor(Color.BLUE);
      }
});

// Kotlin 
binding.btnShare.setOnClickListener {
     imageViewerAdapter.shareImage()
}

// 람다를 메서드가 하나뿐인 무명 객체를 대신해서 사용할 수 있음.

5.1.2) 람다와 컬렉션

  • 코드에서 중복을 제거하는 것은 프로그래밍 스타일을 개선하는 중요한 방법
  • 우리는 많은 컬렉션을 다루고, 그 방식은 대부분 일정한 패턴을 가진다.
  • 람다를 지원하지 않았던 자바에서는 그러한 라이브러리가 많지 않았음.
  • 대부분의 그런 작업들은 코틀린 라이브러리 함수를 통해 개선할 수 있음

5.1.3) 람다 식의 문법

{ x: Int, y: Int → x + y }

  • 파라미터와 본문으로 구성됨
  • 인자에 괄호가 없으며, 화살표로 인자와 본문을 구분
  • 람다 전체는 항상 중괄호 사이에 위치함

val sum = { x: Int, y: Int → x + y }
println(sum(1,2))

  • 변수에 람다를 저장하고 호출할수 있음
val people = listOf(Person("상윤", 35), Person("종훈", 36), Person("혜지", 30))

1) println(people.maxBy({p:Persion -> p.age}) // 맨뒤 인자가 람다면 람다를 괄호밖 가능
2) println(people.maxBy() { p:Persion -> p.age }) // 어떤함수의 유일한 인자, 괄호밖이면
3) println(people.maxBy{ p:Persion -> p.age })//  이렇게 () 를 생략 가능함.
4) println(people.maxBy{ p -> p.age }) // 컴파일러가 타입추론이 가능하므로 타입 생략가능
5) println(people.maxBy{ it.age }) // it 자동 생성된 파라미터 이름
---------------------------------------------
*Persion(name=종훈, age=36)*

---------------------------------------------
/**
 * Returns the first element yielding the largest value of the given function.
 * 
 * @throws NoSuchElementException if the collection is empty.
 * 
 * @sample samples.collections.Collections.Aggregates.maxBy
 */
@SinceKotlin("1.7")
@kotlin.jvm.JvmName("maxByOrThrow")
@Suppress("CONFLICTING_OVERLOADS")
public inline fun <T, R : Comparable<R>> Iterable<T>.maxBy(selector: (T) -> R): T {
    val iterator = this.iterator()
    if (!iterator.hasNext()) throw NoSuchElementException()
    var maxElem = iterator.next()
    if (!iterator.hasNext()) return maxElem
    var maxValue = selector(maxElem)
    do {
        val e = iterator.next()
        val v = selector(e)
        if (maxValue < v) {
            maxElem = e
            maxValue = v
        }
    } while (iterator.hasNext())
    return maxElem
}

---------------------------------------------
/**
 * A generic ordered collection of elements. Methods in this interface support only read-only access to the list;
 * read/write access is supported through the [MutableList] interface.
 * @param E the type of elements contained in the list. The list is covariant in its element type.
 */
public interface List<out E> : Collection<E> {

---------------------------------------------
/**
 * A generic collection of elements. Methods in this interface support only read-only access to the collection;
 * read/write access is supported through the [MutableCollection] interface.
 * @param E the type of elements contained in the collection. The collection is covariant in its element type.
 */
public interface Collection<out E> : Iterable<E> {

---------------------------------------------

public class Int private constructor() : Number(), Comparable<Int> {

5.1.4) 현재 영역에 있는 변수에 접근

  • 자바에서는 무명내부 클래스 정의할때 메소드 로컬변수를 무명 내부클래스에서 사용할수 있다.
  • 파이널로 된 경우만 가능한데, 왜 이렇게 만들었을까?
public void insert(final Bbs bbs) {
	getJdbcTemplate().update(sql, new PreparedStatementSetter() {
	@Override
	   public void setValues(PreparedStatement ps) throws SQLException {
		  // TODO Auto-generated method stub
		  ps.setString(1, bbs.getSeq());
		  ps.setString(2, bbs.getTitle());
		  ps.setString(3, bbs.getWriter());
		  ps.setString(4, bbs.getContent());
	   }  
	});
}

// 참고
지역변수를 final로 지정하면 JVM constant pool에서 따로 변수를 관리
  • 코틀린은 208P / 209P 참고

5.1.5) 멤버참조

  • Person::age
  • 클래스::멤버
  • 멤버 → 프로퍼티 또는 메서드
people.maxBy(Person::age)
people.maxBy{p→p.age}
people.maxBy{it.age}

5.2 컬렉션 함수형 API

  • 함수형의 장점 중 하나가 컬렉션을 다루기 편의하다는 것
  • 데이터를 표현하기 위해 컬렉션을 많이 다뤄야하고, 사실 이것들은 일정한 패턴이 있음
  • 따라서 라이브러리를 사용하면 간결하게 만들수 있음
  • 람다는 이것들을 매우 적극적으로 쓸수 있게 해준다.

→잘 알아두면 코테를 잘 볼 수 있따.

5.2.1 filter & map

Filter

val people = listOf(person("상윤", 35), Persion("종훈", 36), Persion("혜지", 30))
println(people.filter{it.age>30})

/**
 * Returns a list containing only elements matching the given[predicate].
 *
 *@samplesamples.collections.Collections.Filtering.filter
*/
public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
    return filterTo(ArrayList<T>(), predicate)
}

/**
 * Appends all elements matching the given [predicate] to the given [destination].
 * 
 * @sample samples.collections.Collections.Filtering.filterTo
 */
public inline fun <T, C : MutableCollection<in T>> Iterable<T>.filterTo(destination: C, predicate: (T) -> Boolean): C {
    for (element in this) if (predicate(element)) destination.add(element)
    return destination
}

Map

val people = listOf(person("상윤", 35), Persion("종훈", 36), Persion("혜지", 31))
println(people.map{it.age + 1})

/**
 * Returns a list containing the results of applying the given [transform] function
 * to each element in the original collection.
 * 
 * @sample samples.collections.Collections.Transformations.map
 */
public inline fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R> {
    return mapTo(ArrayList<R>(collectionSizeOrDefault(10)), transform)
}

/**
 * Applies the given [transform] function to each element of the original collection
 * and appends the results to the given [destination].
 */
public inline fun <T, R, C : MutableCollection<in R>> Iterable<T>.mapTo(destination: C, transform: (T) -> R): C {
    for (item in this)
        destination.add(transform(item))
    return destination
}
  • 결과적으로 보면, 이런것들을 쓰지 않았을때랑 코드가 거의 같다는 것을 알수 있음
  • 하지만 이런 패턴을 공용화 했다는 것 → 결국 가독성과 효율성을 높이는 방법이 되었음.

잘못쓰는 경우

people.filter { it.age == people.maxBy(Persion::age)!!.age }

**N명이라고 했을때 시간복잡도는? N^2**

/**
 * Returns the first element yielding the largest value of the given function or `null` if there are no elements.
 * 
 * @sample samples.collections.Collections.Aggregates.maxByOrNull
 */
@SinceKotlin("1.4")
public inline fun <T, R : Comparable<R>> Iterable<T>.maxByOrNull(selector: (T) -> R): T? {
    val iterator = iterator()
    if (!iterator.hasNext()) return null
    var maxElem = iterator.next()
    if (!iterator.hasNext()) return maxElem
    var maxValue = selector(maxElem)
    do {
        val e = iterator.next()
        val v = selector(e)
        if (maxValue < v) {
            maxElem = e
            maxValue = v
        }
    } while (iterator.hasNext())
    return maxElem
}

val maxAge = people.maxBy(Persion::age)!!.age 
people.filter { it.age == maxAge }

**N명이라고 했을때 시간복잡도는? N+N = 2N**

5.2.2 all, any, count, find = firstOrNull

책 참조 (217)

5.2.3 Group By

data class Person(val name: String, val age: Int)

val students = listOf(
    Person("종훈", 21),
    Person("상윤", 21),
    Person("혜지", 20),
    Person("정웅", 20),
)

println(students.groupBy { it.age })

// Map<Int, List<Person>>

5.2.4 Flatmap & Flatten

val numbers = listOf(listOf(1,2,3), listOf(5,6,7), listOf(8,9,0))
val result = numbers.flatten()
println(result)

[1, 2, 3, 5, 6, 7, 8, 9, 0]



val numbers = listOf(listOf(1,2,3), listOf(5,6,7), listOf(8,9,0))
val result = numbers.flatMap { it.map { "num $it"} }
println(result)

[num 1, num 2, num 3, num 5, num 6, num 7, num 8, num 9, num 0]
반응형