본문으로 바로가기

코틀린 기본 문법 정리

category Kotlin 코틀린 2019. 1. 4. 00:26
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.




이번 포스팅에서는 아래와 같은 내용을 정리한다.


1. 함수, 변수, 클래스, enum 프로퍼티를 선언하는 방법

2. 제어 구조

3. 스마트 캐스트

4. 예외 던지기와 예외 잡기


스마트 캐스트는 타입 검사와 타입 캐스트, 타입 강제 변환을 하나로 엮은 기능이다. 자세한 내용은 아래 내용을 참고하면 된다.


1. 함수와 변수


1
2
3
fun main(args: Array<String>) {
    println("Hello, World!");
}



위에서는 프로그래밍의 시작인 Hello World!를 호출하는 함수이다. 여기에서 코틀린의 특징을 발견할 수 있는데 특징은 아래와 같다.


1. 함수는 fun으로 만든다.

2. 파라미터 이름 뒤에 파라미터 타입을 쓴다.

3. 함수를 최상위 수준에 정의한다. 자바는 클래스 안에 함수를 꼭 넣어야 하지만 코틀린은 그럴 필요는 없다.

4. 코틀린에서는 배열 처리를 위한 문법이 따로 존재하지 않는다.

5. System.out.println 대신에 println이라고 쓴다. 코틀린 표준 라이브러리는 자바 표존 라이브러리 함수를 간결하게 사용할 수 있게 감싼 래퍼(Wrapper)를 제공한다.

6. 끝 줄에 세미콜론; 을 붙일 필요가 없다.


함수


위의 예제는 반환이 없는 함수를 선언하였는데 반환이 있는 함수를 어떻게 만들어야 할까 ?


1
2
3
4
5
6
7
fun max(a: Int, b: Int): Int {
    return if(a > b) a else b
}
 
fun main(args: Array<String>) {
    println(max(1,2))
}



위의 특징을 아래 간략히 정리해보면

1. 함수는 fun으로 만든다.

2. 예제는 max라는 함수로, 함수 이름 뒤에 괄호 안에 파라미터 목록이 나온다. 또한 함수의 반환 타입은 파라미터 목록의닫은 괄호 다음에 오는데, 괄호와 반환 타입 사이를 콜론(:) 으로 구분해야 한다.


fun max(a: Int, b:Int) : Int 


fun : 함수 선언

max : 함수 이름

a: Int, b:Int : 파라미터 타입

(:) : 함수파라미터와 반환 타입을 구분하는 콜론

Int : 반환형


문과 식의 구분


코틀린에서는 if는 식이지 문이 아니다. 식은 값을 만들어 내며 다른 식의 하위 요소로 계산에 참여할 수 있는 반면 문은 자신을 둘러 싸고 있는 가장 안쪽의 블록의 최상위 요소로 존재하며, 아무런 값을 만들어 내지 않는다는 차이가 있다.

자바에서는 모든 제어 구조가 문인 반면 코틀린에서는 루프를 제외한 대부분의 제어 구조가 식이다. 제어 구조를 다른 식으로 엮어낼 수 있으면 여러 일반적인 패턴을 아주 간결하게 표현할 수 있다.


식이 본문인 함수


위의 예제를 좀 더 간결하게 표현할 수 있다. 앞의 함수 본문은 if 식 하나로 만 이루어져 있다. 중괄호를 없애고 return을 제거하면서 등호를 식 앞에 붙이면 더 간결하게 표현할 수 있다.


1
fun max(a: Int, b: Int) : Int = if(a > b) a else b



본문이 중괄호로 둘러싸인 함수를 블록이 본문인 함수라하며, 등호와 식으로 이뤄진 함수를 식이 본문인 함수라고 부른다.

코틀린에서는 식이 본문인 함수가 많이 쓰여진다. 그런 함수의 본문 식에는 단순한 산술식이나 함수 호출 식뿐 아니라 if, when, try 등의 더 복잡한 식도 자주 쓰여진다. 


반환 타입을 생략하면 max 함수를 더 간략하게 만들 수 있다.


