본문 바로가기
  • GDG on campus Ewha Tech Blog
3-1기 스터디/Kotlin

[2주차] 코틀린 기초

by kelina 2021. 11. 7.

CH2. 코틀린 기초

2장에서는 아래와 같은 내용을 배운다.

  • 함수, 변수, 클래스, enum, 프로퍼티를 선언하는 방법
  • 제어 구조
  • 스마트 캐스트
  • 예외 던지기와 예외 잡기

 

기본 요소 : 함수와 변수

함수

다음 코드들을 보고 찾아볼 수 있는 코틀린 문법이나 특성을 알아보자.

fun main(arg: Array<String>) {
	println("Hello, world!")
}
  • 함수를 선언할 때 fun 키워드를 사용한다.
  • 마라미터 이름 뒤에 그 타입을 쓴다.
  • 함수를 최상위 수준에 정의할 수 있다. (클래스 안에 넣을 필요 X)
fun max(a: Int, b:Int): Int {	//블록이 본문인 함수
	return if (a>b) a else b
}
println(max(1,2))
  • fun 함수이름 (파라미터 목록) : 반환 타입
  • 괄호와 반환 타입 사이를 콜론으로 구분한다.
  • 식을 본문으로 표현할 수도 있다.
    • fun max(a: Int, b:Int): Int = return if (a>b) a else b   //식이 본문인 함수
  • 식이 본문인 함수의 경우 컴파일러가 타입을 분석해 반환 타입을 대신 정해줄 수 있다. -> 타입 추론 
    • fun max(a: Int, b:Int)= return if (a>b) a else b   //반환타입 생략

변수

val answer = 42		//타입 미지정
val answer: Int = 42  	//타입 지정; 둘다 가능하다

코틀린은 변수를 선언할 때 타입이 맨 앞에 온다. 또한 타입을 지정하지 않아도 컴파일러가 초기화 식을 분석해서 초기화 식의 타입을 변수 타입으로 지정한다. (그러나 초기화 식이 없다면 타입을 반드시 지정해야 함)

  • val - 변경 불가능한 참조 (ex. 자바의 final)
  • var - 변경 가능한 참조

기본적으로 모든 변수를 val로 선언하는 것을 추천한다. val 참조 자체는 불변일지라도 참조가 가리키는 객체의 내부 값은 변경이 가능하다.

var은 변수의 값을 변경할 수 있어도 변수의 타입은 고정돼 바뀌지 않는다. (ex. Int를 string으로 X)

 

문자열 템플릿 기능도 사용할 수 있다.

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

name이라는 변수를 선언하고 그 다음줄의 문자열 리터럴 안에서 변수를 사용했다. 필요한 곳에 변수를 넣되 변수 앞에 $를 추가해야 한다. 복잡한 식도 중괄호로 둘러싸 템플릿 안에 넣을 수도, 그 안에서 큰 따옴표를 사용할 수도 있다.

 

클래스와 프로퍼티

이번에는 클래스를 선언하는 기본 문법을 소개한다.

class Person(
    val name: String,	//읽기 전용 프로퍼티
    var isMarried: Boolean	//쓸 수 있는 프로퍼티
)

fun main(args: Array<String>) {
    val person = Person("Bob", true)	//new 키워드 사용 X
    println(person.name)	//프로퍼티 이름을 직접 사용해도 코틀린이 게더를 자동 호출
    println(person.isMarried)
}

코틀린의 클래스에서 프로퍼티를 선언할 때는 변수와 마찬가지로 val이나 var을 사용한다. val로 선언한 프로퍼티는 읽기 전용이며, var로 선언한 프로퍼티는 변경 가능하다.

코틀린은 간단한 티폴트 접근자 구현을 제공한다. 이 접근자는

  • 값을 저장하기 위한 비공개 필드
  • 필드에 값을 저장하기 위한 세터
  • 필드의 값을 읽기 위한 게터

로 이루어져 있다. 

코틀린에서는 게터를 호출하는 대신 프로퍼티를 직접 사용한다는 점을 유의하자.

또한 패키지를 사용하면 다른 파일에 정의한 선언일지라도 직접 사용할 수 있으며 모든 선언을 import 키워드로 가져올 수 있다.

클래스의 경우 여러 클래스를 한 파일에 넣을 수 있고 파일 이름도 마음대로 정할 수 있다. 하지만 자바와 같이 패키지별로 디렉토리를 구성하는 편을 추천한다.

 

선택 표현과 처리: enum과 when

enum class Color {
    RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET
}

fun getMnemonic(color: Color) =
    when (color) {
        Color.RED -> "Richard"
        Color.ORANGE -> "Of"
        Color.YELLOW -> "York"
        Color.GREEN -> "Gave"
        Color.BLUE -> "Battle"
        Color.INDIGO -> "In"
        Color.VIOLET -> "Vain"
    }

fun main(args: Array<String>) {
    println(getMnemonic(Color.BLUE))
}

다른 곳에서 이름으로도 사용 가능한 enum은 코틀린에서 소프트 키워드라 부르는 존재이며 enum 클래스 안에도 프로퍼티나 메소드를 정의할 수 있다. 일반적 클래스와 마찬가지로 생성자와 프로퍼티를 선언하지만 상수를 정의할 때는 그 상수에 해당하는 프로퍼티 값을 지정해야한다.

