Kotlin Examples > Introduction

HH·2022년 2월 25일
0

Kotlin Examples

목록 보기
1/8

작성완료: 2022-02-25

Hello World


package org.kotlinlang.play         // 1

fun main() {                        // 2
    println("Hello, World!")        // 3
}
  1. 코틀린 코드는 일반적으로 패키지에서 선언된다. 패키지 지정은 선택적이다. 만약 패키지를 소스 파일에서 지정하지 않으면 내용은 디폴트 패키지로 가게 된다.
  2. 코틀린 어플리케이션의 엔트리 포인트는 main 함수이다. 코틀린 1.3부터 main을 파라미터 없이 선언할 수 있다. 리턴 타입은 지정되지 않았다. 이는 이 함수가 아무것도 리턴하지 않는다는 것을 뜻한다.
  3. println은 스탠더드 아웃풋에 문장을 쓴다. 이것은 암묵적으로 import되어 있다. 세미콜론 또한 선택적이다.

1.3 이전의 코틀린에서는 main 함수는 반드시 Array<String> 함수를 가져야 했다.

fun main(args: Array<String>) {
    println("Hello, World!")
}

Functions


기본 파라미터 값들과 명명된 아규먼트


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
}
  1. String 파라미터를 가지고 Unit을 리턴하는 단순한 함수 (즉, 값을 리턴하지 않는다).
  2. info를 기본 값으로 가진 두번째 선택적 파라미터가 있는 함수. 리턴 타입은 생략되었고, 이는 타입이 실제로는 Unit임을 뜻한다.
  3. Int를 리턴하는 함수
  4. 추론된 Int를 반환하는 단일 표현식 함수
  5. Hello 아규먼트로 첫 번째 함수 호출
  6. 두 파라미터 모두에 값을 전달해서 함수를 호출
  7. 두 번째 파라미터를 생략하고 같은 함수를 호출. 기본 값이 사용되었다.
  8. 명명된 아규먼트를 사용해 같은 함수를 호출. 아규먼트 순서가 바뀌었다.
  9. sum 함수 호출 결과를 프린트
  10. multiply 함수 호출 결과를 프린트.

Infix functions


단일 파라미터를 가진 멤버 변수와 표현식은 infix 함수로 바뀔 수 있다.

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
}
  1. Int에 infix 표현식 함수를 정의한다.
  2. infix 함수를 호출한다.
  3. 스탠더드 라이브러리에서 to infix 함수를 호출해 Pair를 생성한다.
  4. onto라고 불리는 당신만의 to 구현.
  5. Infix 표기법은 멤버 함수(메서드)로도 동작한다.
  6. 포함하는 클래스가 첫 번째 파라미터가 된다.

예시는 지역변수(다른 함수 내에 중첩된 함수)를 사용한다는 점을 주의하자.

Operatior Functions


특정 함수들은 operator 심볼로 호출함으로써 연산자로 '업그레이드' 될 수 있다.

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])   

// operator fun Int.times(str: String) = "- $str" 
// println(2 * "Bye") 결과는 - Bye
  1. operator modifier(수식자/제한자))를 사용해 위 infix 함수를 한 단계 더 발전시킨다.
  2. times에 대한 연산자 심볼은 *이므로 2 * "Bye"를 사용해 호출할 수 있다.
  3. 연산자 함수는 문자열 범위에 대한 접근을 쉽게 해 준다.
  4. get() 연산자는 대괄호 엑세스 구문으로 활성화된다.

vararg 파라미터가 있는 함수


Varargs는 아규먼트들을 콤마로 나눔으로써 어떤 수의 아규먼트든 전달할 수 있게 해 준다.

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(entries) //Type mismatch : inferred type is Array<out String> but String was expected
}
  1. vargar modifier는 파라미터를 vararg로 바꾼다.
  2. 이것은 printAll을 어떤 수의 문자열 아규먼트를 사용해서든 호출할 수 있도록 한다.
  3. 명명된 파라미터(named parameters) 덕분에 vararg 뒤에 동일한 종류의 다른 파라미터를 더할 수도 있다. 자바에서는 이 값을 전달할 방법이 없기 때문에 허용되지 않는다.
  4. 명명된 파라미터를 사용해 vararg와 별개로 prefix에 값을 설정할 수 있다.
  5. 런타임에 vararg는 단지 배열이다. 이것을 vararg 파라미터로 전달하려면 특별한 전개 구문(spread operator) *를 사용한다. 이를 통해 entries (Array< String >) 대신 *entries (String vararg)를 전달할 수 있다.

Variables


코틀린은 강력한 타입 추론을 가지고 있다. 변수의 타입을 명시적으로 선언할 수 있지만, 일반적으로는 컴파일러가 추론을 통해 그 작업을 하도록 두게 될 것이다.
(불변성이) 권장되기는 하지만 코틀린은 불변성(immutability)를 강제하지 않는다. 기본적으로 var 보다 val을 사용하도록 하자.

var a: String = "initial"  // 1
println(a)
val b: Int = 1             // 2
val c = 3                  // 3
  1. 변경가능한 변수를 선언하고 초기화한다.
  2. 불변 함수를 선언하고 초기화한다.
  3. 타입 지정 없이 불변 함수를 선언하고 초기화한다. 컴파일러가 타입 Int를 추론한다.
var e: Int  // 1
println(e)  // variable 'e' must be initialized // 2
  1. 초기화 없이 변수를 선언한다.
  2. 변수 사용은 다음과 같은 컴파일러 에러를 가져온다: 변수 'e'는 반드시 초기화되어야 한다.

언제 변수를 초기화할지 자유롭게 선택할 수 있다. 하지만 처음 읽히기 전에는 반드시 초기화되어야 한다.

val d: Int  // 1

