[Kotlin In Action] 2. 코틀린 기초

[Kotlin In Action] 2. 코틀린 기초

#1. 함수

Hello, world!

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

  • 함수를 선언할 때 fun 키워드를 사용한다.
  • 변수를 선언할 때나 파라미터 이름 뒤에 타입을 쓴다.
  • 함수를 최상위 수준에 정의할 수 있다. 클래스 안에 넣어야 할 필요가 없다.
  • 배열도 일반적인 클래스와 마찬가지다.
  • 세미콜론(;)을 붙이지 않아도 된다.

statement(문)과 expression(식)의 구분

  • 식: 값을 만들어내며, 다른 식의 하위요소로 계산에 참여할 수 있다.
  • 문: 자신을 둘러싸고 있는 가장 안쪽 블럭의 최상위 요소로 존재하여 아무런 값을 만들어내지 않는다.

자바에서는 모든 제어구조가 ‘문’인 반면, 코틀린에서는 루프를 제외한 대부분의 제어구조가 ‘식’이다.

식이 본문인 함수 (expression body function)

// 블럭이 본문인 함수
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
// 반환 타입 생략도 가능하다.
fun max(a: Int, b: Int) = if (a > b) a else b

식이 본문인 함수는 반환 타입을 적지 않아도 컴파일러가 함수 본문 식을 분석하여 함수 반환 타입을 정해 준다. -> 타입 추론

#2. 변수

val question = "삶, 우주, 그리고 모든 것에 대한 궁극적인 질문"
val answer = 42 //Int 타입으로 타입 추론
val yearToCompute = 7.5e6 //Double 타입으로 타입 추론

초기화 식을 사용하지 않고 변수를 선언하려면 변수 타입을 반드시 명시해야 한다.

val answer: Int
answer = 42

변경 가능한 / 변경 불가능한 변수

  • val (value): 변경 불가능한 참조를 저장하는 변수 - 자바의 final 변수
  • var (variable): 변경 가능한 참조 - 자바의 일반 변수

기본적으로는 모든 변수를 val로 선언하고, 나중에 꼭 필요할 때에만 var로 변경하는 것이 좋다.

val 변수는 한 번만 초기화돼야 한다.
조건에 따라 val 값을 다른 여러 값으로 초기화할 수도 있다.

val message: String
if (canPerformOperation()) {
  message = "Success"
} else {
  message = "Failed"
}

⭑ val 참조 자체는 불변일지라도, 그 참조가 가리키는 객체 내부 값은 변경될 수 있다.

val languages = arrayListOf("Java") //불변 참조 선언
language.add("Kotlin") //참조가 가리키는 객체 내부 변경

var 변수의 ‘값’은 변경할 수 있지만 변수의 ‘타입’은 고정된다.

var answer = 42
answer = "no answer" //Error: type mismatch

#3. 문자열 템플릿

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

=> 자바의 (“Hello” + name + “!”)과 동일한 기능

복잡한 식은 {}로 둘러싸서 문자열 템플릿 안에 넣을 수 있다.

fun main(args: Array<String>) {
  if (args.size > 0) {
    println("Hello, ${args[0]}!")
  }
}

#4. 프로퍼티

자바에서는 데이터를 필드에 저장하며, 필드의 가시성은 기본 비공개(private)이다.
데이터에 접근하는 통로로 쓸 수 있는 접근자 메서드(보통 Getter)를 제공하고, 필드를 변경할 Setter를 추가 제공할 수 있다.

자바에서는 필드와 접근자를 묶어 프로퍼티라고 부른다.
코틀린은 프로퍼티를 언어 기본 기능으로 제공하며, 자바의 필드와 접근자 메서드를 완전히 대신한다.
프로퍼티 선언은 val이나 var을 사용한다.

class Person(
  val name: String, //읽기 전용, (비공개) 필드와 (공개) Getter 생성됨
  var isMarried: Boolean //변경 가능, (비공개) 필드 / (공개) Getter / (공개) Setter 생성됨
)

코틀린은 값을 저장하기 위한 비공개 필드와 그 필드에 대한 Setter, Getter로 이뤄진 간단한 디폴트 접근자 구현을 제공한다.

val person = Person("Bob", true) //new 키워드없이 생성자 호출
println(person.name) //프로퍼티 이름을 직접 사용해도 코틀린이 자동으로 Getter를 호출한다.
Backing field (뒷받침하는 필드)
프로퍼티의 값을 저장하기 위한 필드

