코딩하는 개굴이

[Kotlin] Kotlin의 기본 한번에 요약하기 본문

안드로이드/KOTLIN

[Kotlin] Kotlin의 기본 한번에 요약하기

개굴이모자 2023. 5. 2. 12:48
반응형

해당 내용은 fast campus 의 “실무 프로젝트로 배우는 Kotlin & Spring : 리팩토링부터 서비스 구현까지” 강의를 기반으로 작성되었음을 알립니다.

변수

  • 탑레벨 즉, 파일 최상단에 변수를 위치할 수 있음
    • 간단한 로직의 경우 굳이 클래스를 생성하지 않고 변수 할당만으로 처리 할 수 있음
var LOG_TAG = "LOG"

fun main() {
	//...
}

함수

//기본
fun sum(a: Int, b: Int): Int {
	return a + b
}

//표현식 스타일
fun sum(a: Int, b: Int): Int = a + b

//표현식 & 반환타입 생략
fun sum(a: Int, b: Int) = a + b

//몸통이 있는 함수는 반환타입을 제거하면 컴파일 오류 발생
/*
fun sum(a: Int, b: Int) { //이러면 컴파일 오류가 발생. :Int 추가 필요
	return a + b
}
*/

//반환 타입이 없으면 Unit 을 반환
fun sum(a: Int, b: Int) {
	println("${a + b}")
}

//디폴트 파라미터
fun sum(a: Int, b: Int = 0) {
	println("${a + b}")
}

//네임드 아규먼트
fun sum(a: Int = 0, b: Int = 0) {
	println("$a + $b = ${a + b}")
}
fun main() {
	sum(a=1) //1 + 0 = 1
	sum(b=2) //0 + 2 = 2
	sum(b=2, a=1) //1 + 2 = 3
	sum(1,2) //1 + 2 = 3
	sum(a=1, b=2) //1 + 2 = 3
}

흐름제어

if else 는 표현식이라서, 아래와 같이 복잡한 삼항 연산자를 사용하지 않고도 대체하여 사용할 수 있다.

val result = if(b > a) b else a 

val resultForWhen = when(type) {
	WEB -> "this is web"
	MOBILE, AND, IOS -> "this is mobile"
	WINDOW -> "this is window"
	else -> "this it others"
}

val resultForType = when(getType()) { //type 이 명확할 경우 else 의 생략 가능
	HUMAN -> "this is human"
	NOTHUMAN -> "this is not human"
}

fun getType():Type = Type.HUMAN

enum class Type {
	HUMAN, NOTHUMAN
}

반복문

while 의 경우 Java 와 동일

for(i in 0..3) {} //0,1,2,3
for(i in 0 until 3) {} //0,1,2
for(i in 0..6 step 2) {} //0,2,4,6
for(i in 3 downTo 1) {} //3,2,1

val numbers = arrayOf(1,2,3)
for(i in numbers) {} //1,2,3

NullSafty

컴파일 오류로 미연에 Nullable 이 아닌 타입에 null 을 재할당하는 것을 방지할 수 있음.

Nullable 한 타입을 따로 제공하고 있기 때문에, 해당 타입만 null 을 할당할 수 있다.

//Nullable 의 접근은, 안전 연산자를 이용해 사용 가능하다. 만일 null 이면 뒤의 로직이 수행되지 않는다.
var a: String? = null
println(a?.length)

//엘비스 연산자를 이용해 null 일 경우의 결과 처리
fun getLengthIfNotNull(str: String?) = str?.length ?: 0

예외 처리

코틀린의 모든 예외 클래스는 최상위 예외 클래스인 Throwable 을 상속한다.

  • Throwable
    • Error
      • UncheckedErrors: outofmemory 등 시스템에 비정상적인 상황이 발생하여 예측이 어려운 에러
    • Exception
      • UncheckedExceptions
        • RuntimeException: NullPointerException, ArrayIndexOutofBounds etc.
      • CheckedExceptions: FileNotFound 와 같이 try catch 로 처리되어야하는 에러

Kotlin 은 기본적으로는 checkedException 을 강제하지는 않고, 직접 try catch 를 사용할 수 있다.

또한, Kotlin 은 try catch 또한 표현식으로 값을 정의할 수 있다.

throw 를 이용해 Exception 을 던질 수도 있다.

만일 함수에서 throw 를 반환하게 되어 정상적인 동작을 보장하지 않는 경우 해당 함수는 Nothing 타입을 반환하게 된다.

  • Nothing 타입의 반환을 elvis 연산자와 함께 사용하면 nullable 값이 null 이 불가한 타입으로 변경된다.
val a = try {
	"1234".toInt()
	//throw Exception("예외")
} catch (e: Exception) {
	println("예외 발생")
} finally {
	println("완료")
}

fun throwExceptionFun(): Nothing { //Nothing 타입 반환
	throw IllegalArgumentException("throwingException")
}

val str: String? = null
val result = b ?: throwExceptionFun() // kotlin 은 해당 result 를 String 타입으로 인식한다.

Getter, Setter

class Cafe(
	var name: String = "",
	var id: Int = 0,
) {
	// custom getter / setter 를 둘 수 있다. 로직이 길어질 경우 활용 가능
		val brand: String 
			get() {
				return "Starbucks"
			}

		var quantity: Int = 0 //기본 값을 지정
			set(value) {
				if(value > 0) { //수량이 0 이상인 경우에만 세팅하도록함
					field = value //field 는 식별자로, 코틀린에서는 Get/setter 에 대해서 field 라는 식별자를 이용해 본 변수에 접근한다.
					//quantity 라는 값을 의미하며, backingfield 라고 부른.
				}
			}
}

