본문 바로가기
Kotlin

Kotlin in Action #2. 코틀린 기초

by jayden-lee 2020. 2. 17.
728x90

'Kotlin in Action' 책을 학습하고 정리한 내용입니다.

2. 코틀린 기초

이번에는 모든 프로그램에서 필수 요소인 변수, 함수, 클래스 등을 코틀린에서 어떻게 선언하고 활용하는지 살펴본다. 또한, 제어 구조문과 스마트 캐스트, 예외처리에 대해서도 알아본다.

함수와 변수

Hello World 프로그램

Hello World를 콘솔에 출력하는 프로그램을 만들어보자. 코틀린에서는 함수 하나로 쉽게 만들 수 있다.

 

fun main(args: Array<String>) {
    println("Hello World")
}

 

  • 함수를 선언할 때 fun 키워드를 사용한다.
  • 자바와 다르게 파라미터 이름 뒤에 파라미터 타입을 쓴다.
  • 함수를 클래스 안이 아닌 최상위 수준에 정의할 수 있다.
  • 배열도 일반적인 클래스와 마찬가지이다.
  • 코틀린은 자바 코드를 간결하게 사용할 수 있게 래퍼를 제공한다.
  • 문장의 끝에 세미콜론을 붙이지 않아도 된다.

함수

Hello World 프로그램에서 작성한 함수는 반환 타입이 없는 자바에서 말하는 void 타입이다. 코틀린에서는 Unit 이라고 한다.

값을 반환하는 함수를 작성할 때, 반환 값의 타입은 파라미터 목록 뒤에 정의한다.

 

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

 

  • 반환 값의 타입은 파라미터 목록 뒤에 : 반환타입을 정의한다.
  • 코틀린에서 if 조건문은 결과를 만드는 식(expression)이다.
  • 코틀린은 =을 붙이고 식(expression)을 본문으로 바로 작성할 수 있다.

변수

자바에서는 변수를 선언할 때 타입이 가장 먼저 나온다. 코틀린에서는 타입을 생략하는 것이 흔하다.

 

val number = 10 // 타입 추론
val number: Int = 20

변경 가능 및 불가능한 변수

변수 선언 시 사용하는 키워드는 valvar 2가지가 있다.

val

변경 불가능한 참조를 저장하는 변수이다. 한번 초기화하고 나면 다시 변경할 수 없다. 자바에서 final 변수에 해당한다.

var

변경 가능한 참조다. 변수의 값은 계속 변경할 수 있다. 하지만 타입은 고정되기 때문에 타입을 변경할 수는 없다. 자바에서 일반 변수에 해당한다.

문자열 템플릿

다음 예제 코드는 문자열 템플릿 기능을 보여준다. name이라는 변수를 선언하고 문자열 리터럴 안에서 그 변수를 사용했다. 문자열 안에서 $를 써서 변수를 쉽게 사용할 수 있다.

 

fun main(args: Array<String>) {
    val name = "Jayden"
    println("Hello, $name")
}

클래스와 프로퍼티

코틀린에서는 클래스는 자바에서 같은 코드가 반복적으로 들어가는 부분을 줄일 수 있다. 코틀린 코드로 작성한 name 프로퍼티 하나를 가진 Person 클래스이다.

 

class Person(val name: String)

 

코틀린에서는 자바에서 사용하던 public 키워드가 보이지 않는다. 이는 코틀린의 기본 가시성은 public 이므로 생략해도 된다.

프로퍼티

자바에서는 클래스 내부에 데이터를 담기 위해서 필드를 선언한다. 필드의 경우에 private 으로 선언해서 외부에 보이지 않게 캡슐화한다. 만약 필요하다면 게터 메서드를 만들어서 접근할 수 있도록 제공한다. 값을 변경하기 위해서는 세터 메서드를 추가한다. 필드와 접근자를 하나로 묶어서 프로퍼티라고 부른다.

 

코틀린은 프로퍼티를 언어 기본 기능을 제공하며 자바의 필드와 접근자 메서드(게터 메서드)를 대신한다. 코틀린에서 변수를 선언할 때 val 또는 var 키워드를 사용한다. val로 선언한 프로퍼티는 읽기 전용이며, var는 읽기 및 쓰기가 가능하다.

 

