JVM/Kotlin

코틀린 문법정리

kyoulho 2023. 12. 13. 22:39

null


Safe Call과 Elvis 연산자

nullable 타입을 다룰 때 사용된다.

val str: String? = "ABC"
var length;
length = str.length       // 불가능
length = str?.length      // 가능
length = str?.length ?: 0 // null이 아니면 길이 null이면 0

early return에도 사용할 수 있다.

fun calculate(number: Long?): Long{
	number ?: return 0
	// 로직
}

단언 연산자

nullable 타입이지만 개발자가 null이 아님을 확신할 때 사용된다.

val nullableValue: String? = "Hello, Kotlin"
val nonNullValue: String = nullableValue!! // 개발자가 null이 아님을 확신

플랫폼타입

플랫폼 타입은 코틀린과 자바 간의 경계를 넘어가는 과정에서 타입에 대한 정확한 정보를 알 수 없는 경우에 사용된다. 자바 코드에서 가져온 타입이 null에 대한 어노테이션이 없을 경우, 코틀린은 해당 타입을 정확하게 알 수 없어서 플랫폼 타입으로 다루게 된다. 이런 경우 자바 코드를 꼼꼼히 읽어서 확인하던가 자바코드를 코틀린 코드로 래핑 하여 단일지점으로 만들어서 사용해야 한다.

// 자바 클래스
public class JavaClass {
    public String getString() {
        return null; // 의도적으로 null을 반환
    }
}

// 코틀린
fun main() {
    val javaClass = JavaClass()
    val str: String = javaClass.string // Kotlin 컴파일러는 str이 null일 가능성을 인식하지 못함
    println(str.length) // NullPointerException 발생 가능성
}

// nullable 처리
fun main() {
    val javaClass = JavaClass()
    val str: String? = javaClass.string // null 가능성을 고려하여 nullable 타입으로 받음
    println(str?.length ?: "Unknown length") // 안전한 호출과 엘비스 연산자 사용
}

// 래핑 클래스 사용
class SafeJavaClass(private val javaClass: JavaClass) {
    fun getString(): String? {
        return javaClass.string // null 가능성을 고려하여 nullable 타입으로 반환
    }
}

 

Type


Primitive Type

코틀린에서는 기본 타입(primitive type)과 참조 타입(reference type)이 모두 있다. 예를 들어, 정수는 Int가 기본 타입이고, Int? 가 참조 타입이다. 기본 타입은 일반적으로 더 효율적이며 메모리 사용이 더 적게 들어간다.
코틀린 컴파일러는 가능한 경우에 기본 타입을 사용하도록 최적화를 수행하며, 자동으로 박싱과 언박싱을 처리한다. 그러나 이러한 최적화는 모든 경우에 적용되는 것은 아니며, 일부 상황에서는 개발자가 직접 기본 타입을 선택해야 할 수도 있다.
자바에서는 더 큰 타입으로는 암시적 변경이 가능하였지만 코틀린에서는 메서드를 이용해 변경해야 한다.

val number1 = 4 
val number2 : Long = number1.toLong()

타입 캐스팅

타입 비교에 is를 사용하고 타입 캐스팅에 as를 사용한다. is의 값이 true 일 경우 스마트 캐스팅이 진행된다.

fun processValue(value: Any) {
    if (value is String) {
        // 여기서는 자동으로 String 타입으로 캐스팅됨 (스마트 캐스트)
        println(value.length)
    }
}

fun getValue(value: Any?): String?{
		return value as? String
}

getValue(1)    // null 반환
getValue(null) // null 반환
getValue("as") // "as" 반환

Any

Any는 자바의 Object 역할로 모든 객체의 최상위 타입이다. 모든 기본값의 최상위 타입은 Any다.

Unit

Unit 타입은 다른 언어에서의 void와 유사한 역할을 한다. 함수가 어떤 값을 반환하지 않을 때 사용된다. Unit은 생략될 수 있으며, 실제로 대부분의 경우에는 생략된다

fun printMessage(message: String): Unit {
    println(message)
}

Nothing

