나 혼자 Kotlin v1.0

  • 해당 문서는 Kotlin Koans로 진행하면서 알게된 내용을 짧게 정리한 내용입니다. Kotlin Koans Workshop은 코틀린(Kotlin) 문법에 익숙해지는 연습을 위한 프로젝트 입니다. 개별 연습문제는 실패한 단위 테스트(unit test)로 만들어지며, Kotlin Koans의 목표는 모든 단위 테스트를 성공시키는 것입니다.
  • 해당 내용의 대부분은 Kotlin.org에서 발췌한 내용입니다. Kotlin에 대한 책을 레퍼런스하고 싶었지만 책을 구매하기엔 언어의 업데이트 속도가 너무 빨라서 문서를 참고하였습니다. 따라서 발췌 내용은 초보자를 대상으로 하지 않고 있기 때문에 개인적으로 판단해서 알만한 내용은 대부분 생략하였습니다(이렇게 말하는건 “단지 귀찮아서 그런거 아니냐?”는 독자의 마음속 질문에 대한 사회적인 답변이라 할 수 있습니다?!).

Getting Started

  • JDK >= 1.8 을 설치하세요.
  • IntelliJ를 권장합니다. Android Studio >= 3.0 Canary 7을 설치하세요.
    • 몇가지 플러그인을 설치하세요. 해당 내용의 자세한 사항은 이 곳을 참고하세요. 설치에 관한 자세한 사항은 다루지 않습니다.
    • 간단한 예제는 Tool -> Kotlin -> Kotlin REPL을 사용하시면 됩니다.
    • 간단하지 않은 내용은 프로젝트 생성등을 활용해 보세요.

Part 0. How to Start a Code?

  • tests 폴더 내부에 주제에 맞춰서 분류된 테스트 케이스를 확인할 수 있습니다. 대부분의 테스트 케이스는 아래의 형태로 구성되어 있습니다.
    • @Test 어노테이션을 사용해서 testOk() 함수 테스트를 진행합니다. 함수 내부에서 assertEquals()을 사용해서 task0() 함수를 호출했을 때 반환하는 결과를 비교해서 테스트 과정을 비교합니다. 결론적으로 말해서 testk0()를 호출하면 "OK"가 출력되도록 수정하면 됩니다.
package i_introduction._0_Hello_World

import org.junit.Assert.assertEquals  
import org.junit.Test

class _00_Start {  
    @Test fun testOk() {
        assertEquals("OK", task0())
    }
}
  • java 폴더 내부에 수정해야 할 파일이 존재합니다. 코틀린의 확장자는 *.kt 입니다. 파일 내부에는 요구사항이 기록되어져 있으며, 테스트 케이스에서 기대되는 결과를 출력하기 위해서 해당 함수를 수정하면 됩니다.
package i_introduction._0_Hello_World

import util.TODO  
import util.doc0

fun todoTask0(): Nothing = TODO(  
    """
        Task 0.
        Read README.md to learn how to work with this project and check your solutions.
        Using 'documentation =' below the task description you can open the related part of the online documentation.
            Press 'Ctrl+Q'(Windows) or 'F1'(Mac OS) on 'doc0()' to call the "Quick Documentation" action;
            "See also" section gives you a link.
            You can see the shortcut for the "Quick Documentation" action used in your IntelliJ IDEA
            by choosing "Help -> Find Action..." (in the top menu), and typing the action name ("Quick Documentation").
            The shortcut in use will be written next to the action name.
        Using 'references =' you can navigate to the code mentioned in the task description.
        Let's start! Make the function 'task0' return "OK". Note that you can return expression directly.
    """,
    documentation = doc0(),
    references = { task0(); "OK" }
)

fun task0(): String {  
    return todoTask0()
}

Part I. Introduction

_0_Hello_World

task0()를 호출하면 "OK"를 출력하는 함수를 작성하면 됩니다.

  • 해당 챕터에서 배워야 하는 가장 중요한 것은 ‘함수 정의’에 관련된 내용입니다.
  • 자바와 달리 kotlin에서 함수를 정의하는 방법은 아래와 같습니다. 기존의 자바에 비해서 fun 키워드가 추가되었고, 매개변수나 반환형의 경우 a: Int 형태로 선언합니다. 기존의 자바 개발자의 경우 반환형이 매개변수(a: Int, b: Int)의 뒷부분에 정의되어 있다는 점을 기억해야 합니다. 아래의 코드는 함수를 사용하는 전형적인 방법입니다.
