작성완료: 2022-02-25
package org.kotlinlang.play // 1
fun main() { // 2
println("Hello, World!") // 3
}
main
함수이다. 코틀린 1.3부터 main
을 파라미터 없이 선언할 수 있다. 리턴 타입은 지정되지 않았다. 이는 이 함수가 아무것도 리턴하지 않는다는 것을 뜻한다. println
은 스탠더드 아웃풋에 문장을 쓴다. 이것은 암묵적으로 import되어 있다. 세미콜론 또한 선택적이다. 1.3 이전의 코틀린에서는 main
함수는 반드시 Array<String>
함수를 가져야 했다.
fun main(args: Array<String>) {
println("Hello, World!")
}
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
}
String
파라미터를 가지고 Unit
을 리턴하는 단순한 함수 (즉, 값을 리턴하지 않는다).info
를 기본 값으로 가진 두번째 선택적 파라미터가 있는 함수. 리턴 타입은 생략되었고, 이는 타입이 실제로는 Unit
임을 뜻한다.Int
를 리턴하는 함수Int
를 반환하는 단일 표현식 함수단일 파라미터를 가진 멤버 변수와 표현식은 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
}
Int
에 infix 표현식 함수를 정의한다.to
infix 함수를 호출해 Pair
를 생성한다.onto
라고 불리는 당신만의 to
구현.예시는 지역변수(다른 함수 내에 중첩된 함수)를 사용한다는 점을 주의하자.
특정 함수들은 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
operator
modifier(수식자/제한자))를 사용해 위 infix 함수를 한 단계 더 발전시킨다.times
에 대한 연산자 심볼은 *
이므로 2 * "Bye"
를 사용해 호출할 수 있다. 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
}
vargar
modifier는 파라미터를 vararg로 바꾼다.printAll
을 어떤 수의 문자열 아규먼트를 사용해서든 호출할 수 있도록 한다.prefix
에 값을 설정할 수 있다.*
를 사용한다. 이를 통해 entries
(Array< String >
) 대신 *entries
(String
vararg)를 전달할 수 있다.코틀린은 강력한 타입 추론을 가지고 있다. 변수의 타입을 명시적으로 선언할 수 있지만, 일반적으로는 컴파일러가 추론을 통해 그 작업을 하도록 두게 될 것이다.
(불변성이) 권장되기는 하지만 코틀린은 불변성(immutability)를 강제하지 않는다. 기본적으로 var
보다 val
을 사용하도록 하자.
var a: String = "initial" // 1
println(a)
val b: Int = 1 // 2
val c = 3 // 3
Int
를 추론한다.var e: Int // 1
println(e) // variable 'e' must be initialized // 2
언제 변수를 초기화할지 자유롭게 선택할 수 있다. 하지만 처음 읽히기 전에는 반드시 초기화되어야 한다.
val d: Int // 1
if (someCondition()) {
d = 1 // 2
} else {
d = 2 // 2
}
println(d) // 3
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
null
을 non-nullable 변수에 할당하려고 시도하면 컴파일 오류가 생성된다.null
을 nullable variable에 설정할 수 있다.null
을 할당하려고 하면 컴파일 오류가 발생한다.가끔씩 코틀린 프로그램은 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
}
}
클래스 선언은 클래스 이름, 클래스 헤더(타입 파라미터, 기본 생성자 등을 지정한다), 그리고 중괄호로 둘러싸인 클래스 바디로 구성된다. 해더와 바디는 선택적이다. 만약 클래스에 바디가 없다면 중괄호는 생략될 수 있다.
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
라는 클래스를 프로퍼티나 유저가 정의한 생성자 없이 선언한다. 코틀린에 의해 파라미터 없는 기본 생성자가 자동으로 생성된다.id
와 가변 변수 email
)와 두 파라미터 id
, email
을 사용하는 생성자로 클래스를 선언한다. Customer
클래스의 인스턴스를 생성한다. 코틀린에는 new
키워드가 없다.Contact
클래스의 인스턴스를 생성한다.id
에 엑세스한다.email
의 값을 업데이트한다.제네릭은 모던 랭귀지에서 표준이 된 일반성 메커니즘(genericity mechanism)이다. 제네릭 클래스와 함수들은 특정한 제너릭 타입과 독립적인 common logic을 캡슐화함으로써 코드 재사용성을 높인다. 한 예로 List<T>
내부 로직은 T
가 무엇인지에 독립적이다.
코틀린에서 제네릭을 사용하는 첫 번째 방법은 제네릭 클래스를 생성하는 것이다.
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
를 사용할 수 있다.구현에는 단일 표현식으로 정의될 수 있는 코틀린의 약식 구문을 많이 사용한다.
로직이 특정 타입과 독립적인 함수를 제네릭으로 생성해 사용할 수 있다. 예를 들어 가변 스택을 생성하는 유틸리티 함수를 작성할 수 있다:
fun <E> mutableStackOf(vararg elements: E) = MutableStack(*elements)
fun main() {
val stack = mutableStackOf(0.62, 3.14, 2.7)
println(stack)
}
컴파일러는 mutableStackOf
의 파라미터들에서 제네릭 타입을 추론할 수 있다. 그러므로 mutableStackOf<Double>(...)
라고 작성할 필요는 없다.
코틀린은 전통적인 객체 지향 상속 메커니즘을 완전히 지원한다.
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()
}
final
이다. 클래스 상속을 허용하고 싶을 경우 open
제한자로 클래스를 표시한다.final
이다. 클래스와 마찬가지로, open
제한자를 사용하면 오버라이딩을 허용한다.: SuperclassName()
을 지정하면 superclass를 상속한다. 빈 괄호 ()
는 superclass default constructor의 호출을 가리킨다.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()
}
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()
}
Asiatic
선언에서 name
은 val
도 var
도 아니다. 이것은 생성자 아규먼트로, 그 값은 슈퍼클래스 Lion
의 name
프로퍼티로 전달된다.Rufo
로 Asiatic
인스턴스를 생성한다. 콜은 Rufo
와 India
아규먼트를 가진 Lion
생성자를 호출한다.