class Person(val name: String, var isMarried: Boolean)

fun main(args: Array<String>) {
    // 코틀린은 new 키워드를 사용하지 않고 생성자를 호출한다
    val person = Person("Jayden", false)

     // person.name = "gglee" // 수정 불가능
     // person.isMarried = true // 수정 가능
}

커스텀 접근자

코틀린에서 프로퍼티를 접근할 때 게터 메서드가 아닌 직접 호출해서 사용할 수 있다. 프로퍼티에는 프로퍼티 값을 저장하기 위한 필드가 있다. 프로퍼티 접근자 메서드를 자신의 환경에 맞게 커스텀 할 수도 있다.

 

Rectangle 클래스에서 정사각형 여부를 나타내는 프로퍼티는 isSquare이다. 이 프로퍼티의 값을 별도의 필드로 저장하고 있지 않다. 커스텀 접근자를 작성하고 넓이와 높이 값을 비교한 값을 반환하고 있다. 단점으로는 정사각형 여부 값을 따로 필드로 갖고 있지 않기 때문에 접근자 메서드를 사용할 때마다 매번 계산해야 하는 단점이 있다.

 

class Rectangle(val height: Int, val width: Int) {
    val isSquare: Boolean
        get() {
            return this.height == this.width
        }
}

fun main(args: Array<String>) {
    val rectangle = Rectangle(100, 100)
    println("정사각형: ${rectangle.isSquare}")
}

코틀린 소스코드 구조

자바와 비슷한 개념의 패키지가 있다. 다른 패키지에 정의된 클래스를 사용하기 위해서는 임포트를 통해 불러와야 한다.

 

package ch02 // 패키지 선언

import java.util.Random // 외부 라이브러리 클래스 임포트

class Rectangle(val height: Int, val width: Int) {
    val isSquare: Boolean
        get() {
            return this.height == this.width
        }
}

fun createRandomRectangle(): Rectangle {
    val random = Random()
    return Rectangle(random.nextInt(), random.nextInt())
}

 

코틀린에서는 자바와 다르게 여러 클래스를 한 파일에 넣을 수 있으며, 파일의 이름도 마음대로 정할 수 있다. 코틀린에서는 자바에서 파일명과 클래스명이 다르면 오류가 발생하는 일이 생기지 않는다.

enum

자바에서 enum을 선언할 때 enum 키워드만 사용했지만, 코틀린에서는 enum class라고 붙여야한다.

 

enum class Color {
    RED, BLUE, BLACK
}

 

코틀린 enum class 안에 프로퍼티와 메서드를 선언할 수 있다.

when

자바에서 제공하는 switch문과 유사한 것이 코틀린에서는 when이다. if 문처럼 when도 값을 만들어내는 식(expression)이다.

 

getColorRgb 함수는 enum class인 Color를 매개변수로 받아서 rgb 값에 해당하는 문자열을 반환한다. 자세히 보면 switch와 유사하다고 했는데 break가 없는 것을 볼 수 있다.

 

fun getColorRgb(color: Color) =
    when (color) {
        RED -> "rgb(255, 0, 0)"
        BLUE -> "rgb(0, 0, 255)"
        BLACK -> "rgb(0, 0, 0)"
        else -> "rgb(255, 255, 255)"
    }

when과 임의의 객체 함께 사용

자바에서는 switch 분기 조건에 상수만 사용할 수 있다. 반면 코틀린에서는 분기 조건으로 임의의 객체를 사용할 수 있다.

 

fun mix(c1: Color, c2: Color) =
    when (setOf(c1, c2)) {
        setOf(RED, YELLOW) -> ORANGE
        setOF(YELLOW, BLUE) -> GREEN
        else -> throw Exception("No Match Color")
    }   

인자 없는 when 사용

인자 없는 when 식을 사용할 수도 있다. 각 조건에 해당하는 순차적으로 매칭한다. 조건들은 boolean 결과를 계산하는 식이어야 한다.

 

when {
    color == RED -> RED
    color == BLUE -> BLUE
    color == BLACK -> BLACK
    else -> throw Exception("No Match Color")
}

스마트 캐스트

interface Expr

class Num(val value: Int) : Expr

class Sum(val left: Expr, val right: Expr) : Expr

