본문으로 바로가기

Kotlin In Action #1

category Programming/Kotlin 2021. 12. 6. 22:17

Kotlin In Action #1


기본요소: 함수와 변수

Hello, World!

fun main(args: Array<String>) {
    println("Hello World!")
}
  • 함수를 선언할 때 fun 키워드 사용.
  • 파라메터 이름 뒤에 그 파라메터의 타입을 작성.
  • 함수를 최상위 수준에 정의. (자바는 클래스 안에 메서드를 넣음.)
  • 코틀린 표준 라이브러리는 여러 표준 자바 라이브러리의 메서드를 간결하게 사용할 수 있게 감싼 레퍼를 제공.
  • 세미콜론을 붙이지 않아도 됨.

반환 값이 있는 함수

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

코틀린에서의 if는 식(expression)이지 문(statement)가 아니다.

  • 식은 값을 만들어 내며 다른 식의 하위 요소로 계산에 참여할 수 있음.
  • 문은 자신을 둘러싸고 있는 가장 안쪽 블록의 최상위 요소로 존재하며 아무런 값을 만들어내지 않음.

식이 본문인 함수

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

reaturn을 제거하면서 등호(=)를 식 앞에 붙이면 더 간결한 함수 표현이 가능.

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

식이 본문인 함수의 경우 굳이 사용자가 반환 타입을 적지 않아도 됨. (타입 추론)

변수

자바와 다르게 타입 지정을 생략하거나, 변수 이름 뒤에 타입을 명시.

fun main(args: Array<String>) {
    val question = "가나다라마바사"
    val answer = 42
    val answer2: Int = 43

    println(question)
    println(answer)
    println(answer2)
}

※ 초기화 식을 사용하지 않는다면 반드시 타입을 지정해야 함

변경 가능한 변수와 변경 불가능한 변수

  • val(value) - 변경 불가능한 참조를 저장하는 변수. 자바로 따지면 final 키워드가 붙은 변수.
    • 참조가 가리키는 객체의 내부 값은 변경될 수 있음.
  • var(variable) - 변경 가능한 참조. 값이 바뀔 수 있음.
    • 타입은 고정되어 바뀌지 않음. ("Error: type mismatch" 오류 발생)

더 쉽게 문자열 형식 지정: 문자열 템플릿

fun main(args: Array<String>) {
    var name = if  (args.size > 0) args[0] else "Kotlin"
    println("Hello, $name")
}

문자열 리터럴의 필요한 곳에 $ 키워드와 함께 변수를 넣어 사용 가능.

복잡한 식은 ${} 중괄호를 사용해서 넣을 수 있음.

클래스와 프로퍼티

Java에서의 Person 클래스

public class Person {

    private final String name;

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

Kotlin에서의 Person클래스

class Person (val name: String)
  • 해당 유형의 클래스(코드가 없이 데이터만 저장하는 클래스)를 값 객체라 함.
  • 코틀린에서의 기본 접근 제어자는 public이다.

프로퍼티

자바에서는 필드와 접근자를 묶은것을 프로퍼티라 부르고, 해당 개념을 활용하는 프레임워크가 많음.

코틀린은 프로퍼티를 언어 기본 기능으로 제공하며, 자바의 필드와 접근자 메서드를 완전히 대신함.

  • val로 선언한 프로퍼티는 읽기 전용. (게터만 만듬)
  • var로 선언한 프로퍼티는 변경도 가능. (게터, 세터 둘 다 만듬)
class Person (
    val name: String,
    var isMarried: Boolean
)
fun main(args: Array<String>) {
    val person = Person("0n1dev", true)

    println(person.name)
    println(person.isMarried)
}

커스텀 접근자

class Rectangle(val height: Int, val width: Int) {