val cafe = Cafe()
cafe.id = 3 //프로퍼티를 이렇게도 사용 가능
println(${Cafe().brand})

Kotlin 의 클래스 상속

kotlin 의 모든 클래스들은 Any 를 상속받고 있다. 해당 클래스의 코드를 보면 Any 클래스와 그 함수는 Open 이라는 키워드를 사용하고 있다.

기본적으로 코틀린의 모든 클래스들은 final 로, 상속이 불가하다.

상속이 가능한 클래스를 생성해보자면 아래와 같다.

만일 open 된 클래스의 특정 메서드를 하위 클래스에서 재정의하지 않게끔 하고 싶다면 final 키워드를 활용할 수 있다.

open class Animal {
	open var age: Int = 0
	open fun move() {
		println("---->")
	}
	final var live: Boolean = true
}

class Snake: Animal() {
	override var age: Int = 0
	//override var live = false // 불가
	override fun move() { //override 키워드를 사용해야 상위 클래스에서 open 한 함수를 재정의할 수 있다.
		println("~~~~~>")
	}
}

class Bird(override var age: Int = 0): Animal() { // 기본 생성자를 이용해서도 오버라이드가 가능하다.
	override fun move() {
		println("      >")
	}
}

class Dog: Animal() {
	override var age: Int = 0
	override fun move() {
			super.move() // 부모 클래스의 구현부를 재사용
	}
}

fun main() {
	val birdy = Bird(age = 2)
	println(birdy.age) //2
	birdy.move() //      >
}

추상 클래스

abstract 키워드를 사용하며, 구현하는 하위 클래스에서는 반드시 이를 구현해야만한다.

abstract class Developer {
	abstract var age: Int
	abstract fun code(language: String)
}

class BackendDeveloper(override var age: Int = 0): Developer() {
	override fun code(language: String) {
		println("I program with $language")
	}
}

fun main() {
	val backendDeveloper = BackendDeveloper(age = 20)
	prinln(backendDeveloper.age) //20
	backgendDeveloper.code("Kotlin") // I program with Kotlin
}

인터페이스

kotlin 에서 인터페이스를 정의하는 방법은 키워드 하나만 사용해서 가능하다.

java 와 동일하게 인터페이스도 상위 인터페이스를 지닐 수 있고, 복수개의 인터페이스를 구현할 수도 있다.

그러나, 동일한 시그니처를 가진 함수를 호출 시에 문제가 발생할 수 있는데 이때는 super<Cart>.add(product) 와 같이 꺽쇠를 이용해 특정 상위 인터페이스를 명시한다.

만일 동일한 시그니처를 가진 인터페이스 함수와 구현된 디폴트 함수가 동시에 존재한다면,

class Product(val name: String, val price: Int)
interface Cart {
	var coin: Int
	val weight: String
		get() = "20kg" //field 즉 backing field 를 사용할 수 없다.
	fun add(product: Product)
	fun rent() {
			if(coin > 0) {
				println("카트를 대여합니다.")
			}
	}
}

class MyCart(override var coin: Int): Cart {
	override fun add(product: Product) { 
		if(coin <= 0) println("코인을 넣어주세요")
		else println("${product.name} 이 담겼습니다.")
 }
}

fun main() {
	val cart = MyCart(coin = 100)
	cart.rent()
	cart.add(Product("장난감", 1000)
}

enum class

enum class DevelopType(val id: Int): Available { //특정 interface 를 상속해 enum 클래스 안에 abstract 를 가지고 있는 것과 같은 효과를 볼 수 있다.
	WEB(0) {
		override fun isPayable(): Boolean = true
	}, 
	NATIVE(1){
			override fun isPayable(): Boolean = true
	}, 
	WINDOW(2){
			override fun isPayable(): Boolean = false
	}, 
	MAC(3){
			override fun isPayable(): Boolean = false
	}

	//abstract fun isAvailable(): Boolean //각 값마다 특정 값 혹은 로직을 별도로 넣고 싶다면 abstract 로 구현할 수 있다.
}

interface Available {
	fun isAvailable(): Boolean
}

fun main() {
	if(DevelopType.WEB.isAvailable()) {
		println("가능")
	}

	val type = DevelopType.valueOf("WINDOW") // 이렇게 가져올 수도 있고,
	type == DevelopType.NATIVE // 비교도 가능하다.

	for (status in PaymentStatus.values()) { // for 문에 하나하나 돌릴 수도 있다.
		println("$status")
	}
}

Collection

//immutable
//더이상 추가할 수 없다.
val immutableList = listOf(1,2,3)

//mutable
//add 등을 이용해 추가할 수 있다.
val mutableList = mutableListOf<Int>()
mutableList.add(1)
mutableList.add(2)
mutableList.add(3)

//apply 를 이용 가능
val mutableSet = mutableSetOf<Int>().apply {
	add(1)
	add(2)
	add(3)
}

//map, set 또한 동일

//collection builder 는 내부에서는 mutable 이나, 반환은 immutable이다.
val numberList = buildList {
	add(1)
	add(2)
	add(3)
}

for(number in numberList) {}

numberList.forEach {}

val iterator = numberList.iterator()
while(iterator.hasNext()) { iterator.next() }

//map 의 사용
val plusList = numberList.map { it+1 } // 공통 작업들을 직접 구현하지 않고 한줄로 줄일 수 있는 filter, map 을 자주 사용한다.
fval filteredList = numberList.filter { it%2 == 0 } 

반응형
Comments