Kotlin 무작정 따라해보기 2일차 - Introduction > Hello World, Functions
Written by munilive on (Updated: )2일차 시작하기 - Learn Kotlin by Example
지난 1일 차 마무리에서 이야기 한 것처럼 2일 차부터는 Learn Kotlin by Example을 무작정 따라 하면서 코틀린(Kotlin)을 배워보려 한다.

첫날 설치했던 IntelliJ IDEA를 이용해서 예제를 따라 해도 되고, 아니면 해당 사이트에 있는 Playground를 이용해서 예제를 실습해봐도 된다.
나는 실습해야 하는 예제가 있으면 Playground와 IDEA를 번갈아 가면서 사용해볼 예정이다.
본격적인 시작에 앞서 앞으로 배워야 할 것에 대한 목차만 한번 살펴보자.
- Introduction
- Control Flow
- Special Classes
- Functional
- Collections
- Scope Functions
- Delegation
- Productivity Boosters
- Kotlin/JS
총 9개의 세션으로 나눠어 있는 것 같다. 그리고 이것을 앞에서 말했던 것처럼 Playground 사이트나 PC에 설치한 IDEA로 실습(?)해 볼 수 있는 것 같고, 나아가, 좀 더 코틀린 문법에 익숙해지라고 Kotlin Koans라는 것도 제공하고 있다.
이건 Koans가 무슨 뜻인지 모르겠지만, 설명을 보니 대충 실습하라고 만들어 둔 것 같다.
앞으로 9개의 세션을 하루에 1개 세션씩 끝내서 9일 동안 진행하고, 나누어서 진행하고, Kotlin Koans의 실습 부분을 해볼까 한다.
그리고, Learn Kotlin by Example의 내용만으로 정확하지 않거나 설명이 부족 할 수 있으니 https://kotlinlang.org/docs/reference/를 참고해서 예제에서 설명하고자 하는 것을 찾아 내용을 보충해 보겠다.
Introduction
Hello World
자. 다시 나왔다. 모든 언어의 시작인 Hello World Kotlin에서 Hello World를 출력하는 코드를 가지고 Kotlin의 시작과 기본 문법(Basic Syntax)를 알려 주려고 하는 것같다.Kotlin Reference의 Getting Started - Basic Syntax를 참고해서 같이 보면 좋을 것 같다.
package org.kotlinlang.play // 1
fun main() { // 2
println("Hello, World!") // 3
}
첫 줄의
package org.kotlinlang.play는 내가 작성하고자 하는 프로그램의 Package 지정 한다.- Package 이름은 정의하지 않아도 상관 없다. 만약 Package를 정의하지 않으면 기본 Package로 지정 된다.
- Package 이름을 정의 할 때는 소스파일의 가장 상위에 위치 해야 한다.
- 소스파일의 디렉토리와 Package가 일치할 필요는 없다.
모든 프로그램의 시작점(entry point)라 할 수 있는
main()함수 이다.Kotlin 1.3버전부터 main() 선언시 파라미터를 지정하지 않아도 된다. 이경우 함수가 아무것도 반환하지 않는 다는 의미 이다.
Kotlin 1.3 이전 버전에서는
main(args: Array<String>)라고 지정해야 한다. 그래서Playground로 실험 해봤다.Playground에서 우측에 있는 톱니바퀴 모양의 아이콘을 누르면 Kotlin 버전을 변경 할 수 있다.println()은 콘솔(console)에 출력하는 예약 함수(?)라고 보면 될 것 같다.- 코드를 입력하고 마지막에 세미콜론(
;)은 입력하지 않아도 된다. (선택사항)
- 코드를 입력하고 마지막에 세미콜론(
Functions
이제 Function(함수)의 사용 방법에 대해 알아 보자.
Reference - Functions And Lambdas - Functions를 같이 보면 좋을 것 같다.
Default Parameter Values and Named Arguments
fun printMessage(message: String): Unit { // 1
println(message)
}
fun printMessageWithPrefix(message: String, prefix: String = "Info") { // 2
println("[$prefix] $message")
}
fun sum(x: Int, y: Int): Int { // 3
return x + y
}
fun multiply(x: Int, y: Int) = x * y // 4
fun main() {
printMessage("Hello") // 5
printMessageWithPrefix("Hello", "Log") // 6
printMessageWithPrefix("Hello") // 7
printMessageWithPrefix(prefix = "Log", message = "Hello") // 8
println(sum(1, 2)) // 9
println(multiply(2, 4)) // 10
}
printMessage()함수는String타입의message라는 변수명을 파라미터로 받아 리턴 값 없이message의 내용을 콘솔에 출력하는 함수 이다.ParameterName: ParameterType형태로 파라미터를 정의 한다.- 타입을 생략 할 수 없다.
printMessage()의)뒤에 붙은Unit은printMessage()함수의 리턴 타입을 지정하는 것이다.Unit은Java에서void같다 한다. https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-unit/
printMessageWithPrefix()함수는String타입의message변수와,String타입의prefix변수를 파라미터로 받고 리턴 값 없이message와prefix를 콘솔에 출력하는 함수다. 단,prefix값이 없을 경우Info를 기본값으로 지정한다.ParameterName: ParameterType = "DefaultValue"형태로 함수 호출시 파라미터 값을 지정하지 않으면 기본값을 사용하게 된다.- 1번의
parintMessage()함수와 동일하게 리턴 값이 없지만 리턴 타입을 지정하지 않았다. 즉, 리턴 타입이 없을 경우Unit을 생략 할 수 있다. - 파라미터를 2개 이상 지정할 때는 콤마(
,)로 구분 한다.
sum()함수는Int타입의x와,Int타입의y를 입력 받아,x와y를 더한 후 리턴 한다.- 함수의 리턴 값이 존재 할 때는 리턴값의 리턴 타입을 지정해야 한다.
return키워드를 사용하여 함수의 리턴 값을 전달 한다.- 코틀린에서 더하기를 할때는
+키워드를 사용 한다.
multiply()함수는Int타입의x와,Int타입의y를 입력 받아,x와y의 곱한뒤 리턴 한다.- 중괄호(
{})와return키워드를 사용하지 않고=대입(할당) 연산자를 사용하였다. - 함수의 리턴 타입을 지정하지 않았다.
- 함수의 내용으로 타입을 추촌하여 반환 하였기 때문에 반환 타입을 작성하지 않은 것 같다.
궁금!
sum()함수도 반환 타입을 지정하지 않아도 될까?
결론.+이기 때문에 당연Int가 반환 될 것이라 생각했지만,Type mismatch: inferred type is Int but Unit was expected에러가 발생 했다.- 중괄호(
printMessage()함수를 호출하며 인자값으로(argument)Hello를 입력하여 호출 하였다.- 결과로
Hello가 출력 될 것이다.
- 결과로
printMessageWithPrefix()함수를 호출하며 인자값으로Hello와Log을 입력하여 호출 하였다.- 결과로
[Log] Hello가 출력 될 것이다.
- 결과로
printMessageWithPrefix()함수를 호출하며 두번재 인자(argument)를 생략하고Hello만 입력하여 호출 하였다.- 결과로
[Info] Hello가 출력 될 것이다. - 2번재 파라미터가 없기 때문에
prefix의 기본값인Info가 사용 되었다.
- 결과로
printMessageWithPrefix()함수를 호출하며 인자값으로prefix = "Log",message = "Hello"를 입력하여 호출 하였다.- 일반적으로
Function(함수)호출시 정의된 인자(argument) 순서대로 데이터를 입력해야 하지만, Kotlin에서는 인자명(argument name)과 값(value)을 함께 전달 하면 정의된 인자의 순서와 상관 없이 사용 가능하다. - 결과로
[Log] Hello가 출력 될 것이다.
- 일반적으로
sum()함수의 결과를 출력한다.multiply()함수의 결과를 출력한다.
Infix Functions
난생 처음 듣는 이름 이다. 한국말로 어떻게 불러야 할지도 모르겠다. 구글로 번역하면 중위 함수(Infix Functions)라고 나오는데 이게 맞는지도 모르겠다.
단일 파라미터를 가지고 있는 멤버 함수나 확장 함수일 경우에는 Infix Function으로 표현 할 수 있다고 하는데, 자세한건 Reference - Functions and Lambdas - Functions 참고 하자.
일단 무조건 따라하기니 한번 따라해보자.
fun main() {
infix fun Int.times(str: String) = str.repeat(this) // 1
println(2 times "Bye ") // 2
val pair = "Ferrari" to "Katrina" // 3
println(pair)
infix fun String.onto(other: String) = Pair(this, other) // 4
val myPair = "McLaren" onto "Lucas"
println(myPair)
val sophia = Person("Sophia")
val claudia = Person("Claudia")
sophia likes claudia // 5
}
class Person(val name: String) {
val likedPeople = mutableListOf<Person>()
infix fun likes(other: Person) { likedPeople.add(other) } // 6
}
Int에 Infix extension function을 정의 한다.Int가 단순 키워드인줄 알았는데 이것도 하나의 객체(Object)인거 같다.times()함수를Int의 확장 함수로 정의하고String타입의str파라미터를 지정 한다.times()함수는 전달 받은 인자(str)의 String 타입의repeat()를 호출 한다.repeat()호출시 전달하는 인자는Int자신(this)이다(?)
1번
infix function에 대한 출력 값으로Bye Bye가 출력 된다.Int객체에times()확장 함수를 추가 하고 이를2 times "Bye "라는 형식으로 호출 하였다.2 times "Bye "에서2가Int타입(객체)이기 때문에Int에 방금 추가한times()를 함수를 사용하고 인자로"Bye "를 넣어 준 것이다.- 이것을 다르게 표현하면
2.times("Bye ")와 같지 않을까 생각 한다. (확실하지 않음) - 1번
times()함수안에서 전달 받은 인자str파라미터가String타입(객체)이고 해당String의 라이브러리인repeat()를 호출, 인자로는Int.times()자신인this를 넘겨 주었으니2이가 들어가서,"Bye "라는 문자가 2회 출력 된것으로 보인다. - 여기서 유추 할 수 있는 것은 확장 함수(extension function)의
this는 부모 객체라는 것이다.
val키워드로pair라는 변수를 정의하고, 값으로"Ferrari" to "Katrina"의 값을 담는다.참고로
val은 read-only 프로퍼티(property) 나 지역 변수(local variable)를 선언하는 것이다.- 여기서
to라는infix function이 사용 되었는데 이는 기본 라이브러리로 제공되는infix function이다. - 기본 라이브러리에서 제공하는
infix function이 존재하는 것 같다.
- 여기서
Pair라는 기본 라이브러리 함수를 이용해서String객체에onto()라는infix function을 추가 한다.- 처음에는 3번에는 정의한
val pair = "Ferrari" to "Katrina"를 이용해서Pair()이란 함수를 만든줄 알았으나, 이름을 변경하니 에러가 나는 것을 발견하고 찾아보니Pair()함수는 코틀린이 제공하는 기본 라이브러리 였다. - 3번에서 정의한
pair와 코틀린에서 기본 제공하는Pair()가 하는 동작은 동일 했다. - 4번의 경우
Pair()를 이용해서 String객체에 새로운infix function을 정의하여 사용하는 방식을 보여주려 한것 같다. (이런식으로도 쓸수 있어를 보여주려 한듯.)
- 처음에는 3번에는 정의한
Person이란Class에linkes라는Members Function(Method)를Infix로 표현하고 이를 활용하여 별도로 생성한 클래스를 이용하는 방식을 보여주는 것 같다.sophia라는Person객체에claudia이라는Person객체를 추가 한다.- Todo: 좀더 따라해서 나중에
Person객체안의likedPeople변수의 내용을 확인해 보자.
지금 사용된 예제의 경우
main()함수 안에서 function을 선언하는local functions을 사용했다.
코틀린은Function내에서Local Function을 지원한다는 것을 보여주려는 것 같다. 그럼 당연히Closure도 지원이 되겠지?
Operator Functions
연사자 함수(?), 코틀린은 어떤 함수들은 연산자로 업그레이드가 가능하다. Operator overloading이라는 건데, operator 키워드로 함수를 정의하면 된다.infix function의 사용과 방식이 비슷한 것 같다.
operator fun Int.times(str: String) = str.repeat(this) // 1
println(2 * "Bye ") // 2
operator fun String.get(range: IntRange) = substring(range) // 3
val str = "Always forgive your enemies; nothing annoys them so much."
println(str[0..14]) // 4
Int의*연산자를 overloading 해서str.repeat(this)로 변경 한다.times()가 그냥 지은 이름인 줄 알았는데, 알고 보니Binary operations중 하나였다.- 이런식으로 연산자별로 오버로딩하기 위한 특정
Member Function이 있고, 해당Member Function을 재정의 해야 한다.
*연산자의 변경된 내용을 이용하여2 * "Bye "를 출력 한다.- 위에
infix function으로 했을땐2 times "Bye "로 표현 했지만, Operator를 오버로딩하여2 * "Bye "형식으로 표현 하였다. 표현만 보자면 후자 방식이 더 좋은것 같다. 하지만, 이렇게 쓰면 모든 곱셈이 이렇게 변경될 것이니 조심 해야 할듯; - 출력 결과는
Bye Bye다.
- 위에
Indexed access operator의get()을Overloading한다.a[i]형식의 표현을 변경한다고 한다. 코틀린에서는String의 글자 하나마다 Index를 가지고 있나 보다.IntRange라는 타입은 정수로 범위를 지정하는 타입 같다.0..14이런식으로..으로 연결해 사용하는 듯 하다.substring()코틀린에서 제공하는 기본 라이브러리다.
str[]형식의 연산자의 변경된 내용이 출력 된다.- 출력 결과는
Always forgive다. IntRange에 대해 잘 모르겠지만, 0부터 시작하여 14까지 간거 같다. 총 15자리를 가져온 것.
- 출력 결과는
Functions with vararg Parameters
함수에 가변인자를 사용할 수 있다. 함수의 파라미터를 정의 할때 vararg 키워드를 파라미터 앞에 붙여 주면된다.
함수의 인자를 넘길때 쉼표(,)를 이용해서 인자를 넘기면 함수에 정의한 파라미터에 인자가 배열 형태로 담겨진다.
fun printAll(vararg messages: String) { // 1
for (m in messages) println(m)
}
printAll("Hello", "Hallo", "Salut", "Hola", "你好") // 2
fun printAllWithPrefix(vararg messages: String, prefix: String) { // 3
for (m in messages) println(prefix + m)
}
printAllWithPrefix(
"Hello", "Hallo", "Salut", "Hola", "你好",
prefix = "Greeting: " // 4
)
fun log(vararg entries: String) {
printAll(*entries) // 5
}
printAll()함수에String타입의messages파라미터를 추가하고vararg로 선언(가변 인자)printAll()함수의 내용은messages에 담겨진 내용을 단순println()로 출력 하는 것.- 처음으로
for의 사용방법이 나옴.
콤마(
,)로 구분하여printAll()함수를 호출출력 결과
Hello Hallo Salut Hola 你好
printAllWithPrefix()함수의 파리미터를printAll()과 동일하게 설정하고, 추가로String타입의prefix파라미터를 추가로 선언vararg선언된messages에 담길 내용들은 콤마(,)로 구분해서 입력하고,prefix의 경우 명시적으로 파라미터 이름까지 지정하여 인자를 추가하여printAllWithPrefix()함수 호출출력 결과
Greeting: Hello Greeting: Hallo Greeting: Salut Greeting: Hola Greeting: 你好
vararg를 사용하는printAll()함수를 이용하는 추가적인 방법으로log()함수를 선언하고String타입의entries파라미터를vararg로 선언printAll()함수를 호출하면서entries파라미터 값을 전달 할 때 앞에*기호가 나왔는데 이는vararg파라미티ㅓ를 그대로 전달 하려는 특수 스프레드 연산자(special spread operator) 기호이다.Runtime시에는vararg는 단순Array<String>의 배열일 뿐이라서String의vararg를 그대로 전달하기 위해*연산자를 사용함
2일차 마무리 및 Next
처음 시작할때는 Introduction의 모든 내용은 하루 정도면 충분할줄 알았는데, 생각보다 내용이 많고 검색 할 것이 많았던 것 같다.
다음 시작에는 Introduction을 이어서 Variables를 진행 해보겠다. (왠지 하루에 하나씩 끝내기도 힘들듯 하다.)
- Kotlin 무작정 따라해보기 관련 Post
- 1일차 - Get Started
- 2일차 - Introduction > Hello World, Functions
- 3일차 - Introduction > Variables, Null Safety, Classes
- 4일차 - Introduction > Generics, Inheritance
- 5일차 - Control Flow