    val isSquare: Boolean
        get() {
            return height == width
        }
}

isSquare 프로퍼티에는 자체 구현을 제공한는 게터만 존재.

복잡한 블록문 대신 get() = height == width로 작성해도 됨.

코틀린 소스코드 구조: 디렉터리와 패키지

자바의 경우 모든 클래스를 패키지 단위로 관리.

코틀린에도 비슷한 개념의 패키지가 있음.

자바와 다른점은 자바에서는 패키지와 디렉터리 계층 구조가 동일해야 함, 코틀린은 디렉터리와 상관없이 원하는 대로 소스코드 구성이 가능.

하지만 자바와 같이 패키지별로 디랙터리를 구성하는게 좋음.

선택 표현과 처리: enum과 when

enum 클래스 정의

enum class Color {
    RED, ORANGE, BLUE, GREEN, YELLOW
}
  • 코틀린에서의 enum은 소프트 키워드라 부르는 존재.
  • class 앞에 있을 때는 특별한 의미를 지니지만 다른 곳에서 이름에 사용할 수 있음.
  • 반면 class는 키워드.
enum class Color(
    val r: Int, // 프로퍼티
    val g: Int,
    val b: Int
) {
    RED(255, 0, 0), // 상수 생성시 프로퍼티 값 지정
    ORANGE(255, 165, 0),
    BLUE(0, 0, 255),
    GREEN(0, 255, 0),
    YELLOW(255, 255, 0); // 여기서는 세미콜론 필수

    fun rgb() = (r * 256 + g) * 256 + b // enum 클래스 내부의 메서드 정의
}

when으로 enum 클래스 다루기

fun getMnemonic(color: Color) =
    when (color) {
        Color.RED -> "RED"
        Color.BLUE -> "BLUE"
        Color.GREEN -> "GREEN"
        Color.YELLOW -> "YELLOW"
        Color.ORANGE -> "ORANGE"
    }
  • 자바와 다르게 각 분기의 끝에 break를 넣지 않아도 됨.
  • 한 분기 안에서 여러 값을 매치 패턴으로 적용할 수 있음. (콤마로 분리)
  • 해당 enum class를 임포트 시키면 클래스 수식자 없으 사용 가능.

when과 임의의 객체를 함께 사용

switch와 다르게 분기 조건에 상수만 사용할 수 있는게 아닌 임의의 객체를 허용.

fun mix(color1: Color, color2: Color) =
    when (setOf(color1, color2)) {
        setOf(Color.RED, Color.YELLOW) -> Color.ORANGE
        else -> throw Exception("Dirth Color")
    }

이처럼 when의 분기 조건 부분에 식을 넣을 수 있기 때문에 코드를 더 간결하고 아름답게 작성할 수 있음.

하지만 해당 함수는 호출될 때마다 함수 인자로 주어진 두 색이 when의 분기 조건에 있는 다른 두 색과 같은지 비교하기 위해 여러 Set 인스턴스를 생성. (불필요한 가비지 객체가 늘어남.)

인자 없는 when 사용

fun mixOptimized(color1: Color, color2: Color) =
    when {
        (color1 == RED && color2 == YELLOW) ||
                (color1 == YELLOW && color2 == RED) ->
            ORANGE
        (color1 == YELLOW && color2 == BLUE) ||
                (color1 == BLUE && color2 == YELLOW) ->
            GREEN
        else -> throw Exception("Dirty Color")
    }

추가적으로 객체를 생성하지 않는다는 장점이 있지만 가독성이 떨어지는 단점이 있음.

스마트 캐스트: 타입 검사와 타입 캐스트를 조합

interface Expr
class Num(val value: Int): Expr // value라는 프로퍼티만 존재하는 클래스
class Sum(val left: Expr, val right: Expr): Expr // Expr 타입의 객체라면 어떤 것이나 Sum 연산자의 인자가 될 수 있음
fun main(args: Array<String>) {
    println(eval(Sum(Sum(Num(1), Num(2)), Num(4))))
}

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")
}
  • is는 자바의 instanceof와 비슷함.
  • 자바 경우 타입을 확인 후 해당 타입에 속한 멤버에 접근하기 위해서는 명시적으로 타입을 캐스팅해야 함.
  • 코틀린에서는 프로그래머 대신 컴파일러가 캐스팅 해줌.

리팩토링: if를 when으로 변경

1차 리팩토링: return문과 중괄호 제거