fun eval(e: Expr): Int {
    if (e is Num) {
        val n = e as Num
        return n.value
    }

    if (e is Sum) {
        return eval(e.left) + eval(e.right)
    }

    throw IllegalArgumentException("Unknown expression")
}

fun main(args: Array<String>) {
    val sum = eval(Sum(Sum(Num(1), Num(2)), Num(4)))
    println("Sum: $sum")
}

 

코틀린에서는 is를 사용해 변수 타입을 체크한다. 마치 자바의 instanceof와 비슷하다. 자바에서는 변수 타입을 체크하고 명시적으로 변수 타입으로 캐스팅해야 한다. 그렇지 않으면 그 변수 타입에 속한 멤버 접근을 할 수 없다. 타입 체크, 캐스트를 각각 수행해야 한다.

 

코틀린은 컴파일러가 프로그래머 대신 컴파일러가 캐스팅을 도와준다. is로 타입을 검사하면 컴파일러가 타입 캐스팅을 대신 수행해준다. 이를 스마트 캐스트라고 부른다.

 

원하는 타입을 명시적으로 타입 캐스팅하려면 as 키워드를 사용한다.

 

val n = e as Num

이터레이션

이터레이션 부분은 자바와 가장 비슷하다. while문은 거의 동일하고, for는 자바의 for-each 루프에 해당하는 형태만 존재한다.

while 루프

코틀린에서 사용되는 while, do-while 루프이다. 자바와 동일하다.

 

while (조건) {
    // 로직
}

do {
    // 로직
} while (조건)

수에 대한 이터레이션

코틀린에는 자바의 for 루프(초깃값, 증가 값, 최종 값)을 사용하는 형태는 존재하지 않는다. 범위를 통해 반복하는 for 루프문만 존재한다.

 

for (i in 1..100) {
    println(i)
}

 

step, downTo, until을 통해 for 루프문을 원하는 형식대로 변경할 수 있다.

 

for (i in 100 downTo 1 step 2) {
    // 98, 94, 92, 88 ...
    println(i)
}

맵에 대한 이터레이션

코틀린 for 루프는 맵에 있는 원소를 순회하는 이터레이션을 쉽게 작성할 수 있다.

 

val map = TreeMap<Char, String>()

for ((ch, text) in map) {
    println("$ch : $text")
}

 

맵에 사용한 구조 분해 구문을 맵이 아닌 컬렉션에서도 사용할 수 있다. 이 구조 분해는 자바스크립트에도 있다.

 

var list = arrayListOf("java", "kotlin", "javascript")
for ((index, element) in list.withIndex()) {
    println("$index : $element")
}

코틀린 예외 처리

자바와 같이 오류가 발생하면 예외를 던질 수 있다. 함수를 호출하는 쪽에서 그 예외를 잡아서 처리할 수 있다. 예외를 처리하지 못하면 함수 호출 스택을 거슬러 올라가면서 예외를 처리하는 부분이 나올 때까지 예외를 던진다.

 

if (num !in 0..10) {
    throw new IllegalArgumentException("Invalid Number Value")
}

try-catch-finally

예외를 처리하려면 try, catch, finally 절을 함께 사용한다.

 

try {
    val line = reader.readLine()
    return Integer.parseInt(line)
} catch (e: NumberFormatException) {
    return null
} finally {
    reader.close()
}

 

try를 식으로 사용할 수도 있다. 따라서 try의 값을 변수에 대입할 수 있다.

 

val number = try {
    Integer.parseInt(reader.readLine())
} catch (e: NumberFormatException) {
    return null
}

println(number)

요약

  • 함수를 정의할 때 fun 키워드를 사용한다.
  • val 읽기 전용 변수, var 변경 가능한 변수를 선언할 때 사용한다.
  • 문자열 템플릿을 이용해서 간결하며 가독성 있는 코드를 작성할 수 있다.
  • 값 객체를 data class로 간결하게 표현할 수 있다.
  • 코틀린에서 if 문은 값을 만들어낸다.
  • 코틀린에서 제공하는 when은 자바의 switch와 유사하나 더 많은 기능을 제공한다.
  • 코틀린은 is를 통해서 스마트 캐스트를 제공한다.
  • 코틀린의 이터레이션 while, do-while, for 루프에 대해서 알아봤다.

댓글