[Kotlin] 코틀린의 함수와 람다, 그리고 컬렉션

Rupee🫧·2023년 3월 22일
0
post-thumbnail

☁️ 배열과 컬렉션

1. 배열

  1. 배열 생성
    arrayOf 을 통해 배열 자료구조를 생성할 수 있다.
val array = arrayOf(100, 200)
array.plus(300)  // 배열에 새로운 원소 추가
  1. 배열 조회
    array.withIndex() 를 통해 인덱스와 값을 한꺼번에 조회할 수 있다.
for (i in array.indices)) {  // 0 부터 마지막 idex 까지의 Range
	println(array[i])
}

for ( (idx, value) in array.withIndex()) {  
}

2. 코틀린의 컬렉션

코틀린에서 컬렉션을 선언할 때는, 반드시 불변인지 가변인지 명시해주어야 한다.

  1. 불변 컬렉션 : 컬렉션에 조회를 제외한 원소를 추가 및 삭제 불가능하다. 단, 레퍼런스 타입의 필드에는 접근하여 변경이 가능하다.
  2. 가변 컬렉션 : 컬렉션에 원소 추가 및 삭제가 가능하다.

2-1. List

기본적으로 ArrayList 를 상속받고 있기 때문에 자바에서 제공하는 기능을 모두 제공한다.

  1. 리스트 생성

    listOf : 불변 리스트를 생성
    mutableListOf : 가변 리스트를 생성

참고로 빈 배열을 생성할 때는, 타입을 추론할 수 있는 경우를 제외하고는 타입을 명시해주어야 한다.

val numbers = listOf(100, 200)  // Arrays.asList()
val mutable_numbers = mutableListOf(100, 200)
val emptyList = emptyList<Int>()  // 타입 명시

numbers.add(300)  // 값 추가

private fun userNumbers(numbers: List<Int>) {
}
  1. 리스트 조회
println(numbers[0])  // numbers.get(0)

for (number in numbers) {  
	println(number)
}

for ((idx, value) in numbers.withIndex()) {
	println("${idx} ${number}")
}

2-2. Set

집합은 리스트와 다르게 순서가 없고, 같은 원소는 하나만 존재할 수 있다. 가변과 불변 집합 모두 LinkedHashSet 을 상속받아서 구현된다.

  1. 집합 생성
val numbers = setOf(100, 200)   // 불변 집합
val numbers = mutableSetOf(100, 200)  // 가변 집합 

2-3. Map

가변과 불변 맵 모두 LinkedHashMap 을 상속받아서 구현된다.

  1. 맵 생성 및 원소 추가
val newMap = mapOf(1 to "MONDAY", 2 to "TUESDAY")   // 불변 맵(정적 팩터리 메서드)
val oldMap = mutableMapOf<Int, String>()  // 가변 맵

oldMap[1] = "MONDAY"  // oldMap.put(1, "MONDAY")
  1. 맵 활용

    키만 가져오는 경우 Map.keys 를 사용하고, 키와 값을 한꺼번에 가져오는 경우 Map.entries 를 사용한다.

for (kery in oldMap.keys) {
	println(key)
    println(oldMap[key])  // oldMap.get(key)
}
for ((key, value) in oldMap.entries) {
	println(key, value)
}

3. 컬렉션과 Null

? 위치에 따라 다음과 같이 null 가능성의 의미가 달라진다.

  1. List<Int?> : 리스트에는 null이 들어갈 수 있지만, 리스트 자체는 null 이 아니다.
  2. List<Int>? : 리스트에는 null이 들어갈 수 없지만, 리스트 자체는 null 이 될 수 있다.
  3. List<Int?>? : 리스트에는 null이 들어갈 수 있고, 리스트 자체도 null 이 될 수 있다.

🔖 자바와 함께 사용할 때 주의점
코틀린 쪽의 컬렉션이 자바에서 호출되면, 컬렉션 내용이 변할 수 있다. 혹은, 코틀린 쪽에서 Collections.unmodifableXXX() 를 활용하면 변경 자체를 막을 수는 있다.

