2 코틀린 기초.md
함수, 변수, 클래스, enum, 프로퍼티를 선언하는 방법
제어 구조
스마트 캐스트
예외 던지기와 예외 잡기
변수, 함수, 클래스, 프로퍼티, 제어 구조, 스마트 캐스트 (타입 검사와 타입 캐스트, 타입 강제 변환을 하나로 엮은 기능), 예외처리
2.1 기본 요소: 함수와 변수
2.1.1 Hello, World!
fun main(args: Array<String>) {
println("Hello, world!")
}
함수 선언할 때 fun 키워드 사용
파라미터 이름 뒤에 타입을 쓴다.
함수를 최상위 수준에 정의할 수 있다. 클래스 안에 함수를 넣을 필요 없음.
배열도 일반적인 클래스처럼. 배열 처리를 위한 문법이 따로 존재하지 않음
System.out.prinln 대신 println. 코틀린은 표준 자바 라이브러리 함수를 간결하게 사용할 수 있게 감싼 래퍼들을 제공함.
세미콜론 붙이지 않음
2.1.2 함수
REPL 에서 실습
$ kotlinc
>>> fun max(a: Int, b: Int): Int {
... return if (a > b) a else b
... }
>>> println(max(1, 2))
2
문(statement)과 식(expression)의 구분
코틀린에서 if 는 식이지 문이 아니다.
식은 값을 만들어 내며 다른 식의 하위 요소로 계산에 참여할 수 있음
문은 자신을 둘러싸고 있는 가장 안쪽 블록의 최상위 요소로 존재하며 아무런 값을 만들어내지 않는다
반면 대입문은 자바에서는 식이었으나 코틀린에서는 문
식이 본문인 함수
fun max(a: Int, b: Int): Int = if (a > b) a else b
본문이 중괄호로 둘러싸인 함수를 블록이 본문인 함수라 부름
등호와 식으로 이뤄진 함수를 식이 본문인 함수라 부름
인텔리J 아이디어 팁. 이 두 방식의 함수를 서로 변환하는 메뉴가 있음.
반환 타입을 생략
fun max(a: Int, b: Int) = if (a > b) a else b
타입 추론을 해 주기 때문.
type inference: 컴파일러가 타입을 분석해 프로그래머 대신 프로그램 구성 요소의 타입을 정해주는 기능
식이 본문인 함수의 반호나 타입만 생략 가능하다는 점에 유의
블록이 본문인 함수가 값을 반환한다면 반드시 반환 타입을 지정하고 return 문을 사용해 반환 값을 명시해야 함.
2.1.3 변수
자바는 타입이 맨 앞에 온다. (타입을 생략할 경우 식과 변수 선언을 구별할 수 없다.)
코틀린에서는 타입 지정을 생략하는 경우가 흔함. 키워드로 변수 선인 시작. 변수 이름 뒤에 타입을 명시하거나 생략하게 허용.
val question = "삶, 우주, 그리고 모든 것에 대한 궁극적인 질문" val answer = 42
원한다면 타입을 명시해도 됨
val answer: Int = 42
부동소수점 상수를 사용하면 변수 타입은 Double 이 됨
val yearsToComputer = 7.5e6 // 7.5 x 10^6 = 7500000.0
초기화 식을 사용하지 않고 변수 선언하려면 변수 타입 반드시 명시
val answer: Int
answer = 42
초기화 식이 없다면 컴파일러가 타입을 추론할 수 없기 때문에 반드시 지정
변경 가능한 변수와 변경 불가능한 변수
val(value) - 변경 불가능한(immutable) 참조를 저장하는 변수. 자바의 final 변수
var(variable) - 변경 가능한(mutable) 참조. 자바의 일반 변수
variable이 형용사로 쓰이면 '변할 수 있는'이라는 뜻. 변수 말 자체가 이미 변화라는 개념이 내재돼 있음. 함수형 프로그래머 중에는 변경 불가능한 변수를 표현하기 위해 변수라는 단어 대신 '값'이나 '이름'이라는 단얼르 사용하기도 함. 문맥에 따라 '값'과 '이름'을 혼용하기도 함.
val 을 기본으로 하고 필요할 때에만 var 로 변경
val 변수는 한번만 초기화돼야 함
val message: String
if (canPerforOperation()) {
message = "Success"
// ...
}
else {
message = "Failed"
}
val 참조 자체는 불변일지라도 그 참조가 가리키는 객체의 내부 값은 변경될 수 있다
val languages = arrayListOf("Java") // 불변 참조 선언 languages.add("Kotlin") // 참조가 가리키는 객체 내부 변경
var 키워드 사용하면 변수의 값을 변경할 수 있지만 변수의 타입은 고정돼 바뀌지 않음.
아래는 컴파일 오류. Error: type mismatch
var answer = 42 answer = "no answer"
어떤 타입의 변수에 다른 타입의 값을 저장하고 싶다면 변환 함수를 써서 값을 변수의 타입으로 변환하거나, 값을 변수에 대입할 수 있는 타입으로 강제 형 변환(coerce)해야 한다. 원시 타입의 변환에 대해서는 6.2.3절에서 다룸
2.1.4 더 쉽게 문자열 형식 지정: 문자열 템플릿
fun main(args: Array<String>) {
val name = if (args.size > 0) args[0] else "Kotlin"
println("Hello, $name!")
}
name 이라는 변수를 선언하고 문자열 리터럴 안에서 그 변수를 사용. 변수 앞에 $ 를 추가
자바의 문자열 접합 연산 ("Hello, " + name + "!")과 동일한 기능을 하지만 좀 더 간결
$ escape 하려면 $ 하면 됨
println("\$x")
복잡한 식도 중괄호({})로 둘러싸서 문자열 템플릿 안에 넣을 수 있음.
fun main(args: Array<String>) {
if (args.size > 0) {
println("Hello, ${args[0]}!")
}
}
한글을 문자열 템플릿에서 사용할 경우 주의할 점
변수 이름에 한글이 들어갈 수 있음.
'$name님' unresolved reference 오류. ${name} 처럼 중괄호로 감싸는게 좋음
중괄호 식 안에서 큰 따옴표 사용 가능
fun main(args: Array<String>) { println("Hello, ${if (args.size > 0) args[0] else "someone"}!") }
3.5절에서 더 많이.
2.2 클래스와 프로퍼티
리스트 2.3 간단한 자바 클래스 Person
public class Person { private final String name; public Person(String name) { this.name = name; } public String getName() { return name; } }
필드가 들어나면 생성자의 파라미터가 늘어나고 대입문의 수도 늘어남.
코틀린에서는 훨씬 더 적은 코드로 작성할 수 있음.
코틀린으로 변환
class Person(val name: String)
2.2.1 프로퍼티
클래스라는 개념의 목적은 데이터를 캡슐화(encapsulate)라고 캡슐화한 데이털르 다루는 코드를 한 주체 아래 가두는 것
자바에서는 데이터를 필드(field)에 저장하며, 멤버 필드의 가시성은 보통 비공개(private)
클래스는 자신을 사용하는 클라이언트가 그 데이터에 접근한느 통로로 쓸 수 있는 접근자 메소드(accessor method)를 제공함.
읽기 위한 게터(getter), 변경할 경우 세터(setter)를 추가 제공
자바에서는 필드와 접근자를 한데 묶어 프로퍼티(property)라고 부름. 프로퍼티라는 개념을 활용하는 프레임워크가 많음. (Lombok 등)
코틀린은 프로퍼티를 언어 기본 기능으로 제공. 코틀린 프로퍼티는 자바의 필드와 접근자 메소드를 완전히 대신함.
class Person( val name: String, // 비공개 필드, 공개 게터 만들어냄 var isMarried: Boolean // 비공개 필드, 공개 게터, 공개 세터 만들어냄 )
리스트 2.6 자바에서 Person 클래스를 사용하는 방법
>>> Person person = new Person("Bob", true); >>> System.out.println(person.getName()); Bob >>> System.out.println(person.isMarried()); true
리스트 2.7 코틀린에서 Person 클래스 사용하기
>>> val person = Person("Bob", true) // new 키워드 안씀 >>> println(person.name) // 코틀린이 자동으로 게터를 호출 Bob >>> println(person.isMarried) true
2.2.2 커스텀 접근자
class Rectangle(val height: Int, val width: Int) {
val isSquare: Boolean
get() {
return height == width
}
}
get() = height == width 라고 해도 됨.
>>> val rectangle = Rectangle(41, 43)
>>> println(rectangle.isSquare)
false
파라미터가 없는 함수를 정의하는 방식과 커스텀 게터를 정의하는 방식. 두 방식 모두 비슷. 구현이나 성능상 차이는 없음. 차이는 가독성. 클래스의 특성을 정의하고 싶다면 프로퍼티로 그 특성을 정의해야 함.
2.2.3 코틀린 소스코드 구조: 디렉터리와 패키지
리스트 2.8 클래스와 함수 선언을 패키지에 넣기
pcakge geometry.shapes import java.util.Random class Rectangle(val height: Int, val width: Int) { val isSquare: Boolean get() = height == width } fun createRandomRectangle(): Rectangle { val random = Random() return Rectangle(random.nextInt(), random.nextInt()) }
리스트 2.9 다른 패키지에 있는 함수 임포트하기
package geometry.example import geometry.shapes.createRandomRectangle fun main(args: Array<String>) { println(createRandomRectangle().isSquare) }
2.3 선택 표현과 처리: enum과 when
2.3.1 enum 클래스 정의
리스트 2.10 간단한 enum 클래스 정의하기
enum class Color { RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET }
리스트 2.11 프로퍼티와 메소드가 있는 enum 클래스 선언하기
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
}
>>> println(Color.BLUE.rgb())
255
2.32 when으로 enum 클래스 다루기
리스트 2.12 when을 사용해 올바른 enum 값 찾기
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"
}
>>> println(getMnemonic(Color.BLUE))
Battle
리스트 2.13 한 when 분기 안에 여러 값 사용하기
fun getWarmth(color: Color) = when(color) {
Color.RED, Color.ORANGE, Color.YELLOW -> "warm"
Color.GREEN -> "neutral"
Color.BLUE, Color.INDIGO, Color.VIOLET -> "cold"
}
>>> println(getWarmth(Color.ORANGE))
warm
리스트 2.14 enum 상수 값을 임포트해서 enum 클래스 수식자 없이 enum 사용하기
import ch02.colors.Color
import ch02.colors.Color.*
fun getWarmth(color: Color) = when(color) {
RED, ORANGE, YELLOW -> "warm"
GREEN -> "neutral"
BLUE, INDIGO, VIOLET -> "cold"
}
2.3.3 when과 임의의 객체를 함꼐 사용
리스트 2.15 when의 분기 조건에 여러 다른 객체 사용하기
fun mix(c1: Color, c2: Color) = when (setOf(c1, c2)) { setOf(RED, YELLOW) -> ORANGE setOf(YELLOW, BLUE) -> GREEN setOf(BLUE, VIOLET) -> INDIGO else -> throw Exception("Dirty color") } >>> println(mxi(BLUE, YELLOW)) GREEN
2.3.4 인자 없는 when 사용
리스트 16 인자가 없는 when
fun mixOptimized(c1: Color, c2: Color) = when { (c1 == RED && c2 == YELLOW) || (c1 == YELLOW && c2 == RED) -> ORANGE (c1 == YELLOW && c2 == BLUE) || (c1 == BLUE && c2 == YELLOW) -> GREEN (c1 == BLUE && c2 == VIOLET) || (c1 == VIOLET && c2 == BLUE) -> INDIGO else -> throw Exception("Dirty color") } >>> println(mixOptimized(BLUE< YELLOW)) GREEN
when 에 아무 인자도 없으려면 각 분기의 조건이 불리언 결과를 계산한느 식이어야 함.
mixOptimized 함수는 앞에서 살펴본 mix 함수와 같은 일을 함.
mixOptimized는 추가 객체를 만들지 않는다는 장점이 있지만 가독성은 더 떨어짐
이제 when을 사용하는 과정에서 스마트 캐스트가 쓰이는 예를 살펴보자
2.3.5 스마트 캐스트: 타입 검사와 타입 캐스트를 조합
리스트 2.17 식을 표현하는 클래스 계층
interface Expr class Num(val value: Int) : Expr class Sum(val left: Expr, val right: Expr) : Expr
>>> println(eval(Sum(Sum(Num(1), Num(2)), Num(4)))) 7
리스트 2.18 if 연쇄를 사용해 식을 계산하기
fun eval(e: Expr): Int { if (e is Num) { val n = e as Num // Num 으로 타입 변환하는데 불필요한 중복임 return n.value } if (e is Sum) { return eval(e.right) + eval(e.left) } throw IllegalArgumentException("Unknown expression") } >>> println(eval(Sum(Sum(Num(1), Num(2)), Num(4)))) 7
if (e is Sum) {
return eval(e.right) + eval(e.left)
}
그림 2.5 IDE는 배경색으로 스마트 캐스트를 표시해준다. (e.right, e.left 의 e 가 강조 표시)
2.3.6 리팩토링: if를 when으로 변경
리스트 2.19 값을 만들어내는 if 식
fun eval(e: Expr): Int = if (e is Num) { e.value } else if (e is Sum) { eval(e.right) + eval(e.left) } else { throw IllegalArgumentException("Unknown expression") } >>> println(eval(Sum(Num(1), Num(2)))) 3
if의 분기에 식이 하나밖에 없다면 중괄호를 생략해도 됨. if 분기에 블록을 사용하는 경우 그 블록의 마지막 식이 그 분기의 결과 값임.
리스트 2.20 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") }
2.3.7 if와 when의 분기에서 블록 사용
리스트 2.21 분기에 복잡한 동작이 들어가 있는 when 사용하기
fun evalWithLogging(e: Expr): Int = when (e) { is Num -> { println("num: ${e.value}") e.value } is Sum -> { val left = evalWithLogging(e.left) val right = evalWithLogging(e.right) println("sum: $left + $right") left + right } else -> throw IllegalArgumentException("Unknown expression") }
2.4 대상을 이터레이션: while과 for 루프
2.4.1 while 루프
while (조건) {
}
do {
} while (조건)
2.4.2 수에 대한 이터레이션: 범위와 수열
리스트 2.22 when을 사용해 피즈버즈 게임 구현하기
```kotlin
fun fizzBuzz(i: Int) = when {
i % 15 == 0 -> "FizzBuzz "
i % 3 == 0 -> "Fizz "
i % 5 == 0 -> "Buzz "
else -> "$i "
}
for (i in 1..100) { ... print(fizzBuzz(i)) ... }
### 2.4.3 맵에 대한 이터레이션
* 리스트 2.24 맵을 초기화하고 이터레이션하기
```kotlin
val binaryReps = TreeMap<Char, String>()
for (c in 'A'..'F') {
val binary = Integer.toBinaryString(c.toInt()) // 아스키코드를 2진 표현으로 바꾼다.
binaryReps[c] = binary
}
for ((letter, binary) in binaryReps) {
println("$letter = $binary")
}
binaryReps[c] = binary
라는 코드는
binaryReps.put(c, binary)
라는 자바 코드와 같다.
출력
A = 1000001
B = 1000010
C = 1000011
D = 1000100
E = 1000101
F = 1000110
val list = arrayListOf("10", "11", "1001")
for ((index, element) in list.withIndex()) {
println("$index: $element")
}
0: 10
1: 11
2: 1001
withIndex의 정체에 대해서는 3장에서 살펴본다.
2.4.4 in으로 컬렉션이나 범위의 원소 검사
리스트 2.25
fun isLetter(c: Char) = c in 'a'..'z' || c in 'A'..'Z' fun isNotDigit(c: Char) = c !in '0'..'0'
>>> println(isLetter('q'))
true
>>> println(isNotDigit('x'))
true
c in 'a'..'z' // 'a' <= c && c <= 'z'로 변환된다.
리스트 2.26
fun recognize(c: Char) = when (c) { in '0'..'9' -> "It's a digit!" in 'a'..'z', in 'A'..'Z' -> "It's a letter!" else -> "I don't kwno..." }
>>> println(recognize('8')) It's a digit!
println("Kotlin" in "Java".."Scala") // true. "Java" <= "Kotlin" && "Kotlin" <= "Scala"
println("Kotlin" in setOf("Java", "Scala")) // false. "Java" <= "Kotlin" && "Kotlin" <= "Scala"
2.5 코틀린의 예외 처리
예외를 던지는 방법
if (percentage !in 0..100) { throw IllegalArgumentException( "A percentage value must be between 0 and 100: $percentage") }
val percentage =
if (number in 0..100)
number
else
throw IllegalArgumentException( // "throw"는 식이다.
"A percentage value must be between 0 and 100: $percentage")
자바와 달리 코틀린의 throw는 식이므로 다른 식에 포함될 수 있다.
p282 활용사례
```
fun fail(message: String): Nothing {
throw IllegalStateException(message)
}
var address = company.address ?: fail("No_address") println(address.city)
* Nothing 이라는 특별한 반환 타입
* 엘비스 연산자의 우항에 사용해서 전제 조건(precondition)을 검사할 수 있음.
* 만약 자바 였다면 address 에 오류값을 넣고 address 가 오류값이면 fail 함수를 호출하는 코드를 더 작성해야 한다.
### 2.5.1 try, catch, finally
* 리스트 2.27 자바와 마찬가지로 try 사용하기
```kotlin
fun readNumber(reader: BufferedReader): Int? {
try {
val line = reader.readLine()
return Integer.parseInt(line)
}
catch (e: NumberFormatException) {
return null
}
finally {
reader.close()
}
}
>>> val reader = BufferedReader(StringReader("239"))
>>> println(readNumber(reader))
239
2.5.2 try를 식으로 사용
리스트 2.28 try를 식으로 사용하기
fun readNumber(reader: BufferedReader) { val number = try { Integer.parseInt(reader.readLine()) } catch (e: NumberFormatException) { return } println(number) }
>>> val reader = BufferedReader(StringReader("not a number"))
>>> readNumber(reader)
아무것도 출력되지 않는다.
리스트 2.29 catch에서 값 반환하기
fun readNumber(reader: BufferedReader) { val number = try { Integer.parseInt(reader.readLine()) } catch (e: NumberFormatException) { null } println(number) }
>>> val reader = BufferedReader(StringReader("not a number"))
>>> readNumber(reader)
예외가 발생했으므로 함수가 "null"을 출력한다.
2.6 요약
함수를 정의할 때 fun 키워드 사용. val과 var는 각각 읽기 전용 변수와 변경 가능한 변수 선언할 때 쓰임
문자열 템플릿을 사용하면 문자열을 연결하지 않아도 되므로 코드가 간결해진다. 변수 이름 앞에 $를 붙이거나, 식을 ${식}처럼 ${}로 둘러싸면 변수나 식의 값을 문자열 안에 넣을 수 있다.
코틀린에서는 값 객체 클래스를 아주 간결하게 표현할 수 있다.
다른 언어에도 있는 if는 코틀린에서 식이며, 값을 만들어낸다.
코틀린 when은 자바의 switch와 비슷하지만 더 강력하다.
어떤 변수의 타입을 검사하고 나면 굳이 그 변수를 캐스팅하지 않아도 검사한 타입의 변수처럼 사용할 수 있다. 그런 경우 컴파일러가 스마트 캐스트를 활용해 자동으로 타입을 바꿔준다.
for, while, do-while 루프는 자바가 제공하는 같은 키워드의 기능과 비슷하다. 하지만 코틀린의 for는 자바의 for보다 더 편리. 특히 맵을 이터레이션하거나 이터레이션하면서 컬렉션의 원소와 인덱스를 함께 사용해야 하는 경우 코틀린의 for가 더 편리
1..5와 같은 식은 범위를 만들어낸다. 범위와 수열은 코틀린에서 같은 문법을 사용하며, for 루프에 대해 같은 추상화를 제공. 어떤 값이 범위 안에 들어 있거나 들어있지 않은지 검사하기 위해 in이나 !in을 사용
코틀린 예외 처리는 자바와 비슷. 다만 코틀린에서는 함수가 던질 수 있는 예외를 선언하지 않아도 됨.
Last updated
Was this helpful?