Chapter2. 코틀린 기초

김신영·2022년 9월 24일
0

kotlin-in-action

목록 보기
2/11
post-thumbnail

목차

  • 변수
  • 함수
  • 클래스
  • enum
  • 프로퍼티
  • 제어구조
  • smart cast
  • 예외 처리

Statement(문) vs Expression(식)

  • Expression

    값을 만들어 내며 다른 Expression의 하위 요소로 계산에 참여할 수 있다.

  • Statement

    자신을 둘러싸고 있는 가장 안쪽 블록의 최상위 요소로 존재하며, 아무런 값을 만들어내지 않는다.

문법 요소JavaKotlin
ifif문, StatementExpression

Kotlin에서는 if가 값을 만들어 내기 때문에, 3항 연산자가 필요 없다.
대입 =대입식, ExpressionStatement

대입식과 비교식을 잘못 바꿔 쓰는 버그 방지
제어 구조StatementExpression

Loop를 제외한 모든 제어구조
whenJava에는 없음.Expression

다른 변수에 대입할 수 있다.
throwStatementExpression

다른 식에 포함될 수 있다.
try, catchStatementExpression

try, catch의 값을 변수에 대입할 수 있다.

블록이 본문인 함수

  • 블록이 본문인 함수의 경우, 반드시 함수 반환타입을 명시해야한다.
  • 블록(statement)이 본문인 함수는 내부에 return문이 반드시 있어야 한다.
fun max(a: Int, b: Int): Int {
	return if (a > b) a else b
}

식이 본문인 함수

  • 식이 본문인 함수의 경우, 함수 반환 타입을 생략해도 된다.
  • Type Inference 타입 추론을 통해 컴파일러가 자동으로 함수 반환 타입을 정해준다.
  • 식(expression)이 본문인 함수는 블록 본문을 가질 수 없다.
fun max(a: Int, a: Int) = if (a > b) a else b

val, var

  • val
    - 변경 불가능한 Immutable 참조를 저장하는 변수
    - Java에서 final
  • var
    - 변경 가능한 Mutable 참조
val answer = 1

val answer2: Int  // 초기화 하지 않는다면 타입 추론이 불가능하기 때문에 반드시 타입 지정 해야한다.
// ...
answer2 = 2

val message: String
// ...
if (canPerformOperation()) {
	message = "Success"
}
else {
	message = "Failed"
}

val answer3 = 3
// ...
answer3 = -1   // Compile Error (Val cannot be reassigned)

val answer4 = 4
// ...
answer4 = "no answer"  // Compile Error (Val cannot be reassigned, Type mismatch)

val languages = arrayListOf("Java", "Python", "Go", "C", "Javascript")
languages.add("Kotlin")   // OK, val means just immutable reference

String Template

val language = "Kotlin"
println("Hello, ${language}!")
language = "Python"
print(f"Hello, {language}!")

Class

  • Kotlin의 경우, 기본 가시성(visibility)은 public
class Person(val name: String)
public class Person {
	private final String name;

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

	public String getName() {
		return name;
	}
}

Property

  • val : getter 만 제공
  • var : getter, setter 모두 제공
  • property 이름 그대로 사용
class Person(val name: String, val isMarried: Boolean)
$ kotlinc
Welcome to Kotlin version 1.7.10 (JRE 17.0.4.1+1-LTS-2)
Type :help for help, :quit for quit
>>> class Person(val name: String, val isMarried: Boolean)
>>> val person = Person("rolroralra", true)
>>> println(person.name)
rolroralra
>>> println(person.isMarried)
true

Custom Property Accessor

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

Package

  • Java와 거의 동일
  • import도 거의 동일
  • 추가적인 기능으로, 1개의 xxx.kt 파일에 여러 개의 클래스를 넣을 수 있다.

NOTE
1개의 kt 확장자 파일에 여러 개의 클래스를 넣으면, 해당 파일명이 패키지 마지막 이름이 되어서, 한 개의 kt파일 자체가 패키지 디렉토리를 의미하게 된다.
클래스 각각이 아주 작은 경우, 주로 사용.

