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
Comments