개발자/kotlin 코틀린

Kotlin 핵심 개념 알아보기 1

지구빵집 2022. 2. 7. 14:41
반응형

 

 

이번 포스팅에서는 코틀린 프로그래밍 언어를 이루는 핵심 구성요소를 설명합니다. 구성 요소들 각각은 대단해 보이지 않지만 함께 결합해 강력한 언어 구문을 구성합니다. 엄격한 null 안전성과 스마트 형 변환을 지원하는 코틀린의 형식 시스템을 확인하며, JVM(Java Virtual Machine) 환경에 추가된 새로운 연산자와 자바에 비해 개선된 여러 특징들을 살펴보겠습니다. 또한 애플리케이션의 흐름을 처리하는 새로운 방법과 동등성을 통일된 방법으로 처리하는 방식을 알아봅니다. 여기서 다루는 내용은 다음과 같습니다.

 

  • 변수, 값, 상수
  • 형식 유추
  • 엄격한 null 안전성
  • 스마트 형변환
  • 코틀린 데이터 형식
  • 제어 구조
  • 예외처리 

 

변수

 

Kotlin은 두 키워드(val 및 var)를 사용하여 변수를 선언합니다.

 

  • 값이 변경되지 않는 변수에 val을 사용합니다. val을 사용하여 선언된 변수에 값을 재할당할 수 없습니다.
  • 값이 변경될 수 있는 변수에 var을 사용합니다. 

var = variable = 읽기/쓰기가 가능한 일반 변수

val = baluable = 읽기만 가능한 final 변수

 

아래 예에서 count는 10의 초기 값이 할당되는 Int 유형의 변수입니다.

 

var count: Int = 10

 

Int는 정수를 나타내는 유형이며 Kotlin에서 표현될 수 있는 많은 숫자 유형 중 하나입니다. 다른 언어와 마찬가지로 수치 데이터에 따라 Byte, Short, Long, Float, Double을 사용할 수도 있습니다.

var 키워드는 필요에 따라 count에 값을 재할당할 수 있음을 의미합니다. 예를 들어 count 값을 10에서 15로 변경할 수 있습니다.

 

var count: Int = 10
count = 15

 

하지만 일부 값은 변경되지 않습니다. languageName이라는 String을 고려합니다. languageName에서 'Kotlin'의 값이 항상 유지되도록 하려면 val 키워드를 사용하여 languageName을 선언합니다.

 

val languageName: String = "Kotlin"

 

이러한 키워드를 사용하면 변경 가능한 항목을 명시할 수 있습니다. 필요에 따라 키워드를 유용하게 사용하세요. 변수 참조를 재할당할 수 있어야 하는 경우 var로 선언합니다. 그렇지 않은 경우 val을 사용합니다.

 

