코틀린에서 if는 식(expression)이다. 자바에서는 모든 제어구조가 문(statement)이지만, 코틀린에서는 Loop를 제외하면 대부분 제어 구조가 식(expression)이다.
문(statement)은 자신은 둘러싸고 있는 가장 안쪽 블록의 최상위 요소로 아무런 값을 만들어내지 않는다.
식(expression)은 값을 만들어 내며 다른 식의 하위 요소로 계산에 참여할 수 있다.
반면, 대입문은 자바에서 식이었으나, 코틀린에서는 문(statement) 이다.
2.1.2 변수
val : immutable 한 참조를 저장하는 변수. 자바의 final과 같다.
var : mutable 참조, 변수의 값이 바뀔 수 있다. 자바의 일반 변수
기본적으로 모든 변수는 불변 변수로 선언하고, 추후 필요에 따라 var로 변경하는것을 추천
2.1.3 문자열 템플릿에 변수 할당
코틀린에서 변수를 문자열 안에 사용할 수 있다.
// 문자열 템플릿 사용
fun main(args: Array<String>) {
val name = if(args.size > 0) args[0] else "Kotlin"
println("Hello, $name!")
if(args.size > 0){
println("Bye, ${args[0]}") // args 배열의 원소를 넣기 위해${} 구문 사용
}
println("\$") // 달려 표시 ($)를 쓸 경우에는 이스케이프(\) 사용
}
2.2 클래스와 프로퍼티
2.2.1 클래스
자바의 경우 필드가 늘어날 수록 생성자의 파라미터 개수도 늘어나게 된다. 즉 반복적으로 늘어나는 코드가 많아진다.
// Java Person 클래스
public class Person {
private final String name;
public String getName() {
return name;
}
public Person(String name) {
this.name = name;
}
}
코틀린은 다음형식으로 간단하게 표현가능하다. 접근제어자는 기본이 public이라 생략이 가능하다.
// 코틀린으로 변환한 Person 클래스
class Person (val name : String)
2.2.2 프로퍼티
코틀린에서는 필드와 접근자를 묶어 프로퍼티라고 부른다.
class Person(
val name: String, // private 변수, 읽기 전용, 단순한 공개 getter만 만들어낸다.
var isMarried: Boolean // private 변수, 공개 getter, 공개 setter 만들어낸다.
)
val person = Person("Bob", true) // new 키워드 사용 안하고 생성자 호출
// 프로퍼티 이름을 직접 사용해도 코틀린이 자동으로 getter 호출
println(person.name)
println(person.isMarried)
2.2.3 커스텀 접근자
클라이언트가 프로퍼티에 접근할때(get()) 접근자 로직을 커스텀 할 수 있다.
class Rectangle(val height: Int, val width: Int) {
val isSquare: Boolean
get() {
return height == width
}
// get() = height == width
}
2.2.4 코틀린 소스코드 구조 : 디렉터리와 패키지
모든 코틀린 파일의 맨 앞에는 package 문을 넣을 수 있다.
같은 패키지에 속해 있다면, 다른 파일에서 정의한 선언일지라도 직접 사용이 가능하다.
다른 패키지에 있다면 import를 선언하여 선언
// 클래스와 함수 선언을 패키지에 넣기
package geometry.shapes
import java.util.*
class Rectangle(val height: Int, val width: Int) {
val isSquare: Boolean
get() = height == width
}
fun createRandomRectangle(): Rectangle {
val random = Random()
return Rectangle(random.nextInt(), random.nextInt())
}
// 다른 패키지에 있는 함수 임포트하기
package geometry.shapes.createRandomRectangle
fun main(args: Array<String>) {
println(createRandomRectangle().isSquare)
}
2.3 선택 표현과 처리 : enum과 when
2.3.1 enum 클래스 정의
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 // enum 클래스 안에 메소드 정의
}
fun main() {
println(Color.BLUE.rgb())
}
2.3.2 when으로 enum 클래스 다루기
자바와 달리 각 분기문 끝에 break를 넣지 않아도 된다.
한 분기문 안에 여러 값을 매치 패턴으로 사용할 경우 콤마로 구분한다.
fun getMnemonic(color: Color) =
when (color) { // 함수 반환 값으로 when 식을 사용
Color.RED -> "Rechard"
Color.ORANGE -> "Of"
Color.YELLOW -> "York"
Color.GREEN -> "Grave"
Color.BLUE -> "Battle"
Color.INDIGO -> "In"
Color.VIOLET -> "Vain"
}
fun getWarmth(color: Color) = when (color) {
Color.RED, Color.ORANGE, Color.YELLOW -> "warm"
Color.GREEN -> "neutral"
Color.BLUE, Color.INDIGO, Color.VIOLET -> "cold"
}
fun main() {
println(getMnemonic(Color.BLUE))
println(getWarmth(Color.ORANGE))
}
2.3.3 when 과 임의의 객체 함께 사용
자바의 switch와 달리 코틀린의 when 분기 조건은 임의의 객체를 허용한다.
fun mix(c1: Color, c2: Color) =
// when 식의 인자로 아무 객체나 사용 가능, when은 받은 인자 객체가 각 분기 조건에 있는 객체와 같은지 테스트
when (setOf(c1, c2)) {
setOf(RED, YELLOW) -> ORANGE
setOf(YELLOW, BLUE) -> GREEN
setOf(BLUE, VIOLET) -> INDIGO
// 매치되는 분기 조건이 없으면 이 문장 실행
else -> throw Exception("Dirty color")
}
2.3.4 인자 없는 when 사용
when 에 아무 인자도 없으려면 각 분기문 조건이 불리언 결과를 계산하는 식이어야 한다.
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")
}
2.3.5 스마트 캐스트 : 타입 검사와타입 캐스트를 조합
( 1 + 2 ) + 4 와 같은 산술식을 계산하는 함수를 만들어 보자.
// 식을 위한 인터페이스
interface Expr
// value라는 프로퍼티만 존재하는 단순한 클래스, Expr 인터페이스를 구현
class Num(val value: Int) : Expr
// Expr 타입의 객체라면 Sum 연산의 인자가 될 수 있다.
// 따라서, Num이나 다른 Sum이 Sum의 인자로 올 수 있다.
class Sum(val left: Expr, val right: Expr) : Expr
// Java 스타일
// if 사용해서 식 계산
fun eval(e: Expr): Int {
if (e is Num) {
val n = e as Num
return n.value
}
if (e is Sum) {
// 변수 e 에 대해 스마트 캐스트를 사용한다.
return eval(e.right) + eval(e.left)
}
throw IllegalArgumentException("Unkown expression")
}
Expr은 식을 위한 인터페이스
Sum, Num 클래스는 Expr 인터페이스를 구현하는 클래스
코틀린에서는 is를 사용해서 변수 타입을 검사
자바에서는 instanceof로 변수타입 확인 후, 개발자가 명시적으로 타입 캐스팅 한 후 그 타입에 속한 메버에 접근 가능
코틀린에서는 is 로 검사한 후, 참이면 컴파일러가 알아서 스마트 캐스팅 해줌
스마트 캐스트는 is로 변수에 든 값의 타입을 검사한 다음, 그 값이 바뀔 수 없는 경우에만 동작
명시적으로 타입 캐스팅 하려면 as 키워드를 사용 -> val n = e as Num
2.3.6 리팩토링 : if를 when 으로 변경
코틀린의 if는 식 이며 값을 반환한다.
코틀린에서는 자바와 달리 3항 연산자가 따로 없다.
자바의 if는 값을 반환하지 않는다.
// 값을 만들어내는 if 식
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("Unkown expression")
}
// if 중첩 대신 when 사용하기
fun eval(e: Expr): Int =
when (e) {
is Num -> e.value // 인자 타입을 검사하는 when 분기에서 스마트 캐스트 됨
is Sum -> eval(e.left) + eval(e.right)
else -> throw IllegalArgumentException("Unkown expression")
}
2.3.7 if와 when의 분기에서 블록 사용
if나 when 모두 분기에 브록 사용가능하며, 블록의 마지막 문장이 블록 전체의 결과가 된다.
fun evalWithLogging(e: Expr): Int =
when (e) {
is Num -> {
println("num: ${e.value}")
e.value // 블록의 마지막 식이므로 e.value가 반환된다.
}
is Sum -> {
val left = evalWithLogging(e.left)
val right = evalWithLogging(e.right)
println("sum: $left + $right")
left + right
}
else -> throw IllegalArgumentException("Unkown expression")
}
2.4 대상을 이터레이션 : while과 for 루프
2.4.1. while 루프
// 조건이 참인 동안 본문 반복 실행
while(조건) {
/*....*/
}
// 맨 처음 무조건 본문 한번 실행 후, 조건이 참인 동안 본문 반복 실행
do{
/*....*/
} while(조건)
2.4.2 수에 대한 이터레이션 : 범위와 수열
step : 증가값
downTo : 역방향
.. : 항상 범위의 끝 포함
untill : 끝 값 포함하지 않음
fun fizzbuxx(i: Int) =
when {
i % 15 == 0 -> "FizzBuzz "
i % 3 == 0 -> "Fizz "
i % 5 == 0 -> "Buzz "
else -> "$i "
}
for (i in 1..100) {
println(fizzbuxx(i))
}
for (i in 100 downTo 1 step 2){
print(fizzbuxx(i))
}
2.4.3 맵에 대한 이터레이션
// A ~ F 까지 문자 범위 이터레이션
for(c in 'A'..'F'){
// 아스키 코드를 2진 표현으로 변경
val binary = Integer.toBinaryString(c.toInt())
// c를 키로 c의 2진 표현을 맵에 넣는다.
binaryReps[c] = binary // 자바의 map put 과 유사: binaryReps.put(c, binary)
}
// 맵에 대한 이터레이션, 맵의 Key와 Value를 두 변수에 대입
for((letter, binary) in binaryReps) {
println("$letter = $binary")
}
val list = arrayListOf("10", "11", "1001")
// 인덱스와 함께 컬렉션을 이터레이션 한다.
for ((idx, element) in list.withIndex()){
println("$idx: $element")
}
2.4.4 in 으로 컬렉션이나 범위의 원소 검사
in : 어떤 값이 범위에 속하는지 확인
비교가 가능한 클래스 (java.lang.Comparable 인터페이스를 구현한 클래스)라면 클래스 인스턴스 객체를 사용해 범위를 만들 수 있다.
fun isLetter(c: Char) = c in 'a'..'z' || c in 'A'..'Z'
fun isNotDigit(c: Char) = c !in '0'..'9'
println(isLetter('q')) // true
println(isNotDigit('x')) // true
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"
}
println(recognize('8'))
2.5 코틀린 예외처리
코틀린의 기본 예외 처리 구무은 자바와 유사
예외 인스턴스를 만들 때 new 키워드를 붙이 필요가 없다.
자바와 달리 코틀린의 throw는 식이므로, 다른 식에 포함 가능
al percentage =
if (number in 0..100)
number
else
// throw는 식이다. -> 값을 만들어낸다.
throw IllegalArgumentException("A percentage value must be between 0 and 100: $number")
2.5.1 try, catch, finally
자바 코드와 가장 큰 차이는 throws 절에 코드가 없다는 점.
또 자바에서는 함수를 작성할 때, 함수 선언 뒤에 throws IOException을 붙여야 한다.
IOException은 Checked Exception 이기 때문에, 자바는 Checked Exception을 명시적으로 처리해야한다.
어떤 함수가 던질 가능성이 있는 예외나 그 함수가 호출한 다른 함수에서 발생할 수 있는 모든 예외를 catch로 처리해야 하며, 처리하지 않은 예외는 throws 절에 명시한다.
코틀린도 다른 최신 JVM 언어 처럼 Checked Exception, UnChecked Exception을 구별하지 않는다.
2.5.2 try를 식으로 사용
코틀린의 try 키워드는 식으로 값을 만들어낸다.
따라서 try의 값을 변수에 대입할 수 있다. try는 if와 달리 중괄호로 둘러싸야 한다.
fun readNumber(reader: BufferedReader) {
// 예외가 발생하지 않으면, 이 값을 사용
val number = try {
Integer.parseInt(reader.readLine())
} catch (e: NumberFormatException) {
// return //catch 블록 다음 코드는 실행 되지 않음
null // 예외가 발생하면 null값을 사용
}
println(number)
}