Kotlin in Action. [2] 코틀린 기초

56810 단어 kotlinkotlin

코틀린 기초

함수

hello world 예시 함수

fun main(args: Array<String>) { // example, hello world function
  println("hello world")
}
  • 함수를 선언할때, fun키워드 사용.
  • 파라미터 이름 뒤에 그 파라미터의 타입을 씀.
  • 함수를 최상위 수준에 정의 할 수 있다. 자바와 달리 꼭 클래스 안에 함수를 넣어야 할 필요가 없다.
  • 코틀린에는 자바와 달리 배열 처리를 위한 문법이 따로 존재하지 않음.
  • 코틀린 표준 라이브러리표준 자바 라이브러리 함수를 간결하게 사용할 수 있게 감싼 Wrapper(래퍼)를 제공한다. Ex. println
  • ;을 붙이지 않아도 됨

식이 본문인 함수

문(statement)과 식(experssion)의 구분

  • 코틀린에서는 if는 식이지 문이 아니다.
  • 은 값을 만들어 내며 다른 식의 하위 요소로 계산에 참여할 수 있음.
  • 은 자신을 둘러싸고 있는 가장 안쪽 블록의 최상위 요소로 존재하며 아무런 값을 만들어내지 않는다.
  • 자바에서는 모든 제어 구조가 인 반면 코틀린에서는 루프를 제외한 대부분의 제어 구조가
  • 대입문은 자바에서는 이었으나 코틀린에서는 이 됐다.
fun max1(a : Int, b: Int): Int { // 블록이 본문인 함수, 반환 타입 생략 불가
	return if ( a > b ) a else b
}

fun max2(a : Int, b: Int): = if (a > b) a else b // 식이 본문인 함수
// 타입 추론(type inference) 기능으로 함수의 변환형 Int가 생략되도 무방하다.
// 컴파일러가 함수 본문 식을 분석해 식의 결과 타입을 함수 반환 타입으로 정해준다.

변수

val answer = 42
val answer: Int = 42

/////////////////////////////////////////////////

val answer: Int
answer = 42
  • 식이 본문인 함수에서와 마찬가지로 타입을 지정하지 않아도, 컴파일러가 초기화 을 분석해서 초기화 의 타입을 변수 타입으로 지정한다.

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

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

  • val
    • 변경 불가능한(immutable) 참조를 저장하는 변수다.
    • val로 선언된 변수는 일단 초기화하고 나면 재대입이 불가능.
    • 자바로 치면 final
    • val 참조 자체는 불변일지라도 그 참조가 가리키는 객체의 내부 값은 변경가능.
    • val 변수는 블록을 실행할 때 정확히 한 번만 초기화 돼야 함.
val languages = arrayListOf("Java") // immutable (불변 참조)
languages.add("Kotlin") // 참조가 가리키는 객체 내부를 변경 가능.
  • var
    • 변경 가능한(mutable) 참조.
    • 변수의 값이 바뀔 수 있다.
    • 자바의 일반 변수에 해당한다.
    • 변수의 값을 변경할 수 있지만, 변수의 타입은 고정돼어 바뀌지 않음.
var answer = 42
answer = "no answer" // Error: type mismatch, 컴파일 오류, 강제 형 변환 필요

문자열 템플릿

val name = "myName"
println("hello, $name")
println("hello, ${name}")
println("hello, ${name[0]}")
println("hello, ${if (name.size > 0) name[0] else "someone"}")

Class and Property(프로퍼티)

java vs kotlin

public class Person { //java
    private final String name;

    public Person(String name) {
        this.name = name;
    }
    
    public String getName() {
        return name;
    }
}
  • 자바의 경우 필드가 둘 이상으로 늘어나면 생성자인 Person(string name)의 본문에서 파라미터를 이름이 같은 필드에 대입하는 대입문의 수도 늘어남.