프로퍼티의 값을 그때그때 계산하기 원한다면 Custom Getter를 작성하면 된다.

커스텀 접근자

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

-> 프로퍼티에 접근할 때마다 Getter가 프로퍼티의 값을 매번 다시 계산한다.

• 파라미터가 없는 함수 정의 vs. 커스텀 Getter 정의
구현이나 성능은 비슷, 차이점은 가독성
일반적으로 클래스의 특성을 정의하고 싶다면, 프로퍼티로 정의해야 한다.

#5. enum

코틀린에서 enum은 soft keyword -> class 앞에 있을 때를 제외하면 다른 이름에 쓰일 수 있다.
(cf. keyword: 변수 등 이름에 사용 불가능, 예 - class)

enum class Color(
  val r: Int, val g: Int, val b: Int
) {
  RED(255, 0, 0), ORANGE(255, 165, 0),
  YELLOW(255, 255, 0), GREEN(0, 255, 0), BLUE(0, 0, 255),
  INDIGO(75, 0, 130), VIOLET(238, 130, 238); //여기서는 반드시 세미콜론을 사용해야 한다.

  fun rgb() = (r * 256 + g) * 256 + b //메서드 정의
}
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"
  }
  • when : if와 마찬가지로 when도 값을 만들어내는 ‘식’이다.
    • 자바와 달리 각 분기의 끝에 break를 넣지 않아도 된다.
    • 한 분기 안에서 여러 값을 매치 패턴으로 사용할 수도 있다. (값 사이 ‘,’ 사용)
    • 인자로 아무 객체나 올 수 있다.
    • 인자없이 호출할 수도 있다.

#6. 스마트 캐스트: 타입 검사와 타입 캐스트를 조합

예제: 덧셈 산술식 계산 함수 식을 트리구조로 저장

  • 어떤 식이 수라면 그 값을 반환한다.
  • 어떤 식이 합계라면 좌항과 우항의 값을 계산한 다음 그 두 값을 합한 값을 반환한다.
interface Expr
class Num(val value: Int) : Expr
class Sum(val left: Expr, val right: Expr) : Expr

코틀린에서는 is를 사용해 변수 타입을 검사한다.
is로 검사하면 컴파일러가 캐스팅 해준다. -> 스마트 캐스트

  • 스마트 캐스트를 사용하려면 val이어야 한다.
  • 명시적으로 타입 캐스팅하려면 as 키워드를 사용한다.

if 사용

fun eval(e: Expr): Int =
  if (e is Num) {
    e.value //if 분기에 블럭을 사용하는 경우, 그 블럭의 마지막 식이 그 분기의 결과 값이다.
  } 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")
  }
  • 블럭의 마지막 식이 블럭의 결과 규칙
    • 함수에 대해서는 성립하지 않는다.
    • 식이 본문인 함수는 블럭을 본문으로 가질 수 없고, 블럭이 본문인 함수는 내부에 return문이 반드시 있어야 한다.

#7. 이터레이션

while 루프

while, do-while -> 자바와 같다.

while (조건) {
  ...
}
do {
  ...
} while (조건)

수에 대한 이터레이션

  • 범위 (range): 두 값으로 이루어진 구간
    • .. 연산자로 시작 값과 끝 값을 연결해서 범위를 만든다. (폐구간)
    • 문자 타입의 값에도 적용할 수 있다.
val oneToTen = 1..10


for (i in 100 downTo 1 step 2) { //100부터 1까지 2씩 줄여가며 반복
  ...
}

끝 값을 포함하지 않는 범위 -> until 사용

(x in until size)
//equals
(x in 0..size-1)

맵에 대한 이터레이션

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: 어떤 값이 범위에 속하는지 검사
  • !in: 범위에 속하지 않는지 검사

#8. 코틀린의 예외 처리

fun readNumber(reader: BufferedReader): Int? { //throws 명시할 필요가 없다.
    try {
        val line = reader.readLine()
        return Integer.parseInt(line)
    }
    catch (e: NumberFormatException) {
        return null
}
finally {
        reader.close()
    }
}

코틀린은 체크 예외와 언체크 예외를 구별하지 않는다.
try도 ‘식’이므로, try 값을 변수에 대입할 수 있다. 마지막 식의 값이 전체 결과 값이다.
if와 달리 try는 항상 {}로 감싸야 한다.

Comments