fun main(args: Array<String>) {
	val fruit:String = "orange" //val 변수를 만들고 초기화
    fruit = "banana"	//fruit 변수가 이미 초기화 되어서 컴파일러 오류

 

형식 유추 type inference (유형 추론)

 

이전 예에서 languageName에 초기 값을 할당할 때 Kotlin 컴파일러는 할당된 값의 유형을 기반으로 유형을 추론할 수 있습니다. "Kotlin" 값이 String 유형이므로 컴파일러는 languageName 또한 String이라고 추론합니다. Kotlin은 정적으로 입력되는 언어입니다. 즉, 컴파일 시간에 유형이 결정되고 절대 변경되지 않습니다. 다음 예에서 languageName이 String으로 추론되므로 String 클래스의 일부가 아닌 함수를 호출할 수 없습니다.

 

val languageName = "Kotlin"
val upperCaseName = languageName.toUpperCase()

// Fails to compile
languageName.inc()

 

toUpperCase()는 String 유형의 변수에서만 호출할 수 있는 함수입니다. Kotlin 컴파일러가 languageName을 String으로 추론했으므로 toUpperCase()를 안전하게 호출할 수 있습니다. 하지만 inc()는 Int 연산자 함수이므로 String에서 호출할 수 없습니다. Kotlin의 유형 추론 방식은 간결성과 유형 안전성을 모두 보장합니다. 

 

var title = "Kotlin"
title = 12	//유추된 형식 String에 Int 할당 오류

 

변수 참조의 형식 var, val은 참조된 객체가 아닌 참조 자체에 대한 것이다. 즉, 읽기 전용 참조 val에서는 특정 객체 인스턴스를 가리키는 참조를 바꿀 수는 없지만(변수 값을 재할당할 수 없음), 참조된 객체의 상태는 변경할 수 있다 배열을 사용해 그 의미를 확인해보자.

 

val list = mutableListOf("a","b","c")
list = mutableListOf("d","d")	// 오류
list.remove("a")	//리스트 내용 수정 가능, val 키워드가 원본 객체까지 불변으로 만드는 것은 아니다.

 

Int 값을 title 변수에 할당하려면 title 변수의 형식을 String과 Int의 공통 형식으로 지정해야 한다. 형식 계층에서 가장 가까운 형식으로 Any 타입이 있다. 

 

Any는 자바 객체 형식과 동일하며 코틀린 형식 계층의 루트에 해당한다. String이나 Int 같은 기본형을 포함해 코틀린의 모든 클래스는 Any를 명시적으로 상속한다. Any는 세 메서드 equals, toString, hashCode를 정의한다. 

 

코틀린에서는 타입을 명시하지 않아도, 변수가 선언될 때 할당된 값의 형태로 어떤 자료형을 가지는지 추론해준다. → 반드시 특정 자료형으로 지정해야 하는 상황이 아니라면, 타입 추론 기능을 이용하여 코드량을 줄일 수 있다.  

 

val stringValue:String = "my string" 
val stringValue = "my string" // Type 생략 가능 

var intArr:Array<Int> = arrayOf(1,2,3,4,5) 
var intArr = arrayOf(1,2,3,4,5) // Type 생략 가능

 

코틀린 컴파일러는 기본 타입 외에 객체나 함수를 사용할 때도 타입을 추론하고 제네릭 타입을 사용하는 배열과 컬렉션에서도 추론한다. 

 

fun main(args: Array<String>) {
    val s = cout2("땡")
    println(s)
}
fun cout2(s : String) = s+s+s
/*fun cout2(s : String) : String //위와 같은 함수
{
    return s+s+s
}*/

 

main()을 보면 변수 s가 함수 cout2()의 리턴 값을 받고 있다. 함수의 리턴 타입은 String이므로 변수 s의 타입도 String이 된다. 또한 cout2() 선언부에서 리턴 타입을 지정하지 않았음에도 String으로 추론해준다. 

 

엄격한 null 안전성

 

코틀린의 타입 시스템은 코드에서 null references 위험을 제거하는 것을 목표로 한다. Java를 포함한 많은 프로그래밍 가장 일반적인 오류 중 하나는 Null 참조의 멤버에 액세스 하면 Null reference exception이 발생한다. 일부 언어에서는 초기 값을 명시적으로 제공하지 않고 참조 유형 변수를 선언할 수 있습니다. 이러한 경우 변수에는 일반적으로 null 값이 포함됩니다. Kotlin 변수는 기본적으로 null 값을 보유할 수 없습니다. 즉, 다음 스니펫은 유효하지 않습니다.

 

// Fails to compile
val languageName: String = null	// 이 형식은 null을 허용하지 않음으로 오류 표시

 

null 값을 포함하는 변수는 nullable 유형이어야 합니다. 아래 예와 같이 ?를 변수 유형의 접미사로 지정하여 변수를 nullable로 지정할 수 있습니다.

 

val languageName: String? = null	// ?를 사용해 null을 허용한다고 지정했으므로 null 할당

 

String? 유형을 사용하여 String 값 또는 null을 languageName에 할당할 수 있습니다.

 

코틀린의 모든 null 불허 형식에는 대응되는 null 허용 형식이 있다. null 불허 형식은 null 허용 형식의 하위 형식이다.  아래 이미지를 보면 vehicle은 vechicle?의 하위 형식인 동시에 Any의 하위 형식이다.

 

null 불허 형식에는 대응되는 null 허용 형식

 

nullable 변수는 신중하게 처리해야 합니다. 아니면 심각한 NullPointerException이 발생할 위험이 있습니다. 예를 들어, 자바에서 null 값에 관해 메서드를 호출하려고 하면 프로그램이 비정상 종료됩니다. Kotlin은 nullable 변수로 안전하게 작업하기 위한 많은 메커니즘을 제공합니다. 자세한 내용은 Android의 일반 Kotlin 패턴: Null 허용 여부를 참조하세요. 

 

코틀린 type 시스템은 null(nullable reference)을 저장할 수 있는 참조와 Null이 아닌 참조를 포함할 수 없는 참조를 구별한다. 예를 들어 문자열 유형의 일반 변수는 null을 포함할 수 없다. 

 

var a: String = "abc" // 디폴트로 non-null 초기화
a = null	// null을 할당할 수 없으므로 컴파일 에러

 

null을 허용하기 위해 null문자열로 String?:와 같이 선언할 수 있다.

 

var b: String? = "abc"	// ? 사용으로 null 지정 가능
b = null // ok
print(b)

 

아래 코드에서 null 이 아닌 객체는 null 허용 형식의 변수에 할당할 수 있지만, null 허용 객체는 null 불허 변수에 할당할 수 없다. 위 형식 계층을 참고하면 이해할 수 있다.

 

var nullableVehicle: Vehicle?
var vehible: Vehicle

nullableVehicle = vehible	// 가능
vehible = nullableVehicle	// 오류

 

코틀린에서 Null 형식을 다루는 방법 - 코틀린에서의 null check

 

1. 조건문으로 null check: Java와 같이 명시적으로 null 체크를 수행

 

val l = if (b != null) b.length else -1
val b: String? = "Kotlin"

if (b != null && b.length > 0) {
    print("String of length ${b.length}")
} else {
    print("Empty string")
}

 

2. Safe Calls 연산자 ?.

 

val a = "Kotlin"
val b: String? = null
println(b?.length) 
println(a?.length) // Unnecessary safe call

 

위에서는 b가 null이 아니면 b.length를 반환하고, 그렇지 않으면 null을 반환한다. Null이 아닌 값에 대해서만 특정 작업을 수행하려면 다음과 같이 safe call 연산자를 사용할 수 있다.

 

val listWithNulls: List<String?> = listOf("Kotlin", null)
for (item in listWithNulls) {
    item?.let { println(it) } // prints Kotlin and ignores null
}

 

3. Elvis(엘비스) 연산자 :?

 

?:의 왼쪽에 있는 식이 null이 아니면 엘비스 연산자가 반환하고, 그렇지 않으면 오른쪽 결과를 반환한다. 왼쪽이 null인 경우에만 오른쪽 식으로 반환된다.

 

// if 조건문으로 처리한 경우
val l: Int = if (b != null) b.length else -1

// 동일한 코드를 ?: 연산자 사용 시
val l = b?.length ?: -1

 

4. !! 연산자

 

아래와 같이 !!연산자를 사용하면 값이 null인 경우 예외를 발생시킨다.

 

val l = b!!.length

 

5. Safe Casts as?

 

cast 수행 시 대상 유형이 아닌 경우 ClassCastException이 발생한다. as? 연산자를 사용하면 cast 실패한 경우 null을 반환하여 안전하게 수행할 수 있다.

 

val aInt: Int? = a as? Int

 

6. 컬렉션의 Nullable Type

 

Nullable 요소 컬렉션이 있고 Null이 아닌 요소를 필터링하려는 경우 filterNotNull()을 사용할 수 있다.

 

val nullableList: List<Int?> = listOf(1, 2, null, 4)
val intList: List<Int> = nullableList.filterNotNull()

 

 

Kotlin 핵심 개념 알아보기 1

 

 

 

 

참고

kotlin 프로그래밍 언어 알아보기

kotlin 스타일 가이드

코틀린 문서 참고

 

 

 

반응형