Enum

  • enum 은 soft keyword, enum이라는 이름 사용 가능.
  • 프로퍼티나 메서드 정의 가능
enum class Color {
	RED, ORANGE, YELLOW, GRREN, BLUE, INDIGO, VIOLET
}
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

  • 자바의 switch 대체
  • break 넣지 않아도 됨 break
  • 한 분기 조건 안에 여러 매치 패턴을 조합 가능. ,
  • 분기 조건 안에 상수 뿐 아니라, 임의의 객체도 사용 가능
  • when의 검사 대상을 변수에 포획할 수 있다. when (val response = executeRequest())
import geometry.colors.Color

fun getMnemonic(color: Color) =  
    when (color) {  
        Color.RED -> "Richard"  
        Color.ORANGE -> "Of"  
        Color.YELLOW -> "York"  
        Color.GREEN -> "Gave"  
        Color.BLUE -> "Battle"  
        Color.INDIGO -> "In"  
        Color.VIOLET -> "Vain"  
    }
import geometry.colors.Color
import geometry.colors.Color.*

fun getWarmth(color: Color) = when (color) {  
    RED, ORANGE, YELLOW -> "warm"  
    GREEN -> "neutral"  
    BLUE, VIOLET -> "cold"  
    else -> "not supported"  
}
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("Not Supported")  
    }
fun Request.getBody() =
	when (val response = executeRequest()) {
		is Success -> response.body
		is HttpError -> throw HttpException(response.status)
	}

인자 없는 when

  • when 에 인자가 없을 경우, 분기 조건이 Boolean 결과를 계산하는 식이어야 한다.
fun mixOptimized(c1: Color, c2: Color) =  
    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("Not Supported")  
    }

Smart Cast

  • 타입 검사와 타입 캐스트를 조합

  • is로 변수에 든 값의 타입을 검사한 다음에 그 값이 바뀔 수 없는 경우에만 작동한다.

  • 타입 검사 is
    - java의 타입 검사 instanceof

  • 명시적 타입 캐스트 as

fun eval(expr: Expr): Int {  
    if (expr is Num) {  
        val n = expr as Num     // 명시적 타입 캐스트. 불필요한 코드 (No cast needed)
        return n.value  
    } else if (expr is Sum) {  
        return eval(expr.left) + eval(expr.right)   // Smart Cast  
    } else {  
        throw Exception("Not Supported")  
    }  
}
// smart cast
fun eval(expr: Expr): Int {  
    if (expr is Num)  
        return expr.value  
    else if (expr is Sum)  
        return eval(expr.left) + eval(expr.right)  
    else  
        throw Exception("Not Supported")  
}
// when 활용, smart cast
fun eval(expr: Expr): Int =  
    when (expr) {  
        is Num -> expr.value  
        is Sum -> eval(expr.left) + eval(expr.right)  
        else -> throw Exception("Not Supported")  
}
  • 식(expression)이 본문인 함수는 블록 본문을 가질 수 없다.
  • 블록(statement)이 본문인 함수는 내부에 return문이 반드시 있어야 한다.
fun evalWithLogging(expr: Expr): Int =  
    when (expr) {  
        is Num -> {  
            println("nums: ${expr.value}")  
            expr.value  // 블록의 마지막 식이므로, expr.value가 return 된다.
        }  
        is Sum -> {  
            val left = evalWithLogging(expr.left)  
            val right = evalWithLogging(expr.right)  
  
            println("sums: $left + $right")  
            left + right  
        }  
        else -> throw Exception("Not Supported")  
    }

while

  • while, do while 의 경우, java와 동일
while (isValid) {
	/* ... */
}

do {
	/* ... */
} while (isValid)

for

