KB 08 문과 식 — if 와 try 가 값을 돌려준다
고친 사람 github-actions[bot]
0. 삼항 연산자를 옮기면 컴파일이 안 된다
둘 중 큰 값을 고를 때 자바 개발자의 손은 삼항 연산자로 갑니다. int max = a > b ? a : b; 한 줄입니다. 이 줄을 담은 Ternary.java 는 문제없이 7 을 찍습니다.
같은 줄을 코틀린 파일 Ternary.kt 로 옮겼습니다.
fun main() {
val a = 3
val b = 7
val max = a > b ? a : b
println(max)
}
fun main()— 프로그램이 시작하는 함수입니다. 클래스로 감싸지 않고 파일에 바로 씁니다val— 다시 대입할 수 없는 변수입니다. 타입을 안 적으면 오른쪽 값에서 알아냅니다println(max)— 한 줄 출력입니다. 코틀린은 줄 끝에 세미콜론을 쓰지 않습니다
이 파일을 코틀린 컴파일러 kotlinc 에 넣었습니다. -d out 은 결과물을 out 폴더에 쓰라는 옵션입니다.
$ kotlinc Ternary.kt -d out
Ternary.kt:4:21: error: syntax error: Unexpected tokens (use ';' to separate expressions on the same line).
val max = a > b ? a : b
^^^^^^^
? 부터 줄 끝까지가 「예상하지 못한 토큰」으로 잡혔습니다. 토큰은 컴파일러가 소스를 읽을 때 쪼개는 낱말 단위입니다. ?·a·: 는 코틀린에 있는 토큰이지만, 코틀린 문법에는 조건 ? 값 : 값 이라는 모양이 없습니다. a > b 뒤에 ? 가 올 수 있는 문법이 없어 컴파일러는 거기서 멈췄습니다.
코틀린에서는 이 줄을 val max = if (a > b) a else b 로 씁니다. 이 줄로 바꾼 Max.kt 는 7 을 찍습니다.
코틀린 공식 문서는 삼항 연산자가 빠진 이유를 if 에서 찾습니다(「The if expression serves the same purpose as the ternary operator」 코틀린 문서). if 가 값을 돌려주니 따로 연산자를 둘 필요가 없다는 뜻입니다. 이 편은 그 차이를 문과 식이라는 두 낱말로 가릅니다. 문은 값을 남기지 않고, 식은 값을 남깁니다. 그 선을 그은 뒤 if·try·throw 를 차례로 자바와 맞대 봅니다. 출력은 전부 Kotlin 2.4.10, JDK(Java Development Kit) 21 에서 직접 돌린 결과입니다.
1. 문과 식 — 값이 남느냐 남지 않느냐
식(expression) 은 계산이 끝나면 값이 하나 남는 코드 조각입니다. a + b, a > b, max * 2 가 식입니다. 값이 남으니 = 오른쪽에도, 함수 인자로도 넣을 수 있습니다.
문(statement) 은 무언가를 하되 값을 남기지 않는 코드 조각입니다. 자바의 if, for, try 가 문입니다. 남는 값이 없으니 = 오른쪽에 넣을 수 없습니다.
둘을 가르는 시험은 하나입니다. val x = (그 코드) 로 쓸 수 있으면 식입니다.
대입은 자바에서만 식이다
두 언어는 이 선을 다르게 긋습니다. 가장 먼저 부딪히는 사례가 대입입니다. Assign.java 는 대입 x = 5 를 괄호로 싸서 덧셈에 넣었습니다. 자바에서 대입은 식이라, 대입한 값 5 가 괄호 밖으로 남습니다. 컴파일해 돌렸습니다. 자바 결과물은 코틀린 결과물과 섞이지 않게 jout 폴더에 모읍니다.
public class Assign {
public static void main(String[] args) {
int x;
int y = (x = 5) + 1;
// ↓ 5 6
System.out.println(x + " " + y);
}
}
$ javac -d jout Assign.java
$ java -cp jout Assign
(x = 5) 가 5 를 남겨 y 는 6 이 됐습니다. 같은 줄을 코틀린 Assign.kt 로 옮겼습니다. x 는 var x = 0 으로 선언했습니다. var 는 val 과 달리 다시 대입할 수 있는 변수입니다. 이번에는 컴파일 단계에서 멈춥니다.
$ kotlinc Assign.kt -d out
Assign.kt:3:14: error: only expressions are allowed in this context.
val y = (x = 5) + 1
^^^^^
에러 문구는 「이 문맥에는 식만 올 수 있다」입니다. 코틀린에서 대입 x = 5 는 값을 남기지 않는 문입니다. 그래서 괄호 안에 넣어 값으로 쓸 수 없습니다.
BufferedReader 로 한 줄씩 읽을 때 자바에서 흔히 쓰는 while ((line = r.readLine()) != null) 도 같은 이유로 코틀린에서는 그대로 쓸 수 없습니다. 옮겨 컴파일하면 같은 only expressions are allowed in this context 에러가 납니다.
방향이 반대인 셋
대입과 반대로 움직인 코드도 있습니다. 자바에서 문이던 if·try·throw 가 코틀린에서는 식입니다.
| 코드 | 자바 | 코틀린 | 다루는 절 |
|---|---|---|---|
x = 5 (대입) |
식 | 문 | 1절 |
if (...) ... else ... |
문 | 식 | 2절 |
try { ... } catch ... |
문 | 식 | 3절 |
throw ... |
문 | 식 | 4절 |
식은 값이 남고 문은 남지 않습니다. 코틀린은 대입을 문으로 내리고 if·try·throw 를 식으로 올렸습니다.
2. if 가 값을 돌려준다
0절의 if (a > b) a else b 가 어떻게 값이 되는지 봅니다. 가지가 여러 줄일 때와 else 가 빠질 때를 차례로 봅니다.
자바는 = 오른쪽에서 if 를 거절한다
먼저 자바입니다. Grade.java 의 grade(int score) 메서드는 점수로 등급을 고르는 if 를 = 오른쪽에 두었습니다. javac 는 에러 세 개를 냈습니다. 첫 에러가 원인이라 뒤따르는 not a statement 두 개는 ... 로 줄였습니다.
$ javac -d jout Grade.java
Grade.java:3: error: illegal start of expression
String g = if (score >= 90) "A"; else "B";
^
...
3 errors
illegal start of expression 은 「식이 이렇게 시작할 수 없다」는 뜻입니다. = 오른쪽은 식이 올 곳인데, 자바의 if 는 문입니다. 자바에서는 삼항 연산자로 바꾸거나, 변수를 먼저 선언하고 가지마다 대입해야 합니다.
블록의 마지막 줄이 값이다
코틀린은 가지가 여러 줄이어도 if 가 값이 됩니다. Grade.kt 는 90점 이상이면 한 줄을 찍고 "A" 를 고릅니다.
fun grade(score: Int): String {
val g = if (score >= 90) {
println("top") // top
"A"
} else {
"B"
}
return g
}
fun main() {
println(grade(95)) // A
println(grade(70)) // B
}
score: Int— 코틀린은 이름 뒤에 콜론, 그 뒤에 타입을 씁니다. 괄호 뒤: String은 반환 타입입니다if (...) { ... } else { ... }— 가지를 중괄호 블록으로 쓴if식입니다- 블록의 마지막 줄
"A"가 그 가지의 값입니다. 앞줄println("top")은 실행만 되고 값이 되지 않습니다 return g— 자바의return과 같습니다
컴파일한 뒤 kotlin 명령으로 돌립니다. classpath 는 클래스를 찾을 경로 목록입니다. kotlin 은 코틀린 표준 라이브러리 jar 를 이 목록에 스스로 넣어 줍니다.
실행할 클래스 GradeKt 는 컴파일러가 파일 이름 + Kt 로 지은 이름입니다. 파일에 바로 쓴 함수는 이 클래스의 static 메서드가 됩니다(KB 01 첫 파일 — fun main 한 줄이 JVM 에서 도는 법).
$ kotlinc Grade.kt -d out
$ kotlin -cp out GradeKt
95점일 때는 top 을 찍은 뒤 A 를 돌려주었습니다. 70점일 때는 else 블록의 마지막 줄 "B" 가 값입니다.
else 가 빠지면
if 가 값을 남기려면 조건이 거짓일 때 남길 값도 있어야 합니다. NoElse.kt 는 val score = 95 를 두고 else 없는 if 를 val g 에 넣었습니다. 조건은 참인데도 컴파일러는 거절합니다.
$ kotlinc NoElse.kt -d out
NoElse.kt:3:13: error: 'if' must have both main and 'else' branches when used as an expression.
val g = if (score >= 90) "A"
^^
「식으로 쓸 때는 본 가지와 else 가지가 둘 다 있어야 한다」는 에러입니다. 이 검사에서 컴파일러는 score 의 실제 값을 따지지 않고 가지가 둘 다 있는지만 봅니다. 조건이 거짓인 경우 g 에 넣을 값이 없으니 거절합니다.
단서는 「식으로 쓸 때」입니다. 4절에 나올 findName 의 if (id == 1) return "kim" 은 else 가 없는데도 경고 없이 컴파일됩니다. 그 if 는 값을 쓰지 않는 문이기 때문입니다.
코틀린의 if 는 가지의 마지막 줄을 값으로 남기는 식입니다. 값으로 쓸 때만 else 가 필수입니다.
3. try 도 식이다
자바에서 try 안에서 정한 값을 밖에서 쓰려면 변수를 try 앞에 선언해야 했습니다. 그 변수를 final 로 두려 하면 문제가 생깁니다.
자바 — final 변수는 catch 에서 못 채운다
Parse.java 는 문자열을 숫자로 바꾸고, 실패하면 -1 을 넣습니다. 한 번만 대입할 값이라 final 을 붙였습니다.
public class Parse {
static int parse(String s) {
final int n;
try {
n = Integer.parseInt(s);
} catch (NumberFormatException e) {
n = -1;
}
return n;
}
}
try 가지와 catch 가지 중 한쪽만 실행되니 n 은 한 번만 대입됩니다. 그런데 javac 의 판단은 다릅니다.
$ javac -d jout Parse.java
Parse.java:7: error: variable n might already have been assigned
n = -1;
^
1 error
「n 이 이미 대입됐을 수도 있다」는 에러입니다. javac 는 try 블록의 어느 줄에서 예외가 났는지 모른다고 봅니다. 그래서 catch 에 들어온 시점에 n 이 이미 채워졌을 가능성을 지우지 못합니다.
final 을 뗀 Parse2.java 는 통과하고 parse("abc") 에 -1 을 찍었습니다. 대신 n 은 언제든 다시 대입할 수 있는 변수로 남습니다.
코틀린 — try 가 값을 남긴다
코틀린은 try 전체를 val 의 오른쪽에 둡니다. Parse.kt 입니다.
fun parse(s: String): Int {
val n = try {
s.toInt()
} catch (e: NumberFormatException) {
-1
}
return n
}
fun main() {
println(parse("42")) // 42
println(parse("abc")) // -1
}
s.toInt()— 문자열을Int로 바꿉니다. 숫자가 아니면NumberFormatException을 던집니다catch (e: NumberFormatException)— 자바의catch (NumberFormatException e)입니다. 여기서도 이름이 먼저, 타입이 뒤입니다val n = try { ... } catch ... { ... }— 예외가 없으면try블록의 마지막 줄이, 잡으면catch블록의 마지막 줄이 값입니다
"42" 와 "abc" 를 넣어 돌렸습니다.
$ kotlinc Parse.kt -d out
$ kotlin -cp out ParseKt
다시 대입할 수 없는 val 인데도 에러가 없습니다. 대입은 val n = 한 번뿐입니다. 어느 가지의 값이 들어갈지는 try 식이 정합니다. 앞의 Parse.java 를 막아 세운 might already have been assigned 는 나오지 않습니다. 가지마다 따로 대입하는 줄이 아예 없기 때문입니다.
finally 의 마지막 줄은 값이 되지 않는다
try 식에 finally 를 붙여도 값은 try 나 catch 쪽에서 나옵니다. val r = try { "try" } finally { "finally" } 를 담은 Finally.kt 는 try 를 찍었습니다. "finally" 줄에는 expression is unused(쓰이지 않는 식) 경고가 났습니다.
코틀린 공식 문서의 예외 항목에도 같은 규칙이 있습니다(「The finally block is always executed, but it doesn't change the result of the try-catch block.」 코틀린 문서 「Exceptions」).
코틀린의 try 는 try 나 catch 블록의 마지막 줄을 값으로 남깁니다. 그래서 자바에서 final 을 포기해야 했던 코드를 val 한 줄로 씁니다.
4. throw 도 식이다
값이 null 이면 기본값 대신 예외를 던지고 싶을 때가 있습니다. 이 절에서는 그 한 줄이 자바와 코틀린에서 어떻게 갈리는지 봅니다. 먼저 그 한 줄에 쓰이는 연산자 ?: 부터 봅니다.
?: 는 null 인가를 본다
코틀린에는 ?: 라는 연산자가 있습니다. 모양이 비슷해 삼항 연산자의 줄임꼴처럼 보이지만 뜻이 다릅니다. Elvis.kt 입니다.
fun findName(id: Int): String? {
if (id == 1) return "kim"
return null
}
fun main() {
val name = findName(2) ?: "none"
println(name) // none
}
String?— 타입 뒤의?는null일 수도 있다는 표시입니다. 물음표 없는String에는null을 담지 못합니다?:— 왼쪽이null이 아니면 왼쪽 값을,null이면 오른쪽 값을 씁니다. 엘비스 연산자(Elvis operator)라고 부릅니다
findName(2) 가 null 을 돌려주었으므로 name 은 오른쪽의 "none" 이 됐습니다. findName(2) 결과를 변수 found 에 받았다면 자바로는 found != null ? found : "none" 입니다. 세 모양을 나란히 놓으면 이렇습니다.
| 모양 | 무엇을 보고 고르나 | 코틀린에서 |
|---|---|---|
c ? x : y |
c 가 참인가 |
문법 에러 |
if (c) x else y |
c 가 참인가 |
삼항 연산자 대신 쓴다 |
x ?: y |
x 가 null 인가 |
null 일 때 쓸 값을 정한다 |
?: 가 가르는 기준은 참과 거짓이 아니라 null 인가 아닌가입니다. null 을 다루는 문법은 뒤에 나올 널 편에서 따로 봅니다.
?: throw 한 줄
이번에는 ?: 의 오른쪽에 기본값 대신 throw 를 둡니다. Require.kt 입니다. findName 은 Elvis.kt 와 같아 생략했습니다.
fun nameOf(id: Int): String {
val name = findName(id) ?: throw IllegalArgumentException("no user")
return name
}
fun main() {
println(nameOf(1)) // kim
println(nameOf(2)) // 예외로 멈춘다
}
IllegalArgumentException("no user")— 예외 객체 생성입니다. 코틀린에는new가 없습니다?: throw ...—findName(id)가null이면 곧바로 예외를 던집니다. 아니면 그 값이name에 들어갑니다
nameOf(2) 는 findName 이 null 을 돌려주어 Exception in thread "main" java.lang.IllegalArgumentException: no user 를 찍고 멈춥니다.
자바로 옮기면 삼항 연산자의 한쪽 가지에 throw 를 두는 모양입니다. Require.java 의 nameOf 입니다.
static String nameOf(int id) {
String found = findName(id);
String name = found != null
? found
: throw new IllegalArgumentException("no user");
return name;
}
findName 은 kim 아니면 null 을 돌려주는 같은 메서드라 생략했습니다. 컴파일 결과입니다.
$ javac -d jout Require.java
Require.java:10: error: illegal start of expression
: throw new IllegalArgumentException("no user");
^
1 error
2절과 같은 illegal start of expression 입니다. 자바의 throw 는 문이라 삼항 연산자의 가지에 들어가지 못합니다. JDK 14 부터 쓰는 switch 식의 가지만은 throw 문을 따로 허용합니다(JEP 361, Switch.java 로 확인).
코틀린의 throw 는 값 자리에 들어가는 식입니다. 그래서 ?: 의 오른쪽에 두어 null 검사와 예외를 한 줄로 씁니다.
5. throw 의 타입은 Nothing
4절의 nameOf 를 다시 봅니다. ?: 의 오른쪽에 throw 를 뒀는데도 name 은 String 으로 남았고, return name 이 그대로 통과했습니다. 왜 그럴까요.
?: 식 전체가 무엇이 되는지는 왼쪽 값과 오른쪽 식을 둘 다 담을 수 있느냐로 정해집니다.
값이 하나도 없는 타입
답은 throw 식의 타입인 Nothing 에 있습니다. Nothing 은 코틀린에 내장된 타입으로, 정상적으로 끝나지 않는 식의 타입입니다. 늘 예외를 던지거나 끝없이 도는 코드가 여기에 해당합니다. 끝나지 않으니 실제로 돌려주는 값은 하나도 없습니다.
값이 없으니 어떤 타입이 필요한 곳에 두어도 모순이 생기지 않습니다. 그래서 Nothing 은 모든 타입의 하위 타입입니다. 코틀린 타입 계층의 맨 위에는 자바 Object 에 해당하는 Any 가 있습니다.
아래 그림에서 화살표는 위에서 아래로 「이것의 하위 타입」을 가리킵니다. 화살표가 모두 Nothing 으로 모이는 것이 「모든 타입의 하위 타입」이라는 말의 그림입니다.
flowchart TD
A["Any · 자바 Object 에 해당"] --> S["String"]
A --> I["Int"]
A --> U["Unit · 6절"]
S --> N["Nothing · 값이 하나도 없다"]
I --> N
U --> N
그림은 null 을 못 담는 타입만 그렸습니다. String? 처럼 null 을 담는 타입까지 넣으면 맨 위는 Any? 입니다.
String 이 필요한 곳에도, Int 가 필요한 곳에도 Nothing 이 들어갑니다. 4절의 ?: 도 그렇습니다. String 자리에 Nothing 을 넣어도 되니, 둘을 함께 담을 자리를 String 보다 넓게 잡을 까닭이 없습니다. 그래서 name 은 String 으로 남습니다.
Nothing 을 돌려주는 함수
Nothing 은 함수 반환 타입으로도 씁니다. Fail.kt 의 fail 은 늘 예외를 던지는 함수입니다.
fun fail(message: String): Nothing {
throw IllegalStateException(message)
}
fun check(n: Int): Int {
val size = if (n >= 0) n else fail("negative")
return size
}
: Nothing— 이 함수는 절대 정상적으로 돌아오지 않는다는 선언입니다if (n >= 0) n else fail("negative")— 한쪽 가지는Int, 다른 쪽은Nothing입니다. 식 전체의 타입은Int가 되어size에 들어갑니다
자바에는 이런 타입이 없습니다. 늘 예외를 던지는 일을 메서드 하나로 빼 두고 싶어도, 그 메서드를 void 로 만들면 삼항 연산자 가지에 넣을 수 없습니다. bad type in conditional expression 에러가 납니다.
그래서 자바에서는 그 메서드가 예외를 던지지 않고 예외 객체를 돌려주게 만듭니다. 던지는 것은 부르는 쪽에서 throw fail(...) 로 합니다.
JVM(Java Virtual Machine), 곧 자바 가상 머신에도 Nothing 은 없습니다. 클래스 파일에서 무엇이 됐는지 javap 로 봤습니다. javap -p 는 JDK 에 딸린 도구로, 클래스 파일에 든 선언을 자바 선언문 모양으로 보여 줍니다(-p 는 private 까지).
$ kotlinc Fail.kt -d out
$ javap -p out/FailKt.class
Compiled from "Fail.kt"
public final class FailKt {
public static final java.lang.Void fail(java.lang.String);
public static final int check(int);
}
fail 의 반환 타입이 java.lang.Void 입니다. Void 는 인스턴스를 만들 수 없는 자바 클래스입니다. 값이 하나도 없는 Nothing 을 JVM 의 타입 가운데 뜻이 가장 가까운 것으로 옮긴 결과입니다.
클래스 파일을 자바 코드 모양으로 되돌리는 일을 디컴파일(decompile)이라 부릅니다. 같은 파일을 디컴파일러 CFR 0.152 로 되돌리면, check 안의 fail("negative") 호출 바로 뒤에 throw new KotlinNothingValueException(); 줄이 붙어 있습니다. 소스에 없는 줄입니다. 반환 타입이 Nothing 인 함수가 혹시 돌아오더라도 다음 줄로 넘어가지 못하게 컴파일러가 넣은 줄입니다.
코틀린의 throw 는 타입이 Nothing 인 식입니다. Nothing 은 모든 타입의 하위 타입이라 ?: 의 오른쪽이나 if 의 한 가지처럼 값이 필요한 곳에 들어갑니다.
6. Unit 이 void 를 대신한다
throw 가 Nothing 이라면, 값을 돌려주지 않는 평범한 함수의 타입은 무엇일까요. 자바라면 void 입니다. 이 절에서는 코틀린의 Unit 을 자바 void 와 맞대 봅니다.
Unit 은 값이 딱 하나뿐인 타입입니다. 그 하나뿐인 값의 이름도 Unit 입니다. 공식 문서의 표현으로는 「Unit is a type that has only one value, also called Unit.」 입니다(코틀린 문서 「Functions」).
중괄호 { } 로 본문을 쓰고 반환 타입을 안 적은 함수가 돌려주는 것이 이 Unit 입니다.
Nothing 과 헷갈리기 쉬우니 갈라 둡니다. Nothing 은 값이 하나도 없는 타입입니다. Unit 은 의미 없는 값이 하나 있는 타입입니다.
코틀린 — Unit 은 변수에 담긴다
Log.kt 는 중괄호 본문에 반환 타입을 안 적은 log 와 : Unit 을 적은 logUnit 을 나란히 두었습니다. main 에서는 log 의 결과를 변수에 받아 찍습니다.
fun log(message: String) {
println(message)
}
fun logUnit(message: String): Unit {
println(message)
}
fun main() {
val u = log("hi") // hi
println(u) // kotlin.Unit
}
log 가 돌려준 것이 무엇인지 u 를 찍어 확인합니다.
$ kotlinc Log.kt -d out
$ kotlin -cp out LogKt
u 에는 kotlin.Unit 이 담겼습니다. 함수 호출은 두 언어 모두 식입니다. 다만 자바의 void 호출은 남는 값이 없고, 코틀린 함수는 Unit 이라는 값 하나를 남깁니다. 그래서 변수에 받을 수 있습니다.
이번에는 클래스 파일의 모양입니다. javap -p 로 선언을 봤습니다.
$ javap -p out/LogKt.class
Compiled from "Log.kt"
public final class LogKt {
public static final void log(java.lang.String);
public static final void logUnit(java.lang.String);
public static final void main();
public static void main(java.lang.String[]);
}
log 와 logUnit 둘 다 void 입니다. 반환 타입을 안 적든 : Unit 을 적든 클래스 파일에서는 같습니다. 인자 없는 main() 옆의 main(String[]) 은 JDK 21 까지의 실행기가 찾는 main(String[]) 모양에 맞추려고 컴파일러가 덧붙인 메서드입니다(KB 01 첫 파일 — fun main 한 줄이 JVM 에서 도는 법).
자바 — void 는 값이 아니다
같은 일을 자바로 하면 void 메서드 log 의 결과를 Object u = log("hi"); 로 받는 모양이 됩니다. Object 는 무엇이든 담는 타입인데도 이 줄을 담은 Log.java 는 컴파일되지 않습니다.
$ javac -d jout Log.java
Log.java:7: error: incompatible types: void cannot be converted to Object
Object u = log("hi");
^
1 error
log("hi") 호출은 식이지만, void 라 끝난 뒤 남는 값이 없어 Object 로도 바꿀 수 없습니다. 코틀린의 Unit 은 값이 하나 있는 진짜 타입이라 val 에 담깁니다.
코틀린의 Unit 은 클래스 파일에서 void 가 됩니다. 그런데도 소스에서는 val 에 담겨 kotlin.Unit 으로 찍힙니다.
7. 한 장 요약
코틀린에는 삼항 연산자가 없다.
if·try·throw가 값을 남기는 식이기 때문이다.throw의 타입은 모든 타입의 하위 타입인Nothing, 값이 없는 함수의 타입은 클래스 파일에서void가 되는Unit, 그리고 자바에서 식이던 대입은 코틀린에서 문이다.
| 질문 | 자바 | 코틀린 | 확인한 방법 |
|---|---|---|---|
| 조건으로 값 고르기 | c ? x : y |
if (c) x else y. ? : 는 문법 에러 |
Ternary.kt · Max.kt |
?: |
없음 | 왼쪽이 null 이면 오른쪽 값 |
Elvis.kt |
대입 x = 5 |
식. (x = 5) + 1 이 6 |
문. only expressions are allowed in this context |
Assign |
if 를 = 오른쪽에 |
illegal start of expression |
된다. 블록의 마지막 줄이 값 | Grade |
else 없는 if |
문이라 늘 된다 | 값으로 쓰면 'if' must have both main and 'else' branches |
NoElse.kt |
try 에서 값 정하기 |
final 이면 might already have been assigned |
val n = try { ... } catch ... { ... } |
Parse |
finally 의 마지막 줄 |
해당 없음 | 값이 되지 않고 expression is unused 경고 |
Finally.kt |
삼항 연산자 가지에 throw |
illegal start of expression. switch 식 가지만 허용 |
?: throw 한 줄로 쓴다 |
Require · Switch.java |
| 늘 던지는 함수 | 그런 타입이 없어 예외 객체를 돌려받아 throw fail(...) |
반환 타입 Nothing. 클래스 파일에서 java.lang.Void |
Fail.kt · javap -p |
| 값 없는 함수의 타입 | void. 변수에 못 담는다 |
Unit. 클래스 파일은 void, 담으면 kotlin.Unit |
Log · javap -p |
관련 항목
문과 식을 가르는 코틀린 문법 요소
문장과 식 · if 식 · try 식 · throw 식 · 대입문 · 블록 · 엘비스 연산자 · 삼항 연산자
코틀린 타입 계층에서 Nothing 과 Unit 이 서는 층
Nothing · Unit · Any · 하위 타입 · 바텀 타입 · 널 가능 타입
if 식과 try 식이 번역되는 자바의 문법과 규칙
void · java.lang.Void · final 지역 변수 · 확정 대입 · switch 식 · finally 블록
식으로 쓸 때 컴파일러가 내는 에러와 경고
컴파일 에러 · 쓰이지 않는 식 · NumberFormatException · IllegalArgumentException · KotlinNothingValueException
컴파일 결과를 열어 보는 도구
javap · 디컴파일 · 디컴파일러 · CFR · 클래스 파일