[Kotlin in Action] 02장 - 코틀린 기초


2장 코틀린 기초

2.1 기본 요소: 함수와 변수

  • 함수를 정의할 때 fun 키워들 사용한다.

2.1.1 Hello, World

fun main(args: Array<String>) {
    println("Hello, world!")
}

2.1.2 함수

fun max(a: Int, b:Int): Int {
    return if (a > b) a else b
}
식이 본문인 함수
fun max(a: Int, b: Int): Int = if (a > b) a else b

2.1.3 변수

  • 코틀린에서는 변수 이름 뒤에 타입을 명시하거나 생략 가능
val answer = 42
val answer: Int = 42
변경 가능한 변수와 변경 불가능한 변수
  • val(value)
    • 변경 불가능한 참조를 저장하는 변수
    • 초기화하고 나면 재대입이 불가능
    • 자바의 final
  • var(variable)
    • 변경 가능한 참조
    • 자바의 일반 변수

2.1.4 문자열 템플릿

fun main(args: Array<String>) {
    val name = if (args.size > 0) args[0] else "Kotlin"
    println("Hello, $name!")
}
  • 문자열 템플릿
    • 자바의 (“Hello, “ + name + “!”) 과 동일
    • 존재하지 않는 변수를 문자열 템플릿 안에서 사용하면 컴파일 오류 발생
    • 특수문자 사용 시
      • println(“$x”)
    • 복잡한 식 사용 시 중괄호 사용
      • println(“Hello, ${args[0]}!”)
      • println(“Hello, ${if (args.size > 0) args[0] else “someone”}!”)
    • 컴파일된 코드는 StringBuilder를 사용하고 문자열 상수와 변수의 값을 append로 문자열 빌더 뒤에 추가함

2.2 클래스와 프로퍼티

Person 클래스

/* 자바 */
public class Person {
    private final String name;

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

    public String getName() {
        return name;
    }
}

/* 코틀린 */
class Person(val name: String)
  • 코틀린의 기본 가시성은 public이므로 이런 경우 생략 가능

2.2.1 프로퍼티

  • 자바에서는 필드와 접근자를 한데 묶어 프로퍼티라고 부른다.
클래스 안에서 변경 가능한 프로퍼티 선언하기
class Person {
    val name: String,   // 읽기 전용 프로퍼티 - 코틀린은 (비공개)필드와 (공개) 게터를 만들어낸다.
    var isMarried: Boolean  // 쓸 수 있는 프로퍼티 - 코틀린은 (비공개)필드, (공개) 게터, (공개) 세터를 만들어낸다.
}
Person 클래스 사용
/* 자바 */
Person person = new Person("Bob", true);
person.getName();
person.isMarried();
person.setMarried(false);

/* 코틀린 */
val person = Person("Bob", true)
person.name
person.isMarried
person.isMarried = false

2.2.2 커스텀 접근자

class Rectangle (val height: Int, val width: Int) {
    val isSquare: Boolean
        get() { //프로퍼티 게터 선언
            return height == width
        }
}

2.3 enum과 when

2.3.1 enum 클래스 정의

enum class Color {
    RED, ORANGE, YELLOW
}
프로퍼티와 메소드가 있는 enum 클래스 선언하기
enum class Color(
    val r:Int, val g: Int, val b: Int   //상수의 프로퍼티 정의
) {
    RED(255, 0, 0), ORANGE(255, 165, 0), YELLOW(255, 255, 0);

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

Color.RED.rgb()

2.3.2 when으로 enum 클래스 다루기

when을 사용해 올바른 enum 값 찾기
  • 자바의 switch
fun getMnemonic(color: Color) =
    when (color) {
        Color.RED -> "Richard"
        Color.ORANGE -> "Of"
        Color.YELLOW -> "York"
    }

    getMnemonic(Color.RED)
한 when 분기 안에 여러 값 사용하기
fun getMnemonic(color: Color) =
    when (color) {
        Color.RED, Color.ORANGE -> "warm"
        Color.YELLOW -> "Of"
    }

    getMnemonic(Color.RED)

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

  • 코틀린 when의 분기 조건은 임의의 객체를 허용
fun mix(c1: Color, c2: Color) =
    when (setOf(c1, c2)) {  //when 식의 인자로 아무 객체나 사용 가능
        setOf(RED, YELLOW) -> ORANGE
        setOf(YELLOW, BLUE) -> GREEN
        else -> throw Exception("Dirty color")
    }

    mix(BLUE, YELLOW)
  • Set 객체로 만드는 setOf 함수 사용

2.3.4 인자 없는 when 사용

  • 2.3.3 예제의 비효율점
    • 매번 Set 인스턴스를 생성
      • 가비지 객체 늘어남
    • 인자 없는 when 사용 시 불필요한 객체 생성 막을 수 있음
      • 그러나 가독성은 떨어짐
fun mixOptiomized(c1: Color, c2: Color) =
    when {
        (c1 == RED && c2 == YELLOW) || (c1 == YELLOW && c2 == RED) -> ORANGE
        (c1 == YELLOW && c2 == BLUE) || (c1 == BLUE && c2 == YELLOW) -> GREEN

        else -> throw Exception("Dirty color")
    }
    mixOptiomized(BLUE, YELLOW)

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

  • 스마트 캐스팅 - 변수를 원하는 타입으로 캐스팅하지 않아도 원하는 타입으로 사용할 수 있는 것
    • 컴파일러가 캐스팅 수행
  • 원하는 타입으로 명시적으로 타입 캐스팅하려면 as 키워드 사용
    • val n = e as Num

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

  • 코틀린의 if가 값을 만들어내기 때문에 자바와 달린 3항 연산자가 따로 없다.
값을 만들어내는 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 분기에 식이 하나밖에 없다면 중괄호 생략 가능
  • if 분기에 블록을 사용하는 경우 그 블록의 마지막 식이 그 분기의 결과 값이다.
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")
    }

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

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

  • 코틀린에서는 범위를 사용
for(i in 1..100) {
    print(fizzBuzz(i))
}
증가 값을 갖고 범위 이터레이션 하기
for(i in 1..100 downTo 1 step 2) {
    print(fizzBuzz(i))
}

98 94 92...

2.4.3 맵에 대한 이터레이션

맵을 초기화하고 이터레이션하기
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")
}

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

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

2.5 코틀린의 예외 처리

  • 자바와 비슷

자바와 마찬가지로 try 사용하기

fun readNumber(reader: BufferedReader): Int? {  //함수가 던질 수 있는 예외를 명시할 필요가 없다.
    try {
        val line = reader.readLine()
        return Integer.parseInt(line)
    }
    catch (e: NumberFormatException) {
        return null
    }
    finally {
        reader.close()
    }
}
  • 코틀린은 체크 예외와 언체크 예외를 구별하지 않는다.

2.5.2 try를 식으로 사용

fun readNumber(reader: BufferedReader) {
    val number = try {
        Integer.parseInt(reader.readLine()) //이 식의 값이 try 식의 값이 된다.
    } catch (e: NumberFormatException) {
        null
    }
}