Kotlin/코틀린인액션요약

2장 - 코틀린 기초

Junuuu 2022. 9. 10. 00:01
반응형

2장에서 다루는 내용

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

- 코틀린의 여러  제어구조

- 스마트 캐스트

- 예외 던지기와 예외 잡기

 

단, 여기에서 배운 내용으로 작성하는 코드는 코틀린다운 코드라고 부르기는 어려움

 

함수와 변수

fun main(args: Array<String>){
	println("Hello,world!")
}

함수를 선언할 때 fun 키워드를 사용합니다.

 

파라미터 이름 뒤에 그 파라미터의 타입을 씁니다.

 

System.out.println 대신에 println을 사용합니다.(자바 라이브러리 함수를 간결하게 사용할 수 있는 래퍼를 제공합니다)

 

끝에 세미콜론(;)을 사용하지 않아도 됩니다.

 

함수

fun max(a: Int, b:Int) : Int{
	return if(a>b) a else b
}
println(max(1,2))
//출력 2

fun으로 함수를 선언하고 함수 이름은 max, 파라미터 목록은 a, b 반환 타입은 Int입니다.

 

위처럼 본문이 중괄호로 둘러쌓인 함수를 블록이 본문인 함수라고 합니다.

 

반대로 다음과 등호와 식으로 이뤄진 함수를 식이 본문인 함수라고 부릅니다.

fun max(a: Int, b: Int) Int = if(a>b) a else b //식이 본문인 함수

 

코틀린에서는 식이 본문인 함수가 더 자주 쓰이며 여기에 if, when, try 등의 더 복잡한 식도 자주 쓰입니다.

또한 여기서 반환타입인 Int를 생략해도 컴파일러가 함수 본문 식을 분석하여 결과 타입을 추론합니다. (타입 추론)

 

변수

val question = "삶, 우주, 그리고 모든 것에 대한 궁극적인 질문"
val answer = 42
val answer : Int = 42

타입 추론을 사용해도 되고 타입을 명시해도 됩니다.

부동소수점을 사용하면 변수 타입은 Double이 됩니다.

 

val immutable = 42;
var mutable = 42;

val은 value의 약자로 변경 불가능한 참조를 저장하는 변수입니다. (자바의 final)

var은 variable의 약자로 변경 가능한 참조를 저장하는 변수입니다.

 

var키워드를 사용하면 변수의 값은 변경할 수 있지만 변수의 타입은 바꿀 수 없습니다.

만약 다른 타입의 값을 저장하고 싶다면 변환 함수를 사용하여 타입을 변환시키거나 강제 형 변환을 해야 합니다.

 

문자열 템플릿

fun main(args: Array<String>){
	val name = if(args.size >0) args[0] else "Kotlin"
	println("Hello, $name!")
}

변수 앞에 $를 사용하여 변수를 문자열 안에서 사용할 수 있습니다.

변수 이름뿐만 아니라 복잡한 식도 {}로 감싸서 넣을 수 있습니다.

변수 이름을 사용할 때도 {}로 감싸는 습관을 들이면 더 좋습니다.

 

클래스와 프로퍼티

간단한 자바 클래스

public clas Person{
	private final String name;
    
	public Person(String name){
		this.name = name;
	}
    
	public String getName(){
		return name;    
	}
}

이를 코틀린으로 변환하면 다음과 같습니다. (코틀린 파일에 자바 코드를 복사하면 변환할 거냐는 메시지가 나옴)

class Person(val name: String)

 

프로퍼티

자바에서는 필드와 접근자를 한데 묶어 프로퍼티라고 부릅니다.

코틀린은 프로퍼티를 언어 기본 기능으로 제공합니다.

앞서 변수에서 보았듯이 val은 읽기 전용이며 var는 읽기/쓰기가 가능합니다.

 

코틀린은 val을 사용하면 비공개(private) 필드를 만들며 게터를 만들어냅니다.

코틀린은 var를 사용하면 비공개(private) 필드를 만들며 게터, 세터를 만들어냅니다.

 

코틀린 클래스 사용하기

val person = Person("Bob", true)
println(person.name)
//Bob
println(person.isMarried)
//true

프로퍼티 이름으로 사용해도 코틀린이 자동으로 게터를 호출해줍니다.

마찬가지로 person.isMarreid = false는 세터처럼 동작합니다.

 

프로퍼티 접근자 커스텀하기

직사각형 클래스인 Rectangle을 정의하며 자신이 정사각형인지 알려주는 기능을 만들어보겠습니다.

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

val rectangle = Rectangle(41,43)
println(rectangle.isSqure)
//false

 

디렉터리와 패키지

패키지를 import 하면 패키지 안에 있는 모든 선언을 사용할 수 있습니다.

또한 이름으로 함수를 임포트 하여 사용할 수 있습니다.

 

 

enum과 when

when은 자바의 switch를 대치하면 훨씬 더 강력합니다.

앞으로 더 자주 사용할 프로그래밍 요소입니다.

 

enum은 자바보다 코틀린에서 더 많은 키워드를 사용해야 하는 흔치 않은 예입니다.

enum class Color{
	RED, ORANGE, YELLOW, GREEN
}

 

자바와 마찬가지로 enum 클래스에 프로퍼티나 메서드를 정의할 수 있습니다.

enum class Color(
	var r: Int, var g: Int, var b: Int
){
	RED(255,0,0), GREEN(0,255,0), BLUE(0,0,255);
    
    fun rgb() = (r * 256 + g) * 256 + b
}