fun eval(e: Expr): Int =
    if (e is Num) {
        e.value
    } else if (e is Sum) {
        eval(e.left) + eval(e.right)
    } else {
        throw IllegalArgumentException("Unknown expression")
    }

2차 리팩토링: when으로 변경

fun eval(e: Expr): Int =
    when (e) {
        is Num -> {
            e.value
        }
        is Sum -> {
            eval(e.left) + eval(e.right)
        }
        else -> {
            throw IllegalArgumentException("Unknown expression")
        }
    }

대상을 이터레이션: while과 for 루프

  • while은 자바와 동일.
  • for은 자바의 for-each 루프에 해당하는 형태만 존재.

수에 대한 이터레이션: 범위와 수열

  • 코틀린에서는 초기값, 증가 값, 최종 값을 사용한 루프가 아닌 범위를 사용.
  • 범위는 기본적으로 두 값으로 이뤄진 구간이며, 보통 정수 등의 숫자 타입의 값 .. 연산자로 시작 값과 끝 값을 연결해서 범위를 만듬.
val oneToTen = 1..10

정수 범위로 수행할 수 있는 가장 단순한 작업은 범위에 속한 모든 값에 대한 이터레이션.

어떤 범위에 속한 값을 일정한 순서로 이터레이션하는 경우를 수열이라 함.

fun main(args: Array<String>) {
    val oneToTen = 1..10

    for (i in oneToTen) {
        println(clapGame(i))
    }
}

fun clapGame(i: Int) =
    when {
        i % 3 == 0 -> "짝"
        i % 6 == 0 -> "짝"
        i % 9 == 0 -> "짝"
        else -> "$i"
    }

증가 값을 갖고 범위 이터레이션

    for (i in 100 downTo 1 step 2) {
        println(clapGame(i))
    }
  • 100 dwonTo 1은 역방향 수열.
  • step을 붙이면 증가 값이 변경 됨.
  • 그 외에 until도 있는데 뒤에서 배울 예정.

맵에 대한 이터레이션

import java.util.*

fun main(args: Array<String>) {
    val binaryReps = TreeMap<Char, String>()

    for (c in 'A'..'Z') {
        val binary = Integer.toBinaryString(c.code)
        binaryReps[c] = binary
    }

    for ((letter, binary) in binaryReps) {
        println("$letter = $binary")
    }
}

책에서는 Integer.toBinaryString(c.toInt())로 나와있었다.

Char.toInt()는 Kotlin 1.5부터 Deprecated 되었기 때문에 Char.code로 작성해야 함.

in으로 컬렉션이나 범위의 원소 검사

in 연산자를 사용해 어떤 값이 범위에 속하는지 검사할 수 있음.

fun main(args: Array<String>) {
    println(isLetter('q'))
    println(isNotDigit('x'))
}

fun isLetter(c: Char) = c in 'a'..'z' || c in 'A'..'Z'
fun isNotDigit(c: Char) = c !in '0'..'9'

when에서 in 사용하기

fun recognize(c: Char) = when (c) {
    in '0'..'9' -> "It's digit!"
    in 'a'..'z', in 'A'..'Z' -> "It's a letter!"
    else -> "I don't know.."
}

코틀린의 예외 처리

코틀린의 예외 처리는 자바나 다른 언어의 예외 처리와 비슷.

코틀린에서의 예외는 자바와 비슷하지만, new를 붙일 필요가 없음.

fun main(args: Array<String>) {
    val percentage = 1000
    
    if (percentage !in 0..100) {
        throw IllegalArgumentException("수치를 벗어남.")
    }
}

try, catch, finally

자바와 동일하게 try, catch, finally 절을 함께 사용.

자바와 다르게 함수가 던질 수 있는 예외를 명시할 필요가 없음.

try를 식으로 사용

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

 

'Programming > Kotlin' 카테고리의 다른 글

[당근마켓 밋업 정리] - Kotlin Coroutines 톺아보기  (0) 2022.01.09
Kotlin In Action #4  (0) 2022.01.05
Kotlin In Action #3  (0) 2021.12.22
Kotlin In Action #2  (0) 2021.12.11
Kotlin In Action #0  (0) 2021.12.05