본문 바로가기

2024-겨울 프로젝트 관련 공부

[웹서비스 프로젝트/백엔드] Kotlin 기본 공부

참고한 영상: https://www.youtube.com/watch?v=F9UC9DY-vIU

 

1. main function 작성

fun main() {}

 

 

2. Variable(변수)

2-1. 변수의 종류

val과 var variable 존재. val은 reassign 불가, var은 reassign 가능하다는 차이점 존재. function 밖에 전역변수로 선언 가능

var greeting: String = "hello"

fun main() {
    val name1: String = "Nate"
    //name = "" // cannot reassign

    var name2: String = "Nate"
    name2 = ""

    println(greeting)
    println(name1)

    greeting = "hi"
    println(greeting)
    println(name1)
}

 

2-2. Nullable 변수

Kotlin 내 변수들은 "non null by default"(자바와 가장 큰 차이)

-> 그냥 String 타입(String으로 선언)과 Nullable String 타입(String?으로 선언) 간의 극명한 차이가 존재함. 

-> 일반 String 타입에 null을 넣을 수 없음

val name: String = "Nate"
var greeting: String? = "hello" //nullable

fun main() {
    println(greeting)
    println(name)

    greeting = null
    println(greeting)
}

 

2-3. Omitting variable type

Kotlin은 assign 하려는 value의 type을 인식할 수 있음 -> type을 적어주지 않아도 해당 타입으로 유지 가능 (non-nullable by default)

하지만 null을 assign하는 경우 type을 알 수 없기 때문에 명시해줘야 함

val name = "Nate"
var greeting: String? = null

fun main() {
    println(greeting)
    println(name)

    greeting = "hi"
    println(greeting)
}

 

3. when statement

아래 코드는 greeting이 null이면 "Hi"를, 그렇지 않을 경우 greeting을 출력한 후 name을 출력하는 코드이다. 

Java와 달리 switch문이 없는 대신 when이 있다고 생각하면 된다. 

fun main() {
    when (greeting) {
        null -> println("Hi")
        else -> println(greeting)
    }
    println(name)
}

 

변수에 바로 대입하는 방식으로 활용하면 아래와 같다.

fun main() {
    val greetingToPrintIf = if(greeting != null) greeting else "Hi"
    val greetingToPrintWhen = when (greeting) {
        null -> "Hi"
        else -> greeting
    }
    println(greetingToPrintWhen)
    println(name)
}

 

4. Function Basic

4-1. return 타입 설정하기

기본 함수 형태는 아래와 같다.

fun getGreeting(): String {
    return "Hello Kotlin"
}

 

return type을 Unit으로 적거나 main function과 같이 return type을 생략하고 적을 수 있다. 

fun sayHello(): Unit {
    println(getGreeting())
}

 

4-2. Single Expression

함수가 코드 한 줄로 구성되는 경우 아래와 같이 작성할 수 있다. 리턴타입까지 생략하는 것도 가능하다.

fun getGreeting(): String = "Hi"
fun getGreeting() = "Hi"

 

4-3. parameter 설정하기

함수에서 parameter는 아래와 같이 사용할 수 있다. 

fun sayHello(greeting:String, itemToGreet:String)
    = println("$greeting, $itemToGreet")

fun main() {
    sayHello("Hey", "Kotlin")
    sayHello("Hello", "World")
}

 

5. Collection Data Type

5-1. array type

fun main() {
    val interestingThings = arrayOf("Kotlin", "Programming", "Comic Books")
    println(interestingThings.size)
    println(interestingThings[0])
    println(interestingThings.get(0)) // 윗줄과 같은 표현

    // 모든 element에 접근하는 세가지 방법
    for (interestingThing in interestingThings) {
        println(interestingThing)
    }
    interestingThings.forEach {
        println(it)
    }
    interestingThings.forEach {interestingThing->
        println(interestingThing)
    }

    // 인덱스 유지하면서 전체 element에 접근
    interestingThings.forEachIndexed{ index, interestingThing ->
        println("$interestingThing is at index $index")
    }
    
    // 값 수정
    interestingThings[0] = "Java"
    println("is the list contains 'Kotlin'?: " + interestingThings.contains("Kotlin"))
}

 

