개발/Kotlin

[Kotlin] - 코틀린 기초 (Hello World, 함수, 클래스)

dongdev 2022. 8. 15. 02:17

코틀린 공식문서에 있는 Example들을 하나씩 읽어보며 정리하자.

참고: https://play.kotlinlang.org/byExample/overview

 

Introduction

Hello World

package org.kotlinlang.play         // 1

fun main() {                        // 2
    println("Hello, World!")        // 3
}
  1. 코틀린 코드는 자바와 같이 보통 패키지 안에 정의된다. 패키지를 지정하는 것은 옵션이다. 지정하지 않으면 디폴트 패키지로 지정된다.
  2. 코틀린 애플리케이션의 시작점은 main 함수이다. 코틀린 1.3버전부터 파라미터 없이 main 함수를 선언할 수 있다.  1.3 이전 버전에서는 Array<String> 유형의 매개변수가 있어야한다.
  3. 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
*/
  1. String 타입의 파라미터를 받고 Unit 타입(반환값x, 자바의 void)을 반환하는 간단한 함수이다.
  2. 이 함수는 두번 째 파라미터(prefix)가 옵션으로 적용된다.  디폴트 값으로 "Info"를 가진다. 반환타입은 생략되어 실제로 Unit이 리턴된다.
  3. Integer 타입이 리턴된다.
  4. 함수를 단일 표현식으로 선언할 수 있다. 반환값을 Integer라고 추론한다.
  5. 첫번째 함수를 "Hello" 인수로 호출
  6. 두번째 함수를 두개의 인수로 호출
  7. 두번째 함수를 하나의 인수로 호출, 디폴트 값인 "Info"가 사용된다.
  8. 두번째 함수를 지정 인수를 사용하여 호출
  9. sum() 함수 호출 결과를 출력
  10. 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)
*/
  1. Int 클래스의 중위 확장 함수(infix extension function) 'times'를 정의한다.
  2. 중위 함수를 호출
  3. 표준 라이브러리에서 infix 함수(to)를 호출하여 Pair 클래스를 만든다. (to 함수는 코틀린 제공하는 표준 라이브러리 함수, <key, value> 형식의 Pair 값을 리턴하는 함수, map 만들때 사용)
  4. onto 라는 중위 함수를 정의한다.
  5. 중위 함수는 멤버 함수로도 정의할 수 있다. (Person 클래스의 likes 중의 함수 사용)
  6. 멤버 함수로 중위 함수가 정의되었으면 중위 함수의 첫 번째 파라미터는 클래스 인스턴스가 된다.

 

연산자 함수(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 
*/
  1. operator 키워드를 사용하여 한 단계 더 높은 중위 함수를 선언한다.
  2. times()는 *(곱셈)를 의미한다. 2 * "Bye "를 이용하여 위에 정의한 함수를 호출할 수 있다.
    • + = .plus
    • - = .minus
    • * = .times
    • / = .div
    • % = .rem
    • .. = .rangeTo
  3. 연산자 함수는 문자열에 쉽게 범위 접근을 할 수 있다. (get/set 에 operator 키워드를 사용하여 연산자 함수를 정의)
  4. 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: 你好
*/
  1. vararg 키워드는 파라미터를 가변인자로 바꾼다.
  2. vararg 키워드가 적용된 printAll 함수를 여러 문자열 인자를 사용하여 호출
  3. 지정 인자 덕분에 vararg 인자 뒤에 동일한 타입의 파라미터를 추가할 수 있다. 값을 전달할 방법이 없기 때문에 자바에서는 불가능한 기능이다.
  4. 지정 인자를 사용하여 vararg와 별도로 prefix값을 설정할 수 있다.
  5. 런타임에서 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
}
  1. Customer 클래스를 어떤 프로퍼티(멤버변수)나 사용자 정의 생성자 없이 선언한다. 기본 생성자는 코틀린이 자동 생성해준다.
  2. 2개의 프로퍼티와 그 둘을 파라미터로하는 생성자를 갖는 Contact 클래스를 생성한다.
  3. 디폴트 생성자를 통해 Customer 인스턴스를 생성한다. 코틀린에서는 new 키워드를 쓰지 않는다.
  4. 두개의 인수가 있는 생성자를 사용하여 Contact 클래스의 인스턴스를 생성한다.
  5. id 프로퍼티에 접근한다.
  6. 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()})"
}
  1. MutableStack<E> 라는 제네릭 클래스를 정의했다. 앞에 E는 제네릭 타입 파라미터라고 불린다. 실제 사용할 때는 MutableStack<Int> 처럼 Int 와 같은 특정 타입을 지정해서 사용한다.
  2. 제네릭 클래스 내부에서 E는 어떤 다른 타입의 파라미터로 사용될 수 있다.
  3. 리턴타입으로 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!
  1. 코틀린 클래스들은 final이 디폴트이다. 클래스 상속을 허용하려면 open 키워드를 붙이면 된다.
  2. 코틀린 메소드 또한 final이 디폴트이다. 클래스와 마찬가지로 open 키워드를 붙이면 오버라이드가 가능하다.
  3. 클래스 이름 뒤에 : SuperClassName()을 지정하여 슈퍼클래스(부모클래스)를 상속한 클래스를 만들 수 있다. 빈 괄호()는 슈퍼클래스의(부모클래스)의 기본 생성자를 나타낸다.
  4. 메소드 속성을 오버라이드하려면 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!
  1. 슈퍼클래스를 생성할 대 슈퍼클래스의 파라미터가 있는 생성자를 이용하려면, 서브클래스의 선언부에서 인수를 제공하면 된다.

 

슈퍼클래스로 생성자 인수 전달

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!
  1. Asiatic 클래스 선언의 name은 var도 val도 아니다. 생성자의 인수이며, 이 인수의 값은 상위 클래스 Lion의 name 프로퍼티로 전달된다.
  2. 이름이 Rufo인 Asiatic 클래스의 인스턴스를 생성한다. 이 호출은 Rufo 와 India 인수를 사용하여 Lion 생성자를 호출한다.