1
fun max(a: Int, b: Int= if(a > b) a else b



여기서 반환 타입을 생략할 수 있는 이유는 무엇일까? 코틀린은 정적 타입 지정 언어이므로 컴파일 시점에 모든 식의 타입을 지정해야 하지 않을까 ? 실제로 모든 변수나 모든 식에는 타입이 있으며, 모든 함수는 반환 타입이 정해져야 한다. 하지만 식이 본문인 함수의 경우 사용자가 반환 타입을 적지 않아도 컴파일러가 함수 본문 식을 분석해서 식의 결과 타입을 함수 반환 타입으로 정해준다.

이렇게 컴파일러가 타입을 분석해 프로그래머 대신 구성 요소의 타입을 정해주는 기능을 타입추론이라고 한다.

식이 본문인 함수의 반환 타입만 생략 가능하다는 점을 유의해야 한다.


변수


자바에서는 변수를 선언할 때 타입이 온다. 코틀린에서는 타입 지정을 생략할 수 있다. 타입으로 변수 선언을 시작하면 타입을 생략할 경우 식과 변수 선언을 구별할 수 없다. 그런 이유로 코틀린에서는 키워드로 변수 선언을 시작하는 대신 변수 이름 뒤에 타입을 명시하거나 생략하게 허용한다.


val question = "ktko Kotlin Start"

val result = 2


위와 같이 타입을 표현하지 않았지만 아래와 같이 타입을 선언할 수 있다.


val result: Int = 42


초기화 식을 사용하지 않고 변수를 선언하려면 변수 타입을 반드시 명시해야 한다.


var result: Int

result = 42


변경 가능한 변수와 변경 불가능한 변수


변수 선언시 사용되는 키워드는 아래와 같다.

val(값을 뜻하는 value) : 변경 불가능한 참조를 저장하는 변수다. 자바로 final이라고 생각하면 된다.

var(변수를 뜻하는 variable) : 변경  가능한 참조다. 자바의 일반 변수에 해당한다.


기본적으로는 모든 변수를 val키워드를 사용해 불변 변수로 사용하고, 나중에 꼭 필요할 때에만 var로 변경하라.


val 변수는 블록을 실행할 때 정확히 한 번만 초기화돼야 한다. 하지만 어떤 블록이 실행될 때 오직 한 초기화 문장만 실행됨을 컴파일러가 확인할 수 있다면 조건에 따라 val 값을 다른 여러 값으로 초기화할 수도 있다.


1
2
3
4
5
6
7
val message: String
 
if(isEmpty()) {
    message = "true"
else {
    message = "false"
}




val 참조 자체는 불변일지라도 그 참조가 가리킴는 객체의 내부 값은 변경될 수 있다는 사실을 기억하라. 아래의 코드는 올바른 코틀린 코드이다.


1
2
val language = arrayListOf("Java")
language.add("Kotlin")



var 키워드를 사용하면 변수의 값을 변경할 수 있지만 변수의 타입은 고정돼 바뀌지 않는다.

컴파일러는 변수 선언 시점의 초기화 식으로부터 변수의 타입을 추론하며, 변수 선언 이후 변수 재대입이 이뤄질 때는 이미 추론한 변수의 타입을 염두해 두고 대입문의 타입을 검사한다.


1
2
3
var result = "true"
true = 42
"Error: type mismatch" 컴파일 오류 



문자열 형식 지정 : 문자열 템플릿 


1
2
3
4
fun main(args: Array<String>) {
    var name = if(args.size > 0) args[0else "Theo"
    println("Hello $name!")
}r



위의 예제는 문자열 템플릿이라는 기능을 보여준다. 이 코드는 name이라는 변수를 선언하고 그 다음 줄에 있는 리터럴($name) 에서 변수를 사용했다. 문자열 리터럴의 필요한 곳에 변수를 넣되 변수 앞에 $를 추가해야 한다.


위의 코드를 아래와 같이 변경할 수 있다. $와 중괄호를 이용하여 더 간결하게 코드를 작성해보았다.


1
2
3
fun main(args: Array<String>) {
    println("Hello ${if (args.size > 0) args[0] else "Theo"}!")
}



클래스와 프로퍼티


코틀린도 자바의 객체지향 처럼 사용할 수 있지만 자바보다 더 간결한 코드로 자바 클래스와 관련있는 대부분의 작업을 수행할 수 있다.

아래는 Person의 자바빈 클래스를 아래와 같이 만들었다.


1
2
3
4
5
6
7
8
9
10
11
12
public class Person {
    private String name;
    
    public Person(String name) {
        this.name = name;
    }
 
    public String getName() {
        
        return name;
    }
}



필드가 둘 이상으로 늘어나면 생성자인 Person(String name) 의 본문에서 파라미터를 이름이 같은 필드에 대입하는 대입문이 늘어난다. 코틀린에서는 필드 대입 로직을 훨씬 더 간결하게 작성한다.


코틀린 변환기는 자바 코드를 코틀린 코드로 변환해 주는데 인텔리J에서 쉽게 사용할 수 있다. 위의 코드를 코틀린 코드로 변환하면 아래와 같다.


1
class Person(var name: String)



자바를 코틀린으로 변환한 결과, public 가시성 변경자가 사라졌음을 확인할 수 있다. 코틀린의 기본 가시성은  public이므로 이런 경우 변경자를 생갹해도 된다.


프로퍼티


자바에서는 필드와 접근자를 한데 묶어 프로퍼티라고 부르며, 프로퍼티라는 개념을 활용하는 프레임워크가 많다. 코틀린은 프로퍼티를 언어 기본 기능으로 제공하며, 코틀린 프로퍼티는 자바의 필드와 접근자 메서드를 완전히 대신한다. 클래스에서 프로퍼티를 선언할 때는 앞으로 살펴본 변수를 선언하는 방법과 마찬가지로 val 또는 var을 사용한다. val은 읽기전용, var은 변경가능하다.


1
2
3
4
class Person {
    val name : String//읽기 전용 프로퍼티로 코틀린은 (비공개) 필드와 필드를 읽는 단순한 (공개) 게터를 만들어낸다.
    var isMarried : Boolean = false //쓸 수 있는 프로퍼티로 코틀린은 (비공개) 필드, (공개)게터, (공개)세터를 만들어 낸다.
}



책에서 보았던 위의 코드는 에러가 발생(Property must be initialized or be abstract) 아래와 같이 사용하면 정상동작 확인


1
class Person(val name: Stringvar isMarried: Boolean)



new 키워드를 호출하지 않고 생성자를 호출할 수 있고, 프로퍼티 이름을 직접 사용해도 코틀린이 자동으로 게터를 호출해준다.


1
2
3
4
5
fun main(args: Array<String>) {
    var theo = Person("Theo"true)
    println(theo.name)
    println(theo.isMarried)
}



기본적으로 코틀린에서 프로퍼티를 선언하는 방식은 프로퍼티와 관련 있는 접근자를 선언하는 것이다.(읽기 전용 프로퍼티의 경우 게터만 선언하며 변경할 수 있는 프로퍼티의 경우 게터와 세터를 모두 선언한다.)


게터와 세터의 이름을 정하는 규칙에는 예외가 있다. 이름이 is로 시작하는 프로퍼티의 게터에는 get이 붙지 않고 원래 이름을 그대로 사용하며, 세터에는 is를 set으로 바꾼 이름을 사용한다. 따라서 자바에서 isMarried 프로퍼티의 게터를 호출하려면 isMarried()를 사용해야 한다.


커스텀 접근자 


대부분의 프로퍼티에는 그포러퍼티의 값을 저장하기 위한 필드가 있다. 이를 프로퍼티를 뒷받침하는 필드라고 부른다. 개발자는 원한다면 커스텀 게터를 활용하면 프로퍼티 값을 그때그때 계산할 수 있다.


아래 예제는 사각형이 정사각형인지 확인할 수 있다.  isSquare프로퍼티에 자체 값을 저장하는 필드가 필요 없고, 자체 구현을 제공하는 게터가 존재한다.


1
2
3
4
5
6
class Rectangle(var height: Intvar width: Int) {
    val isSquare: Boolean
        get() {
            return height == width
        }
}



1
2
3
4
5
6
fun main(args: Array<String>) {
    var rect = Rectangle(23)
    println(rect.isSquare)
    rect.height = 3
    println(rect.isSquare)
}



파라미터가 없는 함수를 정의하는 방식과, 커스텀 게터를 정의하는 방식 중 어느 쪽이 더 나은지 판단이 되지 않을 수 있다. 방식은 비슷하고 성능도 비슷하다. 가독성이 차이가 있고, 일반적으로 클래스의 특성을 정의하고 싶다면 프로퍼티로 그 특성을 정의하는 것이 좋다.


디렉터리와 패키지


코틀린도 자바와 같은 비슷한 패키지의 개념이 있다. 코틀린 파일의 맨 앞에 package 문을 넣을 수 있다. 그러면 그 파일 안에 있는 모든 선언(클래스, 함수, 프로퍼티 등)이 해당 패키지에 들어간다. 같은 패키지라면 다른 파일에서 정의한 것이라도 바로 사용 가능하지만, 다른 패키지라면 import 를 해야만 사용 가능하다. 


코틀린에서는 여러 클래스를 한 파일에 넣을 수 있고, 파알의 이름도 마음대로 정할 수 있다. 코틀린에서 디스크상의 어느 디렉토리에 소스코드 파일을 위치시키든 관계없다. 따라서 원하는 대로 소스코드를 구성할 수 있다.


제어 구조


enum 클래스 정의


when은 자바의 switch를 대치하되 훨씬 더 강력하며, 앞으로 더 자주 사용할 프로그램이 요소라고 생각할 수 있다.


enum은 자바 선언보다 코틀린 선언에 더 많은 키워드를 써야 하는 흔치 않은 예다. enum은 소프트 키워드라고 불리며 class 앞에 있을 때는 특별한 의미를 지니지만 다른 곳에서는 이름에 사용할 수 있다. 


1
2
3
4
5
6
7
8
enum class Color (
    var r: Intvar g: Intvar b:Int
) {
    RED(255,0,0), ORANGE(255,1650), YELLOW(255,255,0),
    GREEN(02550), BLUE(0,0,255);
 
    fun rgb() : Int = (r * 256 + g) * 256 + b
}



enum클래스 안에도 프로퍼티나 메서드를 정의할 수 있다. 각 enum 상수를ㄹ 정의할 때 상수에 해당하는 프로퍼티를 지정해야 한다. 위 예제에서는 유일한 세미콜론(;)을 볼 수 있는데, enum안에서 메서드를 정의하는 경우 상수 목록과 메서드 사이에 구분자로 세미콜론을 넣어야 한다.


when으로 enum 다루기


자바에서 switch로 사용해야 할 경우에 코틀린에서는 when을 사용하면 된다. 코틀린에서는 switch는 존재하지 않는다.


1
2
3
4
5
6
7
8
9
10
11
fun getMnemonic(color: Color) =
    when (color) {
        Color.RED -> "빨간"
        Color.BLUE -> "파란"
        Color.GREEN -> "초록"
        Color.ORANGE -> "오렌지"
        Color.YELLOW -> "노란"
    }
 
 
println(getMnemonic(Color.BLUE))



when을 이용한 분기 안에 여러값 사용 방법


1
2
3
4
5
fun getWarmth(color: Color) = when(color) {
    Color.RED, Color.ORANGE, Color.YELLOW -> "WARM"
    Color.GREEN -> "NEUTRAL"
    Color.BLUE -> "COLD"
} //enum 클래스에 정의된 값들이 다 포함되어 있어야 한다.



상수 값을 임포트 하여 코드를 더 간결하게 만들기 -> com.color 패키지를 생성하여 enum class Color을 패키지 안에 옮긴 후 실행


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import com.color.Color
import com.color.Color.*
 
fun main(args: Array<String>) {
    var person = Person("ktko"true)
    println(person.name)
    println(person.isMarried)
 
    println(getWarmth(RED))
}
 
 
fun getWarmth(color: Color) = when(color) {
    RED, ORANGE, YELLOW -> "WARM"
    GREEN -> "NEUTRAL"
    BLUE -> "COLD"
}



when과 임의이 객체를 같이 사용하기


코틀린에서는 자바에서 상수만을 이용하여 switch를 사용한 것과 달리 객체를 사용 하여 분기 조건을 실행할 수 있다.


1
2
3
4
5
fun mix(color1: Color, color2: Color)  = when(setOf(color1, color2)) { //when 식의 인자로 아무 객체나 사용할 수 있다. when은 이렇게 인자로 받은 객체가 각 분기 조건에 있는 객체와 같은지 테스트한다.
    setOf(RED, YELLOW) -> ORANGE //두 색을 혼합하여 다른 색을 만들 수 있는 경우를 열거
    setOf(YELLOW, BLUE) -> GREEN
    else -> throw Exception("Dirty Color"//매칭되는 분기 조건이 없다면 문장을 실행
}

cs


코틀린 표준 라이브러리에는 인자로 전달받은 여러 객체를 그 객체들을 포함하는 집합인 Set 객체로 만들어 주는 setOf라는 함수가 있다. when 식은 인자 값과 매치하는 조건 값을 찾을 때까지 각 분기를 검사한다. 여기서는 setOf(color1, color2)와 분기 조건에 있는 객체 사이를 매치할 때 동등성을 사용한다.

그러므로 그 둘이 같지 않으면 계속 다음 분기의 조건 객체와 setOf(color1, color2)를 차례로 비교하는 식으로 작동한다. 조건을 찾을 수 없다면 else를 실행한다.


인자 없는 when의 사용


위의 예제는 setOf를 사용함으로써 불필요한 Set을 생성하여 메모리 소모를 발생시킨다. 인자가 없는 when을 사용하여 불필요한 객체 생성을 막아 성능을 향상 시킬 수 있다. 대신 코드는 약간 길어져서 가독성은 떨어질 수 있다.


1
2
3
4
5
fun mixOptimized(color1: Color, color2: Color) = when{
    (color1 == RED && color2 == YELLOW) && (color1 == YELLOW && color2 == RED) -> ORANGE
    (color1 == BLUE && color2 == YELLOW) && (color1 == YELLOW && color2 == BLUE) -> GREEN
    else -> throw Exception("Dirty Color")
}



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


아래 예제는 (1 + 2) + 4 와 같은 산술식을 계산하는 산술식을 만든다. 함수가 받을 산술식은 오직 두 수를 더하는 연산이 가능하다. 다른 연산도 비슷한 방식으로 구현할 수 있다. 

식을 트리구조로 저장하면 Num은 항상  말단 노드이고 산술(+)은 중간 노드가 된다. Sum노드의 두 자식은 덧셈의 인자가 된다. 


1
2
class Num(val value: Int) : Expr
class Sum(val left: Expr, val right: Expr) : Expr



식을 위한 Expr인터페이스가 있고, Sum과 Num 클래스는 그  Expr 인터페이스를 구현한다. Expr은 아무 메서드를 선언하지 않고, 여러 타입의 식 객체를 아우르는 공통 타입의 역할을 수행한다.  클래스가 구현하는 인터페이스를 지정하기 위해서 콜론(:) 뒤에 인터페이스 이름을 사용한다.


여기에서 Expr인터페이스는 여러 타입을 is로 구분하기 위한 공통 타입의 역할을 한다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public interface Expr {} //Expr 인터페이스 선언
 
class Num(val value: Int) : Expr
class Sum(val left: Expr, val right: Expr) : Expr
 
fun eval(e: Expr): Int {
 
    if(e is Num) {
 
        return e.value
    }
 
    if(e is Sum) {
 
        return eval(e.left) + eval(e.right)
    }
 
    throw IllegalArgumentException("Unknown expression")
}
 
println(eval(Sum(Sum(Num(1), Num(2)), Num(4))))



코틀린에서는 is를 사용해 변수 타입을 검사한다. 자바에서는  instanceof라고 생각하면 된다. 

코틀린에서는 프로그래머 대신 컴파일러가 캐스팅을 한다. 이를 스마트 캐스트라고 한다. 스마트 캐스트는 is로 변수에 든 값의 타입을 검사한 다음에 그 값이 바뀔 수 없는 경우에만 작동한다.


코틀린에서 원하는 타입으로 변경하기 위해선 아래와 같이 명시적으로 사용할 수 있다.


1
val n = e as Num



위에 있는 eval 메서드를 좀 더 코틀린 답게 수정을 해보면 아래와 같다.

코틀린은 삼항연산자가 없다. 자바에서 삼항연산자는 a > b ? a : b 처럼 사용하지만 코틀린에서는 if를 식처럼 사용할 수 있어 

if(a > b) a else b 처럼 사용할 수 있다. 아래 예제는 코틀린의 if를 식처럼 사용한 예제이다.


1
2
3
4
5
6
7
8
9
10
11
fun eval(e: Expr): Int =
    if(e is Num) {
 
        e.value
    } else if(e is Sum) {
 
        eval(e.left) + eval(e.right)
    } else  {
 
        throw IllegalArgumentException("Unknown expression")
    }



위의 코드를 when을 사용하여 좀 더 코틀린스러운 코드를 만들어 낼 수 있다.


1
2
3
4
5
6
fun eval(e: Expr): Int =
    when(e) {
        is Num -> e.value
        is Sum -> eval(e.left) + eval(e.right)
        else -> throw IllegalArgumentException("Unknwon Expression")
    }



if, when의 분기에서 블록 사용


if, when에서 모두 분기에 블록을 사용할 수 있다. 또한 코틀린에서는 return을 사용하지 않고도 값을 반환할 수 있다. 따라서 블록의 마지막 문장이 반환값이 된다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fun evalWithLog(e: Expr): Int =
    when(e) {
        is Num ->  {
            println("num : ${e.value}")
            e.value
        }
 
        is Sum -> {
            val left = evalWithLog(e.left)
            val right = evalWithLog(e.right)
            println("sum : $left + $right")
 
            left + right
        }
 
        else -> throw IllegalArgumentException("Unknwon Expression")
    }



코틀린의for 루프


코틀린에서 while은 자바의 while과 별다른 차이가 없다. 때문에 for 루프에 대해서 집중적으로 작성하였다.


코틀린에서는 기존의 자바 for(int i=0; i < 10; i++) 같은 형식으로 for문을 제공하지 않는다. 코틀린은 범위를 이용하여 for문을 사용한다. 범위는 기본적으로 두 값으로 이뤄진 구간이며, 보통의 두 값은 정수 등의 숫자 타입의 값이며 ..연산자로 시작 값과 끝 값을 연결하여 범위를 만든다.  아래 Range는 1과 10을 포함한다.


1
val onteToTen = 1..10



코틀린에서 for, when을 이용한 예제


3, 5, 15로 나누어질 때마다 문자를 출력, 아닐 경우에는 숫자 출력 


1
2
3
4
5
6
7
8
9
10
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))
}



증가 값을 음수로 사용하기 위해서는 downTo를 사용하여 100 downTo 1 을 사용하였고 증가값(여기서는 음수)는 2가 됨을 알 수 있다.


1
2
3
for(i in 100 downTo 1 step 2) {
    print(fizzBuzz(i))
}



맵에 대한 이터레이션 


1
2
3
4
5
6
7
8
9
10
val binaryReps = TreeMap<Char, String>() //키에 대해 정렬하기 위해 TreeMap 사용
 
for(c in 'A'..'F') { //A부터 F까지 문자의 범위를 이용한 이터레이션
    val binary = Integer.toBinaryString(c.toInt()) //아스키 코드를 2진수로 변환
    binaryReps[c] = binary //c를 키로 2진 표현의 값을 value에 넣는다.
}
 
for((key, value) in binaryReps) { //맵에 대한 이터레이션, 맵의 키와 값을 출력한다.
    println("$key : $value")
}




맵에서 사용했던 for문을 컬렉션에서도 활용할 수 있다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
 배열은 Array클래스로 표현
 get, set []]연산자 오버로딩
 */
 
