| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 | 31 |
- 스프링 핵심원리
- 알고리즘
- 자바
- 이차전지관련주
- MongoDB
- 클린아키텍처
- ElasticSearch
- 카카오
- 이펙티브자바
- k8s
- 예제로 배우는 스프링 입문
- effectivejava
- 티스토리챌린지
- JavaScript
- 김영한
- 자바스크립트
- kubernetes
- Sort
- 스프링
- Spring
- 엘라스틱서치
- 이펙티브 자바
- 스프링부트
- Effective Java 3
- java
- 알고리즘정렬
- Effective Java
- 스프링핵심원리
- Kotlin
- 오블완
- Today
- Total
Kim-Baek 개발자 이야기
Kotlin 변수와 타입 완벽 가이드 | val vs var, 타입 추론, Nullable 타입 마스터하기 본문
이 글을 읽으면: Kotlin의 변수 선언 방식인 val과 var의 차이를 완벽하게 이해하고, Java에는 없는 강력한 타입 추론과 Null Safety 시스템을 실전 예제로 배울 수 있습니다.
📌 목차
- 들어가며 - Java 변수 선언의 불편함
- val vs var - 언제 무엇을 쓸까?
- 타입 추론 - 타입을 안 써도 되는 마법
- 명시적 타입 선언 - 언제 필요할까?
- 기본 타입 완벽 정리
- Nullable 타입 - NPE와의 전쟁
- 타입 변환과 체크
- 마무리 - 다음 편 예고
들어가며 - Java 변수 선언의 불편함
Java로 개발하면서 이런 경험 있으신가요?
// Java - 매번 타입을 명시해야 함
String name = "규철";
int age = 30;
List<String> hobbies = new ArrayList<String>(); // 타입 중복!
Map<String, Integer> scores = new HashMap<String, Integer>(); // 너무 길다!
// 불변 변수 만들려면 final 붙여야 함
final String company = "우리회사";
// company = "다른회사"; // 컴파일 에러
Kotlin은 이렇게 간단합니다:
// Kotlin - 타입 추론 + 간결한 불변 선언
val name = "규철" // 타입 자동 추론
val age = 30 // Int로 추론
val hobbies = listOf("테니스", "개발") // List<String> 추론
val scores = mapOf("수학" to 95) // Map<String, Int> 추론
오늘은 이 마법 같은 기능들을 하나씩 파헤쳐보겠습니다.
val vs var - 언제 무엇을 쓸까?

