1. 변수 선언: val vs var
Java에서는 변수의 타입을 먼저 명시했지만, Kotlin은 변수가 ‘변경 가능한지’ 여부를 먼저 결정합니다.
| 구분 | Kotlin | Java | 설명 |
|---|---|---|---|
| 변경 불가능 (Immutable) | val name: String = "Kotlin" |
final String name = "Java"; |
val (value): 한 번 할당되면 재할당 불가. Java의 final과 동일. 가장 기본적으로 사용 권장. |
| 변경 가능 (Mutable) | var age: Int = 10 |
int age = 10; |
var (variable): 재할당 가능. |
| 타입 추론 | val message = "Hello"
|
(Java 10+)var message = "Hello"; |
Kotlin은 변수 선언 시 할당되는 값을 보고 타입을 자동으로 추론합니다. 타입을 명시하지 않아도 됩니다. |
Kotlin의 장점: val 사용을 기본으로 하여 불변성을 강제하므로, 코드의 안정성이 높아지고 예측 가능해집니다.
2. Null 안전성 (Null Safety)
Java에서 가장 악명 높은 NullPointerException (NPE)을 컴파일 시점에서 원천적으로 방지하는 Kotlin의 핵심 기능입니다.
| 구분 | Kotlin | Java | 설명 |
|---|---|---|---|
| Null 불허 (기본) | var a: String = "abc"
|
String a = "abc";
|
Kotlin의 모든 타입은 기본적으로 null을 허용하지 않습니다. |
| Null 허용 | var b: String? = "abc"
|
String b = "abc"; |
타입 뒤에 ? 를 붙여야만 null을 할당할 수 있습니다. |
| 안전한 호출 | b?.length |
if (b != null) { b.length(); } |
?. (Safe Call): b가 null이 아니면 length를 호출하고, null이면 null을 반환합니다. NPE가 발생하지 않습니다. |
| Elvis 연산자 | val l = b?.length ?: -1 |
int l = (b != null) ? b.length() : -1; |
?: (Elvis Operator): 왼쪽 표현식이 null이 아니면 그 값을, null이면 오른쪽 값을 반환합니다. 3항 연산자를 대체합니다. |
| Null 아님 단언 | val l = b!!.length |
(동일한 위험) b.length(); |
!! (Not-null Assertion): “b는 절대 null이 아니야!”라고 강제하는 연산자. 만약 b가 null이면 NPE가 발생합니다. 사용을 최소화해야 합니다. |
Kotlin의 장점: NPE 발생 가능성을 컴파일러가 미리 체크해주므로, 런타임 안정성이 비약적으로 향상됩니다.
3. 함수 선언
더 간결하고 다양한 기능을 제공합니다.
| 구분 | Kotlin | Java |
|---|---|---|
| 기본 함수 | fun sum(a: Int, b: Int): Int { return a + b } |
public int sum(int a, int b) { return a + b; } |
| 표현식 형태 | fun sum(a: Int, b: Int) = a + b |
(해당 없음) |
| 기본 인자 (Default Argument) | fun greet(name: String = "Guest") { ... } |
(메서드 오버로딩 필요) |
| 이름 있는 인자 (Named Argument) | sum(b = 2, a = 1) |
(해당 없음) |
Kotlin의 장점: 함수 오버로딩을 줄이고, 인자의 의미를 명확하게 전달할 수 있어 가독성이 높아집니다.
4. 데이터 클래스 (Data Class)
Java에서 Lombok 라이브러리를 써야 했던 getter, setter, equals(), hashCode(), toString()을 자동으로 만들어주는 마법 같은 기능입니다.
| Kotlin | Java (Lombok 없이) |
|---|---|
data class User(val name: String, val age: Int) |
java<br>public class User {<br> private final String name;<br> private final int age;<br><br> public User(String name, int age) { ... }<br> public String getName() { ... }<br> public int getAge() { ... }<br> @Override public boolean equals(Object o) { ... }<br> @Override public int hashCode() { ... }<br> @Override public String toString() { ... }<br>}<br> |
Kotlin의 장점: 단 한 줄로 데이터 모델을 정의할 수 있어, 엄청난 양의 보일러플레이트 코드를 줄여줍니다.
5. 스마트 캐스트 (Smart Casts)
타입을 한번 확인하면, 개발자가 다시 형변환(casting)할 필요 없이 컴파일러가 알아서 타입을 변환해 줍니다.
| Kotlin | Java |
|---|---|
kotlin<br>fun process(obj: Any) {<br> if (obj is String) {<br> // obj는 자동으로 String 타입으로 취급됨<br> println(obj.length)<br> }<br>}<br> |
java<br>void process(Object obj) {<br> if (obj instanceof String) {<br> // 수동으로 형변환 필요<br> String str = (String) obj;<br> System.out.println(str.length());<br> }<br>}<br> |
Kotlin의 장점: 불필요한 형변환 코드를 제거하여 코드를 깔끔하고 안전하게 만듭니다.
6. 확장 함수 (Extension Functions)
기존 클래스의 소스 코드를 수정하지 않고도 새로운 함수를 추가할 수 있는 기능입니다.
예시: String 클래스에 마지막 글자를 가져오는 함수 추가
| Kotlin | Java |
|---|---|
kotlin<br>// String 클래스를 확장<br>fun String.lastChar(): Char = this.get(this.length - 1)<br><br>// 사용<br>val last = "abc".lastChar() // 결과: 'c'<br> |
java<br>// 별도의 유틸리티 클래스 필요<br>class StringUtils {<br> public static char lastChar(String str) {<br> return str.charAt(str.length() - 1);<br> }<br>}<br><br>// 사용<br>char last = StringUtils.lastChar("abc");<br> |
Kotlin의 장점: 마치 원래 그 클래스에 있던 메서드처럼 자연스럽게 사용할 수 있어, 코드 가독성과 재사용성을 높입니다.
그 외 주요 차이점
- 세미콜론(;): Kotlin에서는 문장 끝에 세미콜론을 붙이지 않아도 됩니다.
- Checked Exceptions: Kotlin은 Java의
try-catch를 강제하는 Checked Exception을 사용하지 않습니다. - 100% 상호운용성: Kotlin 코드에서 Java 클래스를, Java 코드에서 Kotlin 클래스를 아무 제약 없이 호출할 수 있습니다. 기존 Java 프로젝트에 점진적으로 Kotlin을 도입할 수 있는 이유입니다.
이처럼 Kotlin은 Java 개발자들이 겪었던 여러 불편한 점들을 언어 차원에서 해결하여, 더 안전하고, 간결하며, 생산성 높은 코드를 작성할 수 있도록 돕습니다. Java에 익숙하시다면 금방 배우고 그 매력에 빠지실 수 있을 겁니다.