본문 바로가기

Kotlin/Kotlin IN ACTION

Kotlin IN ACTION 2장 정리(코틀린 기초 : 변수, 함수, 클래스, enum, 프로퍼티, for, while, is, exception)

반응형

2장 코틀린 기초

변수, 함수, 클래스, ...

변수 선언 방법

val question = "코틀린 기초는 몇 장인가"
val answer = 2
//val answer:Int = 2 타입을 적어도 되고 실제 값에서 추론이 가능하면 생략해도 됨
val pi = 3.14 //Double 추론

val vs var

  • val : 값을 뜻하는 value에서 따온 것으로, 불변(Immutable)값을 저장하는 변수다. 자바의 final 처럼 초기화하고 나면 다른 값으로 재대입이 불가능하다.
  • var : 변수를 뜻하는 variable에서 따온 것으로, 가변(mutable)값을 저장하는 변수다. 재대입이 가능하다.
val languages = arrayListOf("Java")
languages.add("Kotlin") // languages는 불변이지만 객체이기 때문에 재대입을 하지 않았으므로 add와 같은 변경은 가능하다.

var answer = 2
answer = "2" //type mismatch 컴파일 에러, 변수 타입은 한 번 할당하면 고정된다.

문자열 템플릿

val name = "Kotlin"
println("Hello, $name") //$변수명으로 문자열안에서 변수를 사용했다. = 문자열 템플릿
println("Hello, ${name}") //중괄호로 감싸기 가능

함수 선언 방법

fun max(a:Int, b:Int): Int {
    return if(a > b) a else b
}
/*
fun 함수이름(파라미터1, 파라미터2, ...): 리턴타입 {
    함수 본문
}
*/

위 코드의 주석에서 참고할 수 있듯, fun 키워드로 함수를 선언하고 함수이름, 파라미터, 리턴타입, 함수 본문으로 구성하여 함수를 선언할 수 있습니다.

식(Expression) vs 문(statement)

코틀린에서는 if식(expression)이지 if문(statement)이 아닙니다.

식은 리턴 값을 만들어 내기 때문에 어떤 계산에 참여할 수 있는 반면, 문은 일종의 블록으로 리턴 값을 만들어 내지 않는다는 차이가 있습니다.

식이 본문인 함수

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 키워드를 생략하고 = (등호)를 이용하여 함수를 선언할 수 있습니다.

식이 함수 본문인 경우에 컴파일러가 함수 본문 식을 분석하여 타입 추론이 가능하므로 리턴타입을 생략할 수도 있습니다.

식(expression)이 함수 본문인 경우에만 함수의 리턴 타입을 생략할 수 있고, 문(statement)이 본문인 함수는 반드시 리턴 타입을 명시해야하고 return 키워드를 생략할 수 없습니다.


클래스

class Person(val name:String, var isMarried:Boolean)

val name:String은 읽기 전용 프로퍼티로 코틀린은 private 필드와 getter를 만들어냅니다.

var isMarried:Boolean은 쓰기도 가능한 프로퍼티로 코틀린은 private 필드, getter, setter를 만듭니다.

주의할 것은 is로 시작하는 프로퍼티를 지정하면 getIs...()이런 getter함수가 만들어지는게 아니라 원래 필드 이름 그대로를 사용합니다.(isMarried)

코틀린에서는 굳이 getName()이렇게 호출하지 않고 프로퍼티를 그대로 접근하는 듯이(.name) 작성하면 getName() 호출됩니다.

val person = Person("jeongpro", false)
println(person.name) //getName()호출
println(person.isMarried)//isMarried()호출
person.isMarried = true //setter도 이렇게 프로퍼티로 접근한다. setMarried()호출

커스텀 접근자 만드는 방법

class Rectangle(val height:Int, val width:Int) {
    val isSquare: Boolean
        get() {
            return height == width
        }
    //...
}

위 코드와 같이 커스텀 접근자를 만들어 낼 수 있습니다.

이렇게 하면 isSquare에 접근할 때마다 getter를 호출하여 매 번 프로퍼티 값을 다시 계산하게 됩니다.

enum, when

enum 클래스 정의

enum class Color {
    RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET
}

자바와 마찬가지로 enum에 프로퍼티와 메소드를 사용할 수 있다.

프로퍼티와 메소드를 정의할 때는 프로퍼티 정의부분과 메소드 정의부분 사이에 세미콜론(;)을 써야한다.

when

switch에 해당하는 구성요소다. if와 마찬가지로 식(expression)입니다.

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

switch와 다르게 여러 조건을 갖는 분기를 만들 수 있고, break;와 같은 표현도 쓰지 않습니다.

when의 인자로 심지어 아무 객체(ex. Person)도 가능합니다.

when에 인자가 없어도 when을 쓸 수 있습니다.

스마트 캐스트

코틀린에서는 is 키워드를 이용해서 변수 타입을 검사합니다. (자바의 instanceof와 유사합니다.)

코틀린 is 는 자바14의 feature인 pattern matching과 같이 쓸 수 있습니다.

과거 자바에서는 instanceof 로 확인을 한 후 별도로 그 타입으로 캐스팅해서 사용했습니다.

if(obj is Person){
    println(obj.name) //obj가 is 키워드로 타입을 검사를 진행한 블록 내부이므로 바로 캐스팅되어 사용할 수 있습니다.
}

이런 기능을 스마트 캐스트라고 합니다. (컴파일러가 해줍니다)

while, do-while : 자바와 동일

while(조건){
    /*...*/
}
do {
    /*...*/
} while(조건)

for (코틀린에서는 for-each 루프 형태만 존재)

일반적인 for 루프가 없는 대신 범위(range)를 사용합니다.

..연산자로 시작 값과 끝 값을 연결해서 범위를 만듭니다.

val oneToTen = 1..10 // 닫힌 구간으로 1과 10을 포함한다.
//for문 사용법
for( i in 1..100 ){
    print(i)
}

거꾸로 하는 방법

downTo를 사용하고 step정보로 얼만큼씩 증감할지를 정할 수도 있습니다.

for(i in 100 downTo 1 step 2) {
    print(i)
}
//step 값을 -2로 해야하는것 아닌가? 할 수 있으나 절대값으로 칩니다

..이 닫힌 구간의 범위 연산자라면, 반개구간의 범위 연산자는 없을까요?

있습니다. until을 사용하면 뒤에오는 값은 포함되지 않습니다.

for( x in 0 until size){...}

컬렉션에서 순회

Map에서 get과 put 사용법, 순회

val map = mutableMapOf('a' to "jdk")
map['c'] = "hello" //put 자바에서 map.put('c',"hello");와 같다
println(map['c']) // get 자바에서 map.get('c');와 같다

for((key, value) in map){
    println("$key = $value")
}

list 순회

val list = listOf("10","101010", "1010111010")
for((idx, elem) in list.withIndex()){
    println("$idx = $elem")
}

in 으로 범위 검사

fun isLetter(c:Char) = c in 'a'..'z' || c in 'A'..'Z'

예외 처리

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()
    }
}

동작도 자바와 똑같이 동작하지만 다른 점은 함수에 어떤 예외를 던질 수 있는지 적지 않는다는 것입니다. (ex. throws NumberFormatException)

반응형