책 정리/코틀린 인 액션

(코틀린 인 액션) 2장 코틀린 기초

긍.응.성 2022. 7. 13. 01:20
반응형

2장 코틀린 기초

2.1 기본 요소: 함수와 변수

Hello World

fun main(args: Array<String>) {
    println("Hello, World!")
}
  • 함수선언시 fun 키워드를 사용한다.
  • 파라미터 이름 뒤에 그 파라미터의 타입을 쓴다.
  • 클래스 안에 함수를 넣어야 할 필요가 없다.
    • 최상위 수준에 함수를 선언 가능하다.
  • 배열도 클래스다.
  • 세미콜론을 붙이지 않아도 된다.

문(statement)과 식(expression)

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

코틀린에서 if 는 식(expression)이다.

식은 값을 만들며, 문은 값을 만들어내지 않는다.

반면 대입문은 자바에서 식이였으나 코틀린에서는 문이다. 그로 인해 if (boolVar = true) 와 같은 실수를 막을 수 있다.

식이 본문인 함수

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

등호와 식으로 이뤄진 함수를 식이 본문인 함수라고 부른다.

코틀린은 정적 타입 지정 언어이지만 식이 본문인 함수의 경우 굳이 사용자가 반환 타입을 적지 않아도 컴파일러가 타입을 추론(type inference)해주기 때문에 반환형을 생략할 수 있다.

본문이 블록이라면 return 문이 여러 개 이므로 반환형을 추론하기 어렵기에 반환형을 명시하여야 한다.

변수와 상수

  • val: 상수 (final)
  • var: 변수

문자열 템플릿

println("Hello, $name!")

println("Hello, ${args[0]}")

println("Hello, ${if (args.size() > 0) args[0] else "someone"}!")

문자열 리터럴의 필요한 곳에 $와 함께 변수를 추가하여 사용가능하다.

중괄호로 변수명을 감싸며 사용하는 것을 권장한다.

2.2 클래스와 프로퍼티

클래스

class Person(val name: String)
  • 값 객체를 간결하게 기술할 수 있다.
  • 기본 기본 가시성이 public이다.

프로퍼티

class Person(
    val name: String,
    var isMarried: Boolean
)

필드와 접근자를 묶어 프로퍼티라고 하며, 코틀린은 프로퍼티를 언어 기본 기능으로 제공한다.

  • val: 읽기전용 프로퍼티(비공개 필드 + 공개 getter)
  • var: 쓸 수 있는 프로퍼티(비공기 필드 + 공개 getter/setter)

아래와 같은 방법으로 프로퍼티의 getter와 setter를 호출할 수 있다.

val person = Person("Bob", false)

println(person.name) // getter

person.isMarried = true // setter

코틀린에서 프로퍼티를 선언하면 그 프로퍼티의 값을 저장하기 위한 필드가 존재한다. 이를 뒷받침하는 필드(backing field)라고 부른다.

커스텀 접근자

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

프로퍼티의 접근자를 직접 작성할 수 있다. 이 프로퍼티의 같은 경우 자신만의 필드를 만들지 않으며, 프로퍼티에 접근할 때마다 게터가 프로퍼티 값을 매번 다시 계산한다.

프로퍼티와 커스텀 게터 방식은 성능 차이는 없고 가독성의 차이만 있다.

디렉터리와 패키지

선언 및 사용 방법은 대부분 자바와 동일하다

package geometry.shape

import java.util.Random
  • 최상위 함수는 그 함수명을 통해 임포트할 수 있다.
  • 자바와 달리 패키지의 구조와 일치하는 디렉터리 구조를 가질 필요가 없다.
  • 여러 클래스를 한 파일에 넣을 수 있다.
    • 자바와의 호환성을 위해 기존을 방식대로 한 파일에 한 클래스를 넣을 수 도 있지만, 관리를 위해서면 여러 클래스를 한 파일에 넣을 수 있다.

2.3 선택 표현과 처리: enum과 when

enum 정의

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
}
  • 상수 목록의 끝엔 상수와 메서드를 구분하기 위해 유일하게 세미콜론을 필수로 붙여야 한다.
  • 일반적인 클래스와 마찬가지로 생성자와 프로퍼티를 선언한다.

when으로 enum 다루기

fun getMnemonic(color: Color) =
    when (color) {
        Color.RED -> "Richard"
        Color.ORANGE -> "Of"
    }

