본문 바로가기
Back-end

코틀린 - 기본 문법

by 신재권 2023. 10. 25.

Foundation

fun main() {
	println("Hello, World")
}
  • fun 은 함수를 선언하는데 사용된다.
  • fun main()는 프로그램이 시작되는 곳
  • 함수의 본문은 중괄호 안에 작성 { }
  • println(), print() 는 인수를 표준 출력으로 인쇄

Variable

코틀린의 변수는 2가지 종류가 존재

  • 읽기 전용 변수 : val
  • 변경 가능한 변수 : var
  • main() 프로그램 시작 시 함수 외부에서 변수를 선언 가능 → top level variable
  • var 변수는 선언 후 다시 할당 가능
  • 기본적으로 val 변수를 선언, var 변수는 필요한 경우 선언

String templates

String Templates를 사용해 변수의 내용을 표준 출력으로 사용 가능

Template 표현식을 사용하여 이를 수행 가능

문자열은 큰 따옴표(”)로 묶인 일련의 문자

Template 표현식은 달러 기호로 시작 한다.($)

val customer = 10
println("$customer") // 10

println("${customer + 1}") // 11

템플릿 표현식 변수를 핸들링 하려면 ${variable_name}으로 변수 핸들링 가능

  • 또한 코틀린은 type 추론이 가능하다. 위 예제에선 Int로 추론

Basic Types

코틀린의 모든 변수와 데이터 구조에는 data type이 존재

data type은 해당 변수나, 데이터 구조로 수행할 수 있는 작업을 컴파일러에게 알려준다.

위의 예제서 Int 유형을 추론한 것을 type inference 라고 한다.

즉, customer에 정수 값이 할당되고, Kotlin은 customer가 숫자 유형이라고 추론, 결과적으로 컴파일러는 해당 부분에 산술 연산을 수행 할 수 있다고 판단

Category Basic Types

Integers Byte, Short, Int, Long
Unsigned Integers UByte, UShort, UInt, ULong
Floating-point numbers Float, Double
Booleans Booleans
Characters Char
Strings String

Integers

명시적인 유형 지정 없이 변수를 선언하면, 컴파일러는 해당 값을 기준으로 범위에 들어갈 수 있는 유형을 자동으로 유추한다.

기본적으로 Int로 유추한다.

만약 Int의 범위가 초과된다면, Long 유형으로 유추된다.

Long 을 명시적으로 지정하려면 ‘L’ 키워드를 정수 옆에 붙인다.

Floating-Point Numbers

  • Float은 단정 밀도를 반영
  • Double은 배정 밀도를 반영
  • Float 은 32bytes, Si

Variable

  • 코틀린은 컴파일러가 타입을 추론해주기 때문에 타입 생략이 가능하다.
  • 지연 할당 : 변수를 선언하고, 나중에 값을 할당하는 것
    • 지연 할당 시에는 타입 생략이 불가능
  • val : value : 상수
  • var : variable : 변수
  • 변수의 타입이 한번 할당되면, 다른 타입으로 할당이 불가능하다.
  • 코틀린은 클래스나, 함수에 속해 있지 않아도 변수를 선언할 수 있다.
    • 이를 Top Level 변수라고 한다.
    • top level 변수는 private static 으로 선언된다.
    • top level 변수는 get/set이 자동으로 할당된다.

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) {
    println("$a + $b = ${a + b}")
}

//디폴트 파라미터
fun greeting(message: String = "안녕하세요!") {
    println(message)
}
  • 함수를 선언하면 public static final 으로 선언된다
  • 몸통이 있는 함수(”{}”)는 반환 타입을 제거하면 컴파일 오류
  • Unit 반환 형과 main은 final이 붙지 않고, static 만 붙는다

Flow Control

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{
        "아이"
    }

    println(str)

    //코틀린은 삼항 연산자가 없다. if..else가 표현식이므로 불필요
    val a = 1
    val b = 2
    val c = if(b > a) b else a

    println(c)
}
  • if..else 는 표현식 이므로 return이 필요 없다.
  • if..else가 표현식 이므로 코틀린은 삼항 연산자가 없다.
  • 코틀린에서는 원시 타입을 비교할 때는 == 연산자는 동등성을 비교한다.
  • 코틀린에서는 참조 타입을 비교할 때는 == 연산자는 내부적으로 equals()를 호출한다.
  • 코틀린에서는 참조 타입의 동일성을 비교 하고 싶으면 === 연산자를 사용한다.