println(Color.BLUE.rgb())
//255 출력

코틀린에서 유일하게 세미콜론이 필수인 부분입니다.

 

when을 활용한 enum

fun getMnemonic(color: Color) =
	when (color){
		Color.RED -> "Richard"
		COLOR.GREEN -> "Gave"                    
		Color.BLUE -> "Battle"        
    }

각 분기 끝에 break를 넣지 않아도 됩니다.

 

또한 , 을 활용하여 한 분기에 여러 값을 사용할 수 있습니다.

Color.RED, Color.ORANGE, Color.YELLOW -> "warm"

 

enum 상수 값을 임포트 하여 enum 클래스 수식어 없이 enum을 사용할 수 있습니다.

import ch02.colors.Color.*
fun getWarmth(color: Color) = when(color){
	RED, ORANGE, YELLO -> "warm"
}

 

인자 없는 when

매번 인자를 사용하여 분기 조건에 있는 것을 비교해야 합니다.

인자가 없는 when을 사용하면 불필요한 객체 생성을 막을 수 있지만 코드 읽기가 약간 어려워집니다.

fun mixOptimized(c1: Color, c2: Color) =
when{
	(c1 == YELLO && c2 == BLUE) || (c1 == BLUE && c2 == YELLO) -> GREEN
}

println(mixOptimized(BLUE,YELLO))
//출력 : GREEN

 

스마트 캐스트

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

Expr은 인터페이스입니다.

Num 클래스는 value라는 프로퍼티만 존재하는 단순한 클래스로 Expr 인터페이스를 구현합니다.

Sum 클래스는 Expr 타입의 객체라면 어떤 것이나 Sum 연산의 인자가 될 수 있습니다.

따라서 Num, Sum 클래스가 인자로 올 수 있습니다.

 

 

인터페이스를 이용한 연산을 구현한 코드입니다.

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

fun main() {
    println(eval(Sum(Num(1), Num(2))))
}

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

 

코틀린은 is로 변수 타입을 검사합니다.

이때 e as Num으로 캐스팅하는 게 불필요합니다.

코틀린은 is로 검사하고 나면 캐스팅하지 않아도 처음부터 그 변수가 원하는 타입으로 선언된 것처럼 사용할 수 있습니다.

이를 스마트 캐스트라고 부릅니다.

 

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

코틀린에서 if는 값을 만들어 내기 때문에 자바와 달리 3항 연산자가 따로 없습니다.

이런 특성을 이용하면 if 식을 본문으로 사용하여 return 문과 중괄호를 없앨 수 있습니다.

fun eval(e: Expr) : Int = 
if( e is Num){
	e.value
} else if(e is Sum){
	eval(e.right) + eval(e.left)
} else
	throw IllegalArgumentException("Unknown expression")
|

 

이 코드를 when으로 변환하면 더 다듬을 수 있습니다.

fun eval(e: Expr) : Int =
when (e) {
	is Num ->
		e.value
	is Sum ->
		eval(e.right) + eval(e.left)
	else ->
		throw IllegalArgumentException("Unknown expression")
}

 

while과 for 사용

while, do-while이 존재하며 자바와 크게 다르지 않습니다.

 

코틀린에서 자바의 for 루프에 해당하는 요소가 없습니다.

 

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

val oneToTen = 1.. 10

 

코틀린의 범위는 폐구간 즉, 양끝을 포함하는 구간입니다. (즉, 1.. 10은 10을 포함합니다)

 

피즈 버즈 게임을 구현해보겠습니다.

피즈버즈 게임은 3으로 나누어 떨어지면 피즈, 5로 나누어 떨어지면 버즈 , 3과 5로 모두 나누어 떨어지면 피즈 버즈를 외치는 게임입니다.

 

fun fizzBuzz(i: Int) = when{
    i % 15 == 0 -> "FizzBuzz "
    i % 3 == 0 -> "Fizz "
    i % 5 == 0 -> "Buzz "
    else -> "$i" //다른 경우 자기자신 반환
}

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

 

이번에는 100부터 거꾸로 세되 짝수만으로 게임을 진행하는 코드입니다.

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

 

만약 1.. size을 활용하여 1~size-1까지 표현하고 싶다면 for(x in 0 until size)를 활용할 수 있습니다.

 

Map에 대한 이터레이션

import java.util.*

fun main() {
    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")
    }

}

 

in 키워드를 통해 컬렉션이나 범위의 원소 검사

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

 

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.."
    }

 

코틀린의 예외 처리

코틀린의 예외처리는 자바나 다른 언어의 예외 처리와 비슷합니다.

다른 점은 예외 인스턴스를 만들 때도 new를 붙일 필요가 없습니다.

 

try, catch, finally

import java.io.BufferedReader

fun main() {
    fun readNumber(reader: BufferedReader) : Int? { //던질 수 있는 예외를 명시할 필요가 없습니다
        try{
            val line = reader.readLine()
            return Integer.parseInt(line)
        } catch(e: NumberFormatException){ //예외 타입을 :의 오른쪽에 씁니다
            return null
        }
        finally {
            reader.close()
        }
    }
}

 

try 식으로 사용하기

fun readNumber(reader : BufferedReader) {
    val number = try{
        Integer.parseInt(reader.readLine())
    } catch (e: NumberFormatException){
        null
    }
    println(number)
}

fun main() {
    val reader=  BufferedReader(StringReader("not a number"))
    readNumber(reader)
}