기본 요소: 함수와 변수

Hello, World!

  • 함수를 선언할 때 fun 키워드를 사용한다.
  • 파라미터 이름 뒤에 그 파라미터의 타입을 쓴다.
  • 함수를 최상위 수준에서 정의할 수 있다. 꼭 클래스 안에 함수를 넣어야 할 필요가 없다.
  • 배열도 일반적인 클래스와 마찬가지다. 코틀린에는 자바와 달리배열 처리를 위한 문법이 따로 존재하지 않는다.
  • System.out.println 대신에 println이라고 슨다. 코틀린 표준 라이브러리는 여러 가지 표준 자바 라이브러리 함수를 간결하게 사용할 수 있게 감싼 wrapper를 제공한다.
  • 줄 끝에 세미콜론을 붙이지 않아도 좋다.

함수

  • 코틀린에서 if는 식(statement)이지 문(expression)이 아니다.

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

    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
    
  • 코틀린에서는 식이 본문인 함수가 자주 쓰인다.

  • 식이 본문인 함수의 경우 타입 추론을 이용해, 사용자가 반환 타입을 적지 않아도 식의 결과 타입을 함수 변환 타입으로 정해준다.

    • 타입 추론(type inference): 컴파일러가 타입을 분석해 프로그래머 대신 프로그램 구성 요소의 타입을 정해주는 기능
    • 블록이 본문인 함수는 내용이 긴 함수 일 수 있기 때문에, 어떤 타입의 값을 반환하는지 쉽게 알아볼 수 있도록 반환 타입 명시를 강제한다.

변수

  • 변수 이름 뒤에 타입을 명시하거나 생략하게 허용한다.
  • 변수 선언시 사용하는 키워드 2가지
    • val: 변경 불가능한 참조. 자바의 final 변수에 해당한다. 참조 자체는 불벼일지라도 그 참조가 가리키는 객체 내부의 값은 변경될 수 있다.
    • var: 변경 가능한 참조. 자바의 일반 변수에 해당한다. 변경할 수 있지만 변수의 타입은 고정돼 바뀌지 않는다.
  • 기본적으로 모든 변수를 val 키워들르 사용해 불변 변수로 선언하고, 나중에 꼭 필요할 떄에만 var로 변경하라.
    • 변경 불가능한 참조와 변경 불가능한 객체를 부수 효과가 없는 함수와 조합해 사용하면 코드가 함수형 코드에 가까워진다.

더 쉽게 문자열 형식 지정: 문자열 템플릿

  • 문자열 템플릿(string template)을 이용해 문자열 리터럴 안에 변수를 사용할 수 있다.
  • 복잡한 식도 중괄호를 사용해서 문자열 템플릿 안에 넣을 수 있다.
"Hello, $name"
"Hello, ${if (args.size > 0) args[0] else "someone"}!"

클래스와 프로퍼티

  • 코틀린에서는 필드 대입 로직을 훨씬 더 적은 코드로 작성할 수 있다.
  • 값 객체(value object): 코드가 없이 데이터만 저장하는 클래스
  • 코틀린의 기본 가시성은 public이므로 이런 경우 생략해도 된다.

프로퍼티

  • 프로퍼티: 자바의 필드 + 접근자(getter, setter)
  • 프로퍼티가 val 이면 getter만 만들어내고, var 이면 getter, setter 둘 다 만들어낸다.
  • 프로퍼티의 이름이 is로 시작하면 getter는 get이 붙지않고 원래이름을 그대로 사용하며, 세터에는 is를 set으로 바꾼 이름을 사용한다.
  • 자바에서 선언한 클래스에 대해서 코틀린의 프로퍼티처럼 사용도 가능하다.
  • 뒷팓침하는 필드(backing field): 프로퍼티의 값을 저장하기 위한 필드

커스텀 접근자

  • 커스텀 접근자: backing field가 존재하지 않고 그때그때 계산하는 프로퍼티의 접근자

코틀린 소스코드 구조: 디렉터리와 패키지

  • 같은 패키지에속해 있따면 다른 파일에서 정의한 설정일지라도 직접 사용할 수 있다.
  • 다른 패키지에 정의한 선언을 사용하려면 임포트를 통해 선언을 불러와야 한다.
  • 자바와 달리 코틀린은 패키지 구조와 디렉터리 구조가 일치하지 않아도 된다.
    • 하지만 대부분의 경우 자바와 같이 패키지별로 디렉터리를 구성하는 편이 낫다. 특히 자바와 코틀린을 함꼐 사용하는 프로젝트에서는 자바 클래스를 코틀린 클래스로 마이그레이션할 때 문제가 될 수 있어 일치 시켜야된다.

선택 표현과 처리: enum과 when

enum 클래스 정의

  • enum 클래스 안에 메서드를 정의하는 경우 enum 상수 목록과 메서드 정의 사이에 세미콜론을 넣어야한다.