수에 대한 이터레이션

  • 범위: Range
  • 수열: Progression
  • `for (int i = 0; i < 10; i++)
  • 0..10 : [0, 10] 폐구간
  • 0 until 10 : [0, 10) 개구간
  • 100 downTo 1 : 100,99,98,......,1 (included)
  • 100 downTo 1 step 2 : 100, 98, 96, ......, 2
  • 숫자 타입, 문자 타입 모두 적용 가능
    - 'A'..'Z'
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)} ")
	}
	
	println()
}

Map에 대한 이터레이션

  • for ((key, value) in map)
val binaryReps = ('A'..'Z').groupBy ({ it }, { Integer.toBinaryString(it.code)})  
  
for ((alphabet, binary) in binaryReps) {  
    println("$alphabet = $binary")  
}

List에 대한 이터레이션

  • for (value in list)
  • for (index in list.indices)
  • for ((index, value) in list.withIndex())
val list = arrayListOf("10", "11", "1001")  
  
for (element in list) {  
    println(element)  
}  
  
for (i in list.indices) {  
    println("index = $i, element = ${list[i]}")  
}  
  
for ((index, element) in list.withIndex()) {  
    println("list[$index] = $element")  
}

Set에 대한 이터레이션

  • for (value in set)
  • for (index in set.indices) , set.elementAt(index)
  • for ((index, element) in set.withIndex())
val set = setOf("10", "11", "1001")  
  
for (element in set) {  
    println(element)  
}  
  
for (i in set.indices) {  
    println("index = $i, element = ${set.elementAt(i)}")  
}  
  
for ((index, element) in set.withIndex()) {  
    println("set.elementAt($index) = $element")  
}

in

  • in 연산자를 사용해 어떤 값이 범위/컬렉션에 속하는지 검사할 수 있다.
  • !in
  • 범위는 문자에만 국한 되지 않는다.
  • Comparable 을 구현한 클래스의 경우, 범위를 만들 수 있다.
    - 명백히 말하자면, 범위 이터레이션을 도는게 아니라, 범위의 비교 조건으로 알아서 변환해준다.
    - 더 명백히 말하면, 문자나 숫자들도 범위 이터레이션 돌면서 속하는지 확인하지 않고, 자동으로 속하는 조건문으로 변환한다.
fun recognize(c: Char) = when (c) {  
    in '0'..'9' -> "Digit"  
    in 'a'..'z', in 'A'..'Z' -> "Alphabet"  
    in " \t\n\r" -> "LineAndSpace"  
    in "!@#$%^&*()[]{}()<>~\\|;:'\",./?" -> "Special"  
    else -> throw Exception("Not Supported")  
}  
  
println(recognize('8'))  
println(recognize('d'))  
println(recognize(' '))  
println(recognize('@'))
println("Kotlin" in "A".."K")   // false  = "Kotlin" >= "A" && "Kotlin" <= "K"
println("Kotlin" in "A".."L")   // true   = "Kotlin" >= "A" && "Kotlin" <= "L"

println("Kotlin" in setOf("Java", "Python", "Go", "Scala"))  // false

throw

  • Java 예외 처리 방식과 거의 비슷하다.
  • 예외 인스턴스를 만들 때도, 클래스 인스턴스와 마찬가지로 new 를 붙일 필요가 없다.
  • 단, throw 는 식(Expression)이므로 다른 식에 포함될 수 있다.
fun percentage(value: Int, totalValue: Int = 100) =  
    if (value in 0..totalValue) {  
        value.toDouble() * 100 / totalValue  
    } else {  
        throw IllegalArgumentException("Value must be between 0 and $totalValue: $value")  
    }

println("${percentage(47, 250)}%")  // 18.8%

try catch

  • Kotlin의 경우, checked exception , unchecked exception 을 구별하지 않는다.
  • Kotlin에서는 함수가 던지는 예외를 지정하지 않아도 된다.
  • 또한 발생한 예외를 잡아내도 되고 잡아내지 않아도 된다.

NOTE
Java의 경우, checked/unchecked exception을 구분하며, checked exception의 경우는 모두 catch로 처리하거나 throws 절에 명시해야 한다.

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

val br = BufferedReader(InputStreamReader(System.`in`))  
println(readNumber(br))
profile
Hello velog!

0개의 댓글