class Person(val name: String) // kotlin
  • 코틀린의 경우 코드가 없이 데이터만 저장하는 클래스(값 객체, value object)를 지원함으로 자바에 비해 간결함.
  • public 가시성 변경자(visiblity modifier)가 코틀린의 경우 publicdefault임으로 생략 가능.

property(프로퍼티)

  • 자바에서는 데이터를 필드(field)에 저장하며, 멤버 필드의 가시성은 보통 private이다. 클래스는 자신을 사용하는 클라이언트가 그 데이터에 접근하는 통로로 쓸 수 있는 접근자 메서드(accessor method)를 제공한다. (Ex. getter, setter)

  • 자바에서는 필드접근자를 한데 묶어 프로퍼티라고 부름.

  • 코틀린은 프로퍼티를 언어 기본 기능으로 제공하며, 코틀린 프로퍼티는 자바의 필드접근자 메소드를 완전히 대신한다.

  • val로 선언한 프로퍼티는 읽기 전용, var로 선언한 프로퍼티는 변경 가능

class Person(
    val name: String,
    // 읽기 전용 프로퍼티로, 코틀린은 비공개 필드와 필드를 읽는 공개 게터를 만들어낸다.
    var isMarried: Boolean,
    // 쓸 수 있는 프로퍼티로, 코틀린은 비공개 필드와 필드를 읽는 단순한 공개 게터를 공개 세터를 만들어낸다.
)
// 자바
Person person = new Person("Bob", true);
System.out.println(person.getName());
System.out.println(person.isMarried());
person.setMarried(false)


// 코틀린
val person = Person("Bob", true) // new 키워드 사용 안하고 생성자 호출
println(person.name) // 프로퍼티 이름 사용해도 코틀린이 게터 자동 호출해줌
println(person.isMarried)
person.isMarried = fasle
  • 코틀린의 name 프로퍼티는 자바 쪽에서는 getName
  • 이름이 is로 시작하는 프로퍼티게터에는 get이 붙지 않고 원래 이름을 그대로 사용, 세터에는 isset으로 바꾼 이름을 사용.

커스텀 접근자

class Rectangle(val height: Int, val width: Int) {
    val isSquare: Boolean
        get() { // 프로퍼티 게터 선언
            return height == width
        }
}
  • 프로퍼티접근자를 직접 작성하는 코드
  • isSquare 프로퍼티에는 자체 값을 저장하는 필드가 필요 없다. 이 프로퍼티에는 자체 구현을 제공하는 게터만 존재한다. 클라이언트가 프로퍼티에 접근할 때마다 게터프로퍼티 값을 매번 다시 계산한다.

디렉터리와 패키지

  • 패키지 이름 뒤에 .*를 추가하면 패키지 안의 모든 선언을 임포트할 수 있다. 이런 스타 임포트(star import)를 사용하면 패키지 안에 있는 모든 클래스뿐 아니라 최상위에 정의된 함수나 프로퍼티까지 모두 불러오게 된다.
  • 자바에서는 패키지의 구조와 일치하는 디렉토리 계층 구조를 만들고 클래스의 소스코드를 그 클래스가 속한 패키지와 같은 디렉토리에 위치시켜야 한다. (자바에서는 디렉토리 구조가 패키지 구조를 그대로 따라야 한다)

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

  • 자바와 코틀린을 함께 사용하는 프로젝트에서는 자바의 방식을 따라야 한다.

enum과 when

enum class

enum class Color {
	// class라는 키워드 앞에서만 enum 클래스의 의미를 가지며, 단독으로 사용시 이름으로 사용 가능
	RED, ORANGE, YELLOW, GREEN
}

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); 
    // 여기 반드시 세미콜론 사용해야 한다.
    // 각 상수를 생성할 때 그에 대한 프로퍼티 값을 지정한다.
    // enum 상수 목록과 메서드 정의 사이에 사용한다.

    fun rgb() = (r * 256 + g) * 256 + b // enum 클래스 안에서 메서드 정의
}

