[Kotlin] - 코틀린 기초 (Hello World, 함수, 클래스)
코틀린 공식문서에 있는 Example들을 하나씩 읽어보며 정리하자.
참고: https://play.kotlinlang.org/byExample/overview
Introduction
Hello World
package org.kotlinlang.play // 1
fun main() { // 2
println("Hello, World!") // 3
}
- 코틀린 코드는 자바와 같이 보통 패키지 안에 정의된다. 패키지를 지정하는 것은 옵션이다. 지정하지 않으면 디폴트 패키지로 지정된다.
- 코틀린 애플리케이션의 시작점은 main 함수이다. 코틀린 1.3버전부터 파라미터 없이 main 함수를 선언할 수 있다. 1.3 이전 버전에서는 Array<String> 유형의 매개변수가 있어야한다.
- println은 표준 출력 함수이고 암시적으로 임포트 되어있다. 그리고 ;(세미콜론)은 생략해도 되고 써도된다.
Functions
디폴트 파라미터(Default Parameter Values)와 지정 인자(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
}
/*
Hello
[Log] Hello
[Info] Hello
[Log] Hello
3
8
*/
- String 타입의 파라미터를 받고 Unit 타입(반환값x, 자바의 void)을 반환하는 간단한 함수이다.
- 이 함수는 두번 째 파라미터(prefix)가 옵션으로 적용된다. 디폴트 값으로 "Info"를 가진다. 반환타입은 생략되어 실제로 Unit이 리턴된다.
- Integer 타입이 리턴된다.
- 함수를 단일 표현식으로 선언할 수 있다. 반환값을 Integer라고 추론한다.
- 첫번째 함수를 "Hello" 인수로 호출
- 두번째 함수를 두개의 인수로 호출
- 두번째 함수를 하나의 인수로 호출, 디폴트 값인 "Info"가 사용된다.
- 두번째 함수를 지정 인수를 사용하여 호출
- sum() 함수 호출 결과를 출력
- multiply() 함수 호출 결과를 출력
중위 함수(Infix Functions)
중위 함수는 infix 키워드를 사용해 함수 호출에서 "." 과 "()"을 생략한 표현식으로 호출 가능한 함수이다.
- 중위 함수 요구 사항
- 클래스의 멤버 함수이거나 확장 함수여야함
- 함수의 파라미터가 1개여야함
- 파라미터가 가변 인자여도 안되고 디폴트 값을 가져서도 안됨
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
}
/*
Bye Bye
(Ferrari, Katrina)
(McLaren, Lucas)
*/
- Int 클래스의 중위 확장 함수(infix extension function) 'times'를 정의한다.
- 중위 함수를 호출
- 표준 라이브러리에서 infix 함수(to)를 호출하여 Pair 클래스를 만든다. (to 함수는 코틀린 제공하는 표준 라이브러리 함수, <key, value> 형식의 Pair 값을 리턴하는 함수, map 만들때 사용)
- onto 라는 중위 함수를 정의한다.
- 중위 함수는 멤버 함수로도 정의할 수 있다. (Person 클래스의 likes 중의 함수 사용)
- 멤버 함수로 중위 함수가 정의되었으면 중위 함수의 첫 번째 파라미터는 클래스 인스턴스가 된다.
연산자 함수(Operator Functions)
함수를 연산자로 업그레이드 가능하다. 연산자로 해당 함수를 호출할 수 있다.
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])
/*
Bye Bye
Always forgive
*/
- operator 키워드를 사용하여 한 단계 더 높은 중위 함수를 선언한다.
- times()는 *(곱셈)를 의미한다. 2 * "Bye "를 이용하여 위에 정의한 함수를 호출할 수 있다.
- + = .plus
- - = .minus
- * = .times
- / = .div
- % = .rem
- .. = .rangeTo
- 연산자 함수는 문자열에 쉽게 범위 접근을 할 수 있다. (get/set 에 operator 키워드를 사용하여 연산자 함수를 정의)
- get() 함수는 bracket-access syntax를 사용할 수 있다.
vararg 인자가 있는 함수
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
}
/*
Hello
Hallo
Salut
Hola
你好
Greeting: Hello
Greeting: Hallo
Greeting: Salut
Greeting: Hola
Greeting: 你好
*/
- vararg 키워드는 파라미터를 가변인자로 바꾼다.
- vararg 키워드가 적용된 printAll 함수를 여러 문자열 인자를 사용하여 호출
- 지정 인자 덕분에 vararg 인자 뒤에 동일한 타입의 파라미터를 추가할 수 있다. 값을 전달할 방법이 없기 때문에 자바에서는 불가능한 기능이다.
- 지정 인자를 사용하여 vararg와 별도로 prefix값을 설정할 수 있다.
- 런타임에서 vararg 는 array 이다. vararg 인자를 호출 인자로 넘기려면, *(spread operator)를 사용하면 된자. (Array 타입인 entries 대신 *(spread operator)를 이용하여 전달)
Classes
클래스 선언부는 클래스 이름(name)과 클래스 헤더(header = 파라미터 타입, 기본생성자, ...), 클래스 바디(body = {...})로 구성된다.
헤더와 바디는 모두 옵션이다.
class Customer // 1
class Contact(val id: Int, var email: String) // 2
fun main() {
val customer = Customer() // 3
val contact = Contact(1, "mary@gmail.com") // 4
println(contact.id) // 5
contact.email = "jane@gmail.com" // 6
}
- Customer 클래스를 어떤 프로퍼티(멤버변수)나 사용자 정의 생성자 없이 선언한다. 기본 생성자는 코틀린이 자동 생성해준다.
- 2개의 프로퍼티와 그 둘을 파라미터로하는 생성자를 갖는 Contact 클래스를 생성한다.
- 디폴트 생성자를 통해 Customer 인스턴스를 생성한다. 코틀린에서는 new 키워드를 쓰지 않는다.
- 두개의 인수가 있는 생성자를 사용하여 Contact 클래스의 인스턴스를 생성한다.
- id 프로퍼티에 접근한다.
- email 프로퍼티 값을 수정한다.
Generics
Generics은 모던 프로그래밍 언어에서 표준이 되는 제너릭 메커니즘이다.
제네릭 클래스와 제네릭 함수는 특정 제네릭 타입과는 독립적인 공통 로직을 캡슐화하여 코드의 재사용성을 증가시킨다.
List<T> 에서 로직은 제너릭 타입 T와는 무관한 것 처럼.
Generic Class
class MutableStack<E>(vararg items: E) { // 1
private val elements = items.toMutableList()
fun push(element: E) = elements.add(element) // 2
fun peek(): E = elements.last() // 3
fun pop(): E = elements.removeAt(elements.size - 1)
fun isEmpty() = elements.isEmpty()
fun size() = elements.size
override fun toString() = "MutableStack(${elements.joinToString()})"
}
- MutableStack<E> 라는 제네릭 클래스를 정의했다. 앞에 E는 제네릭 타입 파라미터라고 불린다. 실제 사용할 때는 MutableStack<Int> 처럼 Int 와 같은 특정 타입을 지정해서 사용한다.
- 제네릭 클래스 내부에서 E는 어떤 다른 타입의 파라미터로 사용될 수 있다.
- 리턴타입으로 E를 사용할 수도 있다.
구현에서는 단일 표현식으로 정의할 수 있는 함수에 대해 코틀린의 약식 구문을 많이 사용한다.
Generic Functions
로직이 특정 타입에 독립적인라면 제네릭 함수를 쓸 수 있다. 예를들어 다음과 같이 가변(mutable) 스택을 만드는 유틸리티 함수를 만들 수 있다.
fun <E> mutableStackOf(vararg elements: E) = MutableStack(*elements)
fun main() {
val stack = mutableStackOf(0.62, 3.14, 2.7)
println(stack)
}
/*
MutableStack(0.62, 3.14, 2.7)
*/
컴파일러는 mutableStackOf 메소드의 파라미터를 통해서 타입을 유추할 수 있다. 그렇기 때문에 mutableStackOf<Double>(...) 같은 메소드를 작성할 필요가 없다.
Inheritance
코틀린은 전통적인 객체지향의 상속 매커니즘을 완벽하게 지원한다.
open class Dog { // 1
open fun sayHello() { // 2
println("wow wow!")
}
}
class Yorkshire : Dog() { // 3
override fun sayHello() { // 4
println("wif wif!")
}
}
fun main() {
val dog: Dog = Yorkshire()
dog.sayHello()
}
//wif wif!
- 코틀린 클래스들은 final이 디폴트이다. 클래스 상속을 허용하려면 open 키워드를 붙이면 된다.
- 코틀린 메소드 또한 final이 디폴트이다. 클래스와 마찬가지로 open 키워드를 붙이면 오버라이드가 가능하다.
- 클래스 이름 뒤에 : SuperClassName()을 지정하여 슈퍼클래스(부모클래스)를 상속한 클래스를 만들 수 있다. 빈 괄호()는 슈퍼클래스의(부모클래스)의 기본 생성자를 나타낸다.
- 메소드 속성을 오버라이드하려면 override 키워드가 필요하다.
인자가 있는 생성자를 통한 상속
open class Tiger(val origin: String) {
fun sayHello() {
println("A tiger from $origin says: grrhhh!")
}
}
class SiberianTiger : Tiger("Siberia") // 1
fun main() {
val tiger: Tiger = SiberianTiger()
tiger.sayHello()
}
//A tiger from Siberia says: grrhhh!
- 슈퍼클래스를 생성할 대 슈퍼클래스의 파라미터가 있는 생성자를 이용하려면, 서브클래스의 선언부에서 인수를 제공하면 된다.
슈퍼클래스로 생성자 인수 전달
open class Lion(val name: String, val origin: String) {
fun sayHello() {
println("$name, the lion from $origin says: graoh!")
}
}
class Asiatic(name: String) : Lion(name = name, origin = "India") // 1
fun main() {
val lion: Lion = Asiatic("Rufo") // 2
lion.sayHello()
}
//Rufo, the lion from India says: graoh!
- Asiatic 클래스 선언의 name은 var도 val도 아니다. 생성자의 인수이며, 이 인수의 값은 상위 클래스 Lion의 name 프로퍼티로 전달된다.
- 이름이 Rufo인 Asiatic 클래스의 인스턴스를 생성한다. 이 호출은 Rufo 와 India 인수를 사용하여 Lion 생성자를 호출한다.