var array: Array<String> = arrayOf("ktko""Theo")
println(array[0])
println(array.get(0))
println(array.size)
 
//array의 팩토리 함수 사용
var arrayB = Array(5, { i -> i.toString()})
var arrayC = arrayOf("0""1""2""3""4")
 
for(c in arrayB) {
    println(c)
}
 
for(c in arrayC) {
    println(c)
}
 
for((index, element) in arrayC.withIndex()) {
    println("$index: $element")
}
 
/**
 Primitive 타입의 박싱 오버헤드를 없애기 위한 배열
 IntArray, ShortArray,
 Array를 상속한 클래스들은 아니지만 Array와 같은 메소드와 프로퍼티를 가진다.
 size등 유용한 멤버 함수 포함
 */
val intArray: IntArray = intArrayOf(1,2,3)
for(i in intArray) {
    println(i)
}



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


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


1
2
3
4
5
6
7
8
fun isLetter(c: Char) = c in 'a'..'z' || c in 'A'..'Z'
fun isNotDigit(c: Char) = c !in '1'..'9'
 
println(isLetter('q')) //true
println(isLetter('1')) //false
 
println(isNotDigit('1')) //false
println(isNotDigit('a')) //true



위의 비교 로직은 표준 라이브러리 범위 클래스 구현 안에 아래와 같이 표현된다.