>>> println(Color.RED.rgb())
255

when으로 enum 클래스 다루기

  • if와 마찬가지로 when도 값을 만들어내는 식이다. 따라서 식이 본문임 함수when을 바로 사용할 수 있다.
fun getMnemonic(color: Color) = // 함수의 반환 값으로 when 식을 직접 사용
    when(color) { // 색이 특정 enum 상수와 같을 때 그 상수에 대응하는 문자열을 돌려준다.
        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
  • 자바와 달리 분기의 끝에 break를 넣지 않아도 됨.
  • 한 분기 안에서 여러 값을 매치 패턴으로 사용 할려면 값 사이를 , 로 분리한다.
import ch02.colors.Color   // 다른 패키지에서 정의한 Color 클래스를 임포트한다.
import ch02.colors.Color.* // 짧은 이름으로 사용하기 위해 enum 상수를 모두 임포트 한다.

fun getWarmth(color: Color) = when(color) {
    RED, ORANGE, YELLOW -> "warm"
    GREEN -> "neutral"
    BLUE, INDIGO, VIOLET -> "cold"
}

when과 임의의 객체를 함께 사용

  • 코틀린의 when은 자바의 switch보다 강력하다.
  • 분기 조건에 상수만을 사용할 수 있는 자바 switch와 달리 코틀린 when의 분기 조건은 임의의 객체를 허용한다.
fun mix(cl: Color, c2: Color) = 
    when(setOf(c1,c2)) { // 여러 객체를 그 객체들을 포함하는 집합인 Set 객체로 만드는 setOf 라는 함수
        setOf(RED,YELLOW) -> ORANGE
        setOf(YELLOW,BLUE) -> GREEN
        setOf(BLUE,VIOLET) -> INDIGO
        else -> throw Exception("Dirty color") // 매치되는 분기 조건이 없으면 이 문장을 실행
    }

>>> println(mix(BLUE,YELLOW))
GREEN
  • when 식은 인자 값과 매치하는 조건 값을 찾을 때까지 각 분기를 검사한다.

인자 없는 when 사용

  • mix 함수가 자주 호출된다면 불필요한 가비치 객체가 늘어남.
  • 코드는 읽기 어려워지지만 성능을 위해 감수해야 하는 경우임.
  • when에 아무런 인자도 없으려면 각 분기의 조건이 Boolean 결과를 계산하는 식이어야 한다.
fun mixOptimized(c1: Color, c2: Color) = 
    when { // 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")
    }

>>> printn(mixOptimized(BLUE,YELLOW))
GREEN

스마트 캐스트 : 타입 검사 + 타입 캐스트

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

식을 위한 Expr 인터페이스, Sum, Num 클래스는 그 Expr 인터페이스를 구현
Expr는 아무 메소드도 선언하지 않으며, 단지 여러 타입의 식 객체를 아우르는 공통 타입 역할만 수행.
  • Expr 인터페이스에는 두 가지 구현 클래스가 존재한다.
    • 어떤 식이 수라면 그 값을 반환한다
    • 어떤 식이 합계라면 좌항과 우항을 계산한 다음 그 두 값을 합한 값을 반환한다
fun eval(e: Expr) : Int {
    if (e is Num) {
        val n = e as 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
  • 코틀린에서는 is를 사용해 변수 타입을 검사
  • is 검사는 자바의 instanceof와 비슷
  • 자바에서 변수의 타입을 확인한 다음에 그 타입에 속한 멤버에 접근하기 위해서는 명시적으로 변수 타입을 캐스팅해야 함. 이런 멤버 접근을 여러 번 수행해야 한다면 변수에 따로 캐스팅한 결과저장한 후 사용해야함.
  • 코틀린에서는 이것을 프로그래머 대신 컴파일러가 캐스팅해줌.
  • 어떤 변수가 원하는 타입인지 일단 is로 검사하고 나면 굳이 변수를 원하는 타입으로 캐스팅하지 않아도 마치 처음부터 그 변수가 원하는 타입으로 선언된 것처럼 사용할 수 있다.
  • 이것은 컴파일러가 캐스팅을 수행해줌. => 스마트 캐스팅
  • 스마트 캐스트is로 변수에 든 값의 타입을 검사한 다음에 그 값이 바꿜 수 없는 경우에만 작동.
  • 프로퍼티에 대해 스마트 캐스트를 사용한다면 그 프로퍼티는 반드시 val이어야 하며, 커스텀 접근자를 사용한 것이어도 안 된다.

원하는 타입으로 명시적으로 타입 캐스팅을 하기 위해서는 as를 사용한다.

val n = e as Num

리팩토링 : if를 when으로 변경

fun eval(e: Expr) : Int =
    when(e) {
        is Num -> // 인자 타입을 검사하는 when 분기
        	e.value // 스마트 캐스팅가 쓰여짐.
        is Sum -> // 인자 타입을 검사하는 when 분기
        	eval(e.right) + eval(e.left) // 스마트 캐스팅가 쓰여짐.
        else ->
        	throw IllegalArgumentException("Unknown expression")
    }

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

fun evalWithLogging(e: Expr) : Int =
    when(e){
        is Num -> {
            println("num: ${e.value}")
            e.value // e의 타입이 Num이면 e.value가 return
        }
        is Sum -> {
            val left = evalWithLogging(e.left)
            val right = evalWithLogging(e.right)
            println("sum: $left + $right")
            left + right // e의 타입이 Sum이면 이 식의 값이 return
        }
        else -> throw IllegalArgumentException("Unknown expression")
    }
  • 블록의 마지막 식블록의 결과이다.

이터레이션 : while, for

while문

  • 자바와 코틀린의 문법이 동일함.

수에 대한 이터레이션 : 범위와 수열

  • 자바의 for 루프 => 코틀린의 range

  • 코틀린의 range는 양끝을 포함하는 구간임.

  • ..연산자로 시작 값과 끝 값을 연결해서 범위를 만든다. 문자도 가능 ('A'..'F')

  • 역방향은 downTo, 증감값 조절은 step을 사용.

  • 끝 값을 포함하지 않는 반만 닫힌 범위는 until 함수를 사용해야함.

  • Map의 키와 값에 대해 이터레이션 가능.

  • withIndex로 인덱스와 함께 컬렉션을 이터레이션 할 수 있음.

val oneToTen = 1..10 // 1 ~ 10

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

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))
    }