fun main() {

    //자바 switch -> 코틀린 when
    val day = 2

    val result = when(day){
        1 -> "월요일"
        2-> "화요일"
        3 -> "수요일"
        4 -> "목요일"
        else -> "기타"
    }
    println(result)

    //else를 생략 가능
    when (getColor()) {
        Color.RED -> println("red")
        Color.GREEN -> println("green")
    }

    //여러개의 조건을 콤마로 구분해 한줄에서 정의 가능
    when (getNumber()) {
        0, 1 -> println("0 또는 1")
        else -> println("0 또는 1이 아님")
    }
}

enum class Color{
    RED, GREEN
}

fun getColor() = Color.RED

fun getNumber() = 2
  • when 문을 Java로 변환하면 switch & break 문으로 변환된다.
  • enum 처럼 값의 범위가 정해져 있다면 else 생략 가능, 범위가 정해져 있지 않다면 else 를 생략하면 컴파일 에러 발생
  • 여러 개의 조건을 콤마로 구분해 정의 가능
fun main() {
    //범위 연산자 .. 를 사용해 for loop
    for (i in 0..3) {
        println(i)
    }

    //until을 사용해 반복
    //뒤에 온 숫자는 포함하지 않음
    //until은 ..< 으로 표현이 가능하다.
    //unitl은 자바로 변환되면 for 문을 사용해 반복한다.
    //..<은 자바로 변환되면 while 문을 사용해 반복한다.
    for (i in 0 until 3) {
        println(i)
    }
    for (i in 0 ..< 3) {
        println(i)
    }

    //step 에 들어온 값 만큼 증가
    //step을 사용하면 자바의 while 문으로 변환되어 반복된다.
    for (i in 0..6 step 2) {
        println(i)
    }

    //dowTo를 사용해 반복해서 값을 감소
    for (i in 3 downTo 1) {
        println(i)
    }

    //전달받은 배열을 반복
    val numbers = arrayOf(1,2,3)

    for (i in numbers) {
        println(i)
    }

}
  • 범위 연산자 (..)를 사용하여 for loop를 진행시킨다.
    • 자바로 변환되면 for 문으로 변환된다.
  • until을 사용하면 마지막 반복을 포함하지 않음
    • 자바로 변환되면 for 문을 사용해 반복 but 그냥 범위 연산자보다 코드가 길어짐
    • until은 ..< 으로 표현 가능, 이때는 자바로 변환되면 while 문으로 변환
  • step을 사용하여 증가량 설정 가능
    • step을 사용하면 자바의 while 문으로 변환된다.
  • downTo를 사용하여 역순으로 반복 가능
    • 자바의 for문으로 변환된다.
  • 전달받은 배열, 리스트를 반복할 수 있다.
    • 자바의 for-each와 동일
    • 배열은 자바 기본 for 문으로 변환되고, index 접근을 시도한다.
    • ArrayList는 Iterator로 변환되고, while(hasNext())를 통해 접근을 시도한다.
      • 매번 돌때마다 Null 체크를 한다.
fun main() {

    //자바의 while 문과 동일
    //조건을 확인하고 코드 블록을 실행한 후 다시 조건을 확인
    var x = 5

    while (x > 0) {
        println(x)
        x--
    }

    var y = 4

    do {
        println(y)
    }while (y -- > 0)

}
  • while문 내부에서 조건을 변경할 경우 자바의 for 문으로 변환된다.
  • while문 조건 검사 식 내부 ‘( )’ 에서 조건을 변경할 경우 while로 대체
  • do-while 문은 자바의 do-while문으로 그대로 변환된다.

Null 안전성

  • 자바를 포함한 많은 프로그래밍 언어에서 가장 많이 발생하는 예외 유형이 NullPointerException, NPE 이다.
  • null 을 발명한 토니호어는 실수라고 고백
  • 자바에서는 NPE를 줄이기 위해 JDK8 부터 Optional을 지원
  • 자바의 Optional은 값을 래핑하기 때문에, 객체 생성에 따른 오버헤드가 발생하고, 컴파일 단계에서 Null 가능성을 검사하지 않는다.
  • 코틀린은 Null 가능성을 컴파일러가 미리 감지해서 NPE 가능성을 줄일 수 있다.