5-2. list type

array와 유사하지만 수정이 불가능하다는 차이점이 있다. 

val interestingThings = listOf("Kotlin", "Programming", "Comic Books")
interestingThings.forEach {
    println(it)
}

 

5-3. map type

key와 value값을 설정할 수 있다. 

val map = mapOf(1 to "a", 2 to "b", 3 to "c")
map.forEach { key, value -> println("$key -> $value")}

 

5-4. mutable type

일반적인 list와 map은 immutable함. 따라서 mutableList와 mutableMap을 사용해야, add나 put function을 이용하여 element를 추가할 수 있음

fun main() {
    val interestingThings = mutableListOf("Kotlin", "Programming", "Comic Books")
    interestingThings.add("Dogs")

    val map = mutableMapOf(1 to "a", 2 to "b", 3 to "c")
    map.put(4, "d")
    map.forEach { key, value -> println("$key -> $value")}
}

 

5-5. sayHello function collection type 이용해서 수정해 보기

fun sayHello(greeting:String, itemsToGreet:List<String>) {
	itemsToGreet.forEach { itemsToGreet ->
    	println("$greeting $itemToGreet")
    }
}

fun main() {
	val interestingThings = listOf("Kotlin", "Programming", "Comic Books")
    sayHello("Hi", interestingThings)
}

 

 

6. 함수 Parameter 이용하기

6-1. vararg(가변인자) 사용하기

가변인자를 사용하면 함수를 호출할 대 인자 개수를 유동적으로 지정할 수 있다. 만약 가변인자 자리에 이미 생성된 리스트를 넣고 싶다면 변수 이름 앞에 특수부호 *를 붙이면 된다.

fun sayHello(greeting:String, vararg itemsToGreet:String) {
    itemsToGreet.forEach { itemToGreet ->
        println("$greeting $itemToGreet")
    }
}

fun main() {
    val interestingThings = arrayOf("Kotlin", "Programming", "Comic Books")

    sayHello("Hi") // vararg에 아무것도 넣지 않으면 빈 리스트로 인식
    sayHello("Hi", "Kotlin") // 하나만 넣는 경우
    sayHello("Hi", "Kotlin", "Java", "Python") // 다수개 넣는 경우
    sayHello("Hi", *interestingThings) // 기존 리스트를 넣는 경우
}

 

6-2. named 인자 사용하기

named 인자를 사용하면 순서에 관계없이 parameter를 지정하여 함수를 호출할 수 있다. 단, 하나라도 named로 사용할 경우 모두 named로 사용해야 한다.

fun sayHello(greeting:String, vararg itemsToGreet:String) {
    itemsToGreet.forEach { itemToGreet ->
        println("$greeting $itemToGreet")
    }
}

fun main() {
    val interestingThings = arrayOf("Kotlin", "Programming", "Comic Books")
    sayHello(itemsToGreet = *interestingThings, greeting = "Hi")
}

 

6-3. default 설정하기

default를 설정하면 해당 parameter 없이도 함수를 호출할 수 있다. 

fun greetPerson(greeting: String = "Hello", name: String = "Kotlin") = println("$greeting $name")

fun main() {
    greetPerson(name = "Nate")
    greetPerson("Hi")
    greetPerson()
}

 

 

7. class

7-1. Constructor(생성자)

생성자 내 특별한 내용이 없으면, 정의하지 않아도 기본적으로 제공된다

//Person.kt file
class Person

//Main.kt file
fun main() {
    val person = Person()
}

 

constructor에서 parameter를 받아 properties를 initialize 할 수 있다 (init을 이용할 수도 있고, 선언 시 바로 초기화하는 것도 가능)

class Person(_firstName: String, _lastName: String) {
    val firstName: String = _firstName
    val lastName: String = _lastName

//    init {
//        firstName = _firstName
//        lastName = _lastName
//    }
}

 