함수가 예외를 던지거나 무한 루프 등의 비정상적인 종료를 하는 경우를 표현하는 데 사용된다. Nothing은 어떤 값을 리턴하지 않는 함수의 반환 타입으로 사용되며, 정상적인 종료가 보장되지 않는 상황을 나타낸다. Nothing 타입은 모든 다른 타입의 하위 타입이기 때문에, 예외를 던지거나 무한 루프 등에서 정상적인 종료를 보장할 수 없는 상황에서 사용될 수 있다.

Type Alias

기존 타입에 대한 새로운 이름을 지정하는 기능으로 코드의 가독성을 높이고 코드 변경을 용이하게 한다.

typealias StringFilter = (String)-> Boolean

private fun filterStrings(strings: List<String>, filter: StringFilter) {
}

data class UltraSuperGuardianTribe()

typealias USGTMap = Map<String, UltraSuperGudianTribe>

as import

패키지의 일부만을 가져오는 방법 중 하나로 패키지 내의 특정 요소들에 대해 충돌을 방지하거나 더 간결한 코드를 작성할 수 있다.

import kotlin.random.Random as KRandom

fun generateRandomNumber(): Int {
    return KRandom.nextInt(1, 10)
}

 

연산자


in 연산자

컬렉션이나 범위에 포함되어 있는지 판단한다.

println(1 in numbers)
println(score in 0..100)

a.. b

범위 객체를 생성한다. IntRange는 IntProgression이라는 객체를 상속한다. IntProgression은 시작값, 끝값, 공차가 존재하는 등차수열을 의미한다.

val range : IntRange = 1..10
val intProgression1 : IntProgression = 10 downTo 1 step 3
val intProgression2 = 10.downTo(1).step(3)

println(intProgression1 == intProgression2) // true

String Index

val str = "ABCDE"
val ch = str[1]

비교연산자

비교 연산자 사용 시 compareTo()를 호출해 준다

fun main(args: Array<String>) {
    val value = A(19) > A(10)
}

class A(
    val value: Int,
) {
    operator fun compareTo(a: A): Int {
        return this.value - a.value
    }
}

연산자 오버라이딩

fun main(args: Array<String>) {
    val value = A(19) + A(10)
}

data class A(
    val value: Int,
) {
    operator fun plus(other: A): A {
        return A(other.value + this.value)
    }
}

 

조건문 & 반복문


if

자바에서 if-else는 Statement이지만 코틀린에서는 Expression이다.
Statement는 프로그램의 문장, 하나의 값으로 도출되지 않는다. Expression 하나의 값으로 도출되는 문장
코틀린에는 3항 연산자가 없다.

val value = if (19 > 10) {
        true
    } else {
        false
    }

when

switch 문이 when문으로 대체되었고 더 강력한 기능을 갖고 있다.

when(값){
    Expression -> 구문
}

for-each

// 3부터 1까지 감소
for (i in 3 downTo 1){
	println(i) // 3,2,1
}

// 2씩 증가
for (i in 1..5 step 2){
	println(i) // 1,3,5
}
// 10부터 1까지 2씩 감소
for (i in 10 downTo 1 step 2) {
    println(i)
}

 

Lable & Jump

코드 블록에 이름을 붙이는 데 사용된다. 주로 중첩된 반복문이나 중첩된 흐름 제어 구조에서 특정 블록을 식별하는데 활용된다.

outer@ for (i in 1..5) {
    inner@ for (j in 1..5) {
        if (i == 3 && j == 3) {
            println("Breaks at i=$i, j=$j")
            break@outer //outer 까지 빠져나간다.
        }
    }
}

fun example() {
    val list = listOf(1, 2, 3, 4, 5)

    list.forEach {
        if (it == 3) {
            return@forEach // 함수 전체가 아닌 람다를 빠져나가는데 사용된다.
        }
        println(it)
    }

    println("Done")
}

 

예외


코틀린에서는 Checked Exception과 Unchecked Exception을 구분하지 않는다. 모두 Unchecked Exception이다.

use

try-resources가 없다. use라는 inline 확장함수를 사용해야 한다.