fun main() {

//    val a : String = null //컴파일 오류
//    var b : String = "var"
//    b = null //컴파일 오류

    // 타입 뒤에 ? 를 붙여 null 가능성을 컴파일러에 알려준다.
    // nullable 타입으로 할당 후, 해당 변수에 접근하려면 안전 연산자를 사용해야 한다.

    var a : String? = null
    println(a?.length)

    //기존 자바 스타일 if..else + 표현식 사용하여 null 체크
    val b :Int = if(a != null) a.length else 0
    println(b)

    //엘비스 연산자 : 좌변이 null인경우 우변을 리턴, null이 아니면 좌변을 리턴
    val c = a?.length ?: 0
    println(c)

}
  • 코틀린인 일반 참조형에 null 할당이 불가능하다. 컴파일 에러가 발생한다.
  • null 할당을 하려면 참조형 타입에 (?) 을 붙여야 한다.
  • nullable 타입으로 할당 후, 해당 변수에 접근하려면 안전 연산자를 사용해야 한다.
    • 변수?.메서드
    • 변수!!.메서드 : 단언 연산자
  • 엘비스 연산자를 사용해 기본값을 할당할 수 있다.
    • 기본값을 할당하지 않으면 null 이 반환된다.
fun getNullStr(): String? = null

fun getLengthIfNotNull(str: String?) = str?.length ?: 0

fun main() {

    val nullableStr = getNullStr()

    val nullableStrLength=  nullableStr?.length
    println(nullableStrLength)

    val length = getLengthIfNotNull(nullableStr)
    println(length)
}
  • 코틀린에서 직접 throws NullPointerException을 호출하여 NPE를 발생시킬 수 있다.
    • NPE의 발생 가능성을 줄여줄 뿐이지, 없어지지는 않는다.
  • 엘비스 연산자를 통해 기본 값으로 예외를 throws 할 수 있다.
  • !! 단언 연산자를 사용하여 null 값을 강제로 참조하게 되면, NPE를 throws 한다.
    • 즉, 단언 연산자는 개발자가 직접 핸들링한다고, 컴파일러에게 알린다.
  • 안전 연산자를 사용해야 NPE 에러 발생 빈도가 줄어든다. 출력하면 그냥 null로 출력된다.
  • 코틀린과 자바를 혼용할 때, 코틀린에서는 null 체크가 되지만, 코틀린 컴파일러는 자바코드의 null 체크를 하지 못한다. 따라서 안전 연산자와 엘비스 연산자를 잘 사용해야 한다.

Exception Handling

  • 코틀린의 모든 예외 클래스는 최상위 예외 클래스인 Throwable을 상속한다.
  • Throwable → Error, Exception
    • Error → Unchecked errors
    • Exception → RuntimeException, Checked exception
    • RuntimeException → Unchecked exceptions
  • Error : 시스템에 비정상적인 상황이 발생, 예측이 어렵고 기본적으로 복구가 불가능함
    • OutOfMemoryError, StackOverflowError
  • Exception : 시스템에서 포착 가능하며(try-catch) 복구 가능, 예외 처리 강제
    • IOException, FileNotFoundException
    • @Transactional에서 해당 예외가 발생하면 기본적으론 롤백이 동작하지 않음
      • rollbackFor를 사용해야 한다.
  • RuntimeException : 런타임시에 발생하는 예외, 예외 처리를 강제하지 않음
    • NullPointerException, ArrayIndexOutOfBoundsException
  • 자바에서 체크드 익셉션은 컴파일 에러가 발생하기 때문에 무조건 try-catch로 감싸거나, throws로 예외를 전파해야 한다.
  • 코틀린에서는 체크드 익셉션을 강제하지 않는다.
fun main() {
    Thread.sleep(1)
}
  • 필요한 경우 try-catch를 통해 감쌀 수 있다.
    • 코틀린에서도 finally 구문을 지원한다.
  • 코틀린에서는 try-catch는 표현식으로 작성이 가능하다.
val a = try {
        "1234".toInt()
    } catch (e: Exception) {
        println("예외 발생")
    }
    println(a)
throw Exception("예외 발생")

failFast("예외 발생")

fun failFast(message: String): Nothing {
    throw IllegalArgumentException(message)
}
  • 코틀린은 이렇게 직접 Exception을 발생시킬 수 있다.
  • Exception 같이 코드가 정상적으로 수행되지 않는 경우, Unit 타입이 아닌, Nothing Type을 반환한다.
val b :String? = null
    val c = b ?: failFast("a is null")

    println(c.length)
  • 엘비스 연산자와 Nothing 타입을 같이 사용하면 절대로 Null이 나올 수 없기 때문에 컴파일러는 해당 코드는 널 안전 연산자를 사용하지 않아도 된다고 판단한다.

'Back-end' 카테고리의 다른 글

Spring @Async + ThreadPoolTaskExecutor  (1) 2024.01.14
Spring Security Filter  (0) 2024.01.01
JPA Entity 기본 생성자  (0) 2023.09.29
기본 키 할당 전략  (0) 2023.09.29
Wrapper Type vs Primitive Type In JPA  (0) 2023.09.29