자바에서 switch에 해당하는 코틀린 구성 요소는 when이다. 위의 코드는 color로 전달된 값과 같은 분기를 찾는다.

그러나 자바와 달리 when의 분기 조건은 임의의 객체를 허용한다.

fun mix(c1: Color, c2: Color) =
        when (setOf(c1, c2)) {
            setOf(RED, YELLOW) -> ORANGE
            setOf(YELLOW, BLUE) -> GREEN
            setOf(BLUE, VIOLET) -> INDIGO
            else -> throw Exception("Dirty color")
        }

when은 인자 값과 매치하는 조건 값을 찾을 때까지 각 분기를 검사하고, 찾을 수 없을 경우 else로 넘어간다.

불리언 식을 계산하는 when 이라면 인자를 없애고 사용할 수도 있다.

스마트 캐스트

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.right) + eval(e.left)
    }
    throw IllegalArgumentException("Unknown expression")
}

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

코틀린에서는 is를 사용해 변수 타입을 검사하는데, 타입에 속한 멤버에 접근 하기 위해선 변수 타입을 캐스팅해야 한다. 코틀린은 프로그래머 대신 컴파일러가 캐스팅을 수행해주고 이를 스마트 캐스트라고 부른다.

스마트 캐스트는 is로 변수에 든 값의 타입을 검사한 뒤 그 값이 바뀔 수 없는 경우에만 작동한다.

또한 if식을 본문으로 더 간단하게 만들고, 이것 역시 if 중첩 대신 when을 사용하는 것도 가능하다.

//return문과 중괄호를 없애고 if 식을 본문으로 사용한 코드
fun eval(e: Expr): Int =
    if (e is Num) {
        e.value
    } else if (e is Sum) {
        eval(e.right) + eval(e.left)
    } else {
        throw IllegalArgumentException("Unknown expression")
    }
    
 //if 중첩 대신 when 사용한 코드
 fun eval(e: Expr): Int =
    when (e) {
        is Num ->
            e.value
        is Sum ->
            eval(e.right) + eval(e.left)
        else ->
            throw IllegalArgumentException("Unknown expression")
    }

 

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

while

코틀린의 do-while 루프는 자바와 크게 다르지 않다.

for

코틀린에는 for루프에 해당하는 요소가 없다. 대신 범위(range)를 사용한다.

범위란 기본적으로 두 값으로 이루어진 구간이다. .. 연산자로 시작 값과 끝 값을 연결해 범위를 만든다.

fun fizzBuzz(i: Int) = when {
    i % 15 == 0 -> "FizzBuzz "
    i % 3 == 0 -> "Fizz "
    i % 5 == 0 -> "Buzz "
    else -> "$i "
}

//메인 생략
//1..100 범위의 정수에 대해 이터레이션
for (i in 1..100) {
	print(fizzBuzz(i))
}

//증가 값을 갖고 범위 이터레이션
for (i in 100 downTo 1 step 2) {
	print(fizzBuzz(i))
}

 

맵에 대한 이터레이션

아래와 같이 맵을 만들고 2진 표현으로 맵을 채운 다음, 맵을 출력할 수 있다.

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

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

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

 

in을 사용해 어떤 값이 범위에 속하는지 검사할 수 있다. 반대로 !in을 사용하면 범위에 속하지 않는 값을 알려준다.

또한 when에서도 in을 사용할 수 있으며 범위는 문자에만 국한되지 않는다.

fun isLetter(c: Char) = c in 'a'..'z' || c in 'A'..'Z'	//in 사용
fun isNotDigit(c: Char) = c !in '0'..'9'	//!in 사용
fun recognize(c: Char) = when (c) {			//when에서의 in 사용
    in '0'..'9' -> "It's a digit!"
    in 'a'..'z', in 'A'..'Z' -> "It's a letter!"
    else -> "I don't know…​"
}

 

코틀린의 예외 처리

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

자바와 마찬가지로 예외를 처리하려면 try, catch, finally 절을 함께 사용한다.

import java.io.BufferedReader
import java.io.StringReader

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

fun main(args: Array<String>) {
    val reader = BufferedReader(StringReader("239"))
    println(readNumber(reader))
}

자바와 가장 큰 차이는 throws절이 없다는 사실이다. 코틀린은 자바와 달리 체크 예외 처리가 강제가 아니기 때문이다.

또한 코틀린의 try 키워드는 if나 when과 마찬가지로 식이다. 따라서 try 값을 변수에 대입할 수 있으며, catch 블록 다음의 코드를 계속 진행하고 싶다면 catch의 값을 만들어야 한다.

import java.io.BufferedReader
import java.io.StringReader

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

    println(number)
}

fun main(args: Array<String>) {
    val reader = BufferedReader(StringReader("not a number"))
    readNumber(reader)
}

try 코드 블록 실행이 정상적으로 끝나면 그 블록의 마지막 식의 값이 결과이고 예외가 발생하고 잡히면 그 예외에 해당하는 catch 블록의 값이 결과이다.

 

댓글