fun readFile(path: String) {
    BufferedReader(FileReader(path))
        .use { reader -> println(reader.readLine()) }
}

 

 

함수


named 파라미터

코틀린에서 자바 함수를 가져다 사용할 때는 named argument를 사용할 수 없다

가변인자

vararg 지시어를 사용하여 메서드를 정의하고 스프레드 연산자(*)를 붙여 사용한다.

fun main(args: Array<String>) {
    val array = arrayOf("A", "B", "C")
    printAll(*array)
}

fun printAll(vararg string: String) {
    for (s in string) {
        println()
    }
}

확장 함수

자바로 만들어진 라이브러리를 유지보수, 확장할 때 코틀린 코드를 덧붙이기 위해 만들어졌다. 클래스 안에 있는 메서드처럼 호출하지만 함수는 밖에 만들 수 있다.

  •  확장함수는 클래스에 있는 private 또는 protected 멤버를 가져올 수 없다. 캡슐화가 깨지기 때문이다.
  •  같은 이름의 멤버함수가 있다면 멤버함수가 우선적으로 호출된다.
  •  확장함수가 오버라이드 된다면 변수의 타입의 확장함수가 호출된다.
val str: String = "ABC"

fun String.lastChat():Char{
    return this[this.length - 1]
}

// 자바에서 사용할 때
public static void main(String[] args){
	StringUtilsKt.lastChar("ABC");
}

infix 함수

downTo, step은 중위 호출 함수다.

val intProgression1 : IntProgression = 10 downTo 1 step 3
val intProgression2 = 10.downTo(1).step(3)

println(intProgression1 == intProgression2) // true

public infix fun Int.downTo(to: Int): IntProgression {
    return IntProgression.fromClosedRange(this, to, -1)
}

inline 함수

함수가 호출되는 대신, 함수를 호출한 지점에 함수 본문을 그대로 삽입하는 방식이다.

함수 호출 방식은 함수 호출에 필요한 코드가 메모리에 한 번만 저장된다. 하지만 함수 호출 시, 호출 스택에 인자와 반환 주소등을 저장하고 호출하고 돌아오는 오버헤드가 발생한다.

inline 함수는 함수 호출 지점마다 함수 본문이 그대로 삽입되므로, 코드의 양이 늘어나게 되어 더 많은 메모리를 사용한다. 하지만 함수 호출에 따른 오버헤드가 없어진다.

반복문 내에서 자주 호출되는 작은 함수의 경우, inline 함수를 사용하여 호출 오버헤드를 줄여 성능을 향상시킬 수 있다.

고차 함수와 람다 표현식의 경우에도 inline 함수를 사용하여 오버헤드를 줄일 수 있다.

지역 함수

함수로 추출하면 좋을 것 같은데, 이 함수를 지금 함수 내에서만 사용하고 싶을 때 사용한다. 코드가 복잡해진다.

takeIf & takeUnless

단일 객체에 적용하는 filter라고 생각하면 될 것 같다.

takeIf는 주어진 조건(predicate)을 만족하면 그 값을 반환하고, 그렇지 않으면 null을 반환한다. 주로 단일 객체에 대한 조건 검사를 할 때 사용된다.

takeUnless는 takeIf와 반대로 동작한다. 주어진 조건(predicate)을 만족하지 않으면 그 값을 반환하고, 만족하면 null을 반환한다.

val number = 42

val result = number.takeIf { it > 50 }
// null

val result = number.takeUnless { it > 50 }
// 42

 
 

스코프 함수


람다를 사용해 일시적인 영역을 만들고 코드를 더 간결하게 만들거나, method chaining에 활용하는 함수이다.

  it 사용 this 사용
람다의 결과를 반환 let run
객체를 반환 also apply
  with  

 

// let은 일반 함수를 받는다.
public inline fun <T, R> T.let(block: (T) -> R): R {
}

// run은 확장 함수를 받는다.
// 확장 함수는 자신을 this로 호출하고, 생략할 수 있다.
public inline fun <T, R> T.run(block: T.() -> R): R {
}

 

 

클래스와 상속


생성자와 초기화 블록

