나 혼자 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
)로 작성된 JavaCode1
의 task
메소드를 코틀린 코드로 변경하면 됩니다. 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()
를 호출하는 방법은 자바와 동일합니다. -
최종적으로 수정된 코드는 아래와 같습니다.
//
은 주석입니다. 코드를 복사해서 사용하시는 분들은 주의하세요!- 설명 때문에
sb
를var
로 선언한다고 하였으나, 개인적으로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.java
는Predicate<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
,age
는data
키워드를 통해서 필수 메서로 제공됨
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
)을 진행하는 것을 말합니다. -
아래 코드에서
e
가is Num
임을 확인하는 과정에서 만약e
가Num
클래스의 인스턴스라면->
뒷 부분의e
는Num
의 인스턴스 입니다(스마트 캐스트).
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()
}