constructor 내부에서 따로 선언하지 않고, parameter를 받을 때 val을 적어주면, 바로 property로 사용 가능하다. 

class Person(val firstName: String, val lastName: String) {
}

 

main function에서는 아래와 같이 Person class를 활용 가능하다. 

fun main() {
    val person = Person("Nate", "Ebel")
    println("${person.firstName} ${person.lastName}")
}

 

 

앞서 선언한 primary construtor 외의 secondary constructor를 생성 가능하다. default parameter 설정 목적 등으로 사용이 가능하다. 하지만 그냥 paramter에 default를 설정해 주는 방식도 사용 가능하다.

class Person(val firstName: String, val lastName: String) {
    // secondary constructor
    constructor(): this("Peter", "Parker") {
        println("secondary constructor")
    }
}
// primary constructor에 default 설정
class Person(val firstName: String = "Peter", val lastName: String = "Parker") {
}

 

7-3. Getter와 Setter

자바와 달리 코틀린에서는 getter와 setter가 자동으로 만들어진다. (val의 경우 getter만, var의 경우 getter와 setter 모두) 하지만 만약 getter와 setter 내에서 특별히 무언가를 실행시키고 싶다면 아래와 같이 만들 수 있다. 

class Person(val firstName: String = "Peter", val lastName: String = "Parker") {
    var nickName: String? = null
        set(value) {
            field = value
            println("the new nickname is $value")
        }
        get() {
            println("the returned value is $field")
            return field
        }
}

 

위 코드에서 설정한 setter와 getter는 아래와 같이 실행시킬 수 있고, 그러면 위에 설정한 setter 내 코드들이 실행되면서, nickname이 "Shade" 및 "New NickName"으로 변경되고, println 함수 내 문장이 출력된다. 또, getter에서는 println 내 문장이 출력되고, 다시 nickname이 리턴된다.

fun main() {
    val person = Person()
    person.nickName = "Shade" // mutable property
    person.nickName = "New Nickname"
    println(person.nickName)
}

 

7-4. method 만들기

class Person(val firstName: String = "Peter", val lastName: String = "Parker") {
    var nickName: String? = null

    fun printInfo() {
        val nickNameToPrint = nickName ?: "no nickname"
        // nickName이 null이면 "no nickname"으로 설정
        println("$firstName ($nickNameToPrint) $lastName")
    }
}

 

7-5. class 접근제한자

class 자체, 내부 요소들에 적용하여 접근을 제한할 수 있음. (protected는 class 자체에는 적용 불가)

public(default)
internal : available within the module
private : only available within the file
protected: only available within that class or any subclasses

 

 

8. Interfaces

8-1. Interface 기본

interface는 기본적으로 자바에서와 유사하게 아래 코드로 정의할 수 있다. 자바에서와는 달리 override annotation을 강제한다.

interface PersonInfoProvider {
    fun printInfo(person: Person)
}

class BasicInfoProvider : PersonInfoProvider {
    // override 지우면 오류
    override fun printInfo(person: Person) {
        println("basicInfoProvider")
        person.printInfo()
    }
}

fun main() {
    val provider = BasicInfoProvider()
    provider.printInfo(Person())
}

 

하지만, Kotlin의 interface는 자바와 크게 두 가지 차이점이 있는데, 첫 번째는 property 선언이 가능하다는 것이고, 두 번째는 구현이 있는 method도 정의 가능하다는 것이다. 하지만 property를 선언할 때 초기화를 하는 것은 불가능하다. 이미 인터페이스에서 구현된 method도 class 내에서 얼마든지 override가 가능하다.

interface PersonInfoProvider {
    val providerInfo : String

    fun printInfo(person: Person) {
        println("basicInfoProvider")
        person.printInfo()
    }
}

class BasicInfoProvider : PersonInfoProvider {
    override val providerInfo: String
        get() = "BasicInfoProvider"
}

fun main() {
    val provider = BasicInfoProvider()
    provider.printInfo(Person())
}

 

8-2. multiple interfaces with a single class