if (someCondition()) {
    d = 1   // 2
} else {
    d = 2   // 2
}

println(d) // 3
  1. 초기화 없이 변수를 선언한다.
  2. 조건에 따라 다른 값으로 변수를 초기화한다.
  3. 초기화되었기 때문에 변수를 읽을 수 있다.

Null safety


NullPointerException 세계를 지우기 위한 노력으로 코틀린의 변수 타입은 null할당을 허용하지 않는다. 변수가 null이 될 필요가 있다면 ?를 타입 마지막에 붙여 nullable을 선언해야 한다.

var neverNull: String = "This can't be null"            // 1

// Null can not be a value of a non-null type String
neverNull = null                                        // 2

var nullable: String? = "You can keep a null here"      // 3

nullable = null                                         // 4

var inferredNonNull = "The compiler assumes non-null"   // 5

// Null can not be a value of a non-null type String
inferredNonNull = null                                  // 6

fun strLength(notNull: String): Int {                   // 7
    return notNull.length
}

strLength(neverNull)                                    // 8

// Type mismatch: inferred type is Nothing? but String was expected
strLength(nullable)                                     // 9
  1. non-null String 변수를 선언한다.
  2. null을 non-nullable 변수에 할당하려고 시도하면 컴파일 오류가 생성된다.
  3. nullable String variable을 선언한다.
  4. null 을 nullable variable에 설정할 수 있다.
  5. 타입 추론 시, 값으로 초기화된 변수에 대해 컴파일러는 non-null을 가정한다.
  6. 추론된 타입에 대해 null을 할당하려고 하면 컴파일 오류가 발생한다.
  7. non-null string parameter를 가진 함수를 선언한다.
  8. String (non-nullable) argument로 함수를 호출할 수 있다.
  9. String? (nullable) argument로 함수를 호출하려고 하면 컴파일 오류가 발생한다.

Working with Nulls


가끔씩 코틀린 프로그램은 null 값으로 작업해야 할 경우가 있다. 예를 들어 외부 자바 코드와 상호작용할 때나, 존재하지 않는 상태(truly absent state)를 표현해야 할 때 그렇다. 코틀린은 이런 사왕을 우아하게 다루기 위한 null tracking을 제공한다.

fun describeString(maybeString: String?): String {              // 1
    if (maybeString != null && maybeString.length > 0) {        // 2
        return "String of length ${maybeString.length}"
    } else {
        return "Empty or null string"                           // 3
    }
}
  1. nullable string 을 가지고 그에 대한 묘사를 반환하는 함수
  2. 주어진 문자열이 null이 아니고 비어있지 않을 경우 길이에 대한 정보를 반환한다.
  3. 그렇지 않은 경우 호출자에게 해당 문자열이 null이거나 비어 있다고 말한다.

Classes


클래스 선언은 클래스 이름, 클래스 헤더(타입 파라미터, 기본 생성자 등을 지정한다), 그리고 중괄호로 둘러싸인 클래스 바디로 구성된다. 해더와 바디는 선택적이다. 만약 클래스에 바디가 없다면 중괄호는 생략될 수 있다.

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. 두 프로퍼티(불변 변수 id와 가변 변수 email)와 두 파라미터 id, email을 사용하는 생성자로 클래스를 선언한다.
  3. 기본 생성자로 Customer 클래스의 인스턴스를 생성한다. 코틀린에는 new 키워드가 없다.
  4. 두 아규먼트를 가진 생성자를 사용해 Contact 클래스의 인스턴스를 생성한다.
  5. 프로퍼티 id에 엑세스한다.
  6. 프로퍼티 email의 값을 업데이트한다.

Generics


제네릭은 모던 랭귀지에서 표준이 된 일반성 메커니즘(genericity mechanism)이다. 제네릭 클래스와 함수들은 특정한 제너릭 타입과 독립적인 common logic을 캡슐화함으로써 코드 재사용성을 높인다. 한 예로 List<T> 내부 로직은 T가 무엇인지에 독립적이다.

Generic classes


코틀린에서 제네릭을 사용하는 첫 번째 방법은 제네릭 클래스를 생성하는 것이다.

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


로직이 특정 타입과 독립적인 함수를 제네릭으로 생성해 사용할 수 있다. 예를 들어 가변 스택을 생성하는 유틸리티 함수를 작성할 수 있다:

fun <E> mutableStackOf(vararg elements: E) = MutableStack(*elements)

fun main() {
  val stack = mutableStackOf(0.62, 3.14, 2.7)
  println(stack)
}

컴파일러는 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()
}
  1. 코틀린 클래스는 기본적으로 final이다. 클래스 상속을 허용하고 싶을 경우 open 제한자로 클래스를 표시한다.
  2. 코틀린 메서드 또한 기본적으로 final이다. 클래스와 마찬가지로, open 제한자를 사용하면 오버라이딩을 허용한다.
  3. 클래스는 이름 뒤에 : SuperclassName()을 지정하면 superclass를 상속한다. 빈 괄호 ()는 superclass default constructor의 호출을 가리킨다.
  4. 오버라이딩 메서드 또는 속성은 override 제한자를 필요로 한다.

Inheritance with Parameterized Constructor


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()
}
  1. 서브클래스 생성 시 슈퍼클래스의 파라미터가 있는 생성자를 사용하고 싶을 경우 서브클래스 선언에 생성자 아규먼트를 제공한다.

Passing Constructor Arguments to Superclass


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()
}
  1. Asiatic 선언에서 namevalvar도 아니다. 이것은 생성자 아규먼트로, 그 값은 슈퍼클래스 Lionname 프로퍼티로 전달된다.
  2. name RufoAsiatic 인스턴스를 생성한다. 콜은 RufoIndia 아규먼트를 가진 Lion 생성자를 호출한다.

0개의 댓글