매일 매일, 차곡 차곡 쌓기



완벽하지 않은 것을 두려워 말며,
완성도를 높히는데 집중하자.

개발 언어/코틀린

2장. 코틀린 기초

blockbuddy93 2024. 3. 26. 17:33

2.1 기본 요소 : 함수와 변수

2.1.1 함수

  • 함수를 선언할때 func 키워드를 사용한다.
  • 타라미터 이름 뒤에 파라미터의 타입을 쓴다.
  • 함수를 최상위 수준에 정의할 수 있다. 꼭 클래스 안에 함수를 넣어야할 필요가 없다.
  • 배열도 일반적인 클래스, 코틀린에는 자바와 달리 배열 처리를 위한 문법이 없다.
  • 식이 본문인 함수는 반환타입을 적지 않아도 컴파일러가 자동으로 반환타입을 정해준다.
  • 코틀린에서 if는 식(expression)이다. 자바에서는 모든 제어구조가 문(statement)이지만, 코틀린에서는 Loop를 제외하면 대부분 제어 구조가 식(expression)이다.
    • 문(statement)은 자신은 둘러싸고 있는 가장 안쪽 블록의 최상위 요소로 아무런 값을 만들어내지 않는다.
    • 식(expression)은 값을 만들어 내며 다른 식의 하위 요소로 계산에 참여할 수 있다.
  • 반면, 대입문은 자바에서 식이었으나, 코틀린에서는 문(statement) 이다.

 

2.1.2 변수

  • val : immutable 한 참조를 저장하는 변수. 자바의 final과 같다.
  • var : mutable 참조, 변수의 값이 바뀔 수 있다. 자바의 일반 변수
  • 기본적으로 모든 변수는 불변 변수로 선언하고, 추후 필요에 따라 var로 변경하는것을 추천

 

2.1.3 문자열 템플릿에 변수 할당

  • 코틀린에서 변수를 문자열 안에 사용할 수 있다.
// 문자열 템플릿 사용
fun main(args: Array<String>) {
    val name = if(args.size > 0) args[0] else "Kotlin"
    println("Hello, $name!")

    if(args.size > 0){
        println("Bye, ${args[0]}")   // args 배열의 원소를 넣기 위해${} 구문 사용
    }

    println("\$")  // 달려 표시 ($)를 쓸 경우에는 이스케이프(\) 사용

}

 

2.2 클래스와 프로퍼티

2.2.1 클래스

  • 자바의 경우 필드가 늘어날 수록 생성자의 파라미터 개수도 늘어나게 된다. 즉 반복적으로 늘어나는 코드가 많아진다.
// Java Person 클래스
public class Person {
    private final String name;
    
    public String getName() {
        return name;
    }

    public Person(String name) {
        this.name = name;
    }
}

 

  • 코틀린은 다음형식으로 간단하게 표현가능하다. 접근제어자는 기본이 public이라 생략이 가능하다.
// 코틀린으로 변환한 Person 클래스
class Person (val name : String)

 

2.2.2 프로퍼티

  • 코틀린에서는 필드와 접근자를 묶어 프로퍼티라고 부른다.
class Person(
    val name: String,   // private 변수, 읽기 전용, 단순한 공개 getter만 만들어낸다.
    var isMarried: Boolean  // private 변수, 공개 getter, 공개 setter 만들어낸다.
)

val person = Person("Bob", true)  // new 키워드 사용 안하고 생성자 호출
// 프로퍼티 이름을 직접 사용해도 코틀린이 자동으로 getter 호출
println(person.name)
println(person.isMarried)

 

2.2.3 커스텀 접근자

  • 클라이언트가 프로퍼티에 접근할때(get()) 접근자 로직을 커스텀 할 수 있다.
class Rectangle(val height: Int, val width: Int) {
    val isSquare: Boolean
        get() {
            return height == width
        }
			// get() = height == width
}

 

2.2.4 코틀린 소스코드 구조 : 디렉터리와 패키지

  • 모든 코틀린 파일의 맨 앞에는 package 문을 넣을 수 있다.
  • 같은 패키지에 속해 있다면, 다른 파일에서 정의한 선언일지라도 직접 사용이 가능하다.
  • 다른 패키지에 있다면 import를 선언하여 선언

 

// 클래스와 함수 선언을 패키지에 넣기
package geometry.shapes

import java.util.*

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

fun createRandomRectangle(): Rectangle {
    val random = Random()
    return Rectangle(random.nextInt(), random.nextInt())
}
// 다른 패키지에 있는 함수 임포트하기
package geometry.shapes.createRandomRectangle