interface PersonInfoProvider {
    val providerInfo : String

    fun printInfo(person: Person) {
        println("basicInfoProvider")
        person.printInfo()
    }
}

interface SessionInfoProvider {
    fun getSessionId() : String
}

class BasicInfoProvider : PersonInfoProvider, SessionInfoProvider {
    override val providerInfo: String
        get() = "BasicInfoProvider"

    override fun printInfo(person: Person) {
        super.printInfo(person)
        println("additional print statement")
    }

    override fun getSessionId(): String {
        return "Session"
    }
}

 

8-3. type checking & type casting

parameter에서 PersonInfoProvider 타입으로 받았지만, if문을 통해 SessionInfoProvider인 것을 확인했으므로 따로 explicitly cast 하지 않아도 smart cast를 해줘서 SessionInfoProvider의 property나 function에 접근이 가능함. 

fun checkTypes(infoProvider: PersonInfoProvider) {
    if (infoProvider !is SessionInfoProvider) {
        println("not a session info provider")
    } else {
        println("is a session info provider")
        infoProvider.getSessionId() // smart cast
    }
}

 

 

9. Inheritance

코틀린에서 classes are closed by default. 따라서 inherit 할 수 없다. 만약 상속을 하고 싶다면, open keyword를 써줘야 한다. 해당 클래스에서 정의된 property나 function에도 open을 붙여야 상속 가능하다. 해당 property에 protected 접근제한자를 추가하면, 자식클래스에서만 접근이 가능하고, 다른 API에서는 사용이 불가하다. (같은 파일 내 다른 함수 및 클래스에서도 접근 불가)

// class open
open class BasicInfoProvider : PersonInfoProvider, SessionInfoProvider {
    override val providerInfo: String
        get() = "BasicInfoProvider"

    // property open
    protected open val sessionPrefix = "Session"

    override fun printInfo(person: Person) {
        super.printInfo(person)
        println("additional print statement")
    }

    override fun getSessionId(): String {
        return sessionPrefix
    }
}
// child class
class FancyInfoProvider : BasicInfoProvider() {
    override val providerInfo: String
        get() = "Fancy Info Provider"

    override fun printInfo(person: Person) {
        super.printInfo(person)
        println("Fancy Info")
    }

    override val sessionPrefix: String
        get() = "Fancy Session"
}

 

10. Object

10-1. Object Expression

open  class나 interface에 대해 새로운 하위 클래스를 명시적으로 선언하지 않고, 객체를 만들어 사용하는 방식이다. Android 개발 시 click listener 등에 유용하게 사용될 수 있다. 

val provider = object : PersonInfoProvider {
    override val providerInfo: String
    get() = "New Info Provider"

    fun getSessionId() = "id"
}

 

10-2. Companion Objects

companion object는 클래스 내부의 객체 선언을 위한 object 키워드이다. 클래스 인스턴스 없이 어떤 클래스 내부에 접근하고 싶을 때 선언한다. 

참고 자료: https://junyoung-developer.tistory.com/192 

 

[Kotlin] Kotlin의 싱글톤 패턴(Singleton Pattern)

Kotlin으로 프로그래밍을 하다보니 싱글톤에 대한 개념이 정확하게 서있는 것 같지 않아서, 포스팅을 통해 정리해보려고 한다. 우선 Java의 static과의 차이, object와 companion object의 차이를 중심적으

junyoung-developer.tistory.com

 

아래 예제 코드에서 Entity class의 construct가 private이므로 main function에서 construct가 불가능하다. 따라서 이를 companion object 내 create function으로 construct 하고 있는 것을 볼 수 있다. 또한, compainon object 내에는 property도 정의할 수 있다. 

class Entity private constructor(val id: String) {
    companion object {
        const val id = "id"
        fun create() = Entity("id")
    }
}

fun main() {
    val entity = Entity.Companion.create()
    Entity.id
}

 

companion object는 한 class 당 하나로 제한되기 때문에 따로 이름을 지정하지 않아도 되지만, 만약 이름을 지정하고 싶다면 아래와 같이 할 수 있다. 

