네이버 D2 - Google I/O 2017 참관기 - Kotlin

최창효·2023년 4월 3일
0

기업_IT블로그_리딩

목록 보기
9/14
post-thumbnail

들어가기 전에


Kotlin에 대하여

  • 코틀린 공식 사이트에서는 코틀린을 Statically typed programming language for modern multiplatform applications (최신 멀티플랫폼 애플리케이션을 위한 정적 타입 언어)라고 정의합니다.
    • 정의에서 말했듯이 코틀린은 정적 타입 언어입니다. 코틀린을 사용하다보면 val num = 0처럼 타입을 생략하고 변수를 선언하는 경우가 있는데 이는 동적 타입 언어라서 그런게 아니라 추론을 통해 타입을 생략할 수 있기 때문입니다.
      컴파일 시점에 객체의 타입이 결정되기 때문에 동적 타입 언어보다 안정적인 언어라고 할 수 있습니다. 반면에 동적 타입 언어는 런타임까지 타입 결정을 미룰 수 있기 때문에 유연하다는 장점이 있습니다.
      자바 역시 변수를 선언할 때 타입을 선언하는 정적 타입 언어입니다. 하지만 자바는 런타임 시에 타입이 결정되는 동적 바인딩이 가능합니다. 즉, 컴파일 시 타입이 결정되기는 하지만(정적 타입 언어) 해당 타입으로 올 수 있는 여러 타입 중 어느 타입이 사용될지는 컴파일 시 결정(동적 바인딩)됩니다.
  • 코틀린은 JVM에서 실행됩니다. 이 말은 코틀린이 JVM에서'만' 실행된다는 것과 다른 의미입니다. 코틀린은 JVM외에도 Javascript나 iOS, 임베디드 시스템에도 사용할 수 있습니다. 다만 게시글이 작성된 2017년으로부터 6년이 지난 오늘날도 아직까지는 iOS개발에 코틀린을 적극적으로 활용하고 있지 않습니다.
  • 정적 타입 언어 외에도 코틀린은 다음과 같은 특징을 가지고 있습니다.
    • 간결함: 코틀린은 코드를 직관적이고 간결하게 표현할 수 있으며, 반복되는 코드를 줄일 수 있습니다.
    • 안전함: NPE와 같은 오류를 피할 수 있습니다.
    • 상호 운용성: Java와 100% 호환되며 기존 라이브러리를 활용할 수 있습니다.
    • 도구 친화적: IntelliJ를 만든 JetBrains에서 만든 언어이기 때문에 IntelliJ와의 궁합이 좋습니다.

Introduction To Kotlin

강의 영상은 유튜브에서 확인할 수 있습니다.
여기서는 간단한 예제를 통해 코틀린의 장점을 소개해 주고 있습니다.

간결한 코드

아래 두 코드는 완전히 동일합니다.
자바 코드

package com.example.SimpleKotlinTest.introduction;

public class JavaMoney {

    private int amount;
    private String currency;
    