☁️ 다양한 함수 다루기

1. 확장 함수

자바 코드가 있는 상황에서, 코틀린 코드로 추가 기능 개발을 하기 위해 확장함수와 확장 프로퍼티라는 개념이 등장하였다. Standard Library 또는 다른 사람이 만든 라이브러리를 사용할 때 함수를 추가하기가 어렵기 때문이다.

확장 함수를 사용하면, 함수를 기존에 클래스 안에 있던 멤버 함수 같이 사용할 수 있다. 즉 어떤 클래스 안에 있는 메서드처럼 호출 할 수 있지만, 함수는 외부에 생성하는 것이다.

fun main() {
	val str = "ABC"
    println(str.lastChar())
}

fun String.lastChar(): Char {   // String 클래스 확장
	return this[this.length - 1]  // this를 통해 실제 클래스 안의 값에 접근
}

확장 함수는 다음과 같은 특징을 가진다.

  1. 확장 함수는 캡슐화가 깨지는 것을 막기 위해 클래스에 있는 private 혹은 protected 멤버를 가져올 수 없다.
  2. 멤버와 확장 함수의 시그니처가 같다면 멤버 함수가 우선적으로 호출된다.
  3. 정적인 타입(현재 타입)에 의해 어떤 확장 함수가 호출될지 결정된다.
val train: Train = Train()
train.isExpensive()  // Train의 확장함수

val srt1: Train = Srt()  
srt1.isExpensive()  // Train의 확장함수

val srt2: Srt = Srt()
srt2.isExpensive()  // Srt의 확장함수

2. 중위 함수

infix는 함수를 호출하는 새로운 방법으로, 변수.함수이름(매개변수) 대신 변수 함수이름 매개변수 형식으로 호출하는 방식이다.

예를 들어, 아래 함수를 부를 때는 infix 가 붙어 있으므로 3 add 4 형태로 부를 수 있다.

infix fun Int.add(other: Int): Int { 
	return this + other 
}

3. 인라인 함수

inline 은 함수가 호출되는 대신, 함수를 호출하는 지점에 함수 본문을 복사 붙여넣기 해주는 키워드이다.

실제 함수가 호출되지 않고 로직 자체가 바이트 코드로 들어온 것을 볼 수 있는데, 이는 함수를 파라미터로 전달할 때 오버헤드를 줄일 수 있다는 장점이 존재한다.(단, 성능 측정과 함께 신중하게 사용되어야 한다)

☁️ 코틀린과 람다

1. 코틀린에서의 람다

자바에서는, 함수형 인터페이스를 통해 메서드 자체를 넘기는 것처럼 보이게 사용하였다.즉, 자바에서 함수는 변수에 할당되거나 파라미터로 전달할 수 없는 것이다.

하지만 코틀린에서는 함수가 그 자체로 값이 될 수 있다.
아래는 특정 조건에 따라서 과일을 찾는 예제이며, 람다 생성 방법은 다음과 같이 2가지가 존재한다.

val fruits = listOf(
	Fruit("사과", 1_000)
    Fruit("바나나", 2_000)
)
    
// 1. 람다 생성 방법(1)
val isApple = fun(fruit: Fruit): Boolean {  // 함수를 변수에 할당
	return fruit.name == "사과"
}

// 2. 람다 생성 방법(2)
val isApple2 = { fruit: Fruit -> fruit.name == "사과" }

🔖 람다의 타입
함수 자체의 타입을 표현할 때는, (파라미터 타입,,) -> 반환 타입 형태로 표기한다.

val isApple: (Fruit) -> Boolean = {...}

자바에서 Predicate 과 같이 인터페이스가 아니라, 함수 자체를 타입으로 받고 있는 것을 볼 수 있다.

filterFruits(fruits, isApple)   // 파라미터로 함수 전달