class Entity private constructor(val id: String) {
    companion object Factory{
        fun create() = Entity("id")
    }
}

fun main() {
    val entity = Entity.Factory.create()
}

 

companion object에는 다른 interface를 implement 할 수 있다. 

interface IdProvider {
    fun getId(): String
}

class Entity private constructor(val id: String) {
    companion object : IdProvider{
        override fun getId(): String {
            return "123"
        }
        
        const val id = "id"
        fun create() = Entity(getId())
    }
}

fun main() {
    val entity = Entity.Companion.create()
    Entity.id
}

 

10-3. Object Declaration

object EntityFactory {
    fun create() = Entity("id", "name")
}

class Entity(val id: String, val name:String) {
    override fun toString() : String {
        return "id:$id name:$name"
    }
}

fun main() {
    val entity = EntityFactory.create()
    println(entity)
}

 

 

11. Enum Classes

enum class로 정의하며, 내부에 function도 만들 수 있다.

import java.util.*

enum class EntityType {
    EASY, MEDIUM, HARD;

    fun getFormattedName() = name.toLowerCase().capitalize()
}

object EntityFactory {
    fun create(type: EntityType) : Entity {
        val id = UUID.randomUUID().toString()
        val name = when(type) {
            EntityType.EASY -> type.name // 해당 값이 이름("EASY") 할당
            EntityType.MEDIUM -> type.getFormattedName() // 함수 정의한 것처럼 ("Medium") 할당
            EntityType.HARD -> "Hard"
        }
        return Entity(id, name)
    }
}

class Entity(val id: String, val name:String) {
    override fun toString() : String {
        return "id:$id name:$name"
    }
}

fun main() {
    val entity = EntityFactory.create(EntityType.EASY)
    println(entity)

    val mediumEntity = EntityFactory.create(EntityType.MEDIUM)
    println(mediumEntity)
}

 

 

12. Sealed Class

sealed class는 자기 자신이 추상 클래스이고, 자신을 상속받는 여러 서브 클래스를 가질 수 있다. 하지만 지정된 서브 클래스 외의 다른 클래스에서 상속하는 것을 제한하기 때문에, 서브 클래스들은 sealed class의 내부에 정의되어야 한다. 서브 클래스로는 class, data class, object를 모두 가질 수 있다.  abstract class이기 때문에 직접 인스턴스를 생성하는 것은 불가능하다.

enum class의 확장판이라고 생각할 수 있으나, 가장 큰 차이점은 sealed class는 각각 다른 property를 가질 수 있다는 점이다. 상속이 되기 때문에 가능한 일이다. 

참고자료: https://youngdroidstudy.tistory.com/entry/Kotlin-%EC%BD%94%ED%8B%80%EB%A6%B0%EC%9D%98-%08Sealed-Class

 

Kotlin 코틀린의 Sealed Class

Kotlin sealed class 란? 코틀린에서 sealed class는 다른 클래스가 상속을 받지 못하도록 제한하는 클래스이다. 이 클래스는 클래스 계층 구조에서 제한된 개수의 클래스를 나타낼때 사용하게 된다. seale

youngdroidstudy.tistory.com

 

import java.util.*

enum class EntityType {
    HELP, EASY, MEDIUM, HARD;

    fun getFormattedName() = name.toLowerCase().capitalize()
}

object EntityFactory {
    fun create(type: EntityType) : Entity {
        val id = UUID.randomUUID().toString()
        val name = when(type) {
            EntityType.EASY -> type.name // 해당 값이 이름("EASY") 할당
            EntityType.MEDIUM -> type.getFormattedName() // 함수 정의한 것처럼 ("Medium") 할당
            EntityType.HARD -> "Hard"
            EntityType.HELP -> type.getFormattedName()
        }
        return when (type) {
            EntityType.EASY -> Entity.Easy(id, name)
            EntityType.MEDIUM -> Entity.Medium(id, name)
            EntityType.HARD -> Entity.Hard(id, name, 2f)
            EntityType.HELP -> Entity.Help
        }
    }
}

