본문 바로가기

Kotlin

[Kotlin] Kotlin 기본 문법 by "Code with Joyce" 1편 - (1)

kotlin을 공부하게 된 계기는 바로 '취업'이다.

불과 2년 전까진 kotlin의 인기를 몰랐고, Java로 충분히 취업할 수 있다고 생각하고 있었다.(사실 외면하고 있던 걸지도)

학교를 졸업하고 취준생이 된 지금은 kotlin의 강세를 온몸으로 느끼고 있다. 엄청난 위기감을 체감하고 있기 때문에 kotlin도 공부하게 되었다.

 

그래도 '컴퓨터공학 전공자'라는 명찰을 달고 있기도 하고 kotlin이 Java 문법과 유사해서 금방 익힐 수 있었다.

 

유튜버 "Code with Joyce"님의 '코틀린 3강으로 끝내기 - 1편 기본 문법'으로 kotlin 문법을 공부했다.

간략하게 핵심만 짚어서 알려주셔서 기본 문법을 살펴보기 좋다고 느꼈고, 공부한 내용을 정리하려고 한다.

 

kotlin은 안드로이드 앱 개발을 위해 고안된 언어인 만큼 Android studio에서 동작하므로 새로운 프로젝트를 empty activity로 생성해줘야 한다. 이때 클래스 명과 파일 명은 달라도 상관없다고 한다. 심지어 한 파일 안에 여러 개의 클래스가 존재할 수도 있다고 한다..!

 

여하튼 문법만 살펴볼 예정이므로 프로젝트 생성 과정은 생략하고 Java와 유사점이 많으므로 새로운 내용이 아니면 노코멘트하겠다.

 

1. 함수

함수의 기본형은 다음과 같다.

 

fun 함수명() : 반환 값 { }

 

이제 Hello World!로 시작하지 않으면 섭섭하다. 

 

fun main() {
    helloWorld()
}

fun helloWorld(): Unit {
    println("Hello World!")
}

// 혹은

fun helloWorld(): String {
    "Hello World!"
}

 

함수의 반환 값이 없는 void형을 Unit으로 표현하고 Unit은 생략할 수 있다.

 

함수의 parameter는 다음과 같이 작성한다.

 

fun add(a: Int, b: Int): Int {
    return a + b
}

 

2. var과 val

var과 val은 서로 다른 의미를 지니는데 영어를 알면 이해하기 쉽다. (개발자가 영어 공부를 해야 하는 이유.. :D)

var은 variable의 약자이고 val은 value의 약자여서 값을 변경할 수 있는 변수는 var이고, val은 그 자체로 값이므로 상수이다.

 

상수를 조금 더 자세히 다뤄보자면 run time에 할당되는 상수 val과 compile time에 할당되는 const val이 있다.

 

변수 선언의 기본형은 다음과 같다.

 

var 변수 명 : 자료형 = 값
val ...

 