    public JavaMoney(int amount, String currency) {
        this.amount = amount;
        this.currency = currency;
    }
    public int getAmount() {
        return amount;
    }
    public String getCurrency() {
        return currency;
    }
    @Override
    public int hashCode() {
        return super.hashCode();
    }
    @Override
    public boolean equals(Object obj) {
        return super.equals(obj);
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
    @Override
    public String toString() {
        return "JavaMoney{" +
                "amount=" + amount +
                ", currency='" + currency + '\'' +
                '}';
    }
}

코틀린 코드

package com.example.SimpleKotlinTest.introduction
data class Money(val amount: Int, val currency: String)
  • 자바는 파라미터를 '타입 이름(int amount)'로 하지만 코틀린은 '이름: 타입(amount Int)'으로 합니다.
  • 코틀린은 파라미터를 선언할 때 val 또는 var을 반드시 붙여줘야 합니다.
    • val: 변경 불가능한(read-only) 변수입니다. 자바의 final과 동일합니다.
    • var: 변경 가능한 변수입니다. 자바의 일반적인 변수 선언과 동일합니다.
  • 클래스 앞에 data를 붙인 data class는 컴파일러가 자동으로 equals()메서드, hashCode()메서드, toString()메서드를 생성합니다.

객체 생성

코틀린 코드

package com.example.SimpleKotlinTest.introduction

fun main(args: Array<String>) {
    val tickets = Money(100, "$")
    val popcorn1 = tickets.copy(500, "EUR")
    var popcorn2 = tickets.copy(100, "$")
    if (tickets != popcorn1) {
        println("popcorn1 is different!")
    }
    if (tickets != popcorn2) {
        println("popcorn2 is different!")
    }
}

data class Money(val amount: Int, val currency: String)
  • 코틀린 코드의 main메서드 역시 자바보다 훨씬 간략한 형태를 지니고 있습니다.
  • 코틀린에서는 객체를 생성할 때 new생성자를 사용하지 않습니다.
  • 인스턴스를 생성할 때 정확한 타입을 명시하지 않아도 타입을 추론할 수 있습니다. 물론 타입을 명시하는 것도 가능합니다.(val popcorn1: Money = tickets.copy(500, "EUR"))
  • 위 코드를 실행하면 popcorn1 is different!라는 내용만 출력됩니다. popcorn2와 tickets는 amount와 currency값이 모두 같기 때문에 ==비교를 통과합니다. 코틀린에서는 ==연산자로 값이 같은지 비교하고, ===연산자가 참조값이 같은지를 비교합니다.
val tickets = Money(100, "$")
val popcorn1: Money = tickets.copy(500, "EUR")
var popcorn2 = tickets.copy(100, "$")
println(popcorn1 == tickets); // false
println(popcorn2 == tickets); // true
println(popcorn1 === tickets); // false
println(popcorn2 === tickets); // false

Java코드 호환

Java로 작성한 클래스를 Kotlin에서 사용할 수 있고, Kotlin으로 작성한 클래스를 Java에서 사용할 수 있습니다.

코틀린 코드

package com.example.SimpleKotlinTest.introduction

fun main(args: Array<String>) {
    val javaMoney = JavaMoney(100, "$")
    println("money : ${javaMoney.amount}, currency : ${javaMoney.currency}")
}
  • 자바 코드로 작성한 JavaMoney를 코틀린 파일에서 활용할 수 있습니다.
  • 코틀린은 getter와 setter를 .변수명으로 호출합니다.

코틀린 코드와 자바 코드를 호환해서 사용하다보면 플랫폼 타입과 관련된 문제가 발생할 수 있습니다. 플랫폼 타입이란 코틀린이 null과 관련된 정보를 알 수 없는 타입을 말합니다. 자바에서 @Nullable@NotNull을 모두 선언하지 않은 변수를 코틀린에서 사용할 때는 해당 변수가 null이 허용되는지 아닌지를 잘 확인하고 써야 합니다.

함수 선언

코틀린에서는 함수를 fun 함수 이름(파라미터 이름: 타입, …) : 리턴 타입형식으로 선언합니다. 자바의 void와 유사한 타입은 코틀린에서 Unit이 있습니다. Unit은 아무것도 반환하지 않는 반환 타입을 말합니다. 리턴 타입을 생략하면 Unit으로 간주합니다.
코틀린 코드

fun sendPayment(money: Money, message: String = "") {
    println("Sending ${money.amount}")
}
fun main(args: Array<String>) {
    val tickets = Money(100, "$")
    val popcorn = tickets.copy(500, "EUR")
    sendPayment(tickets)
    sendPayment(message = "Good luck!!", money = popcorn)
}
  • 기본생성자에서 message의 기본값을 빈 값으로 선언했습니다. 이로 인해 message를 넣지 않은 생성자를 활용할 수 있습니다.(오버로딩)
  • 함수를 호출할 때 파라미터 이름과 값을 함께 적어주면 파라미터의 순서를 바꿔서 호출하는 것도 가능합니다.
    위 예에서 sendPayment함수는 money, message순서로 선언했지만 main에서 message,money로 값을 넣어주고 있습니다.

확장 함수

코틀린에서는 이미 존재하는 클래스에 함수를 덧붙일 수 있습니다. 그러헌 함수를 바로 확장 함수라고 부릅니다.
코틀린 코드

import java.math.BigDecimal
fun main(args: Array<String>) {
    val bd = BigDecimal(100)
    val popcorn = Money(100.bd, "$")
    bd.percent(7)
    7.percentOf(popcorn)
}

data class Money(val amount: BigDecimal, val currency: String)

fun sum(x: Int, y: Int) = x + y

fun BigDecimal.percent(percentage : Int) = this.multiply(BigDecimal(percentage)).divide(BigDecimal(100))

fun Int.percentOf(money: Money) = money.amount.multiply(BigDecimal(this)).divide(BigDecimal(100))

val Int.bd: BigDecimal
    get() = BigDecimal(this)
  • BigDecimal은 Java의 클래스로 원래 percent()메서드가 존재하지 않습니다. 하지만 우리가 확장함수로 percent를 선언함으로써 bd.percent(7)와 같은 사용이 가능해졌습니다.
  • 확장 속성은 기존의 클래스에 속성을 추가할 수 있는 기능입니다. custom getter를 이용해 Int.bd라는 확장 속성을 만들었습니다.

리턴 타입

코틀린 코드

fun convertToDollars(money: Money) : Money {  
    when (money.currency) {
        "$" -> return money
        "EUR" -> return Money(money.amount * BigDecimal(1.10), "$")
        else -> throw IllegalArgumentException("not the currency you're interested in!")
    }
}
  • when은 자바의 switch문과 바슷합니다.
  • 자바처럼 함수에서 선언한 타입을 return으로 반환합니다.
fun convertToDollars(money: Money) : Money {  
    return when (money.currency) {
        "$" -> money
        "EUR" -> Money(money.amount * BigDecimal(1.10), "$")
        else -> throw IllegalArgumentException("not the currency you're interested in!")
    }
}
  • return을 when 앞에 붙여 조건에 따른 결과를 return하도록 함수를 구현할 수도 있스빈다.
fun convertToDollars(money: Money) = when (money.currency) {  
    "$" -> money
    "EUR" -> Money(money.amount * BigDecimal(1.10), "$")
    else -> throw IllegalArgumentException("not the currency you're interested in!")
}
  • :대신 = 함수를 활용할 수도 있습니다. 코틀린에서의 함수는 1급객체이기 때문에 이러한 문법이 가능합니다.

연산자 오버로딩

기본 연산자를 클래스에서 사용할 수 있게 연산자를 정의할 수 있습니다.
코틀린 코드

import java.math.BigDecimal

fun main(args: Array<String>) {
    val tickets = Money(BigDecimal(100), "$")
    val popcorn = tickets.copy(BigDecimal(500), "EUR")
    val cost = tickets + popcorn
}

data class Money(val amount: BigDecimal, val currency: String)

operator fun Money.plus(money: Money) =
        if (currency == money.currency) {
            Money(amount + money.amount, currency)
        } else {
            throw IllegalArgumentException("We're gonna have a problem here!")
        }
  • Money의 plus를 오버로딩했기 때문에 Money타입의 tickets와 popcorn을 +로 연산할 수 있습니다.

null처리

코틀린에서는 null을 사용하기 위해서는 변수나 파라미터가 null일 수 있다는 걸 명시적으로 표현해야 합니다. 코틀린은 명시적으로 표현하지 않은 곳에서는 null을 입력할 수 없게 만들어 NPE오류를 피할 수 있게 되었습니다.

코틀린 코드

fun main(args: Array<String>) {
    var train : Money = Money(BigDecimal(100), "$")
    train = null    // compilation error

    var bus : Money? = Money(BigDecimal(50), "$")
    money(bus)    // compilation error
    bus = null    // ok
}

data class Money(val amount: BigDecimal, val currency: String)

fun money(money : Money) {
    println("${money.amount} is valid.")
}

fun javaMoney(money : JavaMoney?) {

    // first
    if (money != null) {
        println("${money.amount} is valid.")
    }

    // second
    println("${money?.amount} is valid.")
}
  • null이 들어갈 수 있는 변수에는 타입 뒤에 ?를 붙인 타입?을 선언해야 합니다. 타입타입?은 서로 다르게 취급됩니다.(IntInt?는 서로 다른 타입입니다.)
  • train의 타입은 Money로 null을 허용하지 않습니다. 그렇기 때문에 train에 null을 넣으려고 하면 에러가 발생합니다.
  • bus의 타입은 Money?로 null을 허용합니다. 그렇기 때문에 bus에 null을 넣는건 가능합니다. 하지만 Money타입을 받는 money함수에 인자로 넘기는 건 불가능합니다.
  • money함수는 인자의 타입을 Money로 선언함으로써 null을 들어올 수 없게 했습니다. 하지만 Java와 함께 사용할 때 자바코드의 실행 결과는 null을 가질 수 있기 때문에 javaMoney함수처럼 null을 대비해야 합니다.

고차원 함수

코틀린에서의 함수는 함수를 파라미터로 받거나 함수를 결과로 반환할 수 있는 고차원 함수입니다.

코틀린 코드

data class User(val id: Int, val username: String, val email: String, val role: Role)

enum class Role {
    Admin,
    Regular
}
fun usersFromJSONFile(fileName: String) : List<User> {
    TODO("LATER")
}
fun findEmails(users: List<User>, predicate: (String) -> (Boolean)) : List<User> {
    TODO("LATER")
}
  • findEmails함수는 users와 predicate를 인자로 받습니다. 여기서 predicate는 String을 입력변수로 받고 Boolean을 결과로 반환하는 함수를 인자로 받습니다.
fun main(args: Array<String>) {  
    val users = usersFromJSONFile("users.json")

    // 01
    findEmails(users, { value -> value.endsWith(".com") })

    // 02
    findEmails(users, { it.endsWith(".com") })

    // 03
    findEmails(users) {
        it.endsWith(".com")
    }

}
  • predicate파라미터에 전달하는 함수를 {}로 감싸서 전달하고 있습니다.
  • 01: value라는 String을 인자로 받아서 Boolean을 반환하는 기본적인 형태입니다.
  • 02: 함수로 전달되는 파라미터가 하나일 때 이 파라미터를 it으로 대체할 수 있습니다.
  • 03: 파라미터로 와야 할 함수가 전달받는 파라미터가 하나이고, 해당 파라미터가 마지막 파라미터라면 마지막 인자로 받는 함수를 괄호 밖에 따로 적어줄 수 있습니다.

상속

해당 클래스를 상속이 가능하게 만들려면 open키워드나 sealed키워드를 사용해야 합니다. open키워드는 어디에서든 상속 가능한 클래스라는 의미이고, sealed키워드는 해당 피알에서 작성된 클래스만 상속이 가능한 클래스라는 의미입니다.
코틀린 코드

sealed class UserResult
data class Success(val users: List<User>): UserResult()
data class Failure(val message: String): UserResult()
  • 상속받은 클래스를 정의할 때 함수의 리턴 타입을 표시했던 것과 유사하게 :를 사용합니다.
profile
기록하고 정리하는 걸 좋아하는 개발자.

0개의 댓글