기본 요소: 함수와 변수
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) }