Kotlin in Action. [2] 코틀린 기초
코틀린 기초
함수
hello world 예시 함수
fun main(args: Array<String>) { // example, hello world function
println("hello world")
}
- 함수를 선언할때,
fun
키워드 사용.
- 파라미터 이름 뒤에 그 파라미터의 타입을 씀.
- 함수를 최상위 수준에 정의 할 수 있다. 자바와 달리 꼭 클래스 안에 함수를 넣어야 할 필요가 없다.
- 코틀린에는 자바와 달리 배열 처리를 위한 문법이 따로 존재하지 않음.
코틀린 표준 라이브러리
는 표준 자바 라이브러리 함수
를 간결하게 사용할 수 있게 감싼 Wrapper(래퍼)
를 제공한다. Ex. println
;
을 붙이지 않아도 됨
식이 본문인 함수
문(statement)과 식(experssion)의 구분
- 코틀린에서는 if는 식이지 문이 아니다.
식
은 값을 만들어 내며 다른 식의 하위 요소로 계산에 참여할 수 있음.
문
은 자신을 둘러싸고 있는 가장 안쪽 블록의 최상위 요소로 존재하며 아무런 값을 만들어내지 않는다.
- 자바에서는 모든 제어 구조가
문
인 반면 코틀린에서는 루프를 제외한 대부분의 제어 구조가 식
- 대입문은 자바에서는
식
이었으나 코틀린에서는 문
이 됐다.
fun max1(a : Int, b: Int): Int { // 블록이 본문인 함수, 반환 타입 생략 불가
return if ( a > b ) a else b
}
fun max2(a : Int, b: Int): = if (a > b) a else b // 식이 본문인 함수
// 타입 추론(type inference) 기능으로 함수의 변환형 Int가 생략되도 무방하다.
// 컴파일러가 함수 본문 식을 분석해 식의 결과 타입을 함수 반환 타입으로 정해준다.
변수
val answer = 42
val answer: Int = 42
/////////////////////////////////////////////////
val answer: Int
answer = 42
-
식이 본문인 함수
에서와 마찬가지로 타입을 지정하지 않아도, 컴파일러가 초기화 식
을 분석해서 초기화 식
의 타입을 변수 타입으로 지정한다.
-
초기화 식을 사용하지 않고 변수를 선언하려면 반드시 변수 타입을 명시해야 한다.
변경 가능한 변수와 변경 불가능한 변수
val
- 변경 불가능한(
immutable
) 참조를 저장하는 변수다.
- val로 선언된 변수는 일단 초기화하고 나면
재대입
이 불가능.
- 자바로 치면
final
- val 참조 자체는 불변일지라도 그 참조가 가리키는 객체의 내부 값은 변경가능.
- val 변수는 블록을 실행할 때 정확히 한 번만 초기화 돼야 함.
val languages = arrayListOf("Java") // immutable (불변 참조)
languages.add("Kotlin") // 참조가 가리키는 객체 내부를 변경 가능.
var
- 변경 가능한(
mutable)
참조.
- 변수의 값이 바뀔 수 있다.
- 자바의 일반 변수에 해당한다.
- 변수의 값을 변경할 수 있지만, 변수의 타입은 고정돼어 바뀌지 않음.
var answer = 42
answer = "no answer" // Error: type mismatch, 컴파일 오류, 강제 형 변환 필요
문자열 템플릿
val name = "myName"
println("hello, $name")
println("hello, ${name}")
println("hello, ${name[0]}")
println("hello, ${if (name.size > 0) name[0] else "someone"}")
Class and Property(프로퍼티)
java vs kotlin
public class Person { //java
private final String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
- 자바의 경우 필드가 둘 이상으로 늘어나면 생성자인
Person(string name)
의 본문에서 파라미터를 이름이 같은 필드에 대입하는 대입문의 수도 늘어남.
class Person(val name: String) // kotlin
- 코틀린의 경우 코드가 없이 데이터만 저장하는 클래스(
값 객체, value object
)를 지원함으로 자바에 비해 간결함.
public
가시성 변경자(visiblity modifier
)가 코틀린의 경우 public
이 default
임으로 생략 가능.
property(프로퍼티)
-
자바에서는 데이터를 필드(field)
에 저장하며, 멤버 필드의 가시성은 보통 private
이다. 클래스는 자신을 사용하는 클라이언트가 그 데이터에 접근하는 통로로 쓸 수 있는 접근자 메서드(accessor method)
를 제공한다. (Ex. getter, setter
)
-
자바에서는 필드
와 접근자
를 한데 묶어 프로퍼티
라고 부름.
-
코틀린은 프로퍼티
를 언어 기본 기능으로 제공하며, 코틀린 프로퍼티
는 자바의 필드
와 접근자 메소드
를 완전히 대신한다.
-
val
로 선언한 프로퍼티
는 읽기 전용, var
로 선언한 프로퍼티
는 변경 가능
class Person(
val name: String,
// 읽기 전용 프로퍼티로, 코틀린은 비공개 필드와 필드를 읽는 공개 게터를 만들어낸다.
var isMarried: Boolean,
// 쓸 수 있는 프로퍼티로, 코틀린은 비공개 필드와 필드를 읽는 단순한 공개 게터를 공개 세터를 만들어낸다.
)
// 자바
Person person = new Person("Bob", true);
System.out.println(person.getName());
System.out.println(person.isMarried());
person.setMarried(false)
// 코틀린
val person = Person("Bob", true) // new 키워드 사용 안하고 생성자 호출
println(person.name) // 프로퍼티 이름 사용해도 코틀린이 게터 자동 호출해줌
println(person.isMarried)
person.isMarried = fasle
- 코틀린의
name
프로퍼티
는 자바 쪽에서는 getName
- 이름이
is
로 시작하는 프로퍼티
의 게터
에는 get
이 붙지 않고 원래 이름을 그대로 사용, 세터
에는 is
를 set
으로 바꾼 이름을 사용.
커스텀 접근자
class Rectangle(val height: Int, val width: Int) {
val isSquare: Boolean
get() { // 프로퍼티 게터 선언
return height == width
}
}
프로퍼티
의 접근자
를 직접 작성하는 코드
isSquare
프로퍼티
에는 자체 값을 저장하는 필드
가 필요 없다. 이 프로퍼티
에는 자체 구현을 제공하는 게터
만 존재한다. 클라이언트가 프로퍼티
에 접근할 때마다 게터
가 프로퍼티 값
을 매번 다시 계산한다.
디렉터리와 패키지
- 패키지 이름 뒤에 .
*
를 추가하면 패키지 안의 모든 선언을 임포트할 수 있다. 이런 스타 임포트(star import)
를 사용하면 패키지 안에 있는 모든 클래스뿐 아니라 최상위에 정의된 함수나 프로퍼티까지 모두 불러오게 된다.
-
자바에서는 패키지의 구조와 일치하는 디렉토리 계층 구조를 만들고 클래스의 소스코드를 그 클래스가 속한 패키지와 같은 디렉토리에 위치시켜야 한다. (자바에서는 디렉토리 구조가 패키지 구조를 그대로 따라야 한다)
-
코틀린에서는 여러 클래스를 한 파일에 넣을 수 있고, 파일의 이름도 마음대로 정할 수 있다. 코틀린에서는 디스크상의 어느 디렉토리에 소스 코드 파일을 위치시키든 관계없다.
-
자바와 코틀린을 함께 사용하는 프로젝트에서는 자바의 방식을 따라야 한다.
enum과 when
enum class
enum class Color {
// class라는 키워드 앞에서만 enum 클래스의 의미를 가지며, 단독으로 사용시 이름으로 사용 가능
RED, ORANGE, YELLOW, GREEN
}
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);
// 여기 반드시 세미콜론 사용해야 한다.
// 각 상수를 생성할 때 그에 대한 프로퍼티 값을 지정한다.
// enum 상수 목록과 메서드 정의 사이에 사용한다.
fun rgb() = (r * 256 + g) * 256 + b // enum 클래스 안에서 메서드 정의
}
>>> println(Color.RED.rgb())
255
when으로 enum 클래스 다루기
if
와 마찬가지로 when
도 값을 만들어내는 식이다. 따라서 식이 본문임 함수
에 when
을 바로 사용할 수 있다.
fun getMnemonic(color: Color) = // 함수의 반환 값으로 when 식을 직접 사용
when(color) { // 색이 특정 enum 상수와 같을 때 그 상수에 대응하는 문자열을 돌려준다.
Color.RED -> "Richard"
Color.ORANGE -> "Of"
Color.YELLOW -> "York"
Color.GREEN -> "Gave"
Color.BLUE -> "Battle"
Color.INDIGO -> "In"
Color.VIOLET -> "Vain"
}
>>> println(getMnemonic(Color.BLUE))
Battle
- 자바와 달리 분기의 끝에
break
를 넣지 않아도 됨.
- 한 분기 안에서 여러 값을 매치 패턴으로 사용 할려면 값 사이를
,
로 분리한다.
import ch02.colors.Color // 다른 패키지에서 정의한 Color 클래스를 임포트한다.
import ch02.colors.Color.* // 짧은 이름으로 사용하기 위해 enum 상수를 모두 임포트 한다.
fun getWarmth(color: Color) = when(color) {
RED, ORANGE, YELLOW -> "warm"
GREEN -> "neutral"
BLUE, INDIGO, VIOLET -> "cold"
}
when과 임의의 객체를 함께 사용
- 코틀린의
when
은 자바의 switch
보다 강력하다.
- 분기 조건에 상수만을 사용할 수 있는 자바
switch
와 달리 코틀린 when
의 분기 조건은 임의의 객체를 허용한다.
fun mix(cl: Color, c2: Color) =
when(setOf(c1,c2)) { // 여러 객체를 그 객체들을 포함하는 집합인 Set 객체로 만드는 setOf 라는 함수
setOf(RED,YELLOW) -> ORANGE
setOf(YELLOW,BLUE) -> GREEN
setOf(BLUE,VIOLET) -> INDIGO
else -> throw Exception("Dirty color") // 매치되는 분기 조건이 없으면 이 문장을 실행
}
>>> println(mix(BLUE,YELLOW))
GREEN
when
식은 인자 값과 매치하는 조건 값을 찾을 때까지 각 분기를 검사한다.
인자 없는 when 사용
- mix 함수가 자주 호출된다면 불필요한
가비치 객체
가 늘어남.
- 코드는 읽기 어려워지지만 성능을 위해 감수해야 하는 경우임.
when
에 아무런 인자도 없으려면 각 분기의 조건이 Boolean
결과를 계산하는 식이어야 한다.
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")
}
>>> printn(mixOptimized(BLUE,YELLOW))
GREEN
스마트 캐스트 : 타입 검사 + 타입 캐스트
interface Expr
class Num(val value: Int) : Expr
class Sum(val left: Expr, val right: Expr) : Expr
식을 위한 Expr 인터페이스, Sum, Num 클래스는 그 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")
}
>>> println(eval(Sum(Sum(Num(1), Num(2)), Num(4))))
7
- 코틀린에서는
is
를 사용해 변수 타입을 검사
is
검사는 자바의 instanceof
와 비슷
- 자바에서 변수의 타입을 확인한 다음에 그 타입에 속한 멤버에 접근하기 위해서는 명시적으로 변수 타입을 캐스팅해야 함. 이런 멤버 접근을 여러 번 수행해야 한다면 변수에 따로
캐스팅한 결과
를 저장
한 후 사용해야함.
- 코틀린에서는 이것을 프로그래머 대신 컴파일러가
캐스팅
해줌.
- 어떤 변수가 원하는 타입인지 일단
is
로 검사하고 나면 굳이 변수를 원하는 타입으로 캐스팅하지 않아도 마치 처음부터 그 변수가 원하는 타입으로 선언된 것처럼 사용할 수 있다.
- 이것은 컴파일러가 캐스팅을 수행해줌. =>
스마트 캐스팅
스마트 캐스트
는 is
로 변수에 든 값의 타입을 검사한 다음에 그 값이 바꿜 수 없는 경우에만 작동.
프로퍼티
에 대해 스마트 캐스트
를 사용한다면 그 프로퍼티
는 반드시 val
이어야 하며, 커스텀 접근자
를 사용한 것이어도 안 된다.
원하는 타입으로 명시적으로 타입 캐스팅
을 하기 위해서는 as
를 사용한다.
val n = e as Num
리팩토링 : if를 when으로 변경
fun eval(e: Expr) : Int =
when(e) {
is Num -> // 인자 타입을 검사하는 when 분기
e.value // 스마트 캐스팅가 쓰여짐.
is Sum -> // 인자 타입을 검사하는 when 분기
eval(e.right) + eval(e.left) // 스마트 캐스팅가 쓰여짐.
else ->
throw IllegalArgumentException("Unknown expression")
}
if와 when의 분기에서 블록 사용
fun evalWithLogging(e: Expr) : Int =
when(e){
is Num -> {
println("num: ${e.value}")
e.value // e의 타입이 Num이면 e.value가 return
}
is Sum -> {
val left = evalWithLogging(e.left)
val right = evalWithLogging(e.right)
println("sum: $left + $right")
left + right // e의 타입이 Sum이면 이 식의 값이 return
}
else -> throw IllegalArgumentException("Unknown expression")
}
블록의 마지막 식
이 블록의 결과
이다.
이터레이션 : while, for
while문
- 자바와 코틀린의 문법이 동일함.
수에 대한 이터레이션 : 범위와 수열
-
자바의 for
루프 => 코틀린의 range
-
코틀린의 range
는 양끝을 포함하는 구간임.
-
..
연산자로 시작 값과 끝 값을 연결해서 범위를 만든다. 문자도 가능 ('A'..'F'
)
-
역방향은 downTo
, 증감값 조절은 step
을 사용.
-
끝 값을 포함하지 않는 반만 닫힌 범위는 until
함수를 사용해야함.
-
Map
의 키와 값에 대해 이터레이션 가능.
-
withIndex
로 인덱스와 함께 컬렉션을 이터레이션 할 수 있음.
val oneToTen = 1..10 // 1 ~ 10
for(i in 100 downTo 1 step 2) {
println(fizzBuzz(i))
}
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 2 Fizz 4 Buzz Fizz 7 ...
// map의 key, value를 for문으로 풀어낼 수 있다.
for ((key, value) in mutableMapOf(Pair("A", 1))) {
}
// withIndex를 활용하면 리스트의 index도 간편히 가져올 수 있다.
for ((index, value) in mutableListOf(1, 2, 3).withIndex()) {
}
in으로 컬렉션이나 범위의 원소 검사
fun isLetter(c: Char) = c in 'a'..'z' || c in 'A'..'Z' // 'a'<= c && c <= 'z'
fun isNotDigit(c: Char) = c !in '0'..'9'
fun main(args: Array<String>) {
println(isLetter('q'))
println(isNotDigit('x'))
}
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…"
}
fun main(args: Array<String>) {
println(recognize('8'))
}
println("Kotlin" in "Java".."Scala")
// true
println("Kotlin" in setOf("Java","Scala"))
// false
-
in
연산자를 사용해 어떤 값이 범위에 속하는지 검사 할 수 있음.
-
!in
은 in
연산자의 반대 연산.
-
비교가 가능한 클래스(java.lang.Comparable
인터페이스를 구현한 클래스)라면 그 클래스의 인스턴스 객체를 사용해 범위를 만들 수 있다.
-
컬렉션
에도 in
연산을 사용할 수 있음.
코틀린의 예외 처리
- 코틀린의
예외(exception)
처리는 자바나 다른 언어의 예외 처리와 비슷.
- 발생한 예외를 함수 호출 단에서
처리(catch)
하지 않으면 함수 호출 스택을 거슬러 올라가면서 예외를 처리하는 부분이 나올 때까지 예외를 다시 던진다.(rethrow
)
- 자바와 달리 코틀린의
throw
는 식
이므로 다른 식
에 포함될 수 있다.
try, catch, finally
fun readNumber(reader: BufferedReader) : Int? {
// 함수가 던질 수 있는 예외를 명시할 필요가 없다. throws가 없는 것이다.
try {
val line = reader.readLine()
return Integer.parseInt(line)
}
catch (e: NumberFormatException) { // 예외 타입을 :의 오른쪽에 쓴다.
return null
}
finally { // 자바와 동일
reader.close()
}
}
-
자바에서는 함수를 작성할 때 함수 선언 뒤에 throws IOException
을 붙여야 한다. 이유는 IOException
이 체크 예외, checked exception
이기 때문이다. 자바에서는 체크 예외
를 명시적으로 처리해야 한다.
어떤 함수가 던질 가능성이 있는 예외나 그 함수가 호출한 다른 함수에서 발생할 수 있는 예외를 모두 catch
로 처리해야 하며, 처리하지 않은 예외는 throws
절에 명시해야 한다.
-
코틀린에서는 함수가 던지는 예외를 지정하지 않고 발생한 예외를 catch
해도 되고 안해도 된다. 자바는 체크 예외
처리를 강제한다. 코틀린의 경우 체크 예외
와 언체크 예외
를 구분하지 않는다.
fun main(args: Array<String>) { // example, hello world function
println("hello world")
}
fun
키워드 사용.코틀린 표준 라이브러리
는 표준 자바 라이브러리 함수
를 간결하게 사용할 수 있게 감싼 Wrapper(래퍼)
를 제공한다. Ex. println
;
을 붙이지 않아도 됨문(statement)과 식(experssion)의 구분
- 코틀린에서는 if는 식이지 문이 아니다.
식
은 값을 만들어 내며 다른 식의 하위 요소로 계산에 참여할 수 있음.문
은 자신을 둘러싸고 있는 가장 안쪽 블록의 최상위 요소로 존재하며 아무런 값을 만들어내지 않는다.- 자바에서는 모든 제어 구조가
문
인 반면 코틀린에서는 루프를 제외한 대부분의 제어 구조가식
- 대입문은 자바에서는
식
이었으나 코틀린에서는문
이 됐다.
fun max1(a : Int, b: Int): Int { // 블록이 본문인 함수, 반환 타입 생략 불가
return if ( a > b ) a else b
}
fun max2(a : Int, b: Int): = if (a > b) a else b // 식이 본문인 함수
// 타입 추론(type inference) 기능으로 함수의 변환형 Int가 생략되도 무방하다.
// 컴파일러가 함수 본문 식을 분석해 식의 결과 타입을 함수 반환 타입으로 정해준다.
val answer = 42
val answer: Int = 42
/////////////////////////////////////////////////
val answer: Int
answer = 42
식이 본문인 함수
에서와 마찬가지로 타입을 지정하지 않아도, 컴파일러가 초기화 식
을 분석해서 초기화 식
의 타입을 변수 타입으로 지정한다.
초기화 식을 사용하지 않고 변수를 선언하려면 반드시 변수 타입을 명시해야 한다.
val
- 변경 불가능한(
immutable
) 참조를 저장하는 변수다. - val로 선언된 변수는 일단 초기화하고 나면
재대입
이 불가능. - 자바로 치면
final
- val 참조 자체는 불변일지라도 그 참조가 가리키는 객체의 내부 값은 변경가능.
- val 변수는 블록을 실행할 때 정확히 한 번만 초기화 돼야 함.
val languages = arrayListOf("Java") // immutable (불변 참조)
languages.add("Kotlin") // 참조가 가리키는 객체 내부를 변경 가능.
var
- 변경 가능한(
mutable)
참조. - 변수의 값이 바뀔 수 있다.
- 자바의 일반 변수에 해당한다.
- 변수의 값을 변경할 수 있지만, 변수의 타입은 고정돼어 바뀌지 않음.
var answer = 42
answer = "no answer" // Error: type mismatch, 컴파일 오류, 강제 형 변환 필요
val name = "myName"
println("hello, $name")
println("hello, ${name}")
println("hello, ${name[0]}")
println("hello, ${if (name.size > 0) name[0] else "someone"}")
public class Person { //java
private final String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
Person(string name)
의 본문에서 파라미터를 이름이 같은 필드에 대입하는 대입문의 수도 늘어남.class Person(val name: String) // kotlin
값 객체, value object
)를 지원함으로 자바에 비해 간결함.public
가시성 변경자(visiblity modifier
)가 코틀린의 경우 public
이 default
임으로 생략 가능.자바에서는 데이터를 필드(field)
에 저장하며, 멤버 필드의 가시성은 보통 private
이다. 클래스는 자신을 사용하는 클라이언트가 그 데이터에 접근하는 통로로 쓸 수 있는 접근자 메서드(accessor method)
를 제공한다. (Ex. getter, setter
)
자바에서는 필드
와 접근자
를 한데 묶어 프로퍼티
라고 부름.
코틀린은 프로퍼티
를 언어 기본 기능으로 제공하며, 코틀린 프로퍼티
는 자바의 필드
와 접근자 메소드
를 완전히 대신한다.
val
로 선언한 프로퍼티
는 읽기 전용, var
로 선언한 프로퍼티
는 변경 가능
class Person(
val name: String,
// 읽기 전용 프로퍼티로, 코틀린은 비공개 필드와 필드를 읽는 공개 게터를 만들어낸다.
var isMarried: Boolean,
// 쓸 수 있는 프로퍼티로, 코틀린은 비공개 필드와 필드를 읽는 단순한 공개 게터를 공개 세터를 만들어낸다.
)
// 자바
Person person = new Person("Bob", true);
System.out.println(person.getName());
System.out.println(person.isMarried());
person.setMarried(false)
// 코틀린
val person = Person("Bob", true) // new 키워드 사용 안하고 생성자 호출
println(person.name) // 프로퍼티 이름 사용해도 코틀린이 게터 자동 호출해줌
println(person.isMarried)
person.isMarried = fasle
name
프로퍼티
는 자바 쪽에서는 getName
is
로 시작하는 프로퍼티
의 게터
에는 get
이 붙지 않고 원래 이름을 그대로 사용, 세터
에는 is
를 set
으로 바꾼 이름을 사용.class Rectangle(val height: Int, val width: Int) {
val isSquare: Boolean
get() { // 프로퍼티 게터 선언
return height == width
}
}
프로퍼티
의 접근자
를 직접 작성하는 코드isSquare
프로퍼티
에는 자체 값을 저장하는 필드
가 필요 없다. 이 프로퍼티
에는 자체 구현을 제공하는 게터
만 존재한다. 클라이언트가 프로퍼티
에 접근할 때마다 게터
가 프로퍼티 값
을 매번 다시 계산한다.*
를 추가하면 패키지 안의 모든 선언을 임포트할 수 있다. 이런 스타 임포트(star import)
를 사용하면 패키지 안에 있는 모든 클래스뿐 아니라 최상위에 정의된 함수나 프로퍼티까지 모두 불러오게 된다.자바에서는 패키지의 구조와 일치하는 디렉토리 계층 구조를 만들고 클래스의 소스코드를 그 클래스가 속한 패키지와 같은 디렉토리에 위치시켜야 한다. (자바에서는 디렉토리 구조가 패키지 구조를 그대로 따라야 한다)
코틀린에서는 여러 클래스를 한 파일에 넣을 수 있고, 파일의 이름도 마음대로 정할 수 있다. 코틀린에서는 디스크상의 어느 디렉토리에 소스 코드 파일을 위치시키든 관계없다.
자바와 코틀린을 함께 사용하는 프로젝트에서는 자바의 방식을 따라야 한다.
enum class Color {
// class라는 키워드 앞에서만 enum 클래스의 의미를 가지며, 단독으로 사용시 이름으로 사용 가능
RED, ORANGE, YELLOW, GREEN
}
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);
// 여기 반드시 세미콜론 사용해야 한다.
// 각 상수를 생성할 때 그에 대한 프로퍼티 값을 지정한다.
// enum 상수 목록과 메서드 정의 사이에 사용한다.
fun rgb() = (r * 256 + g) * 256 + b // enum 클래스 안에서 메서드 정의
}
>>> println(Color.RED.rgb())
255
if
와 마찬가지로 when
도 값을 만들어내는 식이다. 따라서 식이 본문임 함수
에 when
을 바로 사용할 수 있다.fun getMnemonic(color: Color) = // 함수의 반환 값으로 when 식을 직접 사용
when(color) { // 색이 특정 enum 상수와 같을 때 그 상수에 대응하는 문자열을 돌려준다.
Color.RED -> "Richard"
Color.ORANGE -> "Of"
Color.YELLOW -> "York"
Color.GREEN -> "Gave"
Color.BLUE -> "Battle"
Color.INDIGO -> "In"
Color.VIOLET -> "Vain"
}
>>> println(getMnemonic(Color.BLUE))
Battle
break
를 넣지 않아도 됨.,
로 분리한다.import ch02.colors.Color // 다른 패키지에서 정의한 Color 클래스를 임포트한다.
import ch02.colors.Color.* // 짧은 이름으로 사용하기 위해 enum 상수를 모두 임포트 한다.
fun getWarmth(color: Color) = when(color) {
RED, ORANGE, YELLOW -> "warm"
GREEN -> "neutral"
BLUE, INDIGO, VIOLET -> "cold"
}
when
은 자바의 switch
보다 강력하다.switch
와 달리 코틀린 when
의 분기 조건은 임의의 객체를 허용한다.fun mix(cl: Color, c2: Color) =
when(setOf(c1,c2)) { // 여러 객체를 그 객체들을 포함하는 집합인 Set 객체로 만드는 setOf 라는 함수
setOf(RED,YELLOW) -> ORANGE
setOf(YELLOW,BLUE) -> GREEN
setOf(BLUE,VIOLET) -> INDIGO
else -> throw Exception("Dirty color") // 매치되는 분기 조건이 없으면 이 문장을 실행
}
>>> println(mix(BLUE,YELLOW))
GREEN
when
식은 인자 값과 매치하는 조건 값을 찾을 때까지 각 분기를 검사한다.가비치 객체
가 늘어남.when
에 아무런 인자도 없으려면 각 분기의 조건이 Boolean
결과를 계산하는 식이어야 한다.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")
}
>>> printn(mixOptimized(BLUE,YELLOW))
GREEN
interface Expr
class Num(val value: Int) : Expr
class Sum(val left: Expr, val right: Expr) : Expr
식을 위한 Expr 인터페이스, Sum, Num 클래스는 그 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")
}
>>> println(eval(Sum(Sum(Num(1), Num(2)), Num(4))))
7
is
를 사용해 변수 타입을 검사is
검사는 자바의 instanceof
와 비슷캐스팅한 결과
를 저장
한 후 사용해야함.캐스팅
해줌.is
로 검사하고 나면 굳이 변수를 원하는 타입으로 캐스팅하지 않아도 마치 처음부터 그 변수가 원하는 타입으로 선언된 것처럼 사용할 수 있다.스마트 캐스팅
스마트 캐스트
는 is
로 변수에 든 값의 타입을 검사한 다음에 그 값이 바꿜 수 없는 경우에만 작동.프로퍼티
에 대해 스마트 캐스트
를 사용한다면 그 프로퍼티
는 반드시 val
이어야 하며, 커스텀 접근자
를 사용한 것이어도 안 된다.원하는 타입으로 명시적으로 타입 캐스팅
을 하기 위해서는 as
를 사용한다.
val n = e as Num
fun eval(e: Expr) : Int =
when(e) {
is Num -> // 인자 타입을 검사하는 when 분기
e.value // 스마트 캐스팅가 쓰여짐.
is Sum -> // 인자 타입을 검사하는 when 분기
eval(e.right) + eval(e.left) // 스마트 캐스팅가 쓰여짐.
else ->
throw IllegalArgumentException("Unknown expression")
}
fun evalWithLogging(e: Expr) : Int =
when(e){
is Num -> {
println("num: ${e.value}")
e.value // e의 타입이 Num이면 e.value가 return
}
is Sum -> {
val left = evalWithLogging(e.left)
val right = evalWithLogging(e.right)
println("sum: $left + $right")
left + right // e의 타입이 Sum이면 이 식의 값이 return
}
else -> throw IllegalArgumentException("Unknown expression")
}
블록의 마지막 식
이 블록의 결과
이다.자바의 for
루프 => 코틀린의 range
코틀린의 range
는 양끝을 포함하는 구간임.
..
연산자로 시작 값과 끝 값을 연결해서 범위를 만든다. 문자도 가능 ('A'..'F'
)
역방향은 downTo
, 증감값 조절은 step
을 사용.
끝 값을 포함하지 않는 반만 닫힌 범위는 until
함수를 사용해야함.
Map
의 키와 값에 대해 이터레이션 가능.
withIndex
로 인덱스와 함께 컬렉션을 이터레이션 할 수 있음.
val oneToTen = 1..10 // 1 ~ 10
for(i in 100 downTo 1 step 2) {
println(fizzBuzz(i))
}
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 2 Fizz 4 Buzz Fizz 7 ...
// map의 key, value를 for문으로 풀어낼 수 있다.
for ((key, value) in mutableMapOf(Pair("A", 1))) {
}
// withIndex를 활용하면 리스트의 index도 간편히 가져올 수 있다.
for ((index, value) in mutableListOf(1, 2, 3).withIndex()) {
}
fun isLetter(c: Char) = c in 'a'..'z' || c in 'A'..'Z' // 'a'<= c && c <= 'z'
fun isNotDigit(c: Char) = c !in '0'..'9'
fun main(args: Array<String>) {
println(isLetter('q'))
println(isNotDigit('x'))
}
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…"
}
fun main(args: Array<String>) {
println(recognize('8'))
}
println("Kotlin" in "Java".."Scala")
// true
println("Kotlin" in setOf("Java","Scala"))
// false
in
연산자를 사용해 어떤 값이 범위에 속하는지 검사 할 수 있음.
!in
은 in
연산자의 반대 연산.
비교가 가능한 클래스(java.lang.Comparable
인터페이스를 구현한 클래스)라면 그 클래스의 인스턴스 객체를 사용해 범위를 만들 수 있다.
컬렉션
에도 in
연산을 사용할 수 있음.
예외(exception)
처리는 자바나 다른 언어의 예외 처리와 비슷.처리(catch)
하지 않으면 함수 호출 스택을 거슬러 올라가면서 예외를 처리하는 부분이 나올 때까지 예외를 다시 던진다.(rethrow
)throw
는 식
이므로 다른 식
에 포함될 수 있다.fun readNumber(reader: BufferedReader) : Int? {
// 함수가 던질 수 있는 예외를 명시할 필요가 없다. throws가 없는 것이다.
try {
val line = reader.readLine()
return Integer.parseInt(line)
}
catch (e: NumberFormatException) { // 예외 타입을 :의 오른쪽에 쓴다.
return null
}
finally { // 자바와 동일
reader.close()
}
}
자바에서는 함수를 작성할 때 함수 선언 뒤에 throws IOException
을 붙여야 한다. 이유는 IOException
이 체크 예외, checked exception
이기 때문이다. 자바에서는 체크 예외
를 명시적으로 처리해야 한다.
어떤 함수가 던질 가능성이 있는 예외나 그 함수가 호출한 다른 함수에서 발생할 수 있는 예외를 모두 catch
로 처리해야 하며, 처리하지 않은 예외는 throws
절에 명시해야 한다.
코틀린에서는 함수가 던지는 예외를 지정하지 않고 발생한 예외를 catch
해도 되고 안해도 된다. 자바는 체크 예외
처리를 강제한다. 코틀린의 경우 체크 예외
와 언체크 예외
를 구분하지 않는다.
체크 예외
와언체크 예외
체크 예외, checked exception
Exception
클래스의 서브클래스이면서RuntimeException
클래스를 상속하지 않은 것들.RuntimeExeception
클래스를 상속하지 않은Exception
클래스들이다.체크 예외
는 예외가 발생할 수 있는 메소드를 사용할 경우 반드시 예외를 처리하는 코드를 함께 작성해야 한다.- 예외를 처리하기 위해서
catch
문으로 잡거나throws
를 통해 메소드 밖으로 던질 수 있다. 만약 예외를 처리하지 않으면 컴파일 에러가 발생한다.
언체크 예외, unchecked exception
RuntimeException
클래스를 상속한Exception
클래스들은 예외 처리를 강제하지 않기 때문에언체크 예외
라고 불린다.- 에러를 처리하지 않아도 컴파일 에러가 발생하지 않는다.
RuntimeException
은 주로 프로그램에 문제가 있을 때 오류가 발생하도록 의도된 것으로 대표적으로NullPointerException
이 있다.- 즉,
RuntimeException
은 예상치 못했던 상황에서 발생하는 것이 아니므로 굳이 예외 처리를 강제하지 않는다.
try를 식으로 사용
- 코틀린의
try
는if
나when
과 마찬가지로 식이다. 따라서try
의 값을 변수에 대입할 수 있다. if
와 달리try
의 본문을 반드시{} 중괄호
로 둘러싸야 한다.
fun readNumber(reader: BufferedReader) {
val number = try {
reader.readLine().toInt() // 이 식의 값이 try 식의 값이 된다.
} catch (e: NumberFormatException) {
return // 예외가 발생하면 return하므로 catch 블록 다음의 코드는 실행되지 않는다.
}
println(number)
}
fun readNumber(reader: BufferedReader) {
val number = try {
reader.readLine().toInt() // 이 식의 값이 try 식의 값이 된다.
} catch (e: NumberFormatException) {
null // 예외가 발생하면 null 값을 사용함.
}
println(number) // 예외가 발생한다면 null이 출력됨. (number = null)
}
Author And Source
이 문제에 관하여(Kotlin in Action. [2] 코틀린 기초), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@ba159sal/Kotlin-in-Action.-2-코틀린-기초저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)