코틀린 클래스는 주 생성자와 보조 생성자를 가질 수 있다. 주 생성자는 클래스 헤더에 직접 정의되며, 초기화 블록(init)은 주 생성자와 함께 실행된다.

class Person(
    val name: String,
    var age: Int,
) {
    init {
        if (age < 0) {
            throw IllegalArgumentException("Age cannot be negative")
        }
    }
}

위 예제에서, Person 클래스는 주 생성자를 통해 name과 age를 초기화한다. init 블록은 주 생성자가 호출되는 시점에 실행되며, 이 경우 age가 음수일 경우 예외를 던진다.

주 생성자와 보조 생성자

코틀린 클래스는 여러 보조 생성자를 가질 수 있다. 보조 생성자는 constructor 키워드로 정의되며, 반드시 주 생성자 또는 다른 보조 생성자를 통해 초기화를 위임해야 한다.

class Person(val name: String, var age: Int) {
    init {
        require(age >= 0) { "Age must be non-negative" }
    }

    constructor(name: String) : this(name, 0) {
        // additional initialization
    }
}

Backing Field

Backing field는 프로퍼티의 값을 저장하는 필드이다. 코틀린에서는 프로퍼티를 커스터마이징하여 접근자(getter)와 설정자(setter)를 정의할 수 있다. field 키워드는 프로퍼티의 backing field에 접근하기 위해 사용된다.

class Person(
    name: String,
    var age: Int = 1,
) {
    private val name = name
        get() = field.uppercase() // field는 name의 backing field를 참조합니다

    // 이를 대신 사용할 것을 추천
    val upperCaseName: String
        get() = this.name.uppercase()
}

위 예제에서, name 프로퍼티는 항상 대문자로 반환된다. field 키워드는 무한 루프를 방지하기 위해 사용되며, 프로퍼티의 실제 값을 참조한다.

상속

상속을 사용할 때 주의해야 할 점은, 상위 클래스에서 open 프로퍼티에 접근할 경우, 하위 클래스의 구현에 의해 영향을 받을 수 있다는 것이다. 이는 생성자나 초기화 블록에서 상위 클래스의 open 프로퍼티를 사용할 때 문제가 될 수 있다.

open class Parent {
    open val message: String = "Parent"
    init {
        println(message) // 이 시점에 하위 클래스의 message가 호출될 수 있음
    }
}

class Child : Parent() {
    override val message: String = "Child"
}

fun main() {
    val child = Child() // "Child"가 출력됨
}
  • 초기화 순서 문제: Parent 클래스의 초기화가 완료되기 전에 Child 클래스의 message 프로퍼티가 호출된다. 이 시점에서 Child 클래스의 초기화가 완전히 이루어지지 않았기 때문에, 만약 Child 클래스의 초기화가 복잡하거나 다른 종속성을 필요로 한다면 예기치 않은 동작이 발생할 수 있다.
  • 안정성 문제: 초기화 순서가 복잡해질수록, 특히 상속 계층이 깊어질수록 이러한 동작을 예상하고 코드의 안정성을 보장하기 어려워진다. 의도하지 않은 값이나 상태가 사용될 수 있다.

상속에서 Open 프로퍼티와 초기화 순서

상위 클래스의 초기화 블록에서 open 프로퍼티에 접근하는 것은 권장되지 않는다. 대신 안전한 초기화를 보장하기 위해 하위 클래스의 초기화가 완료된 후에 프로퍼티를 사용해야 한다.

open class Parent {
    open val message: String = "Parent"
    fun printMessage() {
        println(message)
    }
}

class Child : Parent() {
    override val message: String = "Child"
}

fun main() {
    val child = Child()
    child.printMessage() // 안전하게 호출됨
}

이 방식으로 printMessage 메서드를 통해 하위 클래스의 초기화가 완료된 후에 message를 사용하게 할 수 있다.

lateinit var