1 2 Fizz 4 Buzz Fizz 7 ...

// map의 key, value를 for문으로 풀어낼 수 있다.
for ((key, value) in mutableMapOf(Pair("A", 1))) {
    }

    // withIndex를 활용하면 리스트의 index도 간편히 가져올 수 있다.
for ((index, value) in mutableListOf(1, 2, 3).withIndex()) {
    }

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

fun isLetter(c: Char) = c in 'a'..'z' || c in 'A'..'Z' // 'a'<= c && c <= 'z'
fun isNotDigit(c: Char) = c !in '0'..'9'

fun main(args: Array<String>) {
    println(isLetter('q'))
    println(isNotDigit('x'))
}
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(args: Array<String>) {
    println(recognize('8'))
}

println("Kotlin" in "Java".."Scala")
// true

println("Kotlin" in setOf("Java","Scala"))
// false
  • in 연산자를 사용해 어떤 값이 범위에 속하는지 검사 할 수 있음.

  • !inin 연산자의 반대 연산.

  • 비교가 가능한 클래스(java.lang.Comparable 인터페이스를 구현한 클래스)라면 그 클래스의 인스턴스 객체를 사용해 범위를 만들 수 있다.

  • 컬렉션에도 in 연산을 사용할 수 있음.

코틀린의 예외 처리

  • 코틀린의 예외(exception) 처리는 자바나 다른 언어의 예외 처리와 비슷.
  • 발생한 예외를 함수 호출 단에서 처리(catch) 하지 않으면 함수 호출 스택을 거슬러 올라가면서 예외를 처리하는 부분이 나올 때까지 예외를 다시 던진다.(rethrow)
  • 자바와 달리 코틀린의 throw이므로 다른 에 포함될 수 있다.

