2장. 코틀린 기초

Posted by yunki kim on January 8, 2024

1. 함수

  • 코틀린에서 if는 문(statement)이 아닌 식(expression)이다.
    • 식(expression)은 값을 만들어 내며 다른 식의 하위 요소로 계산에 참여할 수 있다.
    • 문(statement)은 자신을 둘러싸고 있는 가장 안쪽 블록의 최상위 요소로 존재하며 아무런 값을 만들어내지 않는다.
  • 따라서 다음과 같은 동작이 가능하다.
1
2
3
fun max(a: Int, b: Int): Int { // a, b를 비교해서 최댓값을 반환
	return if (a > b) a else b
}
  • 오직 식 하나로 이루어진 함수는 다음과 같이 표현할 수 있다.
1
fun max(a: Int, b: Int) = if (a > b) else b // 위 max 함수 예시와 같은 기능을 하는 함수
  • 본문이 중괄호로 둘러싸인 함수를 블록이 본문인 함수라 한다. 등호화 식으로 이뤄진 함수를 식이 본문인 함수라 한다. 식이 본문인 함수는 반환 타입을 명시하지 않아도 컴파일러가 타입 추론을 해준다.

2. 변수

  • 타입으로 변수 선언을 시작한다면, 타입을 생략할 경우 변수 선언과 식을 구별할 수 없다. 따라서 코틀린은 val이라는 키워드를 사용해서 변수 선언을 시작하고 변수명 뒤에 타입을 명시하거나 생략한다.
1
2
3
4
5
6
val number: Int = 12
val number = 12

// 초기화 삭이 없다면 반드시 타입을 명시해야 한다
val number: Int
number = 12
  • 변수 선언 시 다음과 같은 두 가지 키워드를 사용할 수 있다.
    • val(value - 값을 의미): immutable 참조를 저장한다. 초기화 이후 재대입이 불가능하다. 자바의 final에 해당한다. 그러나 어떤 블록이 실행될 때 오직 한 초기화 문장만 실행됨을 컴파일러가 확인할 수 있다면 조건에 따라 val 값을 다른 여러 값으로 초기화할 수도 있다.

      1
      2
      3
      4
      5
      6
      
        val message: String
        if (...) {
        	message = "a"
        } else {
        	messsage = "b"
        }
      
    • var(variable - 변수를 의미): mutable 참조이다. 자바의 일반 변수에 해당한다.

  • 어떤 타입을 가진 변수에 다른 타입의 값을 저장하려면 변환 함수를 사용하거나 강제 형 변환을 해야 한다.
  • 문자열 템플릿
    • 문자열 템플릿은 변수를 문자열 안에서 사용할 수 있게 해주는 유용한 기능이다. 다음과 같이 사용할 수 있다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
      val name = "skull"
      println("I'm $name") // output: I'm skull
      // $name님 처럼 변수명 뒤에 한글을 바로 사용하면 컴파일러가 영문자와 한글을 
      // 한꺼번에 식별자로 인식해서 unresolved references 에러가 발생한다.
      // 따라서 ${}를 사용해야 한다.                  
      println("${name}님 반가워요")
        
      println("\$x") // output: $x
        
      // 복잡한 식은 ${}를 사용
      println("I'm ${if (args.size > 0) args[0] else "someone"}")
    

3. 클래스와 프로퍼티

코틑린은 값 객체를 자바보다 더 간결하게 선언할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Person {

	private final String name;

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

	public String getName() {
		return name;
	}
}

// 위 자바 코드를 코틀린으로 이용하면 다음과 같이 변환할 수 있다.
// 이렇게 변환을 하면 public 가시성 변경자(visibility modifier)가 사라진다.
// 코틀린 기본 가시성은 public이다.
class Person(val name: String)

3.1 프로퍼티

자바에서는 필드와 접근자를 묶어서 프로퍼티(property)라고 한다.코틀린의 프로퍼티는 자바의 필드와 접근자 메서드를 대체한다.