자바의 switch에 해당하는 코틀린의 구성요소는 when이다

  • 코틀린에서 when 도 식이다
  • break가 없어도 된다.
  • 조건이 여러 개일 때 콤마(,)로 이어서 표현한다.
  • defaultelse 로 표현한다.

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

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

코틀린 when의 분기 조건은 임의의 객체를 허용한다.

인자 없는 when 사용

fun mixOptimized(c1: Color, c2: Color) =
    when { // when에 아무인자도 없으면 불필요한 객체 생성을 막을 수 있다.
        // 아무인자도 없을 때는 각 분기의 조건은 Boolean 결과를 계산하는 식이어야한다.
        (c1 == Color.RED && c2 == Color.YELLOW) ||
        (c1 == Color.YELLOW && c2 == Color.RED) -> Color.YELLOW
        else -> throw Exception("Dirty color")
    }

when에 아무 인자가 없는 경우 각 분기의 조건이 불리언 결과를 계산하는 식이어야 한다.

스마트 캐스트

val obj: Any = "The variable obj is automatically cast to a String in this scope"  
if (obj is String) {  
    // 명시적으로 캐스팅 하지 않아도 String 타입처럼 사용가능하다
    // val str = obj as String // as를 이용해 명시적 캐스팅 가능
    println("String length is ${obj.length}")  
}

코틀린의 스마트 캐스트는 타입 검사와 타입 캐스트를 조합한 것이다.

is 는 자바의 instanceOf 처럼 타입을 검사하며 캐스팅해도 안전하다면 자동으로 캐스팅한다.

as를 통해 명시적 캐스팅도 가능하다.

if와 when의 분기에서 블록 사용

앞선 설명과 같이 코틀린의 ifwhen 분기는 식이다. 하지만 블록으로도 선언하여 분기를 처리할 경우, 블록의 마지막 문장이 해당 분기가 반환하는 값이 된다.

val max = if (a > b) {
    print("max value is $a"}
    a
} else {
    print("max value is $b"}
    b
}

블록으로 된 식이라면 블록의 마지막 식이 반환 값이 된다. 단, 함수는 return 으로 반환 값을 명시하기 때문에 블록으로 된 함수에는 이 규칙이 적용되지 않는다.

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

while

자바와 동일하며 whiledo-while 모두 지원한다.

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

for (i in 1..100) {
    print(i)
}

코틀린 for 문은 자바의 for-each 문과 유사하다. for 루프 내부엔 범위를 지정한다.

val oneToTen = 1..10 // 1부터 10까지의 값

.. 연산자를 이용하여 범위를 만든다. 범위는 폐구간(닫힌 구간)이다.

100 downTo 1 step 2 // 100, 98, 96, 94, ..., 4, 2

step 은 범위의 증가 값을 의미한다. downTo 는 범위의 방향이 역방향임을 의미한다.

x in 0 until 100 // 1, 2, 3, ..., 98, 99

until 은 개구간을 의미한다.

맵에 대한 이터레이션

val binaryReps = TreeMap<Char, String>()

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

for ((letter, binary) in binaryReps) {
    println("$letter = $binary")
}
  • .. 연산자는 숫자 타입의 값뿐 아니라 문자 타입의 값에도 적용할 수 있다.
  • put, get[] 연산자로 사용할 수 있다(map[key] = value , map[key])
  • in절로 키와 값을 이터레이션 할 수 있다.
    • map으로부터 in절로 키와 값을 받아오는 것을 구조 분해 구문이라고 한다. 리스트에서도 withIndex() 와 같은 함수를 통해 인덱스와 함께 값을 이터레이션 할 수 있다.

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

in은 범위 외에도 범위에 속하는지 검사하는 연산자로도 사용할 수 있다.

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

Comparable 인터페이스를 구현한 클래스라면in 절을 범위의 원소 검사로 사용할 수 있다.

println("Kotlin" in "Java".."Scala") // true

2.5 코틀린의 예외 처리

방식은 자바와 동일하며 throw가 식이다.

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 절이 코드에 없다. 코틀린에서는 함수가 던지는 예외를 잡아내도 되고, 잡아내지 않아도 된다.
  • try-with-resource 문은 존재하지 않지만 라이브러리 함수 use 를 통해 지원한다.
fun readLineFromFile(path: String): String {
    BufferedReader(FileReader(path)).use { br ->
        return br.readLine()
    }
}

try를 식으로 사용

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

try 도 식이다. try의 값을 변수에 대입할 수 있고, 블록으로 선언한다면 마지막 식이 try 블록 전체의 값이 된다.

반응형