devseop08 님의 블로그
[Basic] 1. 코틀린 기초 본문
2.1 기본 요소 : 함수와 변수
- 코틀린이 어떻게 변경 가능한 데이터보다 변경할 수 없는 불변 데이터 사용을 권장하는지와 왜 불변 데이터가 더 좋은 것인지 살펴본다.2.1.1 첫 번째 코틀린 프로그램 작성 : Hello, World!
fun main(){
println("Hello, world!")
}
- 함수 선언 시, fun 키워드를 선언한다.
- 함수를 모든 코틀린 파일의 최상위 수준에 정의할 수 있으므로 클래스 안에 함수를 넣어야 할 필요는 없다.
- 최상위에 있는 main 함수를 어플리케이션의 진입점으로 지정할 수 있다.
- 코틀린 표준 라이브러리는 수많은 표준 자바 라이브러리 함수에 대해 더 간결한 구문을 사용할 수 있게 해주는 래퍼를 제공한다.
- println도 래퍼 중 하나이다.
2.1.2 파라미터와 반환값이 있는 함수 선언
- 코틀린 함수의 기본 구조
fun max(a : Int, b: Int) : Int {
return if (a > b) a else b
}
- 코틀린에서 if는 식이지 문이 아니다. 코틀린 if 식은 자바의 삼항 연산자와 비슷하다.
- 식은 값을 만들어내며, 다른 식의 하위 요소로 계산에 참여할 수 있다.
- 코틀린에 루프(for, while, do/while)를 제외한 대부분의 제어 구조가 식이라는 점이 자바와 다른 점이다.
- 함수 호출
fun main(){
println(max(1, 2))
// 2
}
- 코틀린에서는 대입을 항상 문(구문)으로 취급한다. 이는 값을 변수에 대입해도 그 대입 연산 자체는 아무값도 돌려주지 않는다는 뜻이다.
- 대입이 항상 문으로 취급되면, 대입 연산과 비교 연산 시의 혼동을 피할 수 있다. 이런 혼동은 자바나 C/C++과 같이 대입을 식으로 취급하는 언어에서 자주 발생하는 버그의 원인이다.
val number : Int
val alsoNumber = i = getNumber()
// 컴파일 에러
// ERROR : Assignments are not expressions
2.1.3 식 본문을 사용해 함수를 더 간결하게 정의
- max 함수 본문이 if 식 하나로만 이뤄져 있기 때문에 이 식을 함수 본문 전체로 하고 중괄호를 없앤 후 return을 제거할 수 있다.
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 // 반환 타입 생략 가능
- 코틀린은 정적 타입 지정 언어이므로, 컴파일 시점에 모든 식의 타입을 지정해야 하지 않는가?
- 실제로 모든 변수나 모든 식에는 타입이 있으며, 모든 함수는 반환 타입이 정해져야 한다.
- 단, 식 본문 함수의 경우에는, 굳이 사용자가 반환 타입을 지정하지 않아도, 컴파일러가 함수
본문 식을 분석해서 식의 결과 타입을 함수 반환 타입으로 정해준다. - 이러한 분석을 타입 추론이라 한다.
- 블록 본문 함수의 경우에는, 반드시 반환 타입을 지정하고, return 문을 사용해 반환값을 명시해야 한다.
- 블록 본문 함수가 긴 경우, return 문이 여럿 들어있는 경우가 있기 때문에, 어떤 타입의 값을 반환하고 어느 위치에서 반환하는지 반드시 명시해야 하는 것이다.
2.1.4 데이터를 저장하기 위해 변수 선언
val question : String = "삶, 우주, 그리고 모든 것에 대한 궁극적인 질문"
val answer : Int = 42
- 타입 추론 이용
val question = "삶, 우주, 그리고 모든 것에 대한 궁극적인 질문"
val answer = 42
- 변수를 선언하면서 즉시 초기화하지 않으면 컴파일러가 변수의 타입을 추론할 수 없다.
fun main{
val answer
answer = 42 // 타입 추론 불가
val answer : Int
answer = 42
}
2.1.5 변수를 읽기 전용 변수나 재대입 가능 변수로 표시
- val은 읽기 전용 참조를 선언한다. val로 선언된 변수는 단 한 번만 대입될 수 있다. 일단 초기화하고 나면 다른 값을 대입할 수 없다(자바의 final 변경자)
- 블록 안에 val로 선언된 변수에 대한 대입문이 여러 개 존재할 수도 있는데, 블록이 실행될 때 블록 안의 val로 선언된 변수에 대한 대입문들 중 오직 하나의 대입문만 실행된다면, 컴파일러는 이를 인지할 수 있기 때문에, val로 선언된 변수에 대한 대입문이 여러 개 존재할 수 있는 것이다.
fun canPerformOperation() : Boolean {
return true
}
fun main() {
val result: String
if(canPerformOperation()){
result = "Success"
}
else{
result = "Can't perform operation"
}
}
- var은 재대입 가능한 참조를 선언한다. 초기화가 이뤄진 다음이라도 다른 값을 대입할 수 있다.
- val로 선언된 변수가 가리키는 객체의 내부 값은 변경 가능할 수도 있음을 잊어선 안 된다.
fun main(){
val languages = mutableListOf("Java")
languages.add("Kotlin")
}
- var 키워드를 사용하면 변수의 값을 변경할 수 있지만, 변수의 타입은 고정된다.
- 컴파일러는 변수 선언 시점의 초기화 식으로부터 변수의 타입을 추론한다.
- 어떤 타입의 변수에 다른 타입의 값을 저장하고자 한다면, 변환 함수를 사용하거나, 강제 형 변환 연산을 사용해야 한다.
2.1.6 더 쉽게 문자열 형식 지정 : 문자열 템플릿
- 변수 이름 앞에 $를 덧붙이면 변수를 문자열 안에 참조할 수 있다.
fun main(){
val input = readln()
val name = if (input.isNotBlank()) input else "Kotlin"
println("Hello, $name!");
}
- $ 문자를 문자열에 넣고 싶으면 백슬래시()를 사용해 $를 이스케이프시켜야 한다.
fun main(){
println("\$x")
}
- 문자열 템플릿 안에 식을 사용할 수도 있다.
fun main(){
val name = readln()
if(name.isNotBlank()){
println("Hello, ${name.length}-letter person")
}
}
- 한글을 문자열 템플릿에서 사용할 때 주의할 점
- 코틀린에서는 한글도 변수명으로 사용 가능하기 때문에 문자열 템플릿 작성이 영문 변수명 바로 뒤에 한글명이 붙는 경우를 주의해야 한다.
- 이 문제를 해결하는 방법은 "${name}님 반가워요!"처럼 변수 이름을 {}로 감싸는 것이다.
- 문자열 템플릿 안에 if 식과 같은 더 복잡한 식을 사용할 수도 있다.
fun main(){
val name = readln()
println("Hello ${if (name.isBlank()) "someone" else name}!")
}
2.2 행동과 데이터 캡슐화: 클래스와 프로퍼티
다른 객체 지향 언어와 마찬가지로 코틀린도 클래스라는 추상화를 제공한다. 하지만 코틀린을 활용하면 다른 객체 지향 언어를 사용할 때보다 더 적은 양의 코드로 대부분의 공통적인 작업을 수행할 수 있다.
/* 자바 */
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 클래스와 데이터를 연관시키고, 접근 가능하게 만들기: 프로퍼티(필드와 접근자: 게터, 세터)
클래스라는 개념은 데이터를 캡슐화시키고 캡슐화한 데이터를 다루는 코드를 한 주체 안에 가두는 것이다.
- 코틀린 프로퍼티는 자바의 필드와 접근자 메소드를 완전히 대신한다.
- 코틀린에서 프로퍼티를 선언하는 방식은 프로퍼티와 관련있는 접근자를 선언하는 것이다.
class Person(
val name : String, // 읽기 전용 프로퍼티로, 코틀린은 (비공개) 필드와
// 필드를 읽는 단순한 (공개) 게터를 만들어낸다.
var isStudent : Boolean // 재대입 가능 프로퍼티로, 코틀린은 (비공개) 필드와
// 필드를 읽는 (공개) 게터, (공개) 세터를 만들어낸다.
)
- 접근자의 디폴트 구현은 뻔하다. 값을 저장하기 위한 필드가 생성되고, 게터는 그 필드 값을 반환하며 세터는 그 필드의 값을 변경한다.
- 원한다면 다른 로직을 사용해 프로퍼티 값을 계산하거나 변경하는 커스텀 접근자를 선언할 수도 있다.
- 코틀린에서 선언된 Person 클래스를 자바 코드에서 사용
public class Demo{
public static void main(String[] args){
Person person = new Person("Bob", true);
System.out.println(person.getName());
System.out.println(person.isStudent());
person.setStudent(false);
System.out.println(person.isStudent());
}
}
- is로 시작하는 프로퍼티의 게터 접근자 메소드에는 get이 붙지 않고 원래 이름을 사용한다.
- is로 시작하는 프로퍼티의 세터 접근자 메소드는 is를 set으로 바꾼 이름을 사용한다.
- 코틀린에서 선언된 Person 클래스를 코틀린 코드에서 사용
fun main(){
val person = Person("Bob", true) // 생성 시 new 연산자 미사용
println(person.name) // name 필드 게터 getName() 호출
println(person.isStudent) // isStudent 필드 게터 isStudent() 호출
person.isStudent = false // isStudent 필드 세터 setStudent() 호출
println(person.isStudent) // isStudent 필드 게터 isStudent() 호출
}
2.2.2 프로퍼티 값을 저장하지 않고 계산 : 커스텀 접근자
프로퍼티 접근자의 커스텀 구현이 필요한 일반적인 경우는 어떤 프로퍼티가 같은 객체 안의 다른 프로퍼티에서 계산된 직접적인 결과인 경우이다.(on the go 프로퍼티: 프로퍼티에 접근할 때 계산할 수 있는 프로퍼티)
class Rectangle(val height: Int, val width: Int){
val isSquare: Boolean // on the go '프로퍼티'
get(){ // 프로퍼티 커스텀 게터 선언
return height == width
}
}
식 본문 함수 구문을 사용해 작성할 수도 있다.
class Rectangle(val height: Int, val width: Int){
val isSquare: Boolean
get() = height == width // 프로퍼티 커스텀 게터 선언, 식 본문 함수 구문 사용
}
프로퍼티 접근자를 커스텀 구현하더라도 프로퍼티 호출과 접근 방식은 이전과 같다.
fun main(){
val rectangle = Rectangle(41, 43)
println(rectangle.isSquare) // isSquare 프로퍼티 게터 isSquare() 호출
}
커스텀 게터를 정의하는 방식과 클래스 안에 파라미터가 없는 함수를 정의하는 방식 모두 구현이나 성능에는 차이가 없다.
일반적으로 클래스의 특성을 기술하고 싶다면 프로퍼티로 그 특성을 정의해야 하고, 클래스의 행동을 기술하고 싶다면 프로퍼티가 아니라 멤버 함수를 선택한다.
2.2.3 코틀린 소스 코드 구조: 디렉터리와 패키지
- 코틀린은 클래스를 조직화하기 위해 패키지라는 개념을 사용한다.
- 모든 코틀린 파일엔 맨 앞에 package 문이 올 수 있다.
- 같은 패키지에 속해 있다면, 다른 파일에서 정의한 선언일지라도 직접 사용할 수 있다. 반면 다른 패키지에 정의한 선언을 사용하려면 해당 선언을 불러와야 한다.
- 파일 맨 앞의 package 문 아래에 import 키워드를 사용해서 다른 패키지를 불러올 수 있다.
- 자바에서는 패키지의 구조와 일치하는 디렉터리 계층 구조를 만들고 클래스의 소스 코드를 그 클래스가 속한 패키지와 같은 디렉터리에 위치시켜야 한다.
- 코틀린에서는 여러 클래스를 같은 파일에 넣을 수 있고, 이름도 마음대로 정할 수 있다.
- 코틀린에서는 디스크 상의 어느 디렉터리에 소스코드 파일을 위치시키든 관계없다.
2.3 선택 표현과 처리: 이넘과 when
2.3.1 이넘 클래스와 이넘 상수의 정의
package ch02.colors
enum class Color {
RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET
}
- 코틀린에서 enum은 소프트 키워드다. 변수명을 enum으로 지을 수 있다. class는 하드 키워드로 변수명으로 사용할 수 없다.
- 이넘 상수도 일반적인 클래스와 마찬가지로, 생성자와 프로퍼티 선언 문법을 사용할 수 있다.
package ch02.colors
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 printColor() = println("$this is $rgb")
}
fun main(){
println(Color.BLUE.rgb)
Color.GREEN.printColor()
}
2.3.2 when으로 이넘 클래스 다루기
- if와 마찬가지로 when도 값을 만들어내는 식이다. => 식 본문 함수에 when을 바로 사용해서 when 식을 바로 반환할 수 있다.
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"
}
fun main(){
println(getMnemonic(Color.BLUE)) // Battle
}
- 자바와 달리 각 분기의 끝에 break를 넣지 않아도 된다.
- 한 분기 안에서 콤마로 분리하면 여러 값을 패턴으로 사용할 수도 있다.
fun measureColor() = Color.ORANGE
fun getWarmthFromSensor() : String {
val color = measureColor()
return when (color){
Color.RED, Color.ORANGE, Color.YELLOW -> "warm (red = ${color.r})"
Color.GREEN -> "neutral (green = ${color.g})"
Color.BLUE, Color.INDIGO, Color.VIOLET -> "cold (blue = ${color.b})"
}
}
fun main(){
println(getWarmthFromSensor()) // warm (red = 255)
}
- 상수 값들을 임포트하면, 이넘 클래스 이름의 반복을 피할 수 있다.
package ch02.colors.Color.*
fun measureColor() = ORANGE
fun getWarmthFromSensor() : String {
val color = measureColor()
return when (color){
RED, ORANGE, YELLOW -> "warm (red = ${color.r})"
GREEN -> "neutral (green = ${color.g})"
BLUE, INDIGO, VIOLET -> "cold (blue = ${color.b})"
}
}
fun main(){
println(getWarmthFromSensor())
}
2.3.3 when 식의 대상을 변수에 캡처
- when 바깥의 코드를 불필요한 변수(color)로 더럽히는 일을 방지하기 위해 when 식의 대상 값을 변수에 넣어주고 사용할 수 있다.(캡처)
- 이런 경우 캡처한 변수의 영역이 when 식의 본문으로 제한되면서 when 식의 각 가지 안에서 변수가 가리키는 값의 프로퍼티에 접근할 수 있다.
package ch02.colors.Color.*
fun measureColor() = ORANGE
fun getWarmthFromSensor() =
when (val color = measureColor()){
RED, ORANGE, YELLOW -> "warm (red = ${color.r})"
GREEN -> "neutral (green = ${color.g})"
BLUE, INDIGO, VIOLET -> "cold (blue = ${color.b})"
}
fun main(){
println(getWarmthFromSensor())
}
2.3.4 when의 분기 조건에 임의의 객체 사용
package ch02.colors.Color.*
fun mix(c1:Color, c2:Color) =
when (setOf(c1, c2)){ // 표준 라이브러리의 setOf 함수 => 객체 집합 Set 객체 반환
setOf(RED, YELLOW) -> ORANGE
setOf(YELLOW, BLUE) -> GREEN
setOf(BLUE, VIOLET) -> INDIGO
else -> throw Exception("Dirty color")
}
fun main(){
println(mix(BLUE, YELLOW))
}
2.3.5 인자 없는 when 사용
- 분기마다 Set 인스턴스를 생성하는 것은 비효율적이다.
- 인자가 없는 when 식을 사용하면 불필요한 객체 생성을 막을 수 있다.
- 코드의 가독성은 떨어지지만 성능 향상
- 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) ->
ORANGE
(c1 == BLUE && c2 == VIOLET) ||
(c1 == VIOLET && c2 == BLUE) ->
ORANGE
else -> throw Exception("Dirty color")
}
fun main(){
println(mixOpimized(BLUE, YELLOW))
}
2.3.6 스마트 캐스트: 타입 검사와 타입 캐스트 조합
- 코틀린의 is 연산자는 자바의 instanceof 연산자와 같다고 할 수 있지만, 거기에 약간의 편의를 더했다.
- 코틀린에서는 is 연산자를 통해 어떤 임의의 변수의 타입을 확인한 후 그 타입에 속한 멤버에 접근하기 위한 명시적인 변수 타입 변환이 따로 필요하지 않다는 것이다.
- 타입을 검사한 변수를 마치 검사한 그 타입의 변수인 것처럼 사용 가능
- 단, 타입 검사 뒤에 변경될 수 없는 변수에만 적용 가능하다.
- 클래스 프로퍼티의 경우, val로 선언돼있으면서, 커스텀 접근자가 없는 경우에만 스마트 캐스트가 가능하다는 것이다.
- val로 선언되지 않았거나 val로 선언했지만 커스텀 접근자를 사용하는 경우에는 해당 프로퍼티에 대한 접근이 항상 같은 값을 내놓는다고 확신할 수 없기 때문이다.
- 원하는 타입으로 명시적으로 타입 캐스팅하려면 as 키워드를 사용한다.
val n = e as Num - if와 when의 분기에서 블록 사용
- 코틀린에서는 if 나 when 식 모두 분기에 블록을 사용할 수 있다.
- 그런 경우 블록의 마지막 문장이 블록 전체의 결과가 된다.
fun evalWithLogging(e: Expr) : Int =
when (e) {
is Num -> {
println("num: ${e.value}")
e.value // e의 타입 Num이면 이 식의 값이 반환된다.
}
is Sum -> {
val right = evalWithLogging(e.right)
println("sum: $left + $right")
left + right // e의 타입이 Sum이면 이 식의 값이 반환된다.
}
else -> throw IllegalArgumentException("Unknown expression")
}
2.4 대상 이터레이션 : while과 for 루프
2.4.1 조건이 참인 동안 코드 반복: while 루프
- 코틀린 while, do-while 문 형식
while(조건){
/*...*/
if(shouldExit) break
}
do{
if(shouldSkip) continue
/*...*/
}while(조건)
- 내포된 루프의 경우 코틀린에서는 레이블을 지정할 수 있다.
outer@ while(outerCondition){ // 바깥 루프에 outer라는 레이블 붙인다.
while(innerCondition){
if(shouldExitInner) break // 레이블을 지정하지 않으면 가장 안쪽 루프 동작
if(shouldSkipInner) continue // 레이블을 지정하지 않으면 가장 안쪽 루프 동작
if(shouldExit) break@outer // 레이블 지정하면 지정한 루프를 빠져나가거나 동작
if(shouldSkip) continue@outer // 레이블 지정하면 지정한 루프를 빠져나가거나 동작
}
}
2.4.2 수에 대해 이터레이션: 범위와 순열
- 코틀린에서는 C와 같은 for 루프, 즉 어떤 변수를 초기화하고 루프를 한번 실행할 때마다 그 변수를 갱신하고, 값이 어떤 경계에 도달하면 중단시켜주는 루프에 해당하는 요소가 없다.
- 이런 루프의 흔한 용례를 대신하기 위해 코틀린에서는 범위를 사용한다.
- 순열의 범위를 쓸 때는
..범위 연산자를 사용한다.
val oneToTen = 1..10 // 순열
- 코틀린의 범위는 폐구간, 즉 양 끝을 포함하는 구간이다. 이는 두 번째 값이 항상 범위에 포함된다는 것이다.
- when을 사용해 피즈버그 게임 구현하기
fun fizzBuzz(i: Int) = when{
i % 15 == 0 -> "피즈버즈"
i % 3 == 0 -> "피즈"
i % 5 == 0 -> "버즈"
else -> "$i"
}
fun main(){
for (i in 1..100){ // in 연산자
print(fizzBuzz(i))
}
}
- 증가 값을 갖는 순열을 이터레이션
fun main(){
for(i in 100 downTo 1 step 2){
print(fizzBuzz(i))
}
}
/*
100 downTo 1 => 100부터 1까지의 역방향 순열
step 2 => 방향은 그대로 유지하고 증가 값을 2로 한다. 역방향이므로 결국엔 증가값은 -2.
step 값은 양수여야 한다.
*/
- 범위에서 끝 부분을 반만 닫힌 구간으로 하려면 범위에
..<연산자 사용하면 된다. - 2.4.3 맵에 대해 이터레이션
- 컬렉션에 대해 이터레이션하기
fun main(){
val collection = listOf("red", "green", "blue");
for(color in collection){
print("$color")
}
}
- 맵을 초기화하고 이터레이션
fun main(){
val binaryReps = mutableMapOf<Char, String>()
for(char in 'A'..'F'){
val binary = char.code.toString(radix=2)
binaryReps[char] = binary
}
for((letter, binary) in binaryReps){
println("$letter = $binary")
}
}
- 맵에 사용한 구조 분해 구문을 맵이 아닌 컬렉션의 현재 인덱스를 유지하면서 컬렉션을 이터레이션 할때도 쓸 수 있다.
- 컬렉션의 withIndex 함수를 사용
fun main(){
val list = listOf("10", "11", "1001")
for((index, element) in list.withIndex()){
println("$index : $element")
}
}
2.4.4 in으로 컬렉션이나 범위의 원소 검사
- in 연산자를 사용해 어떤 값이 범위에 속하는지 검사할 수 있다.
- 반대로 !in을 사용하면 어떤 값이 범위에 속하지 않는지 검사할 수 있다.
fun isLetter(c:Char) = c in 'a'..'z' || c in 'A'..'Z'
fun isNotDigit(c:Char) = c!in '0'..'9'
fun main(){
println(isLetter('q'))
println(isNotDigit('x'))
}
- when 가지에서 in 검사 사용하기
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 know..."
}
fun main(){
println(recognize('8'))
}
- 검사 범위는 문자에 국한되지 않는다.
- 비교가 가능한 클래스라면, 그 클래스의 인스턴스 객체를 사용해, 범위를 만들 수 있다.
- 이렇게 만든 범위의 경우 그 범위 내의 모든 객체를 항상 이터레이션하지는 못한다.
- 하지만 in 연산자를 사용하여 값이 그 범위 안에 속하는지는 항상 결정할 수 있다.
fun main(){
println("Kotln" in "Java".."Scala")
}
2.5 코틀린에서 예외 던지고 잡아내기
- 함수는 정상적으로 종료할 수 있지만 오류가 발생하면 예외를 던질 수 있다.
- 함수를 호출하는 쪽에서는 그 예외를 잡아 처리할 수 있다.
- 발생한 예외를 그 함수를 호출한 쪽에서 처리하지 않으면 함수 호출 스택을 거슬러 올라가면서 예외를 처리하는 부분이 나올 때까지 예외를 다시 던진다.
- 자바와 달리 코틀린은 new 연산자가 필요없이 그냥 throw 키워드를 사용해 예외를 던진다.
- 자바와 달리 코틀린에서 throw는 구문이 아닌 식이므로 다른 식에 포함될 수 있다.
val percentage =
if(number in 0..100)
number
else
throw IllegalArgumentException("exception")
// throw는 식이다.
2.5.1 try, catch, finally를 사용한 예외 처리와 오류 복구
- 예외를 발생시키는 쪽이 아닌 예외를 잡아 해결해야 하는 쪽에서는 자바와 마찬가지로 예외를 처리하려면 try와 catch, finally 절을 함께 사용해 예외를 처리한다.
- 자바와 코틀린의 예외 처리에 있어서 가장 큰 차이점은 코틀린은 호출한 함수에서 던져질 가능성이 있는 예외를 함수 선언부에 throws 키워드를 사용해서 명시해주지 않아도 된다는 점이다.
- 자바의 경우엔 throws 절을 메서드 선언부에 명시하여 발생 가능한 예외를 알려줘야 했다.
fun readNumber(reader:BufferedReader): Int? { // throws IOException이 필요 X
try{
val line = reader.readLine()
return Integer.parseInt(line)
}catch(e: NumberFormatException){
return null
}finally{
reader.close()
}
}
fun main(){
val reader = BufferedReader(StringReader("239"))
println(readNumber(reader))
}
- 자바에서는 체크 예외가 메서드 시그니처의 일부이다.
- 자바에서는 명시적으로 처리해줘야만 하는 유형의 예외를 체크 예외라고 한다.
- 함수가 다른 함수를 호출할 경우 그 함수에 선언된 체크 예외는 반드시 모두 잡아 처리하거나 아니면 그 예외를 다시 던질 수 있다고 선언해야만 한다.
- 하지만 코틀린과 같은 최신 JVM 언어에서는 체크 예외와 언체크 예외를 구분하지 않는다.
- 코틀린은 함수가 던지는 명시적으로 처리해줘야만 하는 예외를 따로 지정해두지 않고, 예외를 잡을지 말지만 결정하면 된다.
- 코틀린에서는 컴파일러가 예외 처리를 강제하지 않는다.
- 2.5.2 try를 식으로 사용
- 지금까지 try를 문으로만 사용했다. 하지만 try는 식이기도 하다.
fun readNumber(reader: BufferedReader){
val number = try {
Integer.parseInt(reader.readLine())
}catch(e: NumberFormatException){
return
}
println(number)
}
fun main(){
val reader = BufferedReader(StringReader("239"))
readNumber(reader) // 아무것도 출력하지 않는다.
}
fun readNumber(reader: BufferedReader){
val number = try {
Integer.parseInt(reader.readLine())
}catch(e: NumberFormatException){
null
}
println(number)
}
fun main(){
val reader = BufferedReader(StringReader("239"))
readNumber(reader) // null 출력
}
- try를 식으로 사용하면 중간 변수를 도입하는 것을 피함으로써 코드를 좀 더 간결하게 만들고, 더 쉽게 예외에 대비한 값을 대입하거나 try를 둘러싼 함수를 반환시킬 수 있다.
2장 요약
- 함수를 정의할 때 fun 키워드를 사용한다. val과 var는 각각 읽기 전용 변수와 변경 가능한 변수를 선언할 때 쓰인다.
- val 참조는 읽기 전용이지만, val 참조가 가리키는 객체의 내부 상태는 여전히 변경 가능할 수 있다.
- 문자열 템플릿을 사용하면 지저분하게 문자열을 연결하지 않아도 된다. 변수 이름 앞에 $를 붙이거나 식을 ${식}처럼 ${}로 둘러싸면 변수나 식의 값을 문자열 안에 넣을 수 있다.
- 코틀린에서는 클래스를 아주 간결하게 표현할 수 있다.
- 프로퍼티 선언으로 자동으로 접근자, (비공개) 필드, (공개) getter, (공개) setter가 선언된다.
- 어떤 프로퍼티가 같은 객체 안의 다른 프로퍼티에서 계산된 직접적인 결과인 경우, 해당 프로퍼티의 접근자는 커스텀으로 구현이 필요하다.
- if는 코틀린에서 구문이 아닌 식이며, 값을 만들어낸다.
- 코틀린 when은 자바의 switch와 비슷하지만 더 강력하다.
- 어떤 변수의 타입을 검사하고 나면 굳이 그 변수를 캐스팅하지 않아도 검사한 타입의 변수처럼 사용할 수 있다. 컴파일러가 스마트 캐스트를 활용해 자동으로 타입을 바꿔준다.
- 단, val로 선언되고 커스텀 접근자가 구현되지 않은 프로퍼티여야 한다.
..연산자를 이용한 식은 범위를 만들어낸다.- 코틀린에서는 자바와 달리 체크 예외와 언체크 예외를 구분하지 않으며 던질 가능성이 있는 예외를 throws 절을 이용해 명시해주지 않아도 된다.
'Language > Kotlin' 카테고리의 다른 글
| [OOP] 3. 기본 타입, 컬렉션, 배열 (1) | 2025.06.09 |
|---|---|
| [API] 1. 람다를 사용한 프로그래밍 (3) | 2025.06.04 |
| [OOP] 2. 널이 될 수 있는 값 (0) | 2025.06.04 |
| [OOP] 1. 클래스, 객체, 인터페이스 (1) | 2025.06.02 |
| [Basic] 2. 함수 정의와 호출 (0) | 2025.06.01 |