기록하는 습관

[Kotlin In Action]2장 - 코틀린 기초 본문

스터디/Kotlin In Action

[Kotlin In Action]2장 - 코틀린 기초

로그뉴 2023. 1. 24. 15:40

 

 

 

** 이 글은 Kotlin In Action을 읽고 정리한 글입니다. **

 

 

1. 기본 요소 : 함수와 변수

1-1. 함수
  • 코틀린에서 if 는 식(expression)이지 문(statement)이 아니다.
    • 식 : 값을 만들어내며 다른 식의 하위 요소로 계산에 참여할 수 있다.
    • 문 : 자신을 둘러싸고 있는 가장 안쪽 블록의 최상위 요소로 존재하며 아무런 값을 만들어내지 않는다.
  • 자바에서는 모든 제어 구조가 ‘문’ 인 반면, 코틀린은 루프를 제외한 대부분의 제어 구조가 ‘식’이다.
// 블록이 본문인 함수 (본문이 중괄호로 둘러싸인 함수)
fun max(a : Int, b : Int) : Int {
    return if ( a > b ) a else b 
}
// 식이 본문인 함수 (등호와 식으로 이뤄진 함수)
fun max(a : Int, b : Int) = if ( a > b ) a else b
  • 식이 본문인 함수 에서는 타입 추론이 가능하다.
    • 블록이 본문인 함수는 반드시 반환 타입을 지정하고 return문을 사용해 반환 값을 명시해야 함.

 

1-2. 변수
  • 코틀린에서는 타입 지정을 생략하는 경우가 흔하므로 변수 선언을 시작하는 대신 변수 이름 뒤에 타입을 명시하거나 생략하게 허용한다.
  • 초기화 식이 있는 경우 변수 타입을 생략할 수 있다.
    • 초기화 식이 없으면 컴파일러가 추론할 수 없다.
// 초기화 식 사용할 때
val answer = 42
val answer: Int =  42

// 초기화 식 사용하지 않을 때
val answer: Int
answer = 42
  • val (value) : 변경 불가능한 (immutable) 참조를 저장하는 변수. 초기화 후에 제대입이 불가능 하고 자바의 final 변수에 해당한다.
  • var (variable) : 변경 가능한 (mutable) 참조. 자바의 일반 변수.

 

기본적으로는 모든 변수를 val 키워드를 사용해 불변 변수로 선언하고,나중에 꼭 필요할 때에만 var로 변경하기.

  • val 변수는 블록 실행시 정확히 한 번만 초기화 되어야 함.
  • val 참조 자체는 불변일지라도 그 참조가 가리키는 객체의 내부 값은 변경될 수 있다.
val languages = arrayListOf("Java") // 불변 참조를 선언
languages.add("Kotlin") // 참조가 가리키는 객체 내부를 변경
 
 
 
var 키워드를 사용하면 변수의 값을 변경할 수 있지만 변수의 타입은 고정된다.
  • 컴파일러는 변수 선언 시점의 초기화 식으로부터 변수 타입을 추론하고, 변수 선언 이후 변수 재대입이 이뤄질 때는 이미 추론한 변수의 타입을 염두에 두고 대입문의 타입을 검사한다.
 
 
1-3. 문자열 템플릿
  • 문자열 리터럴 안에서 변수를 사용하는방법
val name = if (args.size > 0) args[0] else “Kotlin.”
println(“Hello, $name”);

 

2. 클래스와 프로퍼티

  • 값 객체: 코드 없이 데이터만 저장하는 클래스
  • 코틀린 기본 가시성은 public이다.

 

2-1.  프로퍼티
  • 클래스라는 개념의 목적은, 데이터를 캡슐화하고 캡슐화한 데이터를 다루는 코드를 한 주체 아래 가두는 것이다.
  • 자바에서는 데이터 필드에 저장하고, 멤버 필드의 가시성은 보통 비공개다. 클래스는 클라이언트의 접근을 위해 접근자 메소드를 제공한다. 필드를 읽고 쓰기 위해서는 getter, setter 를 제공해야 한다.
  • 자바에서는 필드, 접근자를 묶어 프로퍼티 라고 부른다.
  • 코틀린은 프로퍼티를 언어 기본 기능으로 제공하며 자바의 필드, 접근자 메소드를 대신한다.
  • val: 읽기 전용, getter만 제공
  • var: 읽기/쓰기 가능, getter / setter 모두 제공
