코틀린에서 if는 문이 아니라 식이다. 식이란 값을 만들어 내며 다른식의 하위요소로 참여할 수 있는 반면, 문은 자신을 둘러싸고있는 가장 안쪽블록의 최상위 요소로 존재하며 값을 만들어내지 않는다.
코틀린에서는 루프를 제외한 대부분의 제어구조가 식이다. 반면 대입문은 자바에서 식이었지만 코틀린에서 문이되었다.
코틀린 식 example :
return if (a>b) a else b
식이 본문인 함수
fun max(a: Int, b: Int) : Int = if (a>b) a else b
해당 함수의 경우 반환타입을 생략해도 컴파일러가 해석해서 반환타입 지정해준다.
val question = '삶이란 무엇일까'
val answer = 42
val answer: Int = 42
val answer : Int // 초기화를 하지않고 선언하면 반드시 타입을 명시해야한다.
타입을 지정하지 않으면 컴파일러가 초기화 식의 타입을 변수타입으로 짖어한다.
val : 변경 불가능한 참조를 저장하는 변수
var : 변경 가능한 참조.
println("Hello, $name!")
println("Hello, ${args[0]}")
println("Hello, ${if (args.size>0) args[0] else "someone"}")
코틀린의 클래스의 기본은 public이다.
class Person(val name: String)
class Person (
var name: String, //val 의 경우 읽기전용으로 private선언, getter메서드를 생성
var isMarried: Boolean // var경우 쓰기가능으로, private선언, getter,setter 생성
)
println(person.isMarried) //getter가 아니라 프로퍼티 이름을 직접사용해도 게터를 호출해준다.
person.isMarried = false //를 사용하면 setter가 사용된다.
class Rectangle(val height: Int, val width: Int){
val isSquare: Boolean
get() {
return height == width
}
}
getter를 재설정하여, rectangle.isSquare이 호출되면 위 get함수가 실행된다.
코틀린에서는 자바와 달리 여러클래스를 한 파일에 넣을 수 있고, 파일의 이름도 마음대로 정할 수 있다. 또한 디스크상의 어느 디렉터리에 소스코드 파일을 위치시켜도 상관없다.
하지만 자바와 같이 패키지별로 디렉터리를 구성하는것이 좋다. 다만 여러 클래스를 한 파일에 넣는 것을 주저해서는 안된다.
프로퍼티와 메소드가 있는 enum클래스 선언하기
enum class Color {
RED, ORANGE, YELLOW
}
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 //메소드
}
>>>println(Color.RED.rgb())
자바라면 switch가 코틀린에서는 when이다.
fun getMnemonic(color: Color) =
when (color) {
Color.RED -> "Richard"
Color.ORANGE -> "Of"
...
}
>>>println(getMnemonic(Color.RED)) //Richard
하나의 분기에 여러값을 매칭시키고 싶다면 이렇게 사용하라
fun getMnemonic(color: Color) =
when (color) {
Color.RED, Color.ORANGE -> "warm"
Color.BLUE, Color.VIOLOET -> "cold"
...
}
>>>println(getMnemonic(Color.RED)) //warm
또한 해당 enum클래스를 Import한다면 enum클래스 수식어(Color)를 없이 간단히 사용할 수 있다.
when과 임의의 객체를 함께 사용하기
코틀린에서 when은 분기조건에 객체를 허용하기에 자바보다 훨씬 강력하다.
fun mix(c1: Color, c2: Color) =
when (setOf(c1, c2)) { //when의 식의 인자로 받은 객체가 같은지 테스트한다.
setOf(RED, YELLOW) -> ORANGE
setOf(YELLOW, BLUE) -> GREEN
else -> throw Exception("Dirty Color") // 매칭되는 분기조건이 없는경우
}
>>> println(mix(BLUE,YELLOW)) //GREEN
setOf라는 함수는 집합컬렉션으로 순서는 계산하지않는다.
인자없는 when 사용
위 식은 분기마다 색을 비교하기위해 set인스턴스를 생성한다. 이 비효율성이 크게 문제는 되지않지만, 인자가 없는 when을 만들어 가독성은 떨어지지만 성능을 향상시킬수 있다.
fun mixOptimized(c1: Color, c2: Color) =
when { //when의 식의 인자로 받은 객체가 같은지 테스트한다.
(c1 == RED && c2 ==YELLOW) ||
(c1 == YELLOW && c2 == RED)) ->
ORANGE
...
}
(1+2)+4 와 같은 간단한 산술식을 계산하는 함수를 만들어보자.
interface Expr
class Num(val value: Int) : Expr
class Sum(val left: Expr, val right: Expr) : Expr
print(eval(Sum(Sum(Num(1),Num(2)),Num(4))) //7
다음 식을 계산하기위한 구현클래스를 자바스타일 함수로 작성해보면 다음과 같다.
fun eval(e: Expr) : Int {
if (e is Num) {
val n = e as Num //이는 불필요한 중복이다.
return n.value
}
if (e is Sum) { //e가 Sum인지 체크되어서, e는 스마트캐스팅되어 e.left를 이용할 수 있다.
return eval(e.right) + eval(e.left)
}
throw IllegalArgumentException()
}
코틀린의 is 검사는 자바의 instanceof와 비슷하다. 자바는 instanceof로 변수의 타입을 확인한 다음 변수타입을 캐스팅해야한다.
코틀린에서는 변수가 원하는 타입인지 is로 검사하면 캐스팅하지 않아도 컴파일러가 캐스팅해준다. 이를 스마트캐스팅이라고 한다.
스마트캐스트는 is 검사를 한 후 그 값이 바뀔수 없어야한다. val 이어야하며, 커스텀 접근자를 사용하면 안된다.
원하는 타입으로 명시적으로 타입캐스팅을 하려면 as키워드를 사용하면 된다.
val n = e as Num
코틀린의 if는 값을 만들어 내기 때문에, 자바처럼 3항연산자가 따로 없다. 이 특성을 이용해 return문과 중괄호를 없애 다음처럼 만들수 있으며
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()
}
if 분기에 식이 하나밖에없다면 중괄호를 생략할 수도 있다. if분기에 블록을 사용하는 경우 블록의 마지막 식이 분기의 결과값이다.
fun eval(e: Expr) : Int =
when (e) {
is Num -> //스마트캐스트가 사용됨
e.value
is Sum ->
eval(e.right) + eval(e.left) //블록의 마지막이 결과값이다
else ->
throw IllegalArgumentException()
}
코틀린에는 자바의 for 루프에 해당하는 요소가 없다. 대신 range를 사용한다.
for (i in 1..100) { //1~100 닫힌폐구간
print(i) // 1 to 100 이터레이션
}
for (i in 100 downTo 1 step 2) { // 감소수열
print(i) //100, 98, 96 ..
}
for (i in 0 until 100){ //i in 0..99 와 같다.
print(i) // 1 to 99
}
맵에대한 이터레이션
val binaryReps = TreeMap<Char, String>()
for (c in 'A' .. 'F') { //A~F 에대한 interation
val binary = Integer.toBinaryString(c.toInt())
binaryReps[c] = binary
}
for ((letter,binary) in binaryReps){
println("$letter = $binary")
}
//binaryReps[c] = binary 라는 코드는
//binaryReps.put(c, binary) 와 같다.
혹은 컬렉션에 대해서도 인덱스를 사용할 수 있다.
val list = arrayListOf("10", "11",..)
for ((index, element) in list.withIndex()){
...
}
in으로 원소검사
fun isLetter(c: Char) = c in 'a'..'z' || c in 'A'..'Z'
when의 경우
fun recognize (c: Char) = when(c){
in '0'..'9' -> "It's a digit!"
else -> "I don't know"
}
비교가능한 클래스라면(java.lang.Comparable) 범위를 만들수있다.
println("Kotlin" in "Java".."Scala") // true
// "Java" <= "Kotlin" && "Kotlin" <= "Scala" 와 같다.
String이어서 범위안에 속하는지 결정할 수 있다.
코틀린에서 throw는 식이라서 다른식에 포함 될 수 있다.
val = percentage =
if (number in 1..100)
number
else
throw IllegalArgumentException(
"A percentage value must be between 0 and 100: $number")
코틀린의 자바와의 큰 차이는 throws 절이 코드에 없다는 것이다.
자바는 함수 선언 뒤에 throws IOException을 붙여 체크예외를 명시적으로 처리해야한다. 하지만 코틀린은 (다른 최신JVM언어와 마찬가지로) 체크예외와 언체크예외를 구별하지않는다.
코틀린의 try키워드는 if나 when처럼 식이다. 따라서 변수에 대입이 가능하다.
fun readNumber(reader: BufferedReader){
val unmber = try{
Integer.parseInt(reader.readLine())
} catch(e: NumberFormatException) {
return
}
println(number)
}