fun hi() {
    val a: Int = 10
    var b: Int = 9
    
    // 선언과 동시에 값을 할당하면 자료형 생략 가능
    val c = 100
    var d = 100
    var name = "mimi"
    
    // Val cannot be reassigned
    c = 200
    
    // 선언과 동시에 값을 할당하지 않으면 자료형 명시
    var e: String
    
    // lateinit operator, 초기화를 뒤로 미뤄주는 연산자
    lateinit var f: String
    
    // 그러나 compiler는 null check를 추천함
    var f: String? = null

 

3. string 템플릿

string 템플릿(template)은 text 출력 포맷이다.

 

fun main() {
    val name = "mimmi"
    val lastName = "Kim"
    
    // ${}로 string 안에서 변수 사용이 가능
    println("My name is ${name + lastName}")
    
    // 만약 $를 문자열로 쓰고 싶다면
    println("This's 2\$")
}

 

4. 조건식

if 문은 Java와 동일하게 사용한다.

 

fun maxBy(a: Int, b: Int): Int {
    if (a > b) {
        return a
    } else {
        return b
    }
}

 

kotlin에는 삼항 연산자(?:)가 없는데 if문을 람다로 표현해 아래와 같이 사용하기도 한다.

 

fun maxByLambda(a: Int, b: Int) = if (a > b) a else b

 

Java의 switch case문과 유사한 when

 

fun checkNum(score: Int) {
    // statment
    when (score) {
        0 -> println("this is 0")
        1 -> println("this is 1")
        2, 3 -> println("this is 2 or 3")
        else -> println("I don't know")
    }
    
    // 조건식에 해당하는 값을 반환함으로써 변수 할당에도 활용 가능
    // expression
    var b = when (score) {
        1 -> 1
        2 -> 2
        else -> 3
    }
    
    println("b : ${b}")
    
    // 값의 범위를 나타내는 range expression
    when (score) {
        in 90..100 -> println("You are genius")
        in 10..80 -> println("Not bad")
        else -> println("Okay")
    }
}

 

5. array와 list

kotlin은 배열을 크게 Array와 List로 나타내고, List는 List와 MutableList로 나뉜다. List는 immutable 하다고 표현하고 Array와 MutableList는 mutable 하다고 표현한다.

  • 읽기와 쓰기 모두 가능한 Array, MutableList
  • 읽기만 가능한 List

 

fun array() {
    val array = arrayOf(1, 2, 3)
    val list = listOf(1, 2, 3)
    
    // 서로 다른 자료형을 가질 수 있음
    val array2 = arrayOf(1, "d", 3.4f)
    val list2 = listOf(1, "d", 11L)
    
    // Array는 쓰기 가능
    array[0] = 3
    
    // List는 읽기만 가능 MutableList는 읽기 쓰기 가능
    list[0] = 2	// error
    val value = list[1]
    
    // 배열의 주소값은 변경되지 않기 때문에 var, val 모두 가능
    // mutable한 arrayList
    var arrayList = arrayListOf<Int>()
    arrayList.add(10)
    arrayList.add(20)
    arrayList[0] = 30
}

 

코드 마지막에 arrayList를 var로 선언했는데 compiler가 arrayList는 한 번도 수정된 적이 없기 때문에 val로 수정되는 게 좋다고 알려준다. 0번째 인덱스의 값을 10에서 30으로 수정했는데도 말이다. 이럴 때는 역시 구글링이다.

 

우선 kotlin은 JVM과 Android를 위해서 설계된 언어여서 Java에서 kotlin으로, kotlin에서 Java로의 변환이 용이하다. 따라서 Java에 적용되던 rule이 kotlin에서도 적용되는데 이는 Java에서의 Immutability를 kotlin도 따라야 함을 의미한다.

(만약 이 글이 틀렸고 더 자세하게 알고 있는 분들이 있다면 알려주시길 매우 매우 간절히 바라 옵니다요)

 

sylsTyping님의 Java Immutability Visualised를 참조하면

arrayList가 참조하고 있는 배열이 수정된 것이지 arrayList 자체가 수정된 게 아니라고 한다.

 

var arrayList = arrayListOf(10)

// arrayList가 참조하고 있는 배열의 참조 값이 변경된 것
arrayList[0] = 30

 

C언어의 pointer 개념을 알고 있다면 쉽게 이해할 수 있을 것이다.

 

그리고 List를 MutableList로 casting 하면 mutable 하게 만들 수도 있다는 글이 있다. 이것도 sylsTyping님이 설명하는 것과 동일한 개념이 적용된 듯하다.

 

Immutability 하나로 설명이 옆으로 샜지만 재밌었기 때문에 다음에 자세히 다룰 생각이다.

각설하고 다음으로 넘어간다.

 

6. 반복문

반복문 while 문은 Java와 유사한데 for 문은 python과 닮았다.

 

fun forAndWhile() {
    for (i: Int in 1..10)
        println("$i")        // result : 1, 2, 3, ..., 10
    
    // step 2
    // downTo 2
    for (i in 1 until 10)
        println("$i")        // result : 1, 2, 3, ..., 9
    
    // withIndex() 응용편
    val students = arrayListOf("joyce", "james", "jenny", "jennifer")
    
    for ((index, name) in students.withIndex()) {
        println("${index + 1}번째 학생 : ${name}")
    }
    
    var index = 0
    while (index < 10) {
        println("current index : ${index++}")
    }
}

 

7. NonNull과 Nullable

Java에서는 심심찮게 발생하는 Null pointer Exception, 줄여서 NPE라는 예외가 있다. 해당 예외는 run time 때 발생하는 예외이기 때문에 많은 사람들이 피?를 보고 있다. (처방약은 어노테이션이다.)

 

그에 반해 kotlin은 compile time에 NPE를 감지해서 프로그램의 안정성을 높여준다.

관련된 type으로 null이 될 수 없는 NonNull type이 있고 null도 가질 수 있는 Nullable type이 있다.

 

fun nullCheck() {
    // NonNull type
    var name = "mimi"
    
    // Nullable type
    var nullName: String? = null
}

 

Nullbale 타입을 선언하는 코드는 2. var과 val에서 봤던 코드이다. 물음표(?)를 사용해서 초기화되는 값이 없으면 null을 대입하라는 의미이다. 해당 연산자는 parameter로 사용되는 모습을 보면 쉽게 이해할 수 있다.

 

fun notNull(str: String?) {
    val myEmail: String = str
    
    // myEmail이 null이 아닐 때 다음의 코드를 실행하도록 해주는 .let{}
    myEmail?.let {
        println("my email is "${email}")
    }
}

 

formal parameter인 str에 null이 아닌 actual parameter를 넘겨주면 이메일 주소가 출력될 것이다.

 

참고로 NonNull 타입인데 ?을 사용하면 제거하라고 compiler가 알려준다.

사실 선언과 동시에 초기화를 하는데 이게 null인지 아닌지 검사할 필요가 없기 때문에 compiler의 조언은 타당하다.

 

val email: String? = "abc123@blabla.com"

 

method를 사용할 때도 물음표(?)는 유용하게 쓰인다.

 

fun nullcheck() {
    var name = "mimi"
    var nullName: String? = null
    
    // Nullable type?.method()
    // Nullable type의 값이 실제로 null이면 method()는 null 반환
    // 반대로 값이 null이 아니면 method() 실행 후 결과 값 반환
    var nullNameInUpperCase = nullName?.toUpperCase()
}

 

그 외에 다양한 기능들이 있다.

 

fun checkNull() {

    // ?: (Elvis operator)
    // null checker에게 default 값을 지정할 수 있는 연산자
    
    val lastName: String? = null
    val fullName = name + " " + (lastName ?: "No lastName")
    
    println(fullName)        // result : "mimi No lastName"
    
    // !!
    // 해당 변수가 null이 아님을 개발자가 보증하는 연산자
    // NPE가 발생할 수 있음
    ignoreNulls(fullName)
}

fun ignoreNulss(str: String?) {
    val mNotNull: String = str!!
    
    println(mNotNull)        // result : "mimi No lastName"
}

 

8. Class

대망의 Class이지만 글이 제법 길어져서 따로 작성하겠다.