when으로 enum 클래스 다루기

  • when도 값을 만들어내느 식(expression)이다.

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

  • when 식은 인자 값과 매치하는 조건 값을 찾을 때까지 각 분기를 검사한다.
  • 분기 조건에 있는 객체 사이를 매치할 때 동등성을 사용한다.
enum class Color(
	val r: Int, private val g: Int, private val b: Int
) {
	RED(255, 0, 0), ORANGE(255, 165, 0), YELLOW(255, 255, 0),
	GREEN(0, 255, 0),
	BLUE(0, 0, 255);

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

fun mix(color1:Color, color2:Color): Color {
	return when(setOf(color1, color2)) {
		setOf(Color.RED, Color.GREEN) -> Color.ORANGE
		setOf(Color.YELLOW, Color.BLUE) -> Color.GREEN
		else -> throw Exception("not found")
	}
}

인자 없는 when 사용

  • when에 인자가 없으면 각 분기의 조건이 불리언 결과를 계산하는 식이어야 한다.
fun mix2(color1: Color, color2: Color) =
	when {
		(color1 == Color.RED && color2 == Color.GREEN) ||
			(color1 == Color.GREEN && color2 == Color.RED) ->
			Color.ORANGE

		else -> throw Exception("not found")
	}

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

  • 자바에서 어떤 변수의 타입을 instanceof로 확인한 다음에 그 타입에 속한 멤버에 접근하기 위해서는 명시적으로 변수 타입을 캐스팅해야 한다.
  • 스마트 캐스트: 변수가 원하는 타입인지 일단 is로 검색하고 나면 굳이 변수를 원하는 타입으로 캐스팅하지 않아도 마치 처음부터 그 변수가 원하는 타입으로 선언된 것처럼 사용할 수 있다.
    • 실제로는 컴파일러가 캐스팅을 수행해준다.
  • 스마트 캐스트는 is로 변수에 든 값을 타입을 검사한 다음에 그 값이 바뀔 수 없는 경우에만 작동한다.
    • 클래스의 프로퍼티에 대해 스마트 캐스트를 사용한다면 그 프로퍼티는 반드시 val이어야 하며, 커스텀 접근자를 사용하지 않는 경우여야한다.
    • 프로퍼티에 대한 접근이 항상 같은 값을 내놓는다고 확신할 수 없기 때문이다.

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

  • when 식을 받은 값의 타입을 검사하는 기능에도 사용할 수 있다.
  • if나 when이 블록인 경우 마지막 문장이 블록 전체의 결과가 된다.
    • 블록이 값을 만들어내야 하는 경우 항상 이 규칙이 성립한다. 하지만, 함수에 대해서는 성랍하지 않는다. 함수는 내부에 return 문이 있어야 한다.

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

while 루프

  • while과 do-while은 자바와 다르지 않다.

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

  • 코틀린에서는 자바의 for 루프가 없고, 범위(range)를 사용한다.

  • for .. in 루프를 자주 사용한다.

  • 폐구간(닫힌 구간)

    val oneToTen = 1..10
    
  • 100부터 1까지(폐구간) 짝수만

    val evenFromHundredToOne = 100 downTo step 2
    
  • 반만 닫힌 범위(half-closed range)

    val halfClosedRange = 0 until 10 // 10은 포함되지 않음
    

맵에 대한 이터레이션

  • 문자 범위도 가능하다.

    val aToZ = 'a'..'z'
    
  • 맵에 대한 이터레이션

    val binaryReps = TreeMap<Char, String>()
    // ...
    
    for ((letter, binary) in binaryReps) {
      println("$letter = $binary")
    }
    
  • 인덱스와 함께 컬렉션 이터레이션

    val list = arrayListOf("10", "11", "1001")
    
    for ((index, element) in list.withIndex()) {
      println("$index: $element")
    }
    

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

  • in 연산자를 사용해 어떤 값이 범위에 속하는지 검사할 수 있다. 반대로 !in을 사용하면 어떤 값이 범위에 속하지 않는지 검사할 수 있다.

  • when 식에서 사용해도 된다.

    fun inWithWhen(c: Char) = when(c) {
    	in '0'..'9' -> "It's a digit!"
    	in 'a'..'z' -> "It's a letter!"
    	else -> "I don't know..."
    }
    
  • Comparable 인터페이스를 구현한 클래스라면 in을 통한 범위 검사를 할 수 있다.

코틀린의 예외 처리

try, catch, finally

  • Kotlin은 checked exception과 unchecked exception을 따로 구별하지 않는다.
  • 코틀린에서는 함수가 던지는 예외를 지정하지 않고 발생한 예외를 잡아내도되고 잡아내지 않아도 된다.
  • 코틀린은 try-with-resource를 제공하지 않는다. 라이브러리 함수로 같은 기능을 구현할 수는 있다.

try를 식으로 사용

  • try나 catch의 마지막 식의 값이 해당 블록의 결과 값이다.

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