본문 바로가기
BackEnd/Java & Kotlin

[Kotlin] 코틀린 입문하기

by 경험의 가치 2024. 5. 13.

요즘 대세가 Kotlin이고, 나도 Java만 하다가 좀 관심이 많이 생겨서 Kotlin에 대해서 공부한 내용을 정리해보고자 글을 써본다.

 

Kotlin의 컴파일

Kotlin이란 우리가 자주 쓰는 그 유명한 Intellij, CLion, Pycharm, Datagrip 등을 개발한 jetbrains에서 공개한 JVM 기반의 언어이다. Java와 유사하지만, 더 간결한 문법과 다양한 기능 추가되었고, Java와 상호 운용이 100% 지원된다. 그래서 Android개발이나 Spring개발 등에서 많이 쓰인다.

 

 

앞서 Java랑 유사하다고 했는데, 여기서 중요한 것이 Kotlin 컴파일러는 JVM에서 실행될 수 있는 바이트코드가 포함된 클래스 파일을 생성하는 것이지, Java 코드를 생성하는 것이 아니다!! 위에 그림을 보면 빠르게 이해될 것이다.

 

그런데 앞서 Java와 Kotlin은 100% 상호운용될 수 있는 관계라고 했다. 그렇다면 kotlin과 java가 함께 있을 때는 어떻게 컴파일이 될까? Java코드와 Kotlin 코드의 빌드 과정은 다음과 같은 순서로 이루어진다.

  1. Kotlin 컴파일러가 Kotlin 코드를 컴파일해 .class 파일을 생성한다. 이 과정에서 Kotlin 코드가 참조하는 Java 코드가 함께 로딩되어 사용된다.
  2. Java 컴파일러가 Java 코드를 컴파일해 .class 파일을 생성한다. 이때 이미 Kotlin이 컴파일한 .class 파일의 경로를 클래스 패스에 추가해 컴파일한다.

그런데 여기서 중요한 것이, 2번 과정에서 다시 3단계로 나뉘어지는데... Lombok을 사용한다면 Lombok이 코드를 생성하는 단계는 세 단계 중 Annotation Processing의 단계이다. 그런데 이 단계는 kotlin 코드가 컴파일 된 이후이기 때문에 Kotlin 코드는 Lombok이 생성한 코드를 사용할 수 없다!! 뿐만 아니라 Querydsl을 사용했을 때도 비슷한 문제가 일어난다. 이에 대해서는 추후 글에서 다뤄보고자 한다.


Kotlin 문법

1. 변수

 

var : 값이 변경 가능한 변수

val : 선인시에만 초기화 가능한 변수 (final이랑 비슷)

fun main() {
    var a : Int
    a = 321
}

fun main() {
    val b: Int = 321
    b = 3
}

 

위에는 var은 변경 가능하기 때문에 정상적으로 컴파일 된다. 하지만, 밑에는 불변이기 때문에 에러가 발생한다.

 

그런데 코틀린은 변경 가능성보다 불변성을 더 선호한다. 그래서 되도록이면 val을 사용할 것을 권장하고 있다.

 

또한, 코틀린은 "타입 추론" 기능이 있다. 컴파일러가 문맥으로부터 변수 타입을 자동으로 유추하는 것이 가능하기 때문에 타입 선언을 생략해도 무방하다.

var a = 10

 

이런식으로 말이다.

 

또한, 코틀린은 기본적으로 null을 허용하지 않는 언어이다. 이는 null로 인한 오류를 방지하고 코드를 더욱 안전하게 만든다. 그러나 null이 필요한 상황이 있을 수 있으며, 이를 위해 코틀린은 몇 가지 도구를 제공한다. 

 

fun main() {
    var a:Int? = null
}

 

이런식으로 ?을 붙임으로써 Int타입 또는 null 타입 반환이 가능해진다. 그리고 이런 null인 경우를 대비해서 safe call과 Elvis operator를 제공한다.

 

val name: String? = null
val length = name?.length  // null 반환

 

이렇게 Safe Call Operator를 활용하여 객체가 null인지 확인하고, null이 아닐 경우에만 호출을 진행하도록 한다. 만약 객체가 null이면 length도 null을 반환한다.

 

val name: String? = null
val length = name?.length ?: 0  // name이 null이면 0 반환

 

