Kotlin In Action 2장 - 코틀린 기초

바이너리·2022년 1월 6일
2

Kotlin In Action

목록 보기
2/2
post-thumbnail

2장에서 다루는 내용

  • 함수, 변수, 클래스, enum, 프로퍼티를 선언하는 방법
  • 제어 구조
  • 스마트 캐스트
  • 예외 던지기와 예외 잡기

기본 요소: 함수와 변수

함수

코틀린은 fun 키워드를 이용해서 함수를 나타내고, 파라미터 이름 뒤에 타입을 명시한다. 세미콜론을 붙이지 않아도 된다는 특징이 있다.

fun max(a: Int, b: Int) : Int {
	return if (a > b) a else b
}

1️⃣ max: 함수 이름
2️⃣ (a: Int, b: Int): 파라미터 목록
3️⃣ : Int: 반환 타입
4️⃣ return if (a > b) a else b: 함수 본문
으로 이루어져 있다.

문(statement)와 식(expression)의 차이
식: 값을 만들어 내며 다른 식의 하위 요소로 계산에 참여할 수 있음
문: 자신을 둘러싼 가장 안쪽 블록의 최상위 요소로 존재
자바에서는 모든 제어 구조가 문이지만 코틀린에서는 루프를 제외한 대부분의 제어 구조가 식이다. 대입문은 자바에서는 식이지만 코틀린에서는 문이다.

변수

코틀린에서는 변수를 선언할 때 타입이 뒤에 온다. 타입 지정을 생략(컴파일러의 타입 추론에 의존)하는 경우가 많은데, 책에서는 '타입으로 변수 선언을 시작하면 타입을 생략할 경우 식과 변수 선언을 구별할 수 없다' 라고 표현했다.
그래서 변수의 타입이 선언문에서의 끝에 위치한다.

var foo = 5
var bar: Int = 3

코틀린은 위과 같이 변수 키워드 + 변수명 + (타입)의 형태로 변수를 선언한다.
위처럼 타입 명시를 생략하는 경우도 있는데, int foo = 5와 같은 형태의 경우 타입을 생략하는 foo = 5처럼 되는데, 이는 기존의 변수에 값을 대입한 것인지 새로 선언하는 변수에 타입을 생략한 것인지 구분되지 않기 때문이다.

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

fun main() {
    val foo: Int
    foo = 5
    
    var bar
    bar = 3
}

bar는 타입을 명시하지 않아 에러가 발생한다. 코틀린은 자바와 달리 함수를 꼭 클래스 안에 넣어줄 필요가 없고, 코틀린 REPL도 활용할 수 있는데 위의 foo 선언을 해당 환경에서 진행하면 오류가 발생한다. 왜인지는 아직 모르겠다.

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

val(value): Immutable 참조를 저장하는 변수. 초기화하면 재대입이 불가능
var(variable): Mutable 참조. 변수의 값은 바뀔 수 있다.

fun foo(arr: Array<Int>) {
    arr[1] = 0
}

fun main() {
    val arr = arrayOf(1, 2, 3)
    foo(arr)
    for(num in arr) {
        print(num)
    }
}
// 103

val의 참조 자체는 불변이지만 가리키는 내부 객체의 값은 바뀔 수 있다. var 키워드를 사용했을 때도 값은 변경할 수 있지만 타입은 변경할 수 없다.

문자열 템플릿 println("Hello $name")을 활용해서 문자열 리터럴 안에서 변수를 활용할 수 있다. 복잡한 식도 중괄호로 둘러싸서 표현할 수 있다.

클래스와 프로퍼티

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

위와 같이 클래스를 표현한다. 기본 가시성은 public으로 설정된다.

클래스는 데이터를 캡슐화 하고 데이터를 다루는 코드를 포함한다. 외부에서 필드 및 메서드에 접근할 수 있는 범위를 나타내기 위해 접근 제어자(access modifier)를 사용하고, 일반적으로 필드에 접근할 수 있는 getter/setter 메서드를 제공한다.

클래스에서 val로 선언한 프로퍼티(자바에서의 final)는 읽기 전용이며, var로 선언한 프로퍼티는 변경이 가능하다.

위의 코드에서 name은 val로 선언되어 비공개 필드와 필드를 읽을 수 있는 단순(공개) getter를 생성하고, isMarried는 이에 추가로 공개 setter를 만든다.

외부에서 접근할 때도 자바처럼 getter/setter 메서드를 호출하는 것이 아니라 프로퍼티를 직접 접근하는 문법으로 사용 가능하다(내부에서는 게터세터가 호출됨).

당연히 커스텀 getter/setter를 선언할 수 있다는 것도 알아두자

enum과 when

코틀린에서 enum을 사용할 때는 enum class 키워드를 사용한다. 자바와 마찬가지로 프로퍼티와 메서드도 사용할 수 있다.

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
}

메서드를 정의할 경우에는 반드시 상수 목록과 메서드 정의 사이에 세미콜론을 넣어 구분해줘야 한다.

when

when은 자바의 switch에 해당하는 구성 요소이지만 더 강력한 기능을 제공한다.

