b) a else b // java -> (a > b) ? a : b
}
fun main(args: Array 코틀린에서 if는 식이지 문이 아니다. (문(statement) 과 식(expression)) 반면 대입문은 자바에서는 식이었으나 코틀린에선 문이 됐다. 그로 인해 자바와 달리 대입식과 비교식을 잘못 바꿔 써서 버그가 생기는 경우가 없다. ####스마트 캐스트 [출처] Kotlin IN ACTION# 2장 코틀린기초
### 이번장에서는...
#### 배운점, 느낀점
1. 변수
2. 함수
3. 클래스
4. 프로퍼티
5. 스마트 캐스트 -> 타입 검사와 타입 캐스트, 타입 강제 변환을 하나로 엮은 기능
6. 예외처리
1. 자바에서는 변수를 선언할 때 타입이 맨 앞에 온다. 코틀린에서는 타입 지정을 생략하는 경우가 흔하다는데 실무에서 정말 그렇게 사용할 지?
---
## 2장 시작
### 함수와 변수
- 코틀린에서 타입 선언을 생략해도 된다는 사실을 보고, 코틀린이 어떻게 변경 가능한 데이터 보다 변경할 수 없는 불변 데이터 사용을 장려함
```kotlin
package part2
fun main(args: Array<String>) {
println("Hello, world!")
}
함수
fun max(a: Int, b: Int): Int {
return if (a > b) a else b // java -> (a > b) ? a : b
}
fun main(args: Array<String>) {
println(max(1, 2))
}
// 식이 본문인 함수
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
변수
package part2
fun main() {
val question = "헬로 월드"
val answer = 42
val answer2: Int = 42
val answer3: Int
answer3 = 42
println(question)
println(answer)
println(answer2)
println(answer3)
}
val answer: Int // val : Immutable : java(final) : 블록을 실행할 떄 정확히 한 번만 초기화
var answer2: Int // var: mutable
package part2
fun canPerForOperation(param: Boolean): Boolean {
return param
}
fun main() {
val ls: List<Boolean> = listOf(true, false)
val message: String
//
// for (l in ls) {
// if (canPerForOperation(l)) {
// message = "Success" // var 로 변경해야함
// } else {
// message = "Failed"
// }
// }
if (canPerForOperation(true)) {
message = "Success"
} else {
message = "Failed"
}
val languages = arrayListOf("JAVA")
languages.add("KOTLIN")
println(languages)
var answer = 42
// answer = "no answer" // type mismatch 오류
}
문자열 템플릿
package part2
fun main(args: Array<String>) {
val name = if (args.size > 0) args[0] else "Kotlin"
println("Hello, $name!") // JAVA -> ("Hello," + name + "!")
println("Hello, ${name}!") // 중괄호 쓰는 습관을 가져보자
println("Hello, ${if (args.size > 0) args[0] else "Kotlin"} !") // 식도 가능
}
클래스와 프로퍼티
클래스
package part2;
public class PersonJava {
private final String name;
public PersonJava(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
package part2
class Person(val name: String)
프로퍼티
package part2
class Person(
val name: String, // 읽기 전용 프로퍼티로, 코틀린은 비공개 필드와 필드를 읽는 단순한 공개 게터를 만든다
var isMarried: Boolean // 쓸 수 있는 프로퍼티로, 코틀린은 비공개 필드, 게터, 공개 세터를 만들어낸다.
)
package part2;
import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;
@Metadata(
mv = {1, 7, 1},
k = 1,
d1 = {"\\u0000\\u0018\\n\\u0002\\u0018\\u0002\\n\\u0002\\u0010\\u0000\\n\\u0000\\n\\u0002\\u0010\\u000e\\n\\u0000\\n\\u0002\\u0010\\u000b\\n\\u0002\\b\\u0007\\u0018\\u00002\\u00020\\u0001B\\u0015\\u0012\\u0006\\u0010\\u0002\\u001a\\u00020\\u0003\\u0012\\u0006\\u0010\\u0004\\u001a\\u00020\\u0005¢\\u0006\\u0002\\u0010\\u0006R\\u001a\\u0010\\u0004\\u001a\\u00020\\u0005X\\u0086\\u000e¢\\u0006\\u000e\\n\\u0000\\u001a\\u0004\\b\\u0004\\u0010\\u0007\\"\\u0004\\b\\b\\u0010\\tR\\u0011\\u0010\\u0002\\u001a\\u00020\\u0003¢\\u0006\\b\\n\\u0000\\u001a\\u0004\\b\\n\\u0010\\u000b¨\\u0006\\f"},
d2 = {"Lpart2/Person;", "", "name", "", "isMarried", "", "(Ljava/lang/String;Z)V", "()Z", "setMarried", "(Z)V", "getName", "()Ljava/lang/String;", "playground"}
)
public final class Person {
@NotNull
private final String name;
private boolean isMarried;
@NotNull
public final String getName() {
return this.name;
}
public final boolean isMarried() {
return this.isMarried;
}
public final void setMarried(boolean var1) {
this.isMarried = var1;
}
public Person(@NotNull String name, boolean isMarried) {
Intrinsics.checkNotNullParameter(name, "name");
super();
this.name = name;
this.isMarried = isMarried;
}
}
// Java에서 Person 클래스를 사용하는 방법
package part2;
public class PersonJava {
private final String name;
private Boolean isMarried;
public PersonJava(String name, Boolean isMarried) {
this.name = name;
this.isMarried = isMarried;
}
public String getName() {
return name;
}
public void setMarried(Boolean married) {
isMarried = married;
}
public Boolean getMarried() {
return isMarried;
}
public static void main(String[] args) {
PersonJava person = new PersonJava("Bob", true);
System.out.println(person.getName());
}
}
//kotlin에서 Person 클래스를 사용하는 법
package part2
class Person(
val name: String, // 읽기 전용 프로퍼티로, 코틀린은 비공개 필드와 필드를 읽는 단순한 공개 게터를 만든다
var isMarried: Boolean // 쓸 수 있는 프로퍼티로, 코틀린은 비공개 필드, 게터, 공개 세터를 만들어낸다.
)
fun main() {
val person = Person("Bob", true) // new 키워드를 사용하지 않고 생성자를 호출
person.isMarried = false //setter
println(person.name) // 프로퍼티 이름을 직접 사용해도 코틀린이 자동으로 게터를 호출한다.
println(person.isMarried)
}
커스텀 접근자
package part2
class Rectangle(val height: Int, val width: Int) {
val isSquare: Boolean get() = height == width
// get() { // 프로퍼티 게터 선언
// return height == width
// }
}
디렉터리와 패키지
선택 표현과 처리: enum과 when
package part2
import part2.Color.*
import java.lang.Exception
enum class Color(val r: Int, val g: Int, val b: Int) {
RED(255, 0, 0), ORANGE(255, 165, 0), YELLOW(255, 255, 0); // 반드시 상수 목록과 메서드 정의 사이에 세미콜론을 넣어야 한다
fun rgb() = (r * 256 + g) * 256 + b
}
fun main() {
println(RED.rgb())
println(getMnemonic(RED))
println(mix(RED, YELLOW))
println(mixOptimized(RED, YELLOW))
}
fun getMnemonic(color: Color) =
when (color) {
RED, ORANGE -> "warm"
YELLOW -> "York"
}
fun mix(c1: Color, c2: Color) =
when (setOf(c1, c2)) {
setOf(RED, YELLOW) -> ORANGE
else -> {
throw Exception("Dirty color")
}
}
fun mixOptimized(c1: Color, c2: Color) =
when {
(c1 == RED && c2 == YELLOW || c1 == YELLOW && c2 == RED) -> ORANGE // 추가 객체를 만들지 않는 장점이 있지만 가독성은 떨어진다.
else -> throw Exception("Dirty color")
}
package part2
interface Expr
class Num(val value: Int) : Expr // value라는 프로퍼티만 존재하는 단순한 클래스로 Expr 인터페이스를 구현한다
class Sum(val left: Expr, val right: Expr) :
Expr //Expr 타입의 객체라면 어떤 것이나 Sum 연산의 인자가 될 수 있다. 따라서 Num이나 다른 Sum이 인자로 올 수 있다.
fun main() {
println(eval(Sum(Sum(Num(1), Num(2)), Num(4))))
}
fun eval(e: Expr): Int {
if (e is Num) {
val n = e as Num // 여기서 Num으로 타입을 변환하는데, 이는 불필요한 중복이다.
return n.value
}
if (e is Sum) {
return eval(e.right) + eval(e.left) // 변수 e에 대해 스마트 캐스트를 사용한다.
}
throw IllegalArgumentException("Unknown expression")
}
if 중첩 대신 when 사용하기
fun eval(e: Expr): Int =
when (e) {
is Num -> e.value // 인자 타입을 검사하는 when분기를
is Sum -> eval(e.right) + eval(e.left) // 이부분에 스마트 캐스트가 쓰였다
else -> throw IllegalArgumentException("Unknown expression")
}
분기에 복잡한 동작이 들어가 있는 when 사용하기
fun evalWithLogging(e: Expr): Int =
when (e) {
is Num -> {
println("num: ${e.value}")
e.value // 이 식의 블록이 마지막 식이므로 e의 타입이 Num이면 e.value가 반환된다
}
is Sum -> {
val left = evalWithLogging(e.left)
val right = evalWithLogging(e.right)
println("sum: $left + $right")
left + right //e의 타입이 Sum이면 이 식의 값이 반환 된다.
}
else -> throw IllegalArgumentException("Unknown expression")
}
대상을 이터레이션: while과 for 루프
while 루프
while (condition) {
/*...*/ // 조건이 참인 동안 본문을 반복 실행한다.
}
do {
/*...*/ // 맨 처음에 무조건 본문을 한 번 실행한 다음, 조건이 참인 동안 본문을 반복 실행한다.
} while (condition)
수에 대한 이터레이션: 범위와 수열
val oneToTen = 1..10 // 두 번째 값 10이 항상 범위에 포함된다는 뜻이다.
when을 사용해 피즈버즈 게임 구현하기
package part2
fun fizzBuzz(i: Int) = when {
i % 15 == 0 -> "FizzBuzz" // i가 15로 나워떨어지면 FizzBuzz를 반환한다. 자바와 바찬가지로 % 모듈로 연산자이다.
i % 3 == 0 -> "Fizz" // i가 3으로 나눠떨어지면 Fizz를 반환한다.
i % 5 == 0 -> "Buzz" // i가 5로 나눠떨어지면 Buzz를 반환한다.
else -> "$i" // 다른 경우에는 그 수 자체를 반환한다
}
fun main() {
for (i in 1..100) { //1..100 범위의 정수에 대해 이터레이션 한다.
print(fizzBuzz(i))
}
println()
for (i in 100 downTo 1 step 2) { // 증가 값 step을 갖는 수열에 대해 이터레이션 한다. 증가 값을 사용하면 수를 건너 띌 수 있다.
print(fizzBuzz(i))
}
}
맵을 초기화하고 이터레이션 하기
package part2
import java.util.TreeMap
val binaryReps = TreeMap<Char, String>() // 키에 대해 정렬하기 위해 Tree Map을 사용한다
fun main() {
for (c in 'A'..'F') { // A부터 F까지의 문자의 범위를 사용해 이터레이션한다.
val binary = Integer.toBinaryString(c.code) //아스키 코드를 2진 표현으로 바꾼다
//binaryReps[c] = binary //c를 키로 c의 이진표현을 맵에 넣는다
binaryReps.put(c, binary)
}
for ((letter, binary) in binaryReps) { // 맵에 대해 이터레이션한다. 맵의 키와 값을 두 변수에 각각 대입한다.
println("$letter = $binary")
}
val list = arrayListOf("10", "11", "1001")
for ((index, element) in list.withIndex()) { // 인덱스와 함께 컬렉션을 이터레이션 한다.
println("$index: $element")
}
}
in으로 컬렉션이나 범위의 원소 검사
in을 사용해 값이 범위에 속하는지 검사하기
package part2
fun isLetter(c: Char) = c in 'a'..'z' || c in 'A'..'Z'
fun isNotDigit(c: Char) = c !in '0'..'9'
fun recognize(c: Char) = when (c) {
in '0'..'9' -> "It's a digit!" // c 값이 0부터 9사이에 있는지 검사한다
in 'a'..'z', in 'A'..'Z' -> "It's a letter!" // 여러 범위 조건을 함께 사용해도 된다.
else -> "I don't know.."
}
fun main() {
println(isLetter('q'))
println(isNotDigit('x'))
println(recognize('8'))
println("Kotlin" in "Java".."Scala") // Java <= Kotlin && Kotlin <= Scala 와 같다
println("Kotlin" in setOf("Java", "Scala")) // 이 집합에는 Kotlin이 들어있지 않다
}
코틀린의 예외 처리
package part2
import java.lang.IllegalArgumentException
fun main() {
val number = 1001
val percentage = if (number in 0..100)
number
else throw IllegalArgumentException("A percentage value must be between 0 and 100: $number") // new를 붙일 필요가 없다
if (percentage !in 0..100) {
throw IllegalArgumentException("A percentage value must be between 0 and 100: $percentage") // 자바와 달리 코틀린의 throw는 식이므로 다른 식에 포함 될 수 있다
}
}
try, catch, finally
package part2
import java.io.BufferedReader
import java.io.StringReader
import java.lang.NumberFormatException
fun readNumber(reader: BufferedReader): Int? { // 함수가 던질 수 있는 예외를 명시할 필요가 없다
try {
val line = reader.readLine()
return Integer.parseInt(line)
} catch (e: NumberFormatException) { // 예외 타입을 :오른쪽에 쓴다
return null
} finally { // finally는 자바와 똑같이 작동한다
reader.close()
}
}
fun main() {
val reader = BufferedReader(StringReader("239"))
println(readNumber(reader))
}
try를 식으로 사용하기
package part2
import java.io.BufferedReader
import java.io.StringReader
import java.lang.NumberFormatException
fun readNumber(reader: BufferedReader) {// 함수가 던질 수 있는 예외를 명시할 필요가 없다
val number = try {
Integer.parseInt(reader.readLine()) // 이 식의 값이 try 식의 값이 된다.
} catch (e: NumberFormatException) { // 예외 타입을 :오른쪽에 쓴다
return
}
println(number)
}
fun main() {
// val reader = BufferedReader(StringReader("239"))
val reader = BufferedReader(StringReader("Not a number"))
println(readNumber(reader))
}
catch에서 값 반환하기
package part2
import java.io.BufferedReader
import java.io.StringReader
import java.lang.NumberFormatException
fun readNumber(reader: BufferedReader) {// 함수가 던질 수 있는 예외를 명시할 필요가 없다
val number = try {
Integer.parseInt(reader.readLine()) // 이 식의 값이 try 식의 값이 된다.
} catch (e: NumberFormatException) { // 예외 타입을 :오른쪽에 쓴다
null //예외가 발생하면 null 값을 사용한다.
}
println(number)
}
fun main() {
// val reader = BufferedReader(StringReader("239"))
val reader = BufferedReader(StringReader("Not a number"))
println(readNumber(reader))
}