-
[코틀린 인 액션] 코틀린 기초프로그래밍 언어/kotlin 2021. 7. 6. 14:35728x90반응형
2.1 코틀린 맛보기
함수
- 자바와 달리 함수를 최상위 수준에 정의할 수 있다.
- 배열도 일반적인 클래스와 마찬가지로 사용 가능하다.
- println 과 같이 자바 표준 라이브러리 함수를 간결하게 사용할 수 있게 감싼 래퍼를 제공한다.
함수 기본 구조
- 블록이 본문인 함수
블록이 본문인 함수가 값을 반환할 때는, 반드시 변환 타입을 지정하고 return문을 사용해 반환 값을 명시해야 한다.
fun max(a: Int, b: Int): Int { return if (a > b) a else b }
- 식이 본문인 함수
fun max(a: Int, b: Int): Int = if (a > b) a else b
반환 타입 생략 가능하다. 식이 본문인 함수의 경우, 굳이 사용자가 반환 타입을 적지 않아도 컴파일러가 함수 본문 식을 분석해서 식의 결과 타입을 함수 변환 타입으로 정해준다. => 타입 추론
fun max(a: Int, b: Int) = if (a > b) a else b
문(statement) vs. 식(expression)
- 식: 값을 만들어내며 다른 식의 하위 요소로 계산에 참여할 수 있다.
- 문: 자신을 둘러싸고 있는 가장 안쪽 블록의 최상의 요소로 존재하며, 아무런 값을 만들어내지 않는다.
자바에서는 모든 제어 구조가 문인 반면,
코틀린에서는 루프를 제외한 대부분의 제어구조가 식이다.
변수
타입 지정을 하지 않으면 컴파일러가 초기화 식을 분석해서 초기화 식의 타입을 변수 타입으로 지정한다.
val answer = 42
초기화 식을 사용하지 않고 변수를 선언하려면 변수 타입을 반드시 명시해야 한다.
val answer: Int answer = 42
변수 선언 시 사용하는 키워드
- val: 변경 불가능한 참조를 저장하는 변수. 일단 초기화하고 나면 재대입 불가. val 참조 자체는 불변일지라도, 그 참조가 가리키는 객체의 내부 값은 변경될 수 있다.
- var: 변경 가능한 참조. 이런 변수의 값은 바뀔 수 있다. 타입은 고정.
기본적으로 모든 변수는 val로 선언하고, 나중에 변경이 꼭 필요한 경우에만 var로 변경하자.
변경 불가능한 참조와 변경 불가능한 객체를 부수 효과가 없는 함수와 조합하면 함수형 코드에 가까워 진다.
문자열 템플릿
fun main(args: Array<String>) { val name = if (args.size > 0) args[0] else "Kotlin" println("Hello, $name!") }
2.2 코틀린 주요 특성
클래스
클래스라는 개념의 목적: 데이터를 캡슐화하고 캡슐화한 데이터를 다루는 코드를 한 주체 안에 가두는 것.
- 자바의 Person 클래스
public class Person { private final String name; public Person(String name) { this.name = name; } public String getName() { return name; } }
- 코틀린의 Person 클래스
class Person(val name: String)
코틀린에서 기본 가시성은 public 이므로 생략이 가능하다.
프로퍼티
자바에서는 데이터를 필드에 저장하고, 데이터에 접근하는 통로로 접근자 메소드를 제공한다.
자바에서는 필드와 접근자를 한데 묶어 프로퍼티라고 부른다. 코틀린에서는 프로퍼티를 기본 기능으로 제공한다.
class Person( val name: String, // 읽기 전용 프로퍼티 val isMarried: Boolean // 쓸 수 있는 프로퍼티 )
값을 저장하기 위한 비공개 필드와, 그 필드에 값을 저장하기 위한 세터, 필드의 값을 읽기 위한 게터로 이루어진 간단한 디폴트 접근자 구현을 제공한다.
커스텀 접근자
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 }
코틀린 소스코드 구조
코틀린에서는 여러 클래스를 한 파일에 넣을 수 있고, 파일의 이름도 마음대로 정할 수 있다.
2.3 코틀린 응용
enum과 when
enum class Color { RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET }
enum 클래스 안에도 프로퍼티나 메소드를 정의할 수 있다.
when 도 값을 만들어내는 식이기 때문에, 식이 본문인 함수에 when을 바로 사용할 수 있다. 다음은 여러 줄 식을 본문으로 하는 함수이다. 색이 특정 enum 상수와 같을 때 그 상수에 대응하는 문자열을 돌려준다. 자바와 달리, 각 분기의 끝에 break를 넣지 않아도 된다. 한 분기에 여러 값 매치 패턴을 사용하고 싶으면, 그 값을 콤마(,)로 분리한다.
fun getMnemonic(color: Color) = when (color) { Color.RED -> "Richard" Color.RED -> "Richard" Color.RED -> "Richard" Color.RED -> "Richard" Color.RED -> "Richard" Color.RED -> "Richard" Color.RED -> "Richard" Color.RED -> "Richard" Color.RED -> "Richard" }
코틀린 when 의 분기 조건은 임의의 객체를 허용한다. when 식의 인자로 아무 객체나 사용할 수 있고, 이렇게 인자로 받은 객체가 각 분기 조건에 있는 객체와 같은지 테스트한다. ( 조건의 동등성 검사 )
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("Dirty color") // 매치되는 분기 조건이 없으면 이 문장 실행 }
- setOf: 코틀린 표준 라이브러리에 있는 인자로 전달받은 여러 객체를 그 객체들을 포함하는 집합인 Set 객체로 만드는 함수
임의의 불리언(Boolean)식을 조건으로 사용하는 모습도 보자.
인자가 없는 when식을 사용하면 불필요한 객체의 생성을 막을 수 있다. when 이 아무 인자도 없으려면 각 분기의 조건이 불리언 결과를 계산하는 식이어야 한다.
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("Dirty color") }
추가적인 객체를 만들지 않는다는 장점은 있지만 가독성은 더 떨어진다.
스마트캐스트
타입 검사와 타입 캐스트를 조합한다.
식을 위한 Expr 인터페이스. Sum과 Num클래스는 그 Expr 인터페이스를 구현한다. Expr은 아무 메소드도 선언하지 않으며, 단지 여러 타입의 식 객체를 아우르는 공통 타입 역할만 수행한다.
interface Expr class Num(val value: Int) : Expr // value라는 프로퍼티만 존재하는 단순한 클래스로 Expr 구현 class Sum(val left: Expr, val right: 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") }
코틀린에서는 is를 사용해서 변수 타입을 검사한다.
자바에서는 어떤 변수 타입을 instanceof로 확인한 다음에 그 타입에 속한 멤버에 접근하기 위해서는 명시적으로 변수 타입을 캐스팅 해야 한다. 코틀린에서는 컴파일러가 대신 캐스팅 해준다. 우선 is로 검사하고 나면 굳이 변수를 원하는 타입으로 캐스팅하지 않아도 마치 처음부터 그 변수가 원하는 타입으로 선언된 것처럼 사용할 수 있다. (= 스마트 캐스트)
클래스 프로퍼티에 대해서 스마트 캐스트를 사용한다면 그 프로퍼티는 반드시 val 이어야 하며 커스텀 접근자 사용한것이어도 안된다. 원하는 타입을 명시적으로 캐스팅 하려면 as 키워드를 사용한다.
- 코틀린 스타일로 리팩토링
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") }
코틀린에서는 if 가 값을 만들어내기 때문에 return 문과 중괄호를 없애고 if 식을 본문으로 사용해 더 간단하게 만들 수 있다.
- if 문의 중첩 대신 when 사용
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") }
if 와 when 분기에서 블록을 사용할 수 있고, 블록의 마지막 식이 블록의 결과이다. 하지만, 식이 본문인 함수는 블록을 본문으로 가질 수 없고 블록이 본문인 함수 내부에는 반드시 return 값이 있어야 한다.
2.4 대상을 이터레이션 : while과 for루프
이터레이션
- 대상을 이터레이션: while과 for루프
-> 자바와 동일
- 수에 대한 이터레이션: 범위와 수열
자바에 해당하는 for 루프에 해당하는 요소가 없다. 코틀린에서 범위를 사용.
.. 은 항상 범위의 끝 값을 포함한다. 만약 닫힌 범위를 만들고 싶다면 until 함수를 사용하면 된다.
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부터 100까지의 수열 이터레이션 } for (i in 100 downTo 1 step 2) { print(fizzbuzz(i)) // 증가 값 step을 갖는 수열에 대해 이터레이션 }
- 맵에 대한 이터레이션
2진 표현을 출력하는 프로그램
val binaryReps = TreeMap<Char, String>() // 키에 대해 정렬하기 위해 TreeMap 사용 for (c in 'A'..'F') { val binary = Integer.toBinaryString(c.toInt()) binaryReps[c] = binary // c를 키로 c의 2진 표현을 맵에 넣는다 } for ((letter, binary) in binaryReps) { println("$letter = $binary") }
이터레이션 하려는 컬렉션의 원소를 푸는 방법이다. 키와 값을 풀어서 letter와 binary라는 두 변수에 저장한다.
- in으로 컬렉션이나 범위의 원소 검사
in 을 사용해 어떤 값이 범위에 속하는지 검사할 수 있다. 반대로 !ind을 사용하면 어떤 값이 범위에 속하지 않는지 검사할 수 있다.
2.5 코틀린의 예외 처리
예외 처리
if (percentage !in 0..100) { throw IllegalArgumentException( "A Percentage value must be between 0 and 100: $percentage") }
코틀린의 throw는 식이므로 다른 식에 포함될 수 있다.
- try, catch, finally 절
fun readNumber(reader: BufferedReader): Int? { try { val line = reader.readLine() return Integer.parseInt(line) } catch (e: NumberFormatException) { return null } finally { reader.close() } }
자바는 어떤 함수가 던질 가능성이 있는 예외나 그 함수가 호출한 다른 함수에서 발생할 수 있는 예외를 모두 catch로 처리해야 하며, 처리하지 않은 예외는 throws 절에 명시해야 한다. 하지만, 체크 예외라 하더라도, 예외를 잡아내는 코드가 불필요한 경우가 있다.
코틀린에서는 체크예제와 언체크예제를 구별하지 않고, 또한 함수가 던지는 예외를 지정하지 않고 발생한 예외를 잡아내도 되고 잡아내지 않아도 된다.
- 체크 예외
체크 예외는 RuntimeException을 상속하지 않는 예외를 말한다. 체크 예외가 발생할 수 있는 메소드를 사용할 경우에는, 복구가 가능한 예외들이기 때문에 catch문으로 예외를 잡든지, throws 로 예외를 자신을 호출한 클래스로 던지든지 해야한다. 아니면 컴파일 에러가 발생한다. 대표적인 체크예외로는 IOException 이나 SQLException 등이 존재한다.
- 언체크 예외
RuntimeException을 상속한 예외를 말한다. 명시적으로 예외 처리를 강제하지 않는다. 언체크 예외는 프로그램에 오류가 있을 때 발생하도록 의도된 것으로, catch문으로 잡거나, throws로 선언하지 않아도 된다. 대표적인 언체크 예외로는 NullPointerException이나 IllegalArgumentException 등이 존재한다.
- try를 식으로 사용
fun readNumber(reader: BufferedReader) { val number = try { Integer.parseInt(reader.readLine()) } catch (e: NumberFormatException) { return } println(number) }
try의 본문은 반드시 블록으로 둘러싸야 한다. catch에서 값을 반환할수도 있다.
728x90'프로그래밍 언어 > kotlin' 카테고리의 다른 글
[Kotlin In Action] 코틀린 타입 시스템 (0) 2021.08.06 [Kotlin in Action] 람다로 프로그래밍 (0) 2021.07.26 [Kotlin in action] 클래스, 객체, 인터페이스 (0) 2021.07.25 [Kotlin in Action] 함수의 정의와 호출 (0) 2021.07.13 [Kotlin] 코루틴을 이해해보자 (0) 2021.07.04