lateinit는 주로 의존성 주입(Dependency Injection) 또는 단위 테스트와 같이 나중에 초기화할 필요가 있는 경우에 사용된다. 

  • 초기화 검사: lateinit 프로퍼티가 초기화되었는지 확인하려면 ::property.isInitialized를 사용한다. 이를 통해 초기화 여부를 안전하게 확인할 수 있다.
  • Null 안전성: lateinit 프로퍼티는 null 값을 가질 수 없다. 만약 null을 할당하려면 var text: String? = null과 같이 nullable 타입을 사용해야 한다.
  • 초기화 전에 접근 시 예외 발생: lateinit 프로퍼티를 초기화하기 전에 접근하려고 하면 UninitializedPropertyAccessException 예외가 발생한. 따라서 초기화 여부를 반드시 확인해야 한다.
  • 기본형(primitive) 타입에는 사용 불가: lateinit은 객체 타입에만 사용할 수 있으며, Int, Double, Boolean 등의 기본형 타입에는 사용할 수 없다. 기본형 타입을 늦은 초기화가 필요할 경우에는 nullable 타입을 사용하거나 다른 설계 패턴을 고려해야 한다.
class Example {
    lateinit var text: String

    fun initializeText() {
        text = "Hello, World!"
    }

    fun printText() {
        if (::text.isInitialized) {
            println(text)
        } else {
            println("text is not initialized")
        }
    }
}

fun main() {
    val example = Example()
    example.printText() // "text is not initialized"
    example.initializeText()
    example.printText() // "Hello, World!"
}

 

위임프로퍼티


위임 프로퍼티(Delegated Properties)

위임 프로퍼티를 사용하면 프로퍼티의 getter와 setter 구현을 다른 객체에 위임할 수 있다.

기본 제공 위임 프로퍼티는 lazy, observable, vetoable이 있다.

lazy

lazy는 Kotlin에서 지연 초기화를 위해 사용되는 프로퍼티 delegate이다. 이를 통해 프로퍼티의 초기화를 처음 접근할 때까지 지연시킬 수 있다. 복잡한 초기화나 비용이 큰 연산을 필요할 때 유용하게 사용된다. 예를 들어, 데이터베이스 연결, 네트워크 요청 등 초기화 시간이 긴 작업을 필요할 때 lazy를 활용할 수 있다.

  • thread-safe 하게 동작한다. 여러 스레드에서 동시에 접근되더라도 처음 초기화 시에만 실행되며, 이후에는 초기화된 값을 반환한다.
  • val로만 사용할 수 있다. 즉, var로 선언된 프로퍼티에는 lazy를 사용할 수 없다.
  • 기본형(primitive) 타입에는 사용할 수 없다. 초기화할 값이 객체여야 한다.
class DatabaseHandler {
    val databaseConnection: Connection by lazy {
        // 데이터베이스 연결 초기화 코드
        DriverManager.getConnection(url, user, password)
    }

    fun fetchData(): ResultSet {
        // 데이터베이스 연결을 사용하여 데이터를 가져옴
        return databaseConnection.createStatement().executeQuery("SELECT * FROM table")
    }
}

observable

프로퍼티는 값이 변경될 때마다 특정 로직을 실행할 수 있게 해준다. 초기값과 변경 리스너를 받아들인다. 변경 리스너는 프로퍼티, 이전 값, 새로운 값을 인자로 받는다.

import kotlin.properties.Delegates

var observableValue: String by Delegates.observable("Initial") { prop, old, new ->
    println("Property ${prop.name} changed from $old to $new")
}

fun main() {
    observableValue = "New Value" // "Property observableValue changed from Initial to New Value" 출력
}

vetoable

프로퍼티는 값이 변경될 때 특정 조건을 검사하고, 조건을 만족하는 경우에만 변경을 허용한다. 초기값과 조건을 검사하는 람다를 받아들인다. 람다는 프로퍼티, 이전 값, 새로운 값을 인자로 받아 Boolean 값을 반환하며, true일 경우에만 값이 변경된다.

import kotlin.properties.Delegates

var vetoableValue: Int by Delegates.vetoable(0) { _, old, new ->
    new >= old // 새로운 값이 이전 값보다 크거나 같을 때만 허용
}

fun main() {
    vetoableValue = 10 // 허용됨
    println(vetoableValue) // 10
    vetoableValue = 5 // 거부됨
    println(vetoableValue) // 여전히 10
}

