코틀린 문법 한번에 정리하기 #
- 주석 정리
Variable #
// top-level
var x = 5
fun main() {
x+= 1
println(x)
val a : Int = 1
val b = 1
val c : Int
c = 3
val d : Int
d = 123
//val(value) : 불변(Immutable)
//var(variable) : 가변(Mutable)
var e : String = "Hello"
e = "World"
var f = 123
// f = "hi" // 컴파일 오류 타입은 변경이 불가
}
Function #
// 기본적인 함수 선언 스타일
fun sum(a: Int, b: Int) : Int {
return a + b
}
// 표현식 스타일
fun sum2(a: Int, b: Int) : Int = a + b
// 표현식 & 반환타입 생략
fun sum3(a: Int, b: Int) = a + b
// 몸통이 있는 함수는 반환 타입을 제거하면 컴파일 오류
fun sum4(a: Int, b: Int) : Int {
return a + b
}
// 반환타입이 없는 함수는 Unit을 반환한다
fun printSum(a: Int, b: Int) : Unit {
println("$a + $b = ${a + b}")
}
// 디폴트 파라미터
fun greeting(message: String = "안녕하세요!!") {
println(message)
}
//
//fun main( ) {
// greeting()
// greeting("HI!!!")
//}
fun log(level: String = "INFO", message: String) {
println("[$level]$message")
}
fun main( ) {
log(message = "인포 로그")
log(level = "DEBUG", "디버그 로그")
log("WARN", "워닝 로그")
log(level = "ERROR", message = "에러 로그")
}
For #
fun main() {
// 범위 연산자 .. 를 사용해 for loop 돌리기
for (i in 0..3) {
println(i)
}
// until 을 사용해 반복한다
// 뒤에 온 숫자는 포함하지 않는다
for (i in 0 until 3) {
println(i)
}
// step 에 들어온 값 만큼 증가시킨다
for ( i in 0..6 step 2) {
println(i)
}
// downTo를 사용해 반복하면서 값을 감소시킨다
for (i in 3 downTo 1) {
println(i)
}
// 전달받은 배열을 반복
val numbers = arrayOf(1,2,3)
for (i in numbers) {
println(i)
}
}
If #
fun main() {
//if..else 사용
val job = "Software Developer"
if (job == "Software Developer") {
println("개발자")
} else {
println("개발자아님")
}
//코틀린의 if...else는 표현식이다
val age : Int = 10
val str = if (age > 10) {
"성인"
} else {
"아이"
}
//코틀린은 삼항 연산자가 없다. if..else가 표현식이므로 불필요하다
val a = 1
val b = 2
val c = if (b > a) b else a
}
When #
fun main() {
// 자바 코드를 코틀린의 when식으로 변환한 코드
val day = 2
val result = when (day) {
1 -> "월요일"
2 -> "화요일"
3 -> "수요일"
4 -> "목요일"
else -> "기타"
}
println(result)
// else를 생략할 수 있다
when(getColor()) {
Color.RED -> print("red")
Color.GREEN -> println("green")
else -> println("blue")
}
// 여러개의 조건을 콤마로 구분해 한줄에서 정의할 수 있다
when (getNumber()) {
0, 1 -> print("0 또는 1")
else -> print("0 또는 1이 아님")
}
}
enum class Color {
RED, GREEN, BLUE
}
fun getColor() = Color.RED
fun getNumber() = 2
while #
fun main() {
// 자바의 while문과 동일
// 조건을 확인하고 참이면 코드 블록을 실행한 후 다시 조건을 확인
var x = 5
while (x > 0) {
println(x)
x--
}
}
Exception #
fun main() {
try {
throw Exception()
} catch (e: Exception) {
println("에러 발생!")
} finally {
println("finally 실행!")
}
val a = try {
"1234".toInt()
} catch (e: Exception) {
println("예외 발생 !")
}
println(a)
//throw Exception("예외 발생!")
val b: String? = null
val c: String = b ?: failFast("a is null")
println(c.length)
}
fun failFast(message: String): Nothing {
throw IllegalArgumentException(message)
}
Null Safety #
fun getNullStr(): String? = null
fun getLengthIfNotNull(str: String?) = str?.length ?: 0
fun main() {
val nullableStr = getNullStr()
val nullableStrLength = nullableStr?.length ?: "null인 경우 반환".length
println(nullableStrLength)
val length = getLengthIfNotNull(null)
println(length)
//throw NullPointerException()
// val c: String? = null
// val d = c!!.length
// println(Java_NullSafety.getNullStr()?.length ?: 0)
}
Class Property #
class Coffee(
var name: String = "",
var price: Int = 0,
var iced: Boolean = false,
) {
val brand: String
get() {
return "스타벅스"
}
var quantity : Int = 0
set(value) {
if (value > 0) { // 수량이 0 이상인 경우에만 할당
field = value
}
}
}
class EmptyClass
fun main() {
val coffee = Coffee()
coffee.name = "아이스 아메리카노"
coffee.price = 2000
coffee.quantity = 1
coffee.iced = true
if (coffee.iced) {
println("아이스 커피")
}
println("${coffee.brand} ${coffee.name} 가격은 ${coffee.price} 수량은 ${coffee.quantity}")
}
Inheritance #
open class Dog {
open var age: Int = 0
open fun bark() { // 반드시 오버라이드해야하는건 아니다. open fun일 경우 반드시 body를 구현해야한다.
println("멍멍")
}
}
open class Bulldog(final override var age: Int = 0) : Dog() {
final override fun bark() {
super.bark()
}
}
abstract class Developer {
abstract var age: Int
abstract fun code(language: String)
}
class BackendDeveloper(override var age : Int) : Developer() {
override fun code(language: String) {
println("I code with $language")
}
}
fun main() {
val backendDeveloper = BackendDeveloper(age = 20)
println(backendDeveloper.age)
backendDeveloper.code("Kotlin")
val dog = Bulldog(age = 2)
println(dog.age)
dog.bark()
}
Interface #
class Product(val name: String, val price: Int)
interface Wheel {
fun roll()
}
interface Cart : Wheel {
var coin: Int
val weight: String
get() = "20KG"
fun add(product: Product)
fun rent() {
if (coin > 0) {
println("카트를 대여합니다")
}
}
override fun roll() {
println("카트가 굴러갑니다")
}
fun printId() = println("1234")
}
interface Order {
fun add(product: Product) {
println("${product.name} 주문이 완료되었습니다")
}
fun printId() = println("5678")
}
class MyCart(override var coin: Int) : Cart, Order {
override fun add(product: Product) {
if (coin <= 0) println("코인을 넣어주세요")
else println("${product.name}이(가) 카트에 추가됐습니다")
// 주문하기
super<Order>.add(product)
}
override fun printId() {
super<Cart>.printId()
super<Order>.printId()
}
}
fun main() {
val cart = MyCart(coin = 100)
cart.rent()
cart.roll()
cart.add(Product(name = "장난감", price = 1000))
cart.printId()
}
Collection #
import java.util.*
import java.util.stream.Collectors
import kotlin.collections.ArrayList
fun main() {
// immutable
val currencyList = listOf("달러", "유로", "원")
// mutable
val mutableCurrencyList: MutableList<String> = mutableListOf<String>().apply {
add("달러")
add("유로")
add("원")
}
mutableCurrencyList.add("파운드")
// immutable set
val numberSet = setOf(1, 2, 3, 4)
// mutable set
val mutableSet = mutableSetOf<Int>().apply {
add(1)
add(2)
add(3)
add(4)
}
// immutable map
val numberMap = mapOf("one" to 1, "two" to 2)
// mutable map
val mutableNumberMap = mutableMapOf<String, Int>()
mutableNumberMap["one"] = 1
mutableNumberMap["two"] = 2
mutableNumberMap["three"] = 3
// 컬렉션 빌더는 내부에선 mutable 반환은 immutable
val numberList: List<Int> = buildList{
add(1)
add(2)
add(3)
add(4)
}
// linkedList
val linkedList = LinkedList<Int>().apply {
addFirst(3)
add(2)
addLast(1)
}
// arrayList
val arrayList = ArrayList<Int>().apply {
add(1)
add(2)
add(3)
}
// val iterator = currencyList.iterator()
// while (iterator.hasNext()) {
// println(iterator.next())
// }
//
// println("===============")
//
// for (currency in currencyList) {
// println(currency)
// }
//
// println("===============")
//
// currencyList.forEach {
// println(it)
// }
// for loop -> map
val lowerList = listOf("a","b","c")
//val upperList = mutableListOf<String>()
// for (lowerCase in lowerList) {
// upperList.add(lowerCase.uppercase())
// }
val upperList = lowerList.map { it.uppercase() }
//val filteredList = mutableListOf<String>()
// for (upperCase in upperList) {
// if (upperCase == "A" || upperCase == "C" ) {
// filteredList.add(upperCase)
// }
// }
val filteredList = upperList
.asSequence() // 대량데이터의 경우 이걸 사용하는게 좋다.
.filter { it == "A" || it == "C" }
.toList()
println(filteredList)
}
Data Calss #
data class Person(val name: String, val age: Int) {
}
fun main() {
val person1 = Person(name = "tony", age = 12)
val (name, age) = person1
println("이름=${name}, 나이=${age}")
// val set = hashSetOf(person1)
// println(set.contains(person1))
}
Singleton #
import java.time.LocalDateTime
//object Singleton {
//
// val a = 1234
//
// fun printA() = println(a)
//}
//
//fun main() {
// println(Singleton.a)
// Singleton.printA()
//}
//object DatetimeUtils {
//
// val now : LocalDateTime
// get() = LocalDateTime.now()
//
// const val DEFAULT_FORMAT = "YYYY-MM-DD"
//
// fun same(a: LocalDateTime, b: LocalDateTime) : Boolean {
// return a == b
// }
//
//}
//
//fun main() {
// println(DatetimeUtils.now)
// println(DatetimeUtils.now)
// println(DatetimeUtils.now)
//
// println(DatetimeUtils.DEFAULT_FORMAT)
//
// val now = LocalDateTime.now()
// println(DatetimeUtils.same(now, now))
//}
class MyClass {
private constructor()
companion object MyCompanion {
val a = 1234
fun newInstance() = MyClass()
}
}
fun main() {
println(MyClass.a)
println(MyClass.newInstance())
println(MyClass.a)
println(MyClass.newInstance())
}
Sealed Class #
sealed class Developer {
abstract val name: String
abstract fun code(language: String)
}
data class BackendDeveloper(override val name: String) : Developer() {
override fun code(language: String) {
println("저는 백엔드 개발자입니다 ${language}를 사용합니다")
}
}
data class FrontendDeveloper(override val name: String) : Developer() {
override fun code(language: String) {
println("저는 프론트엔드 개발자입니다 ${language}를 사용합니다")
}
}
object OtherDeveloper : Developer() {
override val name: String = "익명"
override fun code(language: String) {
TODO("Not yet implemented")
}
}
data class AndroidDeveloper(override val name: String) : Developer() {
override fun code(language: String) {
println("저는 안드로이드 개발자입니다 ${language}를 사용합니다")
}
}
data class IosDeveloper(override val name: String) : Developer() {
override fun code(language: String) {
println("저는 Ios 개발자입니다 ${language}를 사용합니다")
}
}
object DeveloperPool {
val pool = mutableMapOf<String, Developer>()
// 컴파일러는 Developer 구현 클래스가 무엇인지를 모름
// else가 없으면 when절에 컴파일 오류남
// Developer 를 sealed Class로 정의하면 else문 생략가능
// 같은 패키지/하위 모듈에 있는 경우에만 sealed class의 하위클래스가 될 수 있다
// 컴파일러가 Developer 의 자식클래스를 알고있끼 때문이다.
fun add(developer: Developer) = when(developer) {
is BackendDeveloper -> pool[developer.name] = developer
is FrontendDeveloper -> pool[developer.name] = developer
is AndroidDeveloper -> pool[developer.name] = developer
is IosDeveloper -> pool[developer.name] = developer
is OtherDeveloper -> println("지원하지않는 개발자종류입니다")
}
fun get(name: String) = pool[name]
}
fun main() {
val backendDeveloper = BackendDeveloper(name="토니")
DeveloperPool.add(backendDeveloper)
val frontendDeveloper = FrontendDeveloper(name="카즈야")
DeveloperPool.add(frontendDeveloper)
val androidDeveloper = AndroidDeveloper(name="안드로")
DeveloperPool.add(androidDeveloper)
println(DeveloperPool.get("토니"))
println(DeveloperPool.get("카즈야"))
println(DeveloperPool.get("안드로"))
}
Extension #
/**
* 문자열 첫번째 원소 리턴
*/
fun String.first() : Char {
return this[0]
}
fun String.addFirst(char: Char) : String {
// this : 수신자 객체
return char + this.substring(0)
}
class MyExample {
fun printMessage() = println("클래스 출력")
}
// MyExample의 확장함수 생성
// printMessage 멤버함수와 이름을 동일하게 했을때 멤버함수가 우선적으로 수행된다.
// 확장함수의 멤버함수와 동일한 시그니처는 멤버함수가 실행됨
fun MyExample.printMessage() = println("확장 출력")
// 시그니처가 다르면 확장함수 실행이 잘 됨
fun MyExample.printMessage(message:String) = println(message)
// MyExample 이 null일 가능성이 존재
// null인 경우와 아닌 경우 분기처리
fun MyExample?.printNullOrNotNull() {
if (this == null) println("널인 경우에만 출력")
else println("널이 아닌 경우에만 출력")
}
fun main() {
var myExample: MyExample? = null
// 함수에서 null 체크를 하고있다는걸 컴파일러가 알고있어서 오류가 안난다.
myExample.printNullOrNotNull()
myExample = MyExample()
myExample.printNullOrNotNull()
//MyExample().printMessage("확장 출력")
// println("ABCD".first())
//
// println("ABCD".addFirst('Z'))
}
Generics #
class MyGenerics<out T>(val t: T) { // 공변성은 자바 제네릭의 extends 코틀린에선 out
}
class Bag<T> {
fun saveAll(
to: MutableList<in T>, // 반공변성은 자바 제네릭의 super 코틀린에선 in
from: MutableList<T>,
) {
to.addAll(from)
}
}
fun main() {
val bag = Bag<String>()
// String이 CharSequence의 하위타입인데,
// 반공변성에서는 mutableListOf<CharSequence>가 mutableListOf<String>의 하위타입이 된다.
bag.saveAll(mutableListOf<CharSequence>("1", "2"), mutableListOf<String>("3", "4"))
// MyGenerics<CharSequence> 가 MyGenerics<String> 보다 상위타입
val generics = MyGenerics<String>("테스트")
val charGenerics : MyGenerics<CharSequence> = generics
// 제네릭을 사용한 클래스의 인스턴스를 만드려면 타입아규먼트를 제공 (컴파일러 타입 추론 가능)
val generics2 = MyGenerics<String>("테스트")
// 생략가능
val generics3 = MyGenerics("테스트")
// 변수의 타입에 제네릭을 사용한 경우
val list1: MutableList<String> = mutableListOf()
// 타입아규먼트를 생성자에서 추가
val list2 = mutableListOf<String>()
val list3 : List<*> = listOf<String>("테스트")
val list4: List<*> = listOf<Int>(1, 2, 3, 4)
// PECS는 Producer-Extends, Consumer-Super
// 공변성은 자바 제네릭의 extends 코틀린에선 out
// -> 공변성 : T’가 T의 서브타입이면, C<T’>는 C<out T>의 서브타입이다.
// 반공변성은 자바 제네릭의 super 코틀린에선 in
// -> 반공변성 : T’가 T의 서브타입이면, C<T>는 C<in T’>의 서브타입이다.
}
Late init #
class `7_LateInit` {
// 가변 프로퍼티에 대한 지연 초기화
// nullable이 아님에도 초기호 안했어도 컴파일 오류가 발생하지 않는다.
lateinit var text: String // var : 가변
val textInitialized: Boolean
// isInitialized 는 클래스 내부에서만 사용 가능하다. (Main 등에서 사용 불가능)
get() = this::text.isInitialized // 초기화 여부
fun printText() {
println(text)
}
}
fun a (str:String, block: (String) -> Unit) {
block(str)
}
fun main() {
"".let { }
a("") {
it.length
}
val test = `7_LateInit`()
if (!test.textInitialized) {
test.text = "하이요"
}
test.printText() // 초기화 전에 출력 요청하면 오류 발생
}
Lazy init #
class HelloBot {
// val 불변
// by lazy 사용 (멀티쓰레드 안전)
// 기본 : LazyThreadSafetyMode.SYNCHRONIZED
// LazyThreadSafetyMode.NONE 등 상태값을 설정하여 쓰레드 안전성 무시 가능
// 불변을 유지하면서 변수에 대한 초기화를 뒤로 미룰수 있다.
// 변수가 처음으로 사용될 때까지 해당 변수의 초기화를 늦춘다.
// -> 즉, 변수에 처음으로 접근하는 시점에서 초기화 코드가 실행
val greeting: String by lazy(LazyThreadSafetyMode.PUBLICATION) {
// 멀티쓰레드 환경에서도 동기화가 필요하지 않을때 : LazyThreadSafetyMode.PUBLICATION
// 한 번 초기화된 이후에는 모든 스레드가 같은 값을 공유
// 따라서 여러 스레드에서 동시에 초기화를 시도하더라도 최초 한 번만 초기화가 이루어지고 나면 이후에는 초기화 코드가 다시 실행되지않음
getHello()
}
fun sayHello() = println(greeting)
}
fun getHello() = "안녕하세요"
fun main() {
val helloBot = HelloBot()
// 초기화 이후에는 더이상 by lazy {}을 수행하지 않음
// ...
// ...
for (i in 1..5) {
Thread { // 쓰레드 생성하여 병렬로 수행해보자
helloBot.sayHello() // 변수 초기화 첫 실행
}.start()
}
}
Pair Destructuring #
// f((1, 3)) = 1 + 3 = 4
// f(1, 3) = 1 + 3 = 4
//data class Tuple(val a : Int, val b: Int)
fun plus(pair: Pair<Int, Int>) = pair.first + pair.second
fun main() {
//println(plus(1,3))
val plus = plus(Pair(1, 3))
println(plus)
val pair = Pair("A", 1) // 불변
val newPair = pair.copy(first = "B") // 새로운 Pair을 생성
println(newPair)
val second = newPair.component2() // second 값 가져오기
println(second)
val list = newPair.toList()
println(list)
/* 3개 요소 (4개 이상부터는 지원하지 않음, Collection 사용하면 됨) */
val triple = Triple("A","B","C")
println(triple) // 출력
triple.first
triple.second
val newTriple = triple.copy(third = "D") // third 값 변경한 새로운 Triple 생성
println(newTriple)
println(newTriple.component3())
/* 구조분해 할당 */
val (a: String, b: String, c: String) = newTriple
println("$a, $b, $c")
val list3: List<String> = newTriple.toList()
val (a1, a2, a3) = list3
println("$a1, $a2, $a3")
list3.component1()
list3.component2()
list3.component3()
// list3.component4()
// list3.component5()
val map = mutableMapOf(Pair("이상훈", "개발자"))
for ( (key, value) in map ) {
println("${key}의 직업은 $value")
}
}
범위 지정 함수 #
Run #
- run 정의
inline fun <T, R> T.run(block: T.() -> R): R {
return block()
}
- 예제코드
class DatabaseClient {
var url: String? = null
var username: String? = null
var password: String? = null
// DB에 접속하고 Boolean 결과를 반환
fun connect(): Boolean {
println("DB 접속 중 ...")
Thread.sleep(1000)
println("DB 접속 완료")
return true
}
}
fun main() {
// val config = DatabaseClient()
// config.url = "localhost:3306"
// config.username = "mysql"
// config.password = "1234"
// val connected = config.connect()
// run 안에서 수신자 객체 참조는 this로 함 (생략도 가능)
// 변수 중복 참조를 생략할 수 있다는 장점 (위에서 config.xx가 반복됨)
val connected: Boolean = DatabaseClient().run {
url = "localhost:3306"
username = "mysql"
this.password = "1234"
connect() // 자동 return
}
println(connected)
val result: Boolean = with(DatabaseClient()) {
url = "localhost:3306"
username = "mysql"
password = "1234"
connect()
}
println(result)
}
Also #
- also 정의
inline fun <T> T.also(block: (T) -> Unit): T {
block(this)
return this
}
- 예제
class User(val name: String, val password: String) {
fun validate() {
if (name.isNotEmpty() && password.isNotEmpty()) {
println("검증 성공!")
} else {
println("검증 실패!")
}
}
fun printName() = println(name)
}
fun main() {
User(name = "tony", password = "1234").also {
// it을 사용해서 간결하게 사용 가능
it.validate()
it.printName()
}
}
Apply #
- apply 정의
inline fun <T> T.apply(block: T.() -> Unit): T {
block()
return this
}
- 예제코드
fun main() {
// return 타입이 Context 객체에 대한 타입 그대로 (DatabaseClient)
DatabaseClient().apply {
url = "localhost:3306"
username = "mysql"
this.password = "1234"
}.connect()
.run { println(this) } // this : connect() 함수의 반환결과
}
Let #
- let 정의
inline fun <T, R> T.let(block: (T) -> R): R {
return block(this)
}
- 예제코드
fun main() {
val str: String? = "안녕"
// str 이 null이 아닌 경우에 동작한다.
val result: Int? = str?.let {
println(it) // it = str
val abc: String? = "abc"
val def: String? = "def"
if (!abc.isNullOrEmpty() && !def.isNullOrEmpty()) {
println("abcdef가 null 아님")
}
// return 키워드 없이도 return 값으로 셋팅된다.
1234
}
println(result)
// val this: String? = null
// val it : String? = null
val hello = "hello"
val hi = "hi"
hello.let { a : String ->
//println(a.length)
hi.let{ b ->
println(a.length)
println(b.length)
}
}
}
With #
- with 정의
inline fun <T, R> with(receiver: T, block: T.() -> R): R {
return receiver.block()
}
- 예제
fun main() {
val str = "안녕하세요"
//
val length: Int = with(str) {
length // return 생략 가능
}
println(length)
}