분기 조건에 상수만이 아닌 임의의 객체를 할당할 수 있으며, 끝에 break를 작성하지 않아도 괜찮다. 한 분기가 여러 조건에 매치될 경우 ,를 사용해서 구분한다.

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("No Specific Color")
	}

인자 값과 매치되는 조건을 찾기 위해 equals 메서드를 사용한다. equals가 정의되지 않은 객체의 경우는 Object의 것을 상속받는 것을 유의하자.

Java Object.equals() 👉

https://docs.oracle.com/javase/7/docs/api/java/lang/Object.html#equals(java.lang.Object)

class Person(val name: String, val age: Int)

fun main() {
    val p = Person("binary", 25)
    
    when(p) {
        Person("binary", 25) -> print("Hi")
        Person("yun", 30) -> print("Hello")
        else -> print("World")
    }
}
// 결과: World
class Person(val name: String, val age: Int) {
    override fun equals(other: Any?)
        = (other is Person)
        && name == other.name
        && age == other.age
}

fun main() {
    val p = Person("binary", 25)
    
    when(p) {
        Person("binary", 25) -> print("Hi")
        Person("yun", 30) -> print("Hello")
        else -> print("World")
    }
}
// 결과: Hi

위의 예제는 커스텀 클래스가 equals를 정의하지 않아 when의 아무 분기에도 매치되지 않았고, else 분기로 빠진다. 반면 아래에서 equals를 정의해주었을 때는 첫 번째 분기에 매치된 것을 볼 수 있다.

when (e) {
	is Num -> {
		println("num: ${e.value}")
		e.value      // e.value 반환
	}
}

if와 when의 경우 분기의 가장 마지막 문장이 블록의 결과가 된다.

스마트 캐스트

코틀린에서는 is를 사용해서 변수 타입을 검사한다(자바의 instanceof와 비슷하다).

자바에서는 해당 타입에 속한 멤버에 접근하기 위해서는 하위 타입으로 캐스팅해줘야 했는데, 코틀린에서는 일단 is로 검사하면 캐스팅하지 않아도 그 변수가 원하는 타입으로 캐스팅된 것 처럼 사용할 수 있다.

이것을 코틀린의 스마트 캐스트라고 한다.

if (e is Sum) {
	return eval(e.right) + eval(e.left)
}

이 때, 변수에 든 값의 타입을 검사해서 Immutable한 경우에만 작동하기 때문에 프로퍼티는 반드시 val이어야 한다. 원하는 타입으로 명시적 타입캐스팅을 하려면 as 키워드를 사용하면 된다.

이터레이션: while과 for 루프

코틀린도 while, do-while, for를 사용해서 이터레이션을 표현할 수 있다.

while, do-while은 자바에서의 문법과 거의 유사하며, for문은 초기/증감/조건문이 없고 범위로만 표현하기 때문에 파이썬과 유사하다고 느꼈다.

fun fizzBuzz(i: Int) = when {
	i % 15 == 0 -> "FizzBuzz"
	i % 3 == 0 -> "Fizz"
	i % 5 == 0 -> "Buzz"
}

for (i in 1..100) {
	print(fizzBuzz(i))
}

for (i in 1 until 100) {
	// 위의 코드와 동일하게 동작
}

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

특이한 건 in을 사용하면 코틀린의 범위는 항상 양끝을 포함하는 폐구간이며, until을 사용하면 끝을 포함하지 않는 반폐구간으로 만들 수 있다.

step 키워드를 사용해서 증감 값을 지정해줄 수 있다(절대값으로 표현).

val binaryReps = TreeMap<Char, String>()

for (c in 'A'..'F') {
	val binary = Integer.toBinaryString(c.toInt())
	binaryReps[c] = binary
}

for ((letter, binary) in binaryReps) {
	println("$letter = $binary")
}

맵에 대한 이터레이션도 간결하게 표현할 수 있다. 코드에서 볼 수 있듯 get/put이 아니라 일반 배열에 접근하는 것 처럼 맵에 접근 가능하다.

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

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

println("Kotlin" in "Java".."Scala")       // lexical order
println("Kotlin" in setOf("Java", "Scala"))       // element of set

코틀린의 예외 처리

코틀린에서는 예외 인스턴스를 만들 때 new 키워드를 붙일 필요가 없다.

fun readNumber(reader: BufferedReader): Int? {
	try {
		val line = reader.readLine() 
		return Integer.parseInt(line) 
	} 
	catch (e: NumberFormatException) {
		return null 
	} finally {
		reader.close() 
	} 
} 

fun main(args: Array<String>) {
	val reader = BufferedReader(StringReader("239")) 
	println(readNumber(reader)) 
}

throws 절이 코드에 없다. 코틀린은 checked/unchecked exception을 구별하지 않으며, 던져지는 예외를 처리해주는 것은 개발자의 선택이다.

try 키워드는 if, when과 마찬가지로 이기 때문에 try의 값을 변수에 대입할 수 있다.

👉 코틀린 인 액션 2장의 이슈 확인하기

(답변은 조만간 달겠습니다 🥲)

profile
01101001011010100110100101101110

0개의 댓글