위임 구현(Delegation)

위임 구현은 클래스가 특정 인터페이스를 구현할 때, 실제 구현을 다른 객체에 위임하는 방식이다.

interface Printer {
    fun print()
}

class SimplePrinter : Printer {
    override fun print() {
        println("Simple Printer")
    }
}

class AdvancedPrinter(private val delegate: Printer) : Printer by delegate

fun main() {
    val simplePrinter = SimplePrinter()
    val advancedPrinter = AdvancedPrinter(simplePrinter)

    advancedPrinter.print() // "Simple Printer" 출력
}

 

 

접근 제어자


클래스, 멤버, 생성자

public 기본값, 모든 곳
protected 선언된 클래스 또는 하위 클래스
internal 같은 모듈
private 선언된 클래스

 

kt 파일

public 기본값, 모든 곳
protected 파일에는 사용 불가능
internal 같은 모듈
private 같은 파일

 
internal은 바이트 코드 상 public이 된다. 때문에 자바 코드에서는 코틀린 모듈의 internal을 가져올 수 있다.
자바는 같은 패키지의 코틀린 protected를 가져올 수 있다.

 

static 함수와 변수


companion object

하나의 객체로 간주되며, 이름을 붙일 수 있고 인터페이스를 구현할 수도 있다. 또한, Java의 static 클래스 멤버처럼 사용할 수 있다.

class Person private constructor(
    private val name: String,
    private val age: Int,
) {
    companion object Factory : Log {
    	private val MIN_AGE = 0            // 런타임 시에 할당
        private const val MIN_AGE = 0      //컴파일 시에 할당
        
        @JvmStatic
        fun newBaby(name: String): Person {
            return Person(name, MIN_AGE)
        }

        override fun log() {
            println("LOG")
        }
    }
}
@JvmStatic 애노테이션은 Kotlin의 컴패니언 객체나 객체 선언의 메서드와 프로퍼티를 Java에서 정적 멤버처럼 사용할 수 있게 한다. 이를 통해 Java 코드에서 더 쉽게 접근할 수 있으며, 상호 운용성을 개선한다. 예를 들어, @JvmStatic이 붙은 메서드는 Java에서 static 함수처럼 호출할 수 있다.

싱글톤

object를 사용해 싱글턴 패턴을 구현할 수 있다.

object MySingleton {
    fun doSomething() {
        println("Doing something")
    }
}

익명 클래스

익명 클래스를 생성할 때는 "object: 타입이름"을 이용한다.

fun main(args: Array<String>) {
    moveSomething(object : Movable {
        override fun move() {
            println("move")
        }
    })
}

private fun moveSomething(movable: Movable) {

}

interface Movable {
    fun move()
}

 
 

다양한 클래스


Data class

계층 간의 데이터를 전달하기 위한 DTO로 생성자와 getter, equals, hashCode, toString 메서드를 가지고 있다.
componentN 함수도 가지고 있어서 구조 분해가 가능하다. 다른 클래스도 구조분해를 사용하고 싶다면 componentN 함수를 구현하면 된다.
 

Sealed class, Sealed Interface

컴파일 타임 때 하위 클래스의 타입을 모두 기억한다. 즉, 런타임 때 클래스 타입이 추가될 수 없다. 컴파일러가 하위 타입을 모두 기억하기 때문에 when 절에서 Enum처럼 다룰 수 있다.
 

 

람다


람다를 만드는 2가지 방법

람다를 만드는 방법은 두 가지가 있고 {} 방법이 더 많이 사용된다.

val isA = fun(string: String): Boolean {
    return string == "a" || string == "A"
}

val isB = { string: String -> string == "b" || string == "B" }

 
함수를 호출할때 마지막 파라미터인 람다를 쓸 때는 소괄홀 밖으로 람다를 뺄 수 있다.

fun filterStrings(strings: List<String>, filter: (String) -> Boolean): List<String> {
    val results = mutableListOf<String>()
    for (string in strings) {
        if (filter(string)) {
            results.add(string)
        }
    }
    return results
}