Elvis Operator를 활용하여 null일 경우, 대체할 값을 지정할 수 있다. 위에 코드는 null이여서 null 대신 0으로 대체된다.

 

또한, 코틀린에서는 Java랑 달리 원시타입(primitive type)과 래퍼 타입(Wrapper type)을 구분하지 않고 최대한 효율적으로 동작하도록 설계되었다. 왜냐하면 항상 래퍼 클래스를 사용하면 성능적으로 비효율적이기 때문이다. 대부분의 경우 Kotlin은 원시타입으로 컴파일된다. 하지만, null을 허용하거나 (java 원시타입은 null을 못가지기 때문임) 제너릭 클래스(제너릭 타입은 객체만 다룰 수 있기 때문임)를 사용해야 할 때는 원시타입으로 컴파일이 불가능하기 때문에 래퍼 타입으로 사용한다.

val nullableNumber: Int? = null
val listOfNumbers: List<Int> = listOf(1, 2, 3)

 

위 코드들은 래퍼 타입으로 컴파일 되는 것이다. 어쨋든, 코틀린은 이러한 최적화 작업을 자동으로 처리하여 개발자가 직접 원시 타입과 래퍼 타입을 구분할 필요 없이 코드를 작성할 수 있게 했다.

 

2. 형변환

 

코틀린에서는 자바와 달리 암시적 형변환을 지원하지 않는다. 

public class Main {
    public static void main(String[] args) {
        int a = 100;
        long b = a; // int가 long으로 자동 형변환
        System.out.println("b: " + b);
    }
}

 

이런식이 암시적 형변환이다. 즉, 코틀린은 즉, 자동으로 타입을 변환하지 않고, 명시적으로 형변환을 해야 한다.

 

fun main() {
    var intVal: Int = 123
    var doubleVal: Double = intVal.toDouble() // Int를 Double로 변환
    var stringVal: String = doubleVal.toString() // Double을 String으로 변환
    var floatVal: Float = stringVal.toFloat() // String을 Float로 변환

    println("Int: $intVal")
    println("Double: $doubleVal")
    println("String: $stringVal")
    println("Float: $floatVal")
}

 

위와 같이 to(Type) 이런식으로 명시적 형변환을 진행할 수 있다.

 

3. 배열

 

val arrayOfNumbers: Array<Int> = Array(5) { 0 } //1
val listOfNumbers: List<Int> = listOf(1, 2, 3) //2
listOfNumbers.get(0)//3
val arrayList1: ArrayList<Int> = ArrayList() //4
arrayList1.add(5) //5
var anyValue : Array<Any> = arrayOf<Any>(1, 2.3f, "Hi") //6
val intArray = arrayOfNulls<Int>(5) //7

 

배열은 위에 코드 처럼 생성할 수 있다.

 

일단 1번 Array 타입은 우리가 잘 아는 자바에서 int[] a = new int[5] 이런 형태라고 볼 수 있다. 위에 코드에서는 크기가 5인 배열을 생성하고 모두 0으로 초기화한다는 의미이다.

 

2번의 List는 Read 전용이다. 즉, 불변의 형태 배열이라고 생각하면 된다. 3번처럼 값을 받아올 수 있다.

 

4번은 ArrayList는 우리가 잘 아는 그 ArrayList이다. 5번처럼 arrayList1.add(5) 이런식으로 추가가 가능하다.

 

6번처럼 Any 타입을 쓰면 최상위 타입의 데이터라서 어떤 데이터 상관없이 다 들어갈 수 있다.

 

7번처럼 배열을 생성하면 null로 초기화된 크기가 5인 배열을 반환한다.

 

4. 조건문

 

코틀린에서는 가독성 높은 분기 처리를 위해서 when 문법을 제공한다. 이진 조건에서는 when보다 if 사용을 권하며, 3개 혹은 그 이상의 경우에는 when 절을 사용하길 권장하고 있다.

 

var b = 
when(a) {
    1 -> a
    "hey" -> a
    else -> a
}

 

이런식으로 작성할 수 있다. switch 문이랑 비슷하다.

 

그냥 if else를 사용할 경우 java에서 사용하듯이 사용하면 된다.

var a = 8
if(a > 6){
	print(a)
}
else if(a is String){
	print(a)
}

 

이런식으로 말이다. 여기서 "is"는 데이터 타입을 비교할 때 쓰인다.

 

5. 반복문

 

 

 

(추가로 공부하면서 작성중...)