1
2
3
4
5
6
7
8
9
class Person (
	val name: String, // read-only 프로퍼티로, 비공개 필드와 단순한 getter를 만들어낸다
	var isMarried: Boolean // write가 가능한 프로퍼티로, 비곡개 필드, getter, setter를 만들어 낸다
)

// 자바와의 상용법을 비교하면 다음과 같다 (주석이 자바)
val person = Person("Bob", true) // Person person = new Person("Bob", true);
person.name // person.getName();
person.isMarried = false; // person.setIsMarried(false);
  • 커스텀 접근자. 프로퍼티의 접근자를 직접 작성하 수도 있다.
1
2
3
4
5
class Rectangle(val height: Int, val width: Int) {
    
    val isSquare
        get() = (height == width)
}

4. 패키지

코틀린에서는 여러 클래스를 한 파일에 넣을 수 있고, 파일의 이름도 마음대로 정할 수 있다. 코틀린은 디스크상의 어느 디렉터리에 소스코드 파일을 위치시키든 상관없다. 그럼에도 자바와 같이 패키지별로 디렉터리를 구성하는 것이 좋다. 그래야 자바와 코틀린을 함께 사용할 때 문제가 생기지 않는다.

5. 선택 표현과 처리: enum과 when

5.1 enum

코틀린에서 enum은 소프트 키워드(soft keyword)이다. enum은 class 앞에 있을 때 특별한 의미를 지니지만 다른 곳에서는 이름에 사용할 수 있다. 반면 class는 키워드이기에 이름으로 사용할 수 없다.

1
2
3
4
5
6
7
8
9
10
11
12
13
enum class Color (
    val r: Int,
    val g: Int,
    val b: Int
) {
    RED(255, 0, 0),
    ORANGE(255, 165, 0)
    ; // 코틀린에서 유일하게 ';'을 사용하는 부분이다

    fun rgb() = (r * 256 + g) * 256 + b
}

println(Color.RED.rgb())

5.2 when으로 enum 클래스 다루기

when은 자바 switch에 해당한다. when 역시 if과 같은 식이므로 식이 본문인 함수를 작성할 수 있다. when과 if의 내용이 길어진다면 {}를 활용해 분기 블록을 만들 수 있다. 이 경우 블록의 맨 마지막에 분기의 결괏값을 넣으면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 위 예제의 enum Color 사용
fun getColor(color: Color) =
        when (color) {
            Color.RED -> "red"
            Color.ORANGE, Color.YELLOW -> "or" // 한 분기에서 여러 값을 매치 패턴으로 사용할 때 ',' 사용
        }

println(getColor(Color.RED)) // output: red
println(getColor(Color.ORANGE)) // output: or
println(getColor(Color.YELLOW)) // output: or

when (color) {
	Color.Red -> {
			...
			"red"
	} // "red" 반환
}

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

코틀린의 when은 분기 조건에 상수뿐만 아니라 임의의 객체를 넣을 수도 있다.

1
2
3
4
5
fun mix(c1: Color, c2: Color) = 
    when (setOf(c1, c2)) { // Set 생성
        setOf(Color.RED, Color.YELLOW) -> Color.ORANGE
        else -> throw Exception("Dirty color")
    }

5.4 인자 없는 when 사용

위 예제는 매번 비교 때마다 setOf()로 객체를 생성한다. 인자가 없는 when 식을 사용하면 불필요한 객체 생성을 막을 수 있다.

1
2
3
4
5
6
fun mix(c1: Color, c2: Color) =
    when {
        (c1 == Color.RED && c2 == Color.YELLOW) 
                ||(c1 == Color.YELLOW && c2 == Color.RED) -> Color.ORANGE
        else -> throw Exception("Dirty Color")
    }

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