fun main(args: Array<String>) {
	println(createRandomRectangle().isSquare)
}

 

2.3 선택 표현과 처리 : enum과 when

2.3.1 enum 클래스 정의

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  // enum 클래스 안에 메소드 정의
}

fun main() {
    println(Color.BLUE.rgb())
}

 

2.3.2 when으로 enum 클래스 다루기

  • 자바와 달리 각 분기문 끝에 break를 넣지 않아도 된다.
  • 한 분기문 안에 여러 값을 매치 패턴으로 사용할 경우 콤마로 구분한다.
fun getMnemonic(color: Color) =
 when (color) {   // 함수 반환 값으로 when 식을 사용
    Color.RED -> "Rechard"
    Color.ORANGE -> "Of"
    Color.YELLOW -> "York"
    Color.GREEN -> "Grave"
    Color.BLUE -> "Battle"
    Color.INDIGO -> "In"
    Color.VIOLET -> "Vain"
}

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

fun main() {
    println(getMnemonic(Color.BLUE))
    println(getWarmth(Color.ORANGE))
}

 

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

  • 자바의 switch와 달리 코틀린의 when 분기 조건은 임의의 객체를 허용한다.
fun mix(c1: Color, c2: Color) =
    // when 식의 인자로 아무 객체나 사용 가능, when은 받은 인자 객체가 각 분기 조건에 있는 객체와 같은지 테스트
    when (setOf(c1, c2)) {
        setOf(RED, YELLOW) -> ORANGE
        setOf(YELLOW, BLUE) -> GREEN
        setOf(BLUE, VIOLET) -> INDIGO
        // 매치되는 분기 조건이 없으면 이 문장 실행
        else -> throw Exception("Dirty color")
    }

 

2.3.4 인자 없는 when 사용

  • when 에 아무 인자도 없으려면 각 분기문 조건이 불리언 결과를 계산하는 식이어야 한다.
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")
    }

 

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

  • ( 1 + 2 ) + 4 와 같은 산술식을 계산하는 함수를 만들어 보자.
// 식을 위한 인터페이스
interface Expr

// value라는 프로퍼티만 존재하는 단순한 클래스, Expr 인터페이스를 구현
class Num(val value: Int) : Expr

// Expr 타입의 객체라면 Sum 연산의 인자가 될 수 있다.
// 따라서, Num이나 다른 Sum이 Sum의 인자로 올 수 있다.
class Sum(val left: Expr, val right: Expr) : Expr

// Java 스타일
// if 사용해서 식 계산
fun eval(e: Expr): Int {
    if (e is Num) {
        val n = e as Num
        return n.value
    }
    if (e is Sum) {
        // 변수 e 에 대해 스마트 캐스트를 사용한다.
        return eval(e.right) + eval(e.left)
    }

    throw IllegalArgumentException("Unkown expression")
}

 

  • Expr은 식을 위한 인터페이스
  • Sum, Num 클래스는 Expr 인터페이스를 구현하는 클래스
  • 코틀린에서는 is를 사용해서 변수 타입을 검사
    • 자바에서는 instanceof로 변수타입 확인 후, 개발자가 명시적으로 타입 캐스팅 한 후 그 타입에 속한 메버에 접근 가능
    • 코틀린에서는 is 로 검사한 후, 참이면 컴파일러가 알아서 스마트 캐스팅 해줌
    • 스마트 캐스트는 is로 변수에 든 값의 타입을 검사한 다음, 그 값이 바뀔 수 없는 경우에만 동작
  • 명시적으로 타입 캐스팅 하려면 as 키워드를 사용 -> val n = e as Num

 

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

  • 코틀린의 if는 식 이며 값을 반환한다.
  • 코틀린에서는 자바와 달리 3항 연산자가 따로 없다.
  • 자바의 if는 값을 반환하지 않는다.

 

// 값을 만들어내는 if 식

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("Unkown expression")
    }
// if 중첩 대신 when 사용하기
fun eval(e: Expr): Int =
    when (e) {
        is Num -> e.value  // 인자 타입을 검사하는 when 분기에서 스마트 캐스트 됨
        is Sum -> eval(e.left) + eval(e.right)
        else -> throw IllegalArgumentException("Unkown expression")
    }

 

 

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

  • if나 when 모두 분기에 브록 사용가능하며, 블록의 마지막 문장이 블록 전체의 결과가 된다.