try, catch, finally

fun readNumber(reader: BufferedReader) : Int? {
	// 함수가 던질 수 있는 예외를 명시할 필요가 없다. throws가 없는 것이다.
	try {
		val line = reader.readLine()
		return Integer.parseInt(line)
	}
	catch (e: NumberFormatException) { // 예외 타입을 :의 오른쪽에 쓴다.
		return null
	}
	finally { // 자바와 동일
		reader.close()
	}
}
  • 자바에서는 함수를 작성할 때 함수 선언 뒤에 throws IOException을 붙여야 한다. 이유는 IOException체크 예외, checked exception 이기 때문이다. 자바에서는 체크 예외를 명시적으로 처리해야 한다.
    어떤 함수가 던질 가능성이 있는 예외나 그 함수가 호출한 다른 함수에서 발생할 수 있는 예외를 모두 catch로 처리해야 하며, 처리하지 않은 예외는 throws 절에 명시해야 한다.

  • 코틀린에서는 함수가 던지는 예외를 지정하지 않고 발생한 예외를 catch 해도 되고 안해도 된다. 자바는 체크 예외 처리를 강제한다. 코틀린의 경우 체크 예외언체크 예외를 구분하지 않는다.

체크 예외언체크 예외

체크 예외, checked exception

  • Exception 클래스의 서브클래스이면서 RuntimeException 클래스를 상속하지 않은 것들.
  • RuntimeExeception 클래스를 상속하지 않은 Exception 클래스들이다. 체크 예외는 예외가 발생할 수 있는 메소드를 사용할 경우 반드시 예외를 처리하는 코드를 함께 작성해야 한다.
  • 예외를 처리하기 위해서 catch 문으로 잡거나 throws 를 통해 메소드 밖으로 던질 수 있다. 만약 예외를 처리하지 않으면 컴파일 에러가 발생한다.

언체크 예외, unchecked exception

  • RuntimeException 클래스를 상속한 Exception 클래스들은 예외 처리를 강제하지 않기 때문에 언체크 예외 라고 불린다.
  • 에러를 처리하지 않아도 컴파일 에러가 발생하지 않는다.
  • RuntimeException은 주로 프로그램에 문제가 있을 때 오류가 발생하도록 의도된 것으로 대표적으로 NullPointerException이 있다.
  • 즉, RuntimeException은 예상치 못했던 상황에서 발생하는 것이 아니므로 굳이 예외 처리를 강제하지 않는다.

try를 식으로 사용

  • 코틀린의 tryifwhen과 마찬가지로 식이다. 따라서 try의 값을 변수에 대입할 수 있다.
  • if 와 달리 try의 본문을 반드시 {} 중괄호로 둘러싸야 한다.
fun readNumber(reader: BufferedReader) {
    val number = try {
        reader.readLine().toInt() // 이 식의 값이 try 식의 값이 된다.
    } catch (e: NumberFormatException) {
        return // 예외가 발생하면 return하므로 catch 블록 다음의 코드는 실행되지 않는다.
    }

    println(number)
}


fun readNumber(reader: BufferedReader) {
    val number = try {
        reader.readLine().toInt() // 이 식의 값이 try 식의 값이 된다.
    } catch (e: NumberFormatException) {
        null // 예외가 발생하면 null 값을 사용함.
    }

    println(number) // 예외가 발생한다면 null이 출력됨. (number = null)
}

좋은 웹페이지 즐겨찾기