fun sum(a: Int, b: Int): Int {  
    return a + b
}
  • 만약 함수가 아주 간단하다면 아래와 같은 형태로 함수를 선언할 수 있습니다. 굉장히 직관적이긴 하지만 기존의 자바 개발자의 경우 어색할 수 있지만, 이런 문법에 맛들이기 시작하면 자바로 돌아갈 수 없을지도 모릅니다. 정말 편합니다.
fun sum(a: Int, b: Int) = a + b  
  • 만약 반환하지 않는 함수(void function)일 경우 Unit 정의하거나 생략하면 됩니다.
// Unit을 사용
fun printSum(a: Int, b: Int): Unit {  
    println("sum of $a and $b is ${a + b}")
}

// Unit을 생략
fun printSum(a: Int, b: Int) {  
    println("sum of $a and $b is ${a + b}")
}`
  • 함수에 대해서 알았으니, 이제 문제를 해결해보겠습니다. 단순하게 "OK"를 반환하면 되기 때문에 아래와 같이 코드를 수정하면 됩니다. 특히 ‘세미콜론’을 사용하지 않습니다. 주의하세요!
fun task0(): String {  
    return "OK"
}
  • 코드가 간단할 경우 아래와 같은 형태로 변경 가능합니다. 간단하죠?
fun task0() = "OK"  

_1_Java_To_Kotlin_Converter

자바(Java)로 작성된 JavaCode1task 메소드를 코틀린 코드로 변경하면 됩니다. task1() 메소드를 실행하면 문자열 {1, 2, 3, 42, 555}를 출력하면 됩니다.

  • IntelliJ를 사용할 경우 Java 파일을 복사해서, Kotlin 파일에 붙여넣기 하면 자동으로 변경됩니다. 그러나 우리는 Kotlin을 공부하기 위해서 차근 차근 변경해 보도록 하겠습니다.
  • 자바 코드를 먼저 확인해 보겠습니다. 자바에서 제공하는 콜렉션의 상위 인터페이스인 collection을 사용해서 순회를 통해서 정수를 문자열로 변경하는 코드 입니다.
public String task1(Collection<Integer> collection) {  
    StringBuilder sb = new StringBuilder();
    sb.append("{");
    Iterator<Integer> iterator = collection.iterator();
    while (iterator.hasNext()) {
        Integer element = iterator.next();
        sb.append(element);
        if (iterator.hasNext()) {
            sb.append(", ");
        }
    }
    sb.append("}");
    return sb.toString();
}
  • 해당 코드를 코틀린으로 변경하기 위해서 일단은 함수 정의를 fun task1(collection: Collection<Int>): String으로 변경합니다. 그리고 함수 내부에서 사용되는 변수는 var sb = StringBuilder()로 선언합니다. 코틀린에서 변수는 var을 사용하고 레퍼런스의 경우 new없이 곧바로 클래스의 생성자를 호출하면 됩니다. 순회를 위한 iterator 상수를 선언하기 위해서 val을 사용하면 됩니다. collection 인터페이스의 iterator()를 호출하는 방법은 자바와 동일합니다.
  • 최종적으로 수정된 코드는 아래와 같습니다. //은 주석입니다. 코드를 복사해서 사용하시는 분들은 주의하세요!
    • 설명 때문에 sbvar로 선언한다고 하였으나, 개인적으로 val이 적당하다고 판단되어 최종 코드는 val로 수정합니다.
//public String task1(Collection<Integer> collection) {
fun task1(collection: Collection<Int>): String  {  
    // StringBuilder sb = new StringBuilder();
    val sb = StringBuilder()
    sb.append("{");
    // Iterator<Integer> iterator = collection.iterator();
    val iterator = collection.iterator()
    while (iterator.hasNext()) {
        // Integer element = iterator.next();
        var element = iterator.next()
        sb.append(element);
        if (iterator.hasNext()) {
            sb.append(", ");
        }
    }
    sb.append("}");
    return sb.toString();
}

_2_Named_Arguments

task2() 메소드를 실행하면 문자열 {1, 2, 3, 42, 555}를 출력하면 됩니다.

  • 해당 챕터는 parameters에 대해서 알아봅시다.
  • 이번 챕터를 해결하기 위해선 collection: Collection<Int> -> task1(collection); collection.joinToString()를 사용하면 됩니다. 다시 말해서 Collection<Int>에 포함된 joinToString() 함수를 사용하면 됩니다.
  • 당연히 해당 함수의 매개변수에 어떤 값을 전달하면 되는데, 값을 전달하는 방법이 기존의 자바와 차이를 매개변수를 전달하는 방법이 ‘순서’가 아니라 ‘이름'(흔히 말하는 파스칼 표기법Pascal notation)을 사용한다는 점 입니다.
fun task2(collection: Collection<Int>): String {  
  // return collection.joinToString(", ","[","]")
  return collection.joinToString(prefix = "{", separator = ", ", postfix = "}")
}

_3_Default_Arguments

foo()함수를 오버로딩하여 해당 테스트에서 요구하는 결과를 출력하면 됩니다.

  • 자바 파일에서 제공하는 파일은 foo() 메서드를 오버로딩하고 있습니다. 자바에서 오버로딩은 매개변수의 갯수나 자로형은 다르고 이름은 동일한 메서드의 집합이라 할 수 있습니다.
  • 코틀린의 경우 메서드에 Default값을 지정할 수 있기 때문에 함수의 오버로딩을 해야 할 경우 Default Arguments를 사용하면 됩니다.
  • foo()를 코틀린으로 변경하면 아래와 같습니다.
public String foo(String name, int number, boolean toUpperCase) {  
    return (toUpperCase ? name.toUpperCase() : name) + number;
}

fun foo(name: String = "", number: Int = 42, toUpperCase: Boolean = false) =  
        (if (toUpperCase) name.toUpperCase() else name) + number
  • 오버로딩된 foo() 메서드는 아래와 같이 사용하면 됩니다. 코틀린은 Default Arguments를 사용해서 오버로딩을 손쉽게 구현할 수 있습니다.
fun task3(): String {  
    return (foo(name = "a") +
            foo(name = "b", number = 1) +
            foo(name = "c", toUpperCase = true) +
            foo(name = "d", number = 2, toUpperCase = true))

_4_Lambdas

자바에서 람다(Lambdas) 표현식은 메서드로 전달할 수 있는 익명 함수를 단순화한 것으로 설명됩니다. 기본적으로 값이 아닌 ‘코드’를 메서드나 여타의 예외처리에 전달할 수 있다는 점에서 중요하게 다뤄집니다.

자바(>= 8)에서 사용하는 람다 표현식은 아래와 같은 형태로 사용됩니다.

// 전형적인 자바코드
Comparator<Gunpla> byGrade = new Comparator<Gunpla>() {  
    public int compare(Gunpla g1, Gunpla g2) {
        return g1.getGrade().compareTo(g2.getGrade());
    }
}

// 람다 표현식을 사용한 자바코드
Comparator<Gunpla> byGrade = (Gunpla g1, Gunpla g2) -> g1.getGrade().compareTo(g2.getGrade());  

람다를 사용하여 JavaCode4.java를 다시 작성하면 됩니다. 코드 작성시 필요한 함수는 Collection에서 적절한 함수를 찾을 수 있습니다. 변경시 Iterables클래스는 사용하지 마세요.

  • JavaCode4.javaPredicate<T>를 사용해서 42로 나눠지는 정수를 반환하는 코드입니다.
  • 코틀린의 람다 표현식을 사용하면 아래와 같습니다. 람다 사용법은 잘 익혀두자!
fun task4(collection: Collection<Int>): Boolean = collection.any { x -> (x % 42) == 0 }  

_5_String_Templates

문자열 템플릿(String Templates)를 다루는 방법을 정규식(regexp)와 함께 배워볼 수 있습니다. 이번 챕터에선 '13 JUN 1992'를 출력하면 됩니다.

  • 코틀린에서 문자열 템플릿을 활용해서 정규식을 사용하는 방법은 """\d{2}\.\d{2}\.\d{4}"""과 같습니다. 탈출문자와 정규식를 조합해서 사용할 수 있으며, 파이썬에서 활용하는 문자열 정의 방법인 """을 사용하고 있습니다.
  • 13 JUN 1992을 출력하기 위한 코드는 아래와 같습니다.
    • """을 사용해서 문자열을 정의하는 방법과 정규식을 활용하는 방법은 차후에 다양한 곳에서 사용될 수 있으니 잘 알아두자!
val month = "(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)"  
fun task5(): String = """\d{2} ${month} \d{4}"""  

_6_Data_Classes

코틀린에서 Idioms으로 제공하는 data는 기존의 set, get, hash 등과 같은 몇가지 기본적인 메서드를 제공합니다.

  • Person 클래스의 생성자(클래스 매개변수)에 정의된 name, agedata 키워드를 통해서 필수 메서로 제공됨
data class Person(val name: String, val age: Int)

fun task6(): List<Person> {  
    return listOf(Person("Alice", 29), Person("Bob", 31))

_7_Nullable_Types

코틀린에서 옵셔널을 사용하는 방법을 알아보는 챕터입니다.

  • 옵셔널은 해당 객체의 값이 null이 될 수 명시적으로 알려주는 키워드 입니다.
  • 옵셔널 개념이 없었던 시절의 자바 코드에서 null을 처리하는 방법은 아래와 같습니다. @Nullable 어노테이션을 사용해서 해당 객체의 값이 null을 가질 수 있기 때문에 if를 사용해서 해당 객체의 값을 확인해야 합니다.
public void sendMessageToClient(@Nullable Client client, @Nullable String message, @NotNull Mailer mailer) {  
    if (client == null || message == null) return;

    PersonalInfo personalInfo = client.getPersonalInfo();
    if (personalInfo == null) return;

    String email = personalInfo.getEmail();
    if (email == null) return;

    mailer.sendMessage(email, message);
}
  • 코틀린의 경우 옵셔널(?)을 사용해서 좀 더 손쉽고, 안전하게 null을 처리할 수 있습니다. 반면 객체를 호출하는 과정에서 약간의 어색한 문법을 사용한다는 점도 잊지 말아야 합니다.
    • 옵셔널 객체를 호출 할 때는 obj?형태로 호출하고, 내부의 프로퍼티가 옵셔널일 경우 해당 프로퍼티도 옵셔널 객체로 취급해야 합니다.
    • 옵셔널이 아닐 경우 null을 항시적으로 체크해야 함을 잊지 말아야 합니다.
fun sendMessageToClient(client: Client?, message: String?, mailer: Mailer) {  
    if (client?.personalInfo?.email != null && message != null) mailer.sendMessage(client?.personalInfo?.email, message)
}

_8_Smart_Casts

이번 챕터에선 스마트 캐스트(smart casts)와 when 표현식을 배워봅니다.

  • 스마트 캐스트란 어떤 인스턴스가 어떤 클래스인지 확인(is)하는 과정에서 해당 클래스의 인스턴스가 참(true)이면 별다른 과정이 곧바로 인스턴스를 형변환(casts)을 진행하는 것을 말합니다.
  • 아래 코드에서 eis Num임을 확인하는 과정에서 만약 eNum클래스의 인스턴스라면 -> 뒷 부분의 eNum의 인스턴스 입니다(스마트 캐스트).
fun eval(e: Expr): Int =  
    when (e) {
        is Num -> e.value
        is Sum -> eval(e.left) + eval(e.right)
        else -> throw IllegalArgumentException("Unknown expression")
    }

_9_Extension_Functions

함수를 확장하는 방법을 배워볼 수 있습니다.

  • 이번 챕터는 별다른 어려움 없이 this를 적절하게 사용하면 문제를 쉽게 해결 할 수 있습니다.
data class RationalNumber(val numerator: Int, val denominator: Int)

fun Int.r(): RationalNumber = RationalNumber(this, 1)  
fun Pair<Int, Int>.r(): RationalNumber = RationalNumber(this.first, this.second)  

_10_Object_Expressions

코틀린과 자바 코드를 혼합해서 사용해 볼 수 있는 챕터입니다.

  • java.util.Collections.sort를 활용해서 리스트를 정렬하는 기능을 구현한 코드 입니다.
    • 코틀린의 경우 배열을 생성하는 방법이 arrayListOf()이며, 기존의 자바 패키지를 사용해서 코틀린의 Comparator()를 구현하는 방법을 소개하는 챕터 입니다.
fun task10(): List<Int> {  
    val arrayList = arrayListOf(1, 5, 2)
    sort(arrayList, kotlin.Comparator() { x, y -> y - x })
    return arrayList
}

_11_SAM_Conversions

SAM(Single Abstract Method) 인터페이스를 활용해서 해당 챕터를 진행합니다.

  • SAM이란 인터페이스의 매개변수 유형이 코틀린에서의 인터페이스 유형과 일치할 때, Java 인터페이스로 자동 변환해주는 것으로 코틀린의 경우 { x, y -> y -x }와 같이 함수를 사용해서 전달할 수 있습니다.
fun task11(): List<Int> {  
    val arrayList = arrayListOf(1, 5, 2)
    Collections.sort(arrayList, { x, y -> y -x })
    return arrayList
}

_12_Extensions_On_Collections

코틀린에선 기존의 컬렉션에서 제공하지 않은 다양한 기능을 제공하고 있습니다.

  • : List<Int>를 반환하는 task12()의 경우 자바의 컬렉션에 sortedDescending()를 확장해서 사용할 수 있습니다.
fun task12(): List<Int> {  
    return arrayListOf(1, 5, 2).sortedDescending()

}