스마트 캐스트란 어떤 변수가 원하는 타입인지 일단 is로 검사하고 나면 굳이 변수를 원하는 타입으로 캐스팅하지 않아도 해당 타입으로 선언된 것처럼 사용할 수 있는 기능이다. 따라서 다음과 같은 동작을 할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface Expr
class Num(val value: Int): Expr // Expr 인터페이스를 implements한다
class Sum(val left: Expr, val right: Expr): Expr

fun eval(e: Expr): Int {
    if (e is Num) {
				// 타입 체크를 한 뒤 명시적으로 캐스팅을 해주지 않아도 된다.
				// 그럼에도 명시적으로 캐스팅을 해주고 싶다면 val n = e as Num 같이 as키워드를 사용하면 된다.
        return e.value
    } else if (e is Sum) {
        return eval(e.right) + eval(e.left)
    }
    throw IllegalArgumentException("Unkown expression")
}

스마트 캐스트는 is로 변수에 든 값의 타입을 검사한 뒤, 그 값이 바뀔 수 없는 경우에만 동작한다. 예컨대, 위 예제에서 클래스의 프로퍼티가 val이며 커스텀 접근자를 사용하지 않았다. 커스텀 접근자를 사용하면 해당 프로퍼티에 대한 접근이 항상 같은 값을 내놓는다고 확신하지 못한다.

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

  • 코틀린의 while, do..while은 자바와 같다.
  • for는 자바의 for-each loop에 해당하는 형태만 존재한다. 때문에 값을 증가, 감소 등의 연산을 하면서 반복하기 위해 downTo, step, until 이란 함수를 사용한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
val oneToTen = 1..10 // 1 ~ 10까지 수를 가진 변수 선언

// 1 ~ 10 까지 1 씩 증가하며 순환
for (i in 1..10) {
	...
}

// 2 씩 감소시키면서 1이 될때 까지 순회, i 초기 값은 10
for (i in 10 downTo 1 step 2) {
	...
}

// 1 ~ 9 까지 1씩 증가하며 순회
for (i in 1 until 10) {
	...
}
  • 컬렉션과 for..in 루프를 같이 사용한다면, 다음과 같은 동작들을 할 수 있다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
val binaryReps = TreeMap<Char, String>()
for (c in 'A'..'F') {
    binaryReps[c] = Integer.toBinaryString(c.code) // 아스키코드를 2진표현으로 바꾼다
}
// key는 맵의 키값, value는 맵의 키에 대응하는 밸류값
for ((key, value) in binaryReps) {
    ...
}

val list = arrayListOf("10", "11", "1001")
// index는 리스트 원소의 인덱스 번호, element는 리스트의 원소
for ((index, element) in list.withIndex()) {
    ...
}

// 변수 c의 값이 a~z 범위에 존재하면 true 반환
fun isLetter(c: Char) = c in 'a'..'z'
// 변수 c의 값이 0~9 범위에 존재하지 않으면 true 반환
fun isNotDigit(c: Char) = c !in '0'..'9'

in 키워드로 인한 비교는 java.lang.Comparable 인터페이스를 구현한, 즉 비교가 가능한 클래스라면 모두 사용할 수 있다. 따라서 다음과 같은 동작도 가능하다.

1
2
println("Kotlin" in "Java".."Scala") // String에는 Comparable 구현이 두 문자열을 알파벳 순서로 비교한다
println("Kotlin" in setOf("Java", "Scala") // 집합에 Kotlin이 들어있나

7. 코틀린의 예외 처리

코틀린의 예외 처리(try, catch, fianlly)는 기본적으로 자바와 같지만 예외를 checked exception와 unchecked exception으로 나누지 않는다. 모두 unchecked exception으로 간주한다. 또 한, try 키워드 역시 if와 같은 식이다. try의 본문은 반드시 {}로 감싸야 하며, try와 catch 모두 식의 마지막 값이 전체 결과이다. 따라서 다음과 같은 방식으로 코드를 작성할 수 있다.

출처 - 코틀린 인 액션