Kotlin:Basic
lastmod 2023-03-29created 2023-03-296 edits
Kotlin basics.
Variables
Mutable and immutable variables
-
val - Comes from value
- A variable that stores an immutable reference. In Java terms, it corresponds to a final variable.
-
var - Comes from variable
- A mutable reference. It corresponds to an ordinary Java variable.
By default, declare every variable as immutable with the val keyword, and change it to var only later when truly needed.
A val variable must be initialized exactly once when the block executes. However, a val can be initialized to one of several different values depending on a condition.
Class
class Person(
val name: String, // 읽기 전용 프로퍼티로, 코틀린은 (비공개) 필드와 필드를 읽는 단순한 (공개) 게터를 만들어 낸다.
var isMarried: Boolean // 쓸 수 있는 프로퍼티로, 코틀린은 (비공개)필드, (공개) 게터/세터를 만들어 낸다.
)
Enum
enum class Color(
val r: Int, val g: Int, val b: Int
) {
RED(255, 0, 0), ORANGE(255, 165, 0),
YELLOW(255, 255, 0), GREEN(0, 255, 0), BLUE(0, 0, 255),
INDIGO(75, 0, 130), VIOLET(238, 130, 238);
fun rgb() = (r * 256 + g) * 256 + b
}
When
fun getMnemonic(color: Color) =
when (color) {
Color.RED -> "Richard"
Color.ORANGE -> "Of"
Color.YELLOW -> "York"
Color.GREEN -> "Gave"
Color.BLUE -> "Battle"
Color.INDIGO -> "In"
Color.VIOLET -> "Vain"
}
In Kotlin, when is much more powerful than Java's switch.
Unlike Java's switch, which allows only constants as branch conditions, Kotlin's when allows arbitrary objects as branch conditions.
fun mix(c1: Color, c2: Color) =
when (setOf(c1, c2)) {
setOf(RED, YELLOW) -> ORANGE
setOf(YELLOW, BLUE) -> GREEN
setOf(BLUE, VIOLET) -> INDIGO
else -> throw Exception("Dirty color")
}
When without an argument
fun mixOptimized(c1: Color, c2: Color) =
when {
(c1 == RED && c2 == YELLOW) ||
(c1 == YELLOW && c2 == RED) ->
ORANGE
(c1 == YELLOW && c2 == BLUE) ||
(c1 == BLUE && c2 == YELLOW) ->
GREEN
(c1 == BLUE && c2 == VIOLET) ||
(c1 == VIOLET && c2 == BLUE) ->
INDIGO
else -> throw Exception("Dirty color")
}
Smart casts: combining type checks and type casts
In Kotlin, you check a variable's type with is. The is check is similar to Java's instanceof. However, in Java, after checking a variable's type with instanceof, you must explicitly cast the variable to access members of that type. In Kotlin, the compiler does the cast for the programmer. This is called a smart cast.
fun eval(e: Expr): Int {
if (e is Num) {
val n = e as Num
return n.value
}
if (e is Sum) {
return eval(e.right) + eval(e.left)
}
throw IllegalArgumentException("Unknown expression")
}
Exception
// 조건이 참이면 number의 값이 초기화되고 거짓이면 초기화되지 않고 throw를 호출한다.
val number = try {
Integer.parseInt(reader.readLine())
} catch (e: NumberFormatException) {
return // 예외가 발생한 경우 catch 블록 다음의 코드는 실행되지 않는다.
}
String templates
You can put the value of a variable or expression into a string by prefixing the variable name with $, or by wrapping the expression in ${ } like ${expression}.
Defining functions
Use fun.
fun main(args: Array<String>) {
val strings = listOf("first", "second", "fourteenth")
println(strings.last()) // 리스트의 마지막 원소를 가져온다.
val numbers = setOf(1, 14, 2)
println(numbers.max()) // 컬렉션에서 최댓값을 가져온다.
}
Generic functions
Use fun<T>.
fun <T> joinToString(
collection: Collection<T>,
separator: String = ";",
prefix: String = "(",
postfix: String = ")"
): String {
val result = StringBuilder(prefix)
for ((index, element) in collection.withIndex()) {
if (index > 0) result.append(separator)
result.append(element)
}
result.append(postfix)
return result.toString()
}
fun main(args: Array<String>) {
val list = listOf(1, 2, 3)
println(joinToString(list, "; ", "(", ")"))
println(joinToString(collection = list, separator = ";", prefix = "(", postfix = ")"))
println(joinToString(list))
println(joinToString(list, "; "))
}
Extension functions
Conceptually, extension functions are simple. An extension function is a function declared outside a class that can be called as if it were a member method of that class. Let's add an extension method that returns the last character of a string
Extension functions cannot be overridden.
| INFORMATION |
| If an extension function of a class and a member function of that class have the same name and signature, the member function is called, not the extension function (member functions take precedence). Always keep this in mind when changing a class's API. |
Imports and extension functions
To use an extension function, you must import it just like any other class or function. If you use an extension function without an import, it may conflict with an extension function of the same name, so you must specify which extension function you mean with an import.
import strings.lastChar // 명시적으로 사용
import strings.* // * 사용 가능
import strings.lastChar as last // as 키워드를 사용 가능
Extension properties
Extension properties let you add APIs to existing classes that can be used with property-style syntax on objects. Although they are called properties, there is no proper way to store state, so extension properties cannot actually have any state.
Since there is no backing field, no default getter implementation can be provided, so you must at least define a getter. Likewise, there is nowhere to store a value computed by an initializer, so you cannot write an initializer either.
var StringBuilder.lastChar: Char
get() = get(length - 1)
set(value: Char) {
this.setCharAt(length - 1, value)
}
fun main(args: Array<String>) {
println("Kotlin".lastChar)
val sb = StringBuilder("Kotlin?")
sb.lastChar = '!'
println(sb)
}
Varargs functions: defining functions that accept a variable number of arguments
Variable-length arguments (varargs) are a feature where, when calling a method, you pass as many values as you want as arguments and the Java compiler puts those values into an array. Kotlin's variable-length arguments are similar to Java's, but the syntax is slightly different. Instead of putting ... after the type, Kotlin puts the varag modifier before the parameter.
public fun <T> listOf(vararg elements: T): List<T> = if (elements.size > 0) elements.asList() else emptyList()
fun main(args: Array<String>) {
val list = listOf("one", "two", "eight")
}
Kotlin and Java syntax also differ when passing elements that are already in an array as variable-length arguments. In Java you can just pass the array, but in Kotlin you must explicitly unpack the array so that each element is passed as an argument. Technically, the spread operator does this.
Infix calls and destructuring declarations (working with pairs of values)
To create a map, use the mapOf function.
Here the word to is not a Kotlin keyword. This code calls an ordinary method named to using a special form called an infix call. In an infix call, the method name is placed between the receiver object and the single method argument.
To allow a function (method) to be used in infix calls, you must add the infix modifier before the function (method) declaration.
/**
* Creates a tuple of type [Pair] from this and [that].
*
* This can be useful for creating [Map] literals with less noise, for example:
* @sample samples.collections.Maps.Instantiation.mapFromPairs
*/
public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)
This feature is called a destructuring declaration. Destructuring can be applied to objects other than Pair instances. For example, you can initialize two variables, key and value, using an element of a map.
Regular expressions and triple-quoted strings
fun parsePath(path: String) {
val regex = """(.+)/(.+)\\.(.+)""".toRegex()
val matchResult = regex.matchEntire(path)
if (matchResult != null) {
val (directory, filename, extension) = matchResult.destructured
println("Dir: $directory, name: $filename, ext: $extension")
}
}
Local functions and extensions
fun saveUser(user: User) {
fun validate(user: User,
value: String,
fieldName: String) {
if (value.isEmpty()) {
throw IllegalArgumentException(
"Can't save user ${user.id}: empty $fieldName")
}
}
validate(user, user.name, "Name")
validate(user, user.address, "Address")
// Save user to the database
}
Interface
interface Clickable {
fun click()
}
class Button : Clickable {
override fun click() = println("I was clicked")
}
// 결과값
// I was clicked
Default implementations
interface Clickable {
fun click() // 일반 메소드 선언
fun showOff() = println("I'm clickable!") // 디폴트 구현이 있는 메소드
}
open, final, abstract modifiers: final by default
open class RichButton : Clickable { // 이 클래스는 열려있다. 다른 클래스가 이 클래스를 상속할 수 있다.
fun disable() {} // 이 함수는 파이널이다. 하위 클래스가 이 메소드를 오버라이드할 수 없다.
open fun animate() {} // 이 함수는 열려있다. 하위 클래스에서 이 메소드를 오버라이드해도 된다.
override fun click() {} // 이 함수는 (상위 클래스에서 선언된) 열려있는 메소드를 오버라이드 한다. 오버라이드한 메소드는 기본적으로 열려있다.
}
To prevent subclasses from overriding the implementation of an overriding method, you must explicitly put final before the overriding method.
open class RichButton : Clickable {
// 여기 있는 'final'은 쓸데 없이 붙은 중복이 아니다.
// 'final'이 없는 'override' 메소드나 프로퍼티는 기본적으로 열려있다.
final override fun click() {}
}
abstract
As in Java, Kotlin lets you declare a class as abstract. An abstract class declared with abstract cannot be instantiated. Therefore, you do not need to specify the open modifier before abstract members.
abstract class Animated { // 이 클래스는 추상클래스다. 이 클래스의 인스턴스를 만들 수 없다.
abstract fun animate() // 이 함수는 추상 함수다. 이 함수에는 구현이 없다. 하위 클래스에서는 이 함수를 반드시 오버라이드해야 한다.
open fun stopAnimating() { ... } // 추상 클래스에 속했더라도 비추상 함수는 기본적으로 파이널이지만 원한다면 open으로 오버라이드를 허용할 수 있다.
fun animateTwice() { ... } // 추상 클래스에 속했더라도 비추상 함수는 기본적으로 파이널이지만 원한다면 open으로 오버라이드를 허용할 수 있다.
}
Visibility modifiers: public by default
internal means "visible only inside the module". A module means a set of Kotlin files compiled together at once.
internal open class TalkativeButton : Focusable {
private fun yell() = println("Hey!")
protected fun whisper() = println("Let's talk!")
}
fun TalkativeButton.giveSpeesh() {
yell()
whisper() // 오류: "whisper"에 접근할 수 없음: "whisper"는 "TalkativeButton"의 "protected" 멤버임
}