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가 없어도 된다.
- 조건이 여러 개일 때 콤마(
,
)로 이어서 표현한다. default
는else
로 표현한다.
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의 분기에서 블록 사용
앞선 설명과 같이 코틀린의 if
와 when
분기는 식이다. 하지만 블록으로도 선언하여 분기를 처리할 경우, 블록의 마지막 문장이 해당 분기가 반환하는 값이 된다.
val max = if (a > b) {
print("max value is $a"}
a
} else {
print("max value is $b"}
b
}
블록으로 된 식이라면 블록의 마지막 식이 반환 값이 된다. 단, 함수는 return
으로 반환 값을 명시하기 때문에 블록으로 된 함수에는 이 규칙이 적용되지 않는다.
2.4 대상을 이터레이션: while과 for 루프
while
자바와 동일하며 while
과 do-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()
와 같은 함수를 통해 인덱스와 함께 값을 이터레이션 할 수 있다.
- map으로부터 in절로 키와 값을 받아오는 것을 구조 분해 구문이라고 한다. 리스트에서도
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
블록 전체의 값이 된다.