val - 불변 변수 (Immutable)
val은 "value"의 약자로, Java의 final과 같습니다.
fun main() {
val name = "규철"
// name = "철규" // ❌ 컴파일 에러!
// Val cannot be reassigned
println(name) // 규철
}
var - 가변 변수 (Mutable)
var는 "variable"의 약자로, 일반 변수입니다.
fun main() {
var age = 30
println(age) // 30
age = 31 // ✅ 가능
println(age) // 31
}
실전 비교 - 사용자 정보 관리
fun main() {
// 변하지 않는 정보 - val
val userId = "user123"
val birthYear = 1990
// 변할 수 있는 정보 - var
var nickname = "코틀린초보"
var point = 1000
// 포인트 적립
point += 500
println("적립 후 포인트: $point") // 1500
// 닉네임 변경
nickname = "코틀린중수"
println("변경된 닉네임: $nickname")
// userId = "user456" // ❌ 에러!
}
언제 val을 쓰고 언제 var를 쓸까?
기본 원칙: 무조건 val을 먼저 쓰고, 필요할 때만 var로 변경
상황 선택 예시
| 상수 | val | val PI = 3.14 |
| 설정값 | val | val maxRetry = 3 |
| 함수 파라미터 | val (자동) | fun test(name: String) |
| 반복문 카운터 | var | var i = 0 |
| 누적 계산 | var | var sum = 0 |
| 상태 변경 | var | var isLoading = false |
Java와 비교
// Java - final을 깜빡하기 쉬움
String name = "규철"; // 실수로 변경 가능
name = "철규"; // 의도치 않은 변경
// 불변으로 만들려면 명시적 final
final String safeName = "규철";
// safeName = "철규"; // 컴파일 에러
// Kotlin - 기본이 val (불변 권장)
val name = "규철"
// name = "철규" // 컴파일 에러 (안전!)
var changeable = "규철"
changeable = "철규" // 명시적으로 var를 선택했으므로 OK
타입 추론 - 타입을 안 써도 되는 마법
기본 타입 추론
Kotlin은 초기값을 보고 타입을 자동으로 추론합니다.
fun main() {
val number = 42 // Int로 추론
val pi = 3.14 // Double로 추론
val message = "안녕" // String으로 추론
val isValid = true // Boolean으로 추론
// 타입 확인하기
println(number::class.simpleName) // Int
println(pi::class.simpleName) // Double
println(message::class.simpleName) // String
}
컬렉션 타입 추론
fun main() {
val numbers = listOf(1, 2, 3, 4, 5)
// List<Int>로 추론
val names = listOf("규철", "영희", "철수")
// List<String>으로 추론
val scores = mapOf(
"국어" to 90,
"영어" to 85,
"수학" to 95
)
// Map<String, Int>로 추론
}
Java와 비교
// Java - 타입 중복의 고통
List<String> names = new ArrayList<String>();
Map<String, Integer> scores = new HashMap<String, Integer>();
// Java 10+ 에서 var 추가 (로컬 변수만)
var names = new ArrayList<String>(); // 그래도 타입 명시 필요
// Kotlin - 타입 한 번도 안 씀
val names = listOf("규철", "영희")
val scores = mapOf("수학" to 95)
명시적 타입 선언 - 언제 필요할까?
타입 명시가 필요한 경우
1. 초기화하지 않을 때
fun main() {
val name: String // 타입 명시 필수
if (조건) {
name = "규철"
} else {
name = "손님"
}
println(name)
}
2. 더 넓은 타입으로 선언할 때
fun main() {
val number: Number = 42 // Int를 Number로
val list: List<Any> = listOf(1, "문자", true) // 여러 타입 섞기
}
3. 인터페이스 타입으로 받을 때
fun main() {
val list: MutableList<String> = arrayListOf()
// ArrayList가 아닌 MutableList 인터페이스 타입
}
타입 선언 형식
// 형식: val/var 변수명: 타입 = 값
val name: String = "규철"
var age: Int = 30
val price: Double = 19.99
val isValid: Boolean = true
기본 타입 완벽 정리
숫자 타입
fun main() {
// 정수
val byte: Byte = 127 // 8bit: -128 ~ 127
val short: Short = 32767 // 16bit
val int: Int = 2147483647 // 32bit (기본)
val long: Long = 9223372036854775807L // 64bit (L 접미사)
// 실수
val float: Float = 3.14f // 32bit (f 접미사)
val double: Double = 3.14 // 64bit (기본)
// 자동 추론
val autoInt = 42 // Int
val autoLong = 42L // Long
val autoDouble = 3.14 // Double
val autoFloat = 3.14f // Float
}
문자 타입
fun main() {
val char: Char = 'A'
val string: String = "안녕하세요"
// 여러 줄 문자열
val multiLine = """
첫 번째 줄
두 번째 줄
세 번째 줄
""".trimIndent()
println(multiLine)
}
Boolean 타입
fun main() {
val isTrue: Boolean = true
val isFalse: Boolean = false
val result = isTrue && !isFalse
println(result) // true
}
Java와 차이점
Java Kotlin 차이점
| int | Int | Kotlin은 객체 (클래스) |
| Integer | Int | 통합됨 (박싱/언박싱 자동) |
| long | Long | L 접미사 동일 |
| String | String | 동일하지만 더 강력한 기능 |
중요: Kotlin은 모든 타입이 객체입니다!
val number = 42
println(number.toString()) // 메서드 호출 가능
println(number.toDouble()) // 42.0
Nullable 타입 - NPE와의 전쟁
Java의 악몽, NullPointerException
// Java - 언제 터질지 모르는 시한폭탄
String name = getName(); // null일 수도 있음
int length = name.length(); // 💥 NPE 발생!
Kotlin의 해결책 - Nullable 타입
기본 원칙: Kotlin은 기본적으로 null을 허용하지 않습니다!
fun main() {
var name: String = "규철"
// name = null // ❌ 컴파일 에러!
// Null can not be a value of a non-null type String
}
Nullable 타입 선언 - ? 연산자
fun main() {
var name: String? = "규철" // ?를 붙이면 null 가능
name = null // ✅ OK
println(name) // null
}
Nullable 타입 안전하게 다루기
1. Safe Call 연산자 (?.)
fun main() {
val name: String? = null
// Java 스타일 (위험)
// val length = name.length() // 컴파일 에러!
// Kotlin 스타일 (안전)
val length = name?.length // null이면 null 반환
println(length) // null
}
2. Elvis 연산자 (?:)
fun main() {
val name: String? = null
// null이면 기본값 사용
val length = name?.length ?: 0
println(length) // 0
val displayName = name ?: "손님"
println(displayName) // 손님
}
3. Not-null 단언 (!!)
fun main() {
val name: String? = "규철"
// 확실히 null이 아닐 때만 사용 (위험!)
val length = name!!.length
println(length) // 2
// 주의: null이면 NPE 발생
val nullName: String? = null
// val badLength = nullName!!.length // 💥 NPE!
}
실전 예제 - 사용자 정보 처리
data class User(
val id: String,
val name: String,
val email: String?, // 이메일은 선택사항
val phoneNumber: String? // 전화번호도 선택사항
)
fun main() {
val user = User(
id = "user123",
name = "규철",
email = null,
phoneNumber = "010-1234-5678"
)
// Safe Call로 안전하게 접근
println("이메일 길이: ${user.email?.length ?: 0}") // 0
// Elvis로 기본값 제공
val displayEmail = user.email ?: "이메일 없음"
println("표시용 이메일: $displayEmail") // 이메일 없음
// let으로 null이 아닐 때만 실행
user.phoneNumber?.let {
println("전화번호: $it") // 전화번호: 010-1234-5678
}
}
Java와 비교
// Java - null 체크 지옥
public void processUser(User user) {
if (user != null) {
String email = user.getEmail();
if (email != null) {
int length = email.length();
System.out.println(length);
}
}
}
// Kotlin - 한 줄로 해결
fun processUser(user: User?) {
println(user?.email?.length ?: 0)
}
타입 변환과 체크
is - 타입 체크 (instanceof)
fun main() {
val value: Any = "문자열"
if (value is String) {
// 자동으로 String으로 캐스팅됨 (Smart Cast)
println(value.length) // 3
}
}
as - 타입 캐스팅
fun main() {
val value: Any = "문자열"
val str = value as String // 강제 캐스팅
println(str.length)
// 안전한 캐스팅 (실패하면 null)
val number = value as? Int
println(number) // null
}
숫자 타입 변환
fun main() {
val intValue = 42
val longValue = intValue.toLong()
val doubleValue = intValue.toDouble()
val stringValue = intValue.toString()
println("Long: $longValue") // 42
println("Double: $doubleValue") // 42.0
println("String: $stringValue") // "42"
}
Java와 비교
// Java - 명시적 캐스팅 필요
Object value = "문자열";
if (value instanceof String) {
String str = (String) value; // 수동 캐스팅
System.out.println(str.length());
}
// Kotlin - Smart Cast로 자동 변환
val value: Any = "문자열"
if (value is String) {
println(value.length) // 자동 캐스팅!
}
마무리 - 다음 편 예고
오늘 배운 것 ✅
- val (불변) vs var (가변) 완벽 이해
- 타입 추론으로 코드 간결하게 작성하기
- Nullable 타입 (?)으로 NPE 방지
- Safe Call (?.), Elvis (?:), Not-null (!!) 연산자
- 타입 체크 (is)와 Smart Cast
다음 편에서 배울 것 📚
3편: 함수 완벽 가이드 | fun으로 시작하는 간결한 코드
- 함수 선언과 반환 타입
- 단일 표현식 함수 (Single Expression Function)
- 기본 매개변수 (Default Parameters)
- Named Arguments로 가독성 높이기
- 확장 함수 (Extension Functions)
실습 과제 💪
// 1. 다음 변수를 val 또는 var로 선언하세요
// - 사용자 이름 (변경 가능)
// - 생년월일 (변경 불가)
// - 현재 포인트 (변경 가능)
// 2. Nullable 타입 연습
// - 이메일을 받아서 길이를 출력 (null이면 0)
// - 전화번호를 받아서 포맷팅 (null이면 "없음")
// 3. 타입 변환 연습
// - 문자열 "123"을 Int로 변환
// - Int 값을 Double로 변환
자주 묻는 질문 (FAQ)
Q: val을 써야 할까요, var를 써야 할까요?
A: 기본은 val! 꼭 변경이 필요할 때만 var를 쓰세요. 불변 변수가 버그를 줄입니다.
Q: nullable 타입은 언제 쓰나요?
A: 값이 없을 수 있는 경우 (선택 정보, API 응답 등). 하지만 최대한 피하는 게 좋습니다.
Q: !! 연산자는 언제 쓰나요?
A: 거의 안 씁니다! 정말 확실할 때만 쓰고, 가능하면 ?.나 ?:를 쓰세요.
Q: Java의 primitive 타입과 다른가요?
A: Kotlin은 모두 객체지만, 컴파일 시 최적화되어 성능 차이는 거의 없습니다.
관련 글
- Kotlin 시작하기 | 개발환경 설정 (이전 편)
- Kotlin 함수 완벽 가이드 (다음 편)
- Kotlin vs Java 완벽 비교
💬 댓글로 알려주세요!
- nullable 타입 사용 시 어려운 점이 있나요?
- val과 var 중 어떤 걸 주로 쓰시나요?
- 이 글이 도움이 되셨나요?
'개발 > java basic' 카테고리의 다른 글
| Kotlin 클래스와 객체 완벽 가이드 | OOP의 Kotlin 스타일 (0) | 2025.12.22 |
|---|---|
| Kotlin 제어 구조 완벽 가이드 | if, when, for, while 마스터하기 (0) | 2025.12.21 |
| Kotlin 함수 완벽 가이드 | fun으로 시작하는 간결한 코드 (0) | 2025.12.21 |
| Kotlin 시작하기 | 왜 Kotlin인가? 개발환경 설정부터 Hello World까지 (0) | 2025.12.21 |
| Kotlin 시작하기 | Java 개발자를 위한 첫 번째 Kotlin 코드 (0) | 2025.12.10 |
