[Kotlin In Action] 2. 코틀린 기초
#1. 함수
Hello, world!
fun main(args: Array<String>) {
println("Hello, world!")
}
- 함수를 선언할 때
fun
키워드를 사용한다. - 변수를 선언할 때나 파라미터 이름 뒤에 타입을 쓴다.
- 함수를 최상위 수준에 정의할 수 있다. 클래스 안에 넣어야 할 필요가 없다.
- 배열도 일반적인 클래스와 마찬가지다.
- 세미콜론(;)을 붙이지 않아도 된다.
statement(문)과 expression(식)의 구분
- 식: 값을 만들어내며, 다른 식의 하위요소로 계산에 참여할 수 있다.
- 문: 자신을 둘러싸고 있는 가장 안쪽 블럭의 최상위 요소로 존재하여 아무런 값을 만들어내지 않는다.
자바에서는 모든 제어구조가 ‘문’인 반면, 코틀린에서는 루프를 제외한 대부분의 제어구조가 ‘식’이다.
식이 본문인 함수 (expression body function)
// 블럭이 본문인 함수
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
식이 본문인 함수는 반환 타입을 적지 않아도 컴파일러가 함수 본문 식을 분석하여 함수 반환 타입을 정해 준다. -> 타입 추론
#2. 변수
val question = "삶, 우주, 그리고 모든 것에 대한 궁극적인 질문"
val answer = 42 //Int 타입으로 타입 추론
val yearToCompute = 7.5e6 //Double 타입으로 타입 추론
초기화 식을 사용하지 않고 변수를 선언하려면 변수 타입을 반드시 명시해야 한다.
val answer: Int
answer = 42
변경 가능한 / 변경 불가능한 변수
val
(value): 변경 불가능한 참조를 저장하는 변수 - 자바의 final 변수var
(variable): 변경 가능한 참조 - 자바의 일반 변수
기본적으로는 모든 변수를 val로 선언하고, 나중에 꼭 필요할 때에만 var로 변경하는 것이 좋다.
val
변수는 한 번만 초기화돼야 한다.
조건에 따라 val
값을 다른 여러 값으로 초기화할 수도 있다.
val message: String
if (canPerformOperation()) {
message = "Success"
} else {
message = "Failed"
}
⭑ val 참조 자체는 불변일지라도, 그 참조가 가리키는 객체 내부 값은 변경될 수 있다.
val languages = arrayListOf("Java") //불변 참조 선언
language.add("Kotlin") //참조가 가리키는 객체 내부 변경
var
변수의 ‘값’은 변경할 수 있지만 변수의 ‘타입’은 고정된다.
var answer = 42
answer = "no answer" //Error: type mismatch
#3. 문자열 템플릿
fun main(args: Array<String>) {
val name = if (args.size > 0) args[0] else "Kotlin"
println("Hello, $name!")
}
=> 자바의 (“Hello” + name + “!”)과 동일한 기능
복잡한 식은 {}
로 둘러싸서 문자열 템플릿 안에 넣을 수 있다.
fun main(args: Array<String>) {
if (args.size > 0) {
println("Hello, ${args[0]}!")
}
}
#4. 프로퍼티
자바에서는 데이터를 필드에 저장하며, 필드의 가시성은 기본 비공개(private)이다.
데이터에 접근하는 통로로 쓸 수 있는 접근자 메서드(보통 Getter)를 제공하고, 필드를 변경할 Setter를 추가 제공할 수 있다.
자바에서는 필드와 접근자를 묶어 프로퍼티라고 부른다.
코틀린은 프로퍼티를 언어 기본 기능으로 제공하며, 자바의 필드와 접근자 메서드를 완전히 대신한다.
프로퍼티 선언은 val이나 var을 사용한다.
class Person(
val name: String, //읽기 전용, (비공개) 필드와 (공개) Getter 생성됨
var isMarried: Boolean //변경 가능, (비공개) 필드 / (공개) Getter / (공개) Setter 생성됨
)
코틀린은 값을 저장하기 위한 비공개 필드와 그 필드에 대한 Setter, Getter로 이뤄진 간단한 디폴트 접근자 구현을 제공한다.
val person = Person("Bob", true) //new 키워드없이 생성자 호출
println(person.name) //프로퍼티 이름을 직접 사용해도 코틀린이 자동으로 Getter를 호출한다.
- Backing field (뒷받침하는 필드)
- 프로퍼티의 값을 저장하기 위한 필드
프로퍼티의 값을 그때그때 계산하기 원한다면 Custom Getter를 작성하면 된다.
커스텀 접근자
class Rectangle(val height: Int, val width: Int) {
val isSquare: Boolean
get() { //프로퍼티 Getter 선언
return height = width
}
}
-> 프로퍼티에 접근할 때마다 Getter가 프로퍼티의 값을 매번 다시 계산한다.
• 파라미터가 없는 함수 정의 vs. 커스텀 Getter 정의
구현이나 성능은 비슷, 차이점은 가독성
일반적으로 클래스의 특성을 정의하고 싶다면, 프로퍼티로 정의해야 한다.
#5. enum
코틀린에서 enum은 soft keyword -> class 앞에 있을 때를 제외하면 다른 이름에 쓰일 수 있다.
(cf. keyword: 변수 등 이름에 사용 불가능, 예 - class)
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 //메서드 정의
}
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"
}
- when : if와 마찬가지로 when도 값을 만들어내는 ‘식’이다.
- 자바와 달리 각 분기의 끝에 break를 넣지 않아도 된다.
- 한 분기 안에서 여러 값을 매치 패턴으로 사용할 수도 있다. (값 사이 ‘,’ 사용)
- 인자로 아무 객체나 올 수 있다.
- 인자없이 호출할 수도 있다.
#6. 스마트 캐스트: 타입 검사와 타입 캐스트를 조합
예제: 덧셈 산술식 계산 함수 식을 트리구조로 저장
- 어떤 식이 수라면 그 값을 반환한다.
- 어떤 식이 합계라면 좌항과 우항의 값을 계산한 다음 그 두 값을 합한 값을 반환한다.
interface Expr
class Num(val value: Int) : Expr
class Sum(val left: Expr, val right: Expr) : Expr
코틀린에서는 is
를 사용해 변수 타입을 검사한다.
is
로 검사하면 컴파일러가 캐스팅 해준다. -> 스마트 캐스트
- 스마트 캐스트를 사용하려면 val이어야 한다.
- 명시적으로 타입 캐스팅하려면
as
키워드를 사용한다.
if 사용
fun eval(e: Expr): Int =
if (e is Num) {
e.value //if 분기에 블럭을 사용하는 경우, 그 블럭의 마지막 식이 그 분기의 결과 값이다.
} else if (e is Sum) {
eval(e.right) + eval(e.left)
} else {
throw IllegalArgumentException("Unknown expression")
}
if 대신 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")
}
- 블럭의 마지막 식이 블럭의 결과 규칙
- 함수에 대해서는 성립하지 않는다.
- 식이 본문인 함수는 블럭을 본문으로 가질 수 없고, 블럭이 본문인 함수는 내부에 return문이 반드시 있어야 한다.
#7. 이터레이션
while 루프
while
, do-while
-> 자바와 같다.
while (조건) {
...
}
do {
...
} while (조건)
수에 대한 이터레이션
- 범위 (range): 두 값으로 이루어진 구간
..
연산자로 시작 값과 끝 값을 연결해서 범위를 만든다. (폐구간)- 문자 타입의 값에도 적용할 수 있다.
val oneToTen = 1..10
for (i in 100 downTo 1 step 2) { //100부터 1까지 2씩 줄여가며 반복
...
}
끝 값을 포함하지 않는 범위 -> until
사용
(x in until size)
//equals
(x in 0..size-1)
맵에 대한 이터레이션
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으로 컬렉션이나 범위의 원소 검사
- in: 어떤 값이 범위에 속하는지 검사
- !in: 범위에 속하지 않는지 검사
#8. 코틀린의 예외 처리
fun readNumber(reader: BufferedReader): Int? { //throws 명시할 필요가 없다.
try {
val line = reader.readLine()
return Integer.parseInt(line)
}
catch (e: NumberFormatException) {
return null
}
finally {
reader.close()
}
}
코틀린은 체크 예외와 언체크 예외를 구별하지 않는다.
try도 ‘식’이므로, try 값을 변수에 대입할 수 있다. 마지막 식의 값이 전체 결과 값이다.
if와 달리 try는 항상 {}로 감싸야 한다.
Comments