private fun filterFruits(
	fruits: List<Fruit>, filter: (Fruit) -> Boolean
): List<Fruit> {
	val results = mutableListOf<Fruit>()
    for (fruit in fruits) {
    	if (filter.invoke(fruit)) {
           results.add(fruit)
        }
    }
}

람다의 특징은 다음과 같다.

  1. fruit -> fruit 과 같이 파라미터가 하나일 뿐에는 줄여서 it 으로 사용할 수 있으며 타입도 생략 가능하다.
val isApple2 = { it.name == "사과" }
  1. 변수를 선언 안하고 파라미터 내부에서 바로 선언할 때 마지막 파라미터가 함수인 경우, 소괄호 밖에서 람다를 사용할 수 있다.
filterFruits(fruits) { it.name == "사과" }
  1. 람다를 여러줄 작성 할 수 있으며, return이 없어도 마지막 줄의 결과가 람다의 반환값이 된다.
filterFruits(fruits) { fruit -> 
	println()
    fruit.name == "사과"
}
  1. 다음과 같이 축약해서 사용할 수 있다.
return carNames.map({ carName -> Car(carName) })
return carNames.map({ Car(it) })
return carNames.map { Car(it) }
return carNames.map(::Car)

2. Closure

자바에서는, 람다를 쓸 때 사용할 수 있는 변수에 제약이 있다. 예를 들어, 다음과 같이 바깥에서 선언된 변수는 사용하지 못한다.

String targetFruitName = "바나나"
targetFruitName = "수박"
filterFruits(fruits, (fruit) -> targetFruitName.equals(fruit.getName()));

하지만, 람다에서는 이것이 바로 Closure 라는 개념을 통해 가능해진다. Clousure 란, 람다가 실행되고 있는 시점에 사용되는 모든 변수들의 정보를 포획한 데이터 구조를 의미하기 때문이다. 따라서, 값이 바뀌는 non-final 변수도 람다에서 참조하여 사용할 수 있게 된다.

그렇다면 어떻게 변수를 포획(capture)할 수 있을까?

코틀린에서는 단순 값을 복사하는 방식이 아닌, 어떤 변수를 Object로 만든 다음 그 Object레퍼런스를 가리키게끔 한다. 즉, 포획 시점에 상관 없이 해당 변수에 값이 바뀌면 람다에서 사용할 때도 영향이 미치게 되므로 주의해야 한다.

또한 레퍼런스를 공유하는 방식으로 이루어지는 만큼, thread-safe 하지 않으므로 동시성 문제를 고려해야 한다.

3. try-with-resources

☁️ 컬렉션을 함수형으로 다루는 방법

1. 필터와 맵

private fun filterFruits(
	fruits: List<Fruit>, filter: (Fruit) -> Boolean
): List<Fruit> {
	return fruits.filter(filter) 
}

2. 다양항 컬렉션 처리 기능

  • all : 조건을 모두 만족하면 true, 아니면 false
  • none : 조건을 모두 불만족하면 true, 아니면 false
  • any : 조건을 하나라도 만족하면 true, 아니면 false
  • sortedBy : 기본 오름차순 정렬
  • distinctBy : 변형된 데이터 기준으로 중복 제거
  • first() : 첫 번째 값을 가져오고, null 일 경우 예외 발생
  • firstOrNull() : 첫 번째 값 혹은 null 을 가져옴
  • last() : 마지막 값을 가져오고, null 일 경우 예외 발생

3. List을 Map으로 변경

리스트를 맵으로 변경하고 싶다면, groupBy { 키, 값 } 를 사용하면 된다.

아래 예제를 보면 그 과일 이름에 해당되는 과일들이 List<Fruit> 으로 되어있는 맵이 생성되는 것을 볼 수 있다.

val map: Map<String, List<Fruit>> = fruits.groupBy { fruit -> fruit.name }
profile
개인용으로 공부하는 공간입니다. 잘못된 부분은 피드백 부탁드립니다!

0개의 댓글