1
in 'a'..'z' <-------- 'a' <= c && c <= 'z'

cs


when 에서도 in, !in을 사용할 수 있다.


1
2
3
4
5
6
7
8
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"
}
 
println(recognize('8'))
println(recognize('a'))



문자에 국한되지 않고 비교가 가능한 클래스라면 그 클래스의 인스턴스 객체를 사용해 범위를 만들 수 있다.


코틀린의 예외 처리


코틀린의 기본 예외 처리는 자바 뿐만 아니라 다른 언어와도 매우 유사하다. 기본적으로는 아래와 같다.


1
2
3
4
5
val persentage: Int = 101
 
if(persentage !in 1..100) {
    throw IllegalArgumentException("0과 100사이가 아니다.")
}



아래 예제는 if문을 식으로 사용한 코드이다. persentage가 1과 100사이라면 number에 persentage값이 초기화되고 아니면 예외가 발생한다.


1
2
3
4
5
val persentage: Int = 100
val number =
    if(persentage in 1..100) persentage else throw IllegalArgumentException("0과 100사이가 아니다.")
 
println(number)



코틀린의 try, catch, finally


자바와 마찬가지로 예외를 처리하려면 try와 catch, finally절을 함께 사용한다. 

아래 예제는 파일에서 각 줄을 읽어 수로 변환하되 그 줄이 올바른 수 형태가 아니면 null을 반환한다.


자바 코드와 가장 큰 차이는 throws 절이 코드에 없다. 자바에서 함수 선언 뒤에 throws을 붙여야 하며, 함수가 던질 가능성이 있는 예외나 다른 함수에서 발생할 수 있는 예외를 모두 catch로 처리해야 하며, 처리하지 않은 예외는 throws 절에 명시해야한다.


1
2
3
4
5
6
7
8
9
10
11
12
13
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))



try를 식으로 사용


코틀린의 try 키워드는 if나 when과 마찬가지로 식이다. 따라서 try의 값을 변수에 대입이 가능하다. if와 달리 try의 본문을 반드시 중괄호 {}로 둘러싸야 한다. 다른 문장과 마찬가지로 try 본문도 내부에 여러 문장이 있으면 마지막 식의 값이 전체 결과 값이다.


1
2
3
4
5
6
7
8
9
10
11
12
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);



위의 예제는 블록 안에서 return문을 사용한다. 예외가 발생한 경우 catch블록 다음의 코드는 실행하지 않는다. 다만 계속 진행하고 싶다면 catch 블록 도 값을 만들어야 한다. catch 블록도 그 안의 마지막 식이 블록 전체의 값이 된다.

 

1
2
3
4
5
6
7
8
9
10
11
12
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);