val strings = listOf("a", "b", "A", "B")

// 람다를 소괄호 밖으로 뺀 호출 방식
val filteredStrings = filterStrings(strings) { it == "a" || it == "A" }

 
람다 내의 마지막 표현식 결과는 람다의 반한값이다.

val filteredStrings = filterStrings(strings) {
    println(it)
    it == "a" || it == "A"
}

 

 

배열과 컬렉션


배열

val array = arrayOf(100, 200)

// 값 추가
val newArray = array.plus(300)

// 인덱스만 출력
for (index in array.indices) {
    println(index)
}

// 인덱스와 값 출력
for ((idx, value) in array.withIndex()) {
    println("$idx $value")
}

List, Set, Map

코틀린에는 가변(Mutable) 컬렉션과 불변 컬렉션이 있다. 불변 컬렉션이라도 내부 객체의 필드는 변경할 수 있다.

// 불변 리스트
val numbers1 = listOf(100, 200)
// 가변 리스트
val numbers2 = mutableListOf(100, 200)
// 불변으로 변경
val unmodifiableList = Collections.unmodifiableList(numbers2)
// 에러 발생: unmodifiableList.add(1)

// 빈 리스트
val emptyList = emptyList<Int>()

// 가변 맵
val map = mutableMapOf<Int, String>()
map[1] = "Mon"
map[2] = "Tue"

// 불변 맵
val immutableMap = mapOf(1 to "Mon", 2 to "Tue")

for ((key, value) in map.entries) {
    println("$key $value")
}

컬렉션 함수

val numbers = listOf(1, 2, 3, 4, 5, 6)

// 인덱스와 함께 필터링
val filtered = numbers.filterIndexed { index, value ->
    index % 2 == 0 && value % 2 != 0
}

// 인덱스와 함께 매핑
val indexedSum = numbers.mapIndexed { index, value ->
    index + value
}

// null이 아닌 경우에만 매핑
val oddSquares = numbers.mapNotNull {
    if (it % 2 != 0) it * it else null
}

// 모든 요소가 조건을 만족하는지 확인
val allPositive = numbers.all { it > 0 }

// 조건을 만족하는 요소가 하나도 없는지 확인
val noNegative = numbers.none { it < 0 }

// 하나라도 만족하는지 확인
val hasEven = numbers.any { it % 2 == 0 }

// 조건을 만족하는 요소의 개수
val countOfEvens = numbers.count { it % 2 == 0 }

// 주어진 키 함수에 따라 정렬
val strings = listOf("apple", "banana", "orange", "kiwi")
val sortedByLength = strings.sortedBy { it.length }
val sortedByLengthDesc = strings.sortedByDescending { it.length }

// 주어진 키 함수의 결과에 따라 중복을 제거
val distinctBySquares = numbers.distinctBy { it * it }

// 조건을 만족하는 첫 번째/마지막 요소 반환
val firstEven = numbers.first { it % 2 == 0 }
val firstOrNull = numbers.firstOrNull { it % 2 == 0 }
val lastOdd = numbers.last { it % 2 != 0 }
val lastOrNull = numbers.lastOrNull { it % 2 != 0 }

컬렉션 전환

// 람다에 따라 그룹화하여 맵으로 반환
val groupedByOddEven = numbers.groupBy { it % 2 == 0 }
// 결과: {false=[1, 3, 5], true=[2, 4, 6]}

// 람다의 결과를 키로 사용하는 맵을 반환 (중복된 키는 덮어쓰기 됨)
val lengthMap = strings.associateBy { it.length }
// 결과: {5=apple, 6=orange, 4=kiwi}

// 람다의 결과로 생성된 여러 리스트를 단일 리스트로 평탄화
val flattenedSquares = numbers.flatMap { listOf(it * it, it * it * it) }
// 결과: [1, 1, 4, 8, 9, 27, 16, 64]

 

 

 

 

 

'JVM > Kotlin' 카테고리의 다른 글

Spring 3.0 이상 Querydsl 설정  (0) 2023.09.27
@Value 어노테이션 사용시 발생 오류  (0) 2023.08.27