class Person{
  val name: String 
  var isMarried: Boolean
}

val person = Person("Hue", false) 

// 필드 이름으로 직접 접근
println(person.name)
println(person.isMarried)

person.isMarried = true

 

2-2. 커스텀 접근자
  • 프로퍼티의 접근자를 사용자 정의로 작성하는 방식
class Rectangle (val height : Int, val width : Int) {
    val isSquare : Boolean
    get() {     // 프로퍼티 게터 선언
        return height == width 
    }
}
   
 
2-3. 코틀린 소스코드 구조 : 디렉터리와 패키지
  • 코틀린의 패키지는 자바의 패키지와 비슷하다. 같은 패키지에 속해 있다면 다른 파일에서 정의한 선언일지라도 직접 사용할 수 있다.
  • 다른 패키지에 정의한 선언을 사용하려면 import 키워드를 통해 사용할 수 있다.
  • 자바에서는 디렉터리 구조가 패키지 구조를 그대로 따라야 하지만, 코틀린에서는 패키지구조와 디렉터리 구조가 동일하지 않아도 상관없다.
  • 코틀린에서는 여러 클래스를 한 파일에 넣을 수 있다. -> 소스코드 크기가 아주 작은 경우 유용하다.
  • 다만, 자바와 코틀린을 함께 사용할 때는 자바의 방식에 따르는 게 중요하다. 자바 클래스를 코틀린 클래스로 마이그레이션시 문제가 생길 수 있다.
 

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

  • 코틀린의 when 은 자바의 switch 를 대치하되 더 강력하다.
3-1. enum 클래스 정의
  • 자바는 enum, 코틀린은 enum class 를 사용한다.
  • 소프트 키워드
    • class 앞에서는 enum 클래스를 의미, class가 없다면 키워드가 아님
    • enum에서도 일반적인 클래스와 마찬가지로 생성자와 프로퍼티를 선언
enum class Color {
    val r : Int, val g : Int, val b : Int 
} {
    RED(255,0,0), … BLUE(0,0,255) … VIOLET(238, 130, 238); 
    fun rgb() = (r * 256 + g) * 256 + b 
}
 
 
3-2. when 으로 enum 클래스 다루기
  • when 은 자바의 switch 와 같이 분기의 끝에 break 를 넣지 않아도 된다.
  • 한 분기 안에서 여러 값을 매치 패턴으로 사용할 때는  콤마(,)를 사용하면 된다.
fun getWarmth(color : Color) = when(color) {
    RED, ORANGE, YELLOW -> “warm”
    GREEN -> “neutral”
}
   
 
 
3-3. when 과 임의의 객체를 함께 사용
  • switch 달리 when은 분기 조건에 임의의 객체를 허용한다.
  • 객체 사이를 매치할 때 동등성(값 비교)을 사용
fun mix(c1: Color, c2: Color) =
    when (setOf(c1, c2)) {
        setOf(RED, YELLOW) -> ORANGE
        ...
        else -> throw Exception(“Dirty Color”)
    }
   
 
 
3-4. 인자 없는 when 사용
  • 위의 코드는 계속해서 setOf 를 호출하기 때문에 효율적으로 보이지 않을 수 있다. 이런 경우 인자가 없는 when 을 사용하려 불필요한 객체 생성을 막을 수 있다.
fun mixOptimized(c1 : Color, c2 : Color ) = 
    when {
        (c1 == RED && c2 == YELLOW) || 
        (c1 == YELLOW && c2 == RED) -> ORANGE
        …
        else -> throw Exception(“Dirty Color”)
    }
   
 
