Expression
값을 만들어 내며 다른 Expression의 하위 요소로 계산에 참여할 수 있다.
Statement
자신을 둘러싸고 있는 가장 안쪽 블록의 최상위 요소로 존재하며, 아무런 값을 만들어내지 않는다.
문법 요소 | Java | Kotlin |
---|---|---|
if | if문, Statement | Expression Kotlin에서는 if가 값을 만들어 내기 때문에, 3항 연산자가 필요 없다. |
대입 = | 대입식, Expression | Statement 대입식과 비교식을 잘못 바꿔 쓰는 버그 방지 |
제어 구조 | Statement | Expression Loop를 제외한 모든 제어구조 |
when | Java에는 없음. | Expression 다른 변수에 대입할 수 있다. |
throw | Statement | Expression 다른 식에 포함될 수 있다. |
try, catch | Statement | Expression try, catch의 값을 변수에 대입할 수 있다. |
fun max(a: Int, b: Int): Int {
return if (a > b) a else b
}
Type Inference
타입 추론을 통해 컴파일러가 자동으로 함수 반환 타입을 정해준다. fun max(a: Int, a: Int) = if (a > b) a else b
val
Immutable
참조를 저장하는 변수final
var
Mutable
참조val answer = 1
val answer2: Int // 초기화 하지 않는다면 타입 추론이 불가능하기 때문에 반드시 타입 지정 해야한다.
// ...
answer2 = 2
val message: String
// ...
if (canPerformOperation()) {
message = "Success"
}
else {
message = "Failed"
}
val answer3 = 3
// ...
answer3 = -1 // Compile Error (Val cannot be reassigned)
val answer4 = 4
// ...
answer4 = "no answer" // Compile Error (Val cannot be reassigned, Type mismatch)
val languages = arrayListOf("Java", "Python", "Go", "C", "Javascript")
languages.add("Kotlin") // OK, val means just immutable reference
val language = "Kotlin"
println("Hello, ${language}!")
language = "Python"
print(f"Hello, {language}!")
public
class Person(val name: String)
public class Person {
private final String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
val
: getter 만 제공var
: getter, setter 모두 제공class Person(val name: String, val isMarried: Boolean)
$ kotlinc
Welcome to Kotlin version 1.7.10 (JRE 17.0.4.1+1-LTS-2)
Type :help for help, :quit for quit
>>> class Person(val name: String, val isMarried: Boolean)
>>> val person = Person("rolroralra", true)
>>> println(person.name)
rolroralra
>>> println(person.isMarried)
true
class Rectangle(val height: Int, val width: Int) {
val isSquare: Boolean
get() {
return height == width
}
}
class Rectangle(val height: Int, val width: Int) {
val isSquare: Boolean
get() = height == width
}
NOTE
1개의 kt 확장자 파일에 여러 개의 클래스를 넣으면, 해당 파일명이 패키지 마지막 이름이 되어서, 한 개의 kt파일 자체가 패키지 디렉토리를 의미하게 된다.
클래스 각각이 아주 작은 경우, 주로 사용.
enum
은 soft keyword, enum이라는 이름 사용 가능.enum class Color {
RED, ORANGE, YELLOW, GRREN, BLUE, INDIGO, VIOLET
}
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
}
when
switch
대체break
,
when (val response = executeRequest())
import geometry.colors.Color
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"
}
import geometry.colors.Color
import geometry.colors.Color.*
fun getWarmth(color: Color) = when (color) {
RED, ORANGE, YELLOW -> "warm"
GREEN -> "neutral"
BLUE, VIOLET -> "cold"
else -> "not supported"
}
fun mix(c1: Color, c2: Color) =
when (setOf(c1, c2)) {
setOf(RED, YELLOW) -> ORANGE
setOf(YELLOW, BLUE) -> GREEN
setOf(BLUE, VIOLET) -> INDIGO
else -> throw Exception("Not Supported")
}
fun Request.getBody() =
when (val response = executeRequest()) {
is Success -> response.body
is HttpError -> throw HttpException(response.status)
}
when
when
에 인자가 없을 경우, 분기 조건이 Boolean 결과를 계산하는 식이어야 한다.fun mixOptimized(c1: Color, c2: Color) =
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("Not Supported")
}
타입 검사와 타입 캐스트를 조합
is로 변수에 든 값의 타입을 검사한 다음에 그 값이 바뀔 수 없는 경우에만 작동한다.
타입 검사 is
- java의 타입 검사 instanceof
명시적 타입 캐스트 as
fun eval(expr: Expr): Int {
if (expr is Num) {
val n = expr as Num // 명시적 타입 캐스트. 불필요한 코드 (No cast needed)
return n.value
} else if (expr is Sum) {
return eval(expr.left) + eval(expr.right) // Smart Cast
} else {
throw Exception("Not Supported")
}
}
// smart cast
fun eval(expr: Expr): Int {
if (expr is Num)
return expr.value
else if (expr is Sum)
return eval(expr.left) + eval(expr.right)
else
throw Exception("Not Supported")
}
// when 활용, smart cast
fun eval(expr: Expr): Int =
when (expr) {
is Num -> expr.value
is Sum -> eval(expr.left) + eval(expr.right)
else -> throw Exception("Not Supported")
}
fun evalWithLogging(expr: Expr): Int =
when (expr) {
is Num -> {
println("nums: ${expr.value}")
expr.value // 블록의 마지막 식이므로, expr.value가 return 된다.
}
is Sum -> {
val left = evalWithLogging(expr.left)
val right = evalWithLogging(expr.right)
println("sums: $left + $right")
left + right
}
else -> throw Exception("Not Supported")
}
while
while (isValid) {
/* ... */
}
do {
/* ... */
} while (isValid)
for
0..10
: [0, 10] 폐구간0 until 10
: [0, 10) 개구간100 downTo 1
: 100,99,98,......,1 (included)100 downTo 1 step 2
: 100, 98, 96, ......, 2'A'..'Z'
fun fizzBuzz(i: Int) = when {
i % 15 == 0 -> "FizzBuzz"
i % 3 == 0 -> "Fizz"
i % 5 == 0 -> "Buzz"
else -> "$i"
}
fun main() {
for (i in 1..100) {
print("${fizzBuzz(i)} ")
}
println()
}
for ((key, value) in map)
val binaryReps = ('A'..'Z').groupBy ({ it }, { Integer.toBinaryString(it.code)})
for ((alphabet, binary) in binaryReps) {
println("$alphabet = $binary")
}
for (value in list)
for (index in list.indices)
for ((index, value) in list.withIndex())
val list = arrayListOf("10", "11", "1001")
for (element in list) {
println(element)
}
for (i in list.indices) {
println("index = $i, element = ${list[i]}")
}
for ((index, element) in list.withIndex()) {
println("list[$index] = $element")
}
for (value in set)
for (index in set.indices)
, set.elementAt(index)
for ((index, element) in set.withIndex())
val set = setOf("10", "11", "1001")
for (element in set) {
println(element)
}
for (i in set.indices) {
println("index = $i, element = ${set.elementAt(i)}")
}
for ((index, element) in set.withIndex()) {
println("set.elementAt($index) = $element")
}
in
in
연산자를 사용해 어떤 값이 범위/컬렉션에 속하는지 검사할 수 있다.!in
Comparable
을 구현한 클래스의 경우, 범위를 만들 수 있다.fun recognize(c: Char) = when (c) {
in '0'..'9' -> "Digit"
in 'a'..'z', in 'A'..'Z' -> "Alphabet"
in " \t\n\r" -> "LineAndSpace"
in "!@#$%^&*()[]{}()<>~\\|;:'\",./?" -> "Special"
else -> throw Exception("Not Supported")
}
println(recognize('8'))
println(recognize('d'))
println(recognize(' '))
println(recognize('@'))
println("Kotlin" in "A".."K") // false = "Kotlin" >= "A" && "Kotlin" <= "K"
println("Kotlin" in "A".."L") // true = "Kotlin" >= "A" && "Kotlin" <= "L"
println("Kotlin" in setOf("Java", "Python", "Go", "Scala")) // false
throw
new
를 붙일 필요가 없다.throw
는 식(Expression)이므로 다른 식에 포함될 수 있다.fun percentage(value: Int, totalValue: Int = 100) =
if (value in 0..totalValue) {
value.toDouble() * 100 / totalValue
} else {
throw IllegalArgumentException("Value must be between 0 and $totalValue: $value")
}
println("${percentage(47, 250)}%") // 18.8%
try catch
checked exception
, unchecked exception
을 구별하지 않는다.NOTE
Java의 경우, checked/unchecked exception을 구분하며, checked exception의 경우는 모두 catch로 처리하거나 throws 절에 명시해야 한다.
fun readNumber(reader: BufferedReader): Int? {
return try {
Integer.parseInt(reader.readLine())
} catch (e: NumberFormatException) {
null
}
}
val br = BufferedReader(InputStreamReader(System.`in`))
println(readNumber(br))