sealed class Entity() {
    object Help : Entity() {
        val name = "Help"
    }
    data class Easy(val id: String, val name: String): Entity()
    data class Medium(val id: String, val name: String): Entity()
    data class Hard(val id: String, val name: String, val multiflier: Float): Entity()
}

fun main() {
    val entity:Entity = EntityFactory.create(EntityType.EASY)
    val msg = when (entity) {
        Entity.Help -> "help class" // Entity.Help는 object이기 때문에 is X
        is Entity.Easy -> "easy class"
        is Entity.Medium -> "medium class"
        is Entity.Hard -> "hard class"
    }
    println(msg)
}

 

추가적으로 when statement 내에서 Entity.Help 앞에만 is를 붙이지 않는 이유는 Entity.Help는 object이기 때문이다. is는 타입 검사에 사용된다. 따라서 Entity.Easy, Entity.Medium, Entity.Hard의 경우 entity가 해당 타입의 인스턴스인지를 검사하는 것이기 때문에 is를 붙인다. 하지만 Entity.Help는 object이므로 해당 타입인지가 아닌 해당 객체와 같은지를 검사해야 한다. 따라서 is 없이 사용하게 된다. 

 

13. Data Class

data class는 다양한 method를 자동으로 생성해 주는 class이다. data class 생성 시 hashCode(), copy(), equals(), toString(), conponentsN()이 함께 생성된다. 

참고자료: https://velog.io/@haero_kim/Kotlin-%EA%B0%90%EB%8F%99-%EC%8B%A4%ED%99%94-Data-Class-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0

 

[Kotlin] 감동 실화 Data Class 알아보기

호랑이 자바 태우던 시절의 고충은 잊어라!

velog.io

import java.util.*

enum class EntityType {
    HELP, EASY, MEDIUM, HARD;

    fun getFormattedName() = name.toLowerCase().capitalize()
}

object EntityFactory {
    fun create(type: EntityType) : Entity {
        val id = UUID.randomUUID().toString()
        val name = when(type) {
            EntityType.EASY -> type.name // 해당 값이 이름("EASY") 할당
            EntityType.MEDIUM -> type.getFormattedName() // 함수 정의한 것처럼 ("Medium") 할당
            EntityType.HARD -> "Hard"
            EntityType.HELP -> type.getFormattedName()
        }
        return when (type) {
            EntityType.EASY -> Entity.Easy(id, name)
            EntityType.MEDIUM -> Entity.Medium(id, name)
            EntityType.HARD -> Entity.Hard(id, name, 2f)
            EntityType.HELP -> Entity.Help
        }
    }
}

sealed class Entity() {
    object Help : Entity() {
        val name = "Help"
    }
    data class Easy(val id: String, val name: String): Entity()
    data class Medium(val id: String, val name: String): Entity()
    data class Hard(val id: String, val name: String, val multiflier: Float): Entity()
}

fun main() {
    val entity1 = EntityFactory.create(EntityType.EASY)
    val entity2 = EntityFactory.create(EntityType.EASY)

    if (entity1 == entity2) {
        println("they are equal")
    } else {
        println("they are not equal")
    } // they are not equal (id is different)

    val entity3 = Entity.Easy("id", "name")
    val entity4 = Entity.Easy("id", "name")
    if (entity3 == entity4) {
        println("they are equal")
    } else {
        println("they are not equal")
    }

    // referential comparison (===) : exact same reference or not
    if (entity3 === entity4) {
        println("referential same")
    } else {
        println("referential different")
    }
}

 

위 코드에서는 entity3과 entity4가 referential different로 나오지만, 만약 Easy data class에서 equal이나 hashCode method를 override 한다면 결과가 달라질 수 있다. 

 

14. Extension Functions / Properties

이미 존재하는 class에 extension functions/properties을 define 할 수 있다.  extension property는 바로 initialize 할 수 없다. 