참고) Kotlin의 동등성이란?
  • 자바에서는 == 동등성 비교 연산자가 원시 타입의 비교에서는 값 비교, 객체의 비교에서는 참조값(주소)를 비교한다.
  • 코틀린에서는 Object에 대응하는 Any 클래스에 equals가 정의되어 있고 Any 클래스는 모든 클래스의 조상이다. == 연산자는 내부적으로 equals 메소드를 수행한다.
    • 참조값 비교는 '==='를 사용한다.
 
 
3-5. 스마트캐스트 : 타임 검사와 타입 캐스트를 조합
  • (1 + 2) + 4 산술식을 계산하는 함수를 만들어보자.
interface Expr
class Num(val value : Int) : Expr
class Sum(val left : Expr, val right : Expr) : Expr


// (1+2)+4 표현
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.right) + eval(e.left) // 스마트 캐스트 사용
    }
    throw IllegalArgumentException(“Unknown expression”)
}
  • 코틀린은 is 를 통해 변수 타입을 검사한다. is 는 자바의 instanceOf 와 비슷하다.
  • 자바의 instanceOf 는 타입 확인 후, 해당 타입에 접근하기 위해서는 명시적으로 변수 타입을 캐스팅 해야 한다. 그러나 코틀린은 컴파일러가 이런 캐스팅을 대신 해준다.
  • 스마트 캐스트란, 코틀린에서 is 로 검사를 하고 나면 컴파일러가 캐스팅을 수행해줘서 해당 타입으로 사용할 수 있게 되는 것이다.
    • val로 정의 되어야하며 커스텀 접근자를 사용한 것이면 안됨.
  • 원하는 타입으로 명시적으로 캐스팅 하려면 as 를 사용하면 된다.

 

3-6 리팩토링 : if 를 when 으로 변경
// 값을 만들어 내는 if 식
fun eval(e : Expr) : Int =
    if (e is Num) {
        e.value
    } else if (e is Num) {
        eval(e.right) + eval(e.left)
    } else {
        throw IllegalArgumentException(“Unknown expression”)
    }
// 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”)
    }

 

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

 
4-1. while 루프
  • 코틀린의 while, do-while 은 자바와 비슷하다.
4-2. 수에 대한 이터레이션 : 범위와 수열
  • 코틀린은 자바의 for 루프 요소가 없다. 대신 범위를 사용한다.
  • 범위는 기본적으로 두 값으로 이뤄져있으며,  ‘..’ 연산자로 시작과 끝을 연결해 범위를 만든다.
    • ex) val oneToTen = 1..10
  • 어떤 범위에 속한 값을 일정한 순서로 이터레이션 하는 경우를 수열이라고 부른다.
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”)
}
  • .. 연산자는 숫자 타입 뿐 아니라 문자 타입에도 적용 가능
4-4. in으로 컬렉션이나 범위의 원소 검사
  • in 을 사용하여 어떤 값이 범위에 속하는지 검사할 수 있다. 반대의 경우는 !in 을 사용하면 된다.
fun isLetter(c : Char) = c in ‘a’..’z’ || c in ‘A’..‘Z'

 

5. 코틀린의 예외 처리

5-1. try, catch, finally
fun readNumber(reader : BufferedReader) : Int? {
    try {
        val line = reader.readLine()
        return Integer.parseInt(line)
    } 

    catch (e : NumberFormatException) {
        return null
    }

    finally {
        reader.close()
    }
}
  • 자바와 코틀린 차이점
    • 코틀린에는 자바와 달리 throws 절이 코드에 없다.
    • 자바에서는 checked exception을 명시적으로 처리해야 한다.
    • 코틀린은 unchecked exception과 checked exception을 구별하지 않는다.
    • 코틀린에서는 함수가 던지는 예외를 지정하지 않고 발생한 예외를 잡아도 되고, 잡아내지 않아도 된다.
5-2. try를 식으로 사용
fun readNumber(reader : BufferedReader) {
    val number = try {
        Integer.pasreInt(reader.readLine())
    } catch ( e : NumberFormatException) {
        return   
    }
    println(number)
}
  • 코틀린의 try 키워드는 if나 when 과 같이 식이다. 따라서 try 의 값을 변수에 대입할 수 있다.

 

 

Comments