fun evalWithLogging(e: Expr): Int =
    when (e) {
        is Num -> {
            println("num: ${e.value}")
            e.value   // 블록의 마지막 식이므로 e.value가 반환된다.
        }
        is Sum -> {
            val left = evalWithLogging(e.left)
            val right = evalWithLogging(e.right)
            println("sum: $left + $right")
            left + right
        }
        else -> throw IllegalArgumentException("Unkown expression")
    }

 

2.4 대상을 이터레이션 : while과 for 루프

2.4.1. while 루프

// 조건이 참인 동안 본문 반복 실행
while(조건) {
	/*....*/
}

// 맨 처음 무조건 본문 한번 실행 후, 조건이 참인 동안 본문 반복 실행
do{
	/*....*/
} while(조건)

 

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

  • step : 증가값
  • downTo : 역방향
  • .. : 항상 범위의 끝 포함
  • untill : 끝 값 포함하지 않음
fun fizzbuxx(i: Int) =
    when {
        i % 15 == 0 -> "FizzBuzz "
        i % 3 == 0 -> "Fizz "
        i % 5 == 0 -> "Buzz "
        else -> "$i "
    }

for (i in 1..100) {
        println(fizzbuxx(i))
    }

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

 

2.4.3 맵에 대한 이터레이션

// A ~ F 까지 문자 범위 이터레이션
for(c in 'A'..'F'){
    // 아스키 코드를 2진 표현으로 변경
    val binary = Integer.toBinaryString(c.toInt())
    // c를 키로 c의 2진 표현을 맵에 넣는다.
    binaryReps[c] = binary  // 자바의 map put 과 유사: binaryReps.put(c, binary)
}

// 맵에 대한 이터레이션, 맵의 Key와 Value를 두 변수에 대입
for((letter, binary) in binaryReps) {
    println("$letter = $binary")
}
val list = arrayListOf("10", "11", "1001")

// 인덱스와 함께 컬렉션을 이터레이션 한다.
for ((idx, element) in list.withIndex()){
    println("$idx: $element")
}

 

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

  • in : 어떤 값이 범위에 속하는지 확인
  • 비교가 가능한 클래스 (java.lang.Comparable 인터페이스를 구현한 클래스)라면 클래스 인스턴스 객체를 사용해 범위를 만들 수 있다.

 

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

println(isLetter('q'))      // true
println(isNotDigit('x'))    // true

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'))   

 

2.5 코틀린 예외처리

  • 코틀린의 기본 예외 처리 구무은 자바와 유사
  • 예외 인스턴스를 만들 때 new 키워드를 붙이 필요가 없다.
  • 자바와 달리 코틀린의 throw는 식이므로, 다른 식에 포함 가능
al percentage =
    if (number in 0..100)
        number
    else
				// throw는 식이다. -> 값을 만들어낸다.
        throw IllegalArgumentException("A percentage value must be between 0 and 100: $number")

 

2.5.1 try, catch, finally

  • 자바 코드와 가장 큰 차이는 throws 절에 코드가 없다는 점.
  • 또 자바에서는 함수를 작성할 때, 함수 선언 뒤에 throws IOException을 붙여야 한다.
  • IOException은 Checked Exception 이기 때문에, 자바는 Checked Exception을 명시적으로 처리해야한다.
  • 어떤 함수가 던질 가능성이 있는 예외나 그 함수가 호출한 다른 함수에서 발생할 수 있는 모든 예외를 catch로 처리해야 하며, 처리하지 않은 예외는 throws 절에 명시한다.
  • 코틀린도 다른 최신 JVM 언어 처럼 Checked Exception, UnChecked Exception을 구별하지 않는다.

 

2.5.2 try를 식으로 사용

  • 코틀린의 try 키워드는 식으로 값을 만들어낸다.
  • 따라서 try의 값을 변수에 대입할 수 있다. try는 if와 달리 중괄호로 둘러싸야 한다.
fun readNumber(reader: BufferedReader) {
		// 예외가 발생하지 않으면, 이 값을 사용
    val number = try {
        Integer.parseInt(reader.readLine())
    } catch (e: NumberFormatException) {
        // return //catch 블록 다음 코드는 실행 되지 않음
        null      // 예외가 발생하면 null값을 사용
    }
    println(number)
}

 

 

'개발 언어 > 코틀린' 카테고리의 다른 글

3장. 함수 정의와 호출  (0) 2024.03.27
1장. 코틀린이란 무엇이며 왜 필요한가?  (0) 2024.03.25