sealed class Entity() {
    object Help : Entity() {
        val name = "Help"
    }
    data class Easy(val id: String, val name: String): Entity()
    data class Medium(val id: String, val name: String): Entity()
    data class Hard(val id: String, val name: String, val multiflier: Float): Entity()
}

// extension method
fun Entity.Medium.printInfo() {
    println("Medium class: $id")
}

// extension property
val Entity.Medium.info: String
    get() = "some info"
// fun Entity.Medium.printInfo() = "some info" // error

fun main() {
    Entity.Medium("id", "name").printInfo()

    // smart casting 이용
    val entity = EntityFactory.create(EntityType.MEDIUM)
    if (entity is Entity.Medium) {
        entity.printInfo()
        println(entity.info)
    }
}

 

 

15. Advanced Functions

15-1. Function that take functions as parameter values

// put parameter called predicate, which will be a function 
// that takes in a String parameter and returns Boolean
fun printFilteredStrings(list: List<String>, predicate: (String) -> Boolean) {
    list.forEach {
        if (predicate(it)) {
            println(it)
        }
    }
}

fun main() {
    val list = listOf("Kotlin", "Java", "C++", "Javascript")
    printFilteredStrings(list, { it.startsWith("J")})
    printFilteredStrings(list) {
        it.startsWith("K")
    }
}

 

parameter function을 nullable로 설정하고 싶은 경우 아래와 같이 작성한다. 이렇게 하면, function이 들어갈 parameter 자리에 null을 넣을 수 있다.

// function을 nullable로 설정할 경우
fun printFilteredStrings(list: List<String>, predicate: ((String) -> Boolean)?) {
    list.forEach {
        // nullable은 invoke가 불가능하기 때문에 따로 invoke 해줘야 함
        if (predicate?.invoke(it) == true) {
            println(it)
        }
    }
}

fun main() {
    val list = listOf("Kotlin", "Java", "C++", "Javascript")
    printFilteredStrings(list, null)
}

 

function을 variable로 저장하여 parameter로 넣을 수도 있다.

// define a variable of a functional type
val predicate: (String) -> Boolean = {
    it.startsWith("J")
}

fun main() {
    val list = listOf("Kotlin", "Java", "C++", "Javascript")
    printFilteredStrings(list, predicate)
}

 

15-2. Function that returns another function

fun getPrintPredicate() : (String) -> Boolean {
    return { it.startsWith("J") }
}

fun main() {
    val list = listOf("Kotlin", "Java", "C++", "Javascript")
    printFilteredStrings(list, getPrintPredicate())
}

 

15-3. 복잡한 Functional Operation을 간단히 표현하기

fun main() {
    val list = listOf("Kotlin", "Java", "C++", "Javascript", null, null)

    list
        .filterNotNull() // guarantted to be not null
        .filter {
            it.startsWith("J")
        }
        .map {
            it.length  // list 내 String들을 그 길이(int)로 변경
        }
        .forEach {
            println(it)
        }
}
fun main() {
    val list = listOf("Kotlin", "Java", "C++", "Javascript", null, null)

    list
        .filterNotNull() // guarantted to be not null
        .take(3)  // take only first three elements
        //.takeLast(3)
        .associate { it to it.length } // value=length, key=itself
        .forEach {
            println("${it.value}, ${it.key}")
        }
}
fun main() {
    val list = listOf("Kotlin", "Java", "C++", "Javascript", null, null)

    val map = list
        .filterNotNull() // guarantted to be not null
        .take(3)  // take only first three elements
        //.takeLast(3)
        .associate { it to it.length } // value=length, key=itself

    var language = list.first()
    println(language)
    language = list.filterNotNull().last()
    println(language)
    language = list.filterNotNull().find { it.startsWith("Java")} // find first
    //language = list.filterNotNull().findLast { it.startsWith("Java") }
    println(language)

    language = list.filterNotNull().find { it.startsWith("foo") }
    println(language) // 찾고자 하는 게 없으면 null
    language = list.filterNotNull().find { it.startsWith("foo") }.orEmpty() // null 대신 공백
    println(language)
}
728x90