2장. 변수와 타입
프로그램은 결국 “값을 다루는 일“입니다.
숫자를 더하고, 이름을 저장하고,
참인지 거짓인지 판단합니다.
이때 값을 담아 두는 상자가 바로 변수입니다.
그리고 그 값이 어떤 종류인지 알려 주는 것이 타입입니다.
이 장에서는 코틀린에서 변수를 만드는 방법과,
값의 종류(타입)를 다루는 방법을 배웁니다.
특히 코틀린이 강조하는 “불변성“이라는 생각도
여기서 처음 만나게 됩니다.
2.1 val과 var
변경 가능한 값과 변경할 수 없는 값
코틀린에서 변수를 만드는 방법은 두 가지입니다.val과 var입니다.
먼저 코드로 보겠습니다.
val name = "홍길동"
var age = 20
val: 한 번 정하면 바꿀 수 없는 값var: 나중에 바꿀 수 있는 값
val은 value(값)에서,var은 variable(변수)에서 왔다고 생각하면 쉽습니다.
var로 만든 값은 다시 넣을 수 있습니다.
var age = 20
age = 21 // 가능: var이므로 값 변경 OK
하지만 val로 만든 값은 바꿀 수 없습니다.
val name = "홍길동"
name = "이순신" // 오류: val은 다시 넣을 수 없음
이 코드는 아예 실행되지 않습니다.
컴파일 단계에서 오류로 걸러집니다.
즉, 잘못된 변경을 프로그램 실행 전에
미리 막아 주는 것입니다.
불변성을 기본으로 생각하기
여기서 코틀린의 중요한 철학이 하나 나옵니다.
값은 되도록 바뀌지 않게 만들자.
바뀌지 않는 성질을
불변성(immutability)이라고 부릅니다.
왜 굳이 못 바꾸게 만들까요?
값이 여기저기서 바뀌면 추적하기가 어렵기 때문입니다.
예를 들어 어떤 값이 잘못되어 있을 때,
그 값이 var이라면 이렇게 고민하게 됩니다.
- 이 값이 대체 어디서 바뀐 거지?
- 몇 번이나 바뀐 거지?
반면 val이라면 고민할 필요가 없습니다.
처음 넣은 값 그대로이기 때문입니다.
그래서 코틀린에서는 이렇게 권장합니다.
일단
val로 시작하고,
꼭 바꿔야 할 때만var로 바꾼다.
이 습관 하나만으로도
버그가 눈에 띄게 줄어듭니다.
2.2 타입과 타입 추론
명시적 타입 선언
모든 값에는 타입(type)이 있습니다.
타입이란 “그 값이 어떤 종류인가“입니다.
숫자인지, 글자인지, 참/거짓인지 같은 것입니다.
타입을 직접 적어 줄 수도 있습니다.
변수 이름 뒤에 콜론(:)을 쓰고 타입을 적습니다.
val name: String = "홍길동"
val age: Int = 20
name은 String(문자열) 타입age는 Int(정수) 타입
여기서 순서를 기억해 둡시다.
val 이름: 타입 = 값
자바와 순서가 반대라는 점이 눈에 띕니다.
자바는 String name처럼 타입을 먼저 씁니다.
Kotlin의 타입 추론
그런데 앞 절에서는 타입을 안 적었습니다.
val name = "홍길동"
val age = 20
이렇게 써도 문제가 없습니다.
코틀린이 값을 보고 타입을 알아채기 때문입니다.
"홍길동"은 딱 봐도 문자열이고,20은 딱 봐도 정수입니다.
이렇게 값으로부터 타입을 알아내는 것을
타입 추론(type inference)이라고 합니다.
덕분에 코드가 짧아집니다.
하지만 타입이 사라진 것은 아닙니다.
적지 않았을 뿐,
타입은 그대로 정해져 있습니다.
그래서 아래 코드는 오류가 납니다.
var age = 20
age = "스무 살" // 오류: age는 Int인데 문자열을 넣음
age는 이미 Int로 정해졌기 때문에
문자열을 넣을 수 없습니다.
Int, Long, Double, Boolean, String
자주 쓰는 기본 타입을 정리해 봅시다.
| 타입 | 담는 값 | 예시 |
|---|---|---|
| Int | 정수 | 20, -3 |
| Long | 아주 큰 정수 | 10000000000 |
| Double | 소수점이 있는 수 | 3.14, 0.5 |
| Boolean | 참 또는 거짓 | true, false |
| String | 문자열(글자의 나열) | "안녕하세요" |
몇 가지만 짚어 보겠습니다.
val count: Int = 100
val big: Long = 10_000_000_000
val price: Double = 3.14
val isActive: Boolean = true
val greeting: String = "안녕하세요"
큰 숫자에는 밑줄(_)을 넣을 수 있습니다.10_000_000_000처럼요.
밑줄은 사람이 읽기 편하라고 넣는 것이며,
값 자체에는 영향을 주지 않습니다.
숫자 타입 변환
자바를 아는 분이라면 여기서 놀랄 수 있습니다.
코틀린은 숫자 타입을 자동으로 바꿔 주지 않습니다.
val a: Int = 10
val b: Long = a // 오류: Int를 Long에 그냥 넣을 수 없음
Int와 Long은 다른 타입이기 때문입니다.
바꾸려면 직접 변환 함수를 불러야 합니다.
val a: Int = 10
val b: Long = a.toLong() // 명시적으로 변환
주요 변환 함수는 이렇습니다.
toInt(): Int로 변환toLong(): Long으로 변환toDouble(): Double로 변환
조금 번거롭게 느껴질 수 있습니다.
하지만 이 규칙 덕분에
“나도 모르게 값이 바뀌는” 실수를 막을 수 있습니다.
2.3 문자열 다루기
문자열(String)은 백엔드에서 가장 많이 다루는 값입니다.
이름, 주소, 메시지, 오류 내용 모두 문자열입니다.
문자열 템플릿
문자열 안에 값을 끼워 넣고 싶을 때가 많습니다.
자바에서는 +로 이어 붙였습니다.
코틀린에도 그 방법이 있지만, 더 편한 방법이 있습니다.
바로 문자열 템플릿입니다.
값 앞에 달러 기호($)를 붙이면 됩니다.
val name = "홍길동"
val age = 20
println("이름: $name, 나이: $age")
// 출력: 이름: 홍길동, 나이: 20
계산식이나 함수 호출을 넣고 싶다면
중괄호(${ })로 감쌉니다.
val price = 1000
val count = 3
println("총액: ${price * count}원")
// 출력: 총액: 3000원
정리하면 이렇습니다.
$변수: 변수 하나를 넣을 때${식}: 계산이나 함수 호출을 넣을 때
여러 줄 문자열
줄바꿈이 여러 번 들어가는 긴 문자열은
큰따옴표 세 개(""")로 감쌉니다.
val message = """
안녕하세요.
코틀린 책에 오신 것을 환영합니다.
즐겁게 배워 봅시다.
""".trimIndent()
println(message)
이것을 여러 줄 문자열(triple-quoted string)이라고 합니다.
끝에 붙인 trimIndent()는
코드에서 생긴 앞쪽 여백을 깔끔하게 정리해 줍니다.
이 방식은 나중에
긴 SQL 문이나 JSON 예시를 적을 때 특히 편합니다.
문자열 비교
두 문자열이 같은지 비교할 때는==를 사용합니다.
val a = "kotlin"
val b = "kotlin"
println(a == b) // true
자바를 아는 분이라면 이 부분이 반가울 것입니다.
자바에서는 == 대신 equals()를 써야 했기 때문입니다.
코틀린에서는 ==가
“값이 같은가“를 자연스럽게 비교해 줍니다.
이 비교의 원리는 2.5에서 더 자세히 다룹니다.
2.4 Kotlin의 특별한 타입
코틀린에는 조금 특별한 타입 몇 가지가 있습니다.
지금 전부 외울 필요는 없습니다.
“이런 게 있구나” 정도로 읽어 두면
나중에 코드에서 만났을 때 당황하지 않습니다.
Any
Any는 모든 타입의 최상위 타입입니다.
즉, 어떤 값이든 담을 수 있습니다.
val something: Any = "문자열"
val other: Any = 123
문자열도, 숫자도 Any에 담깁니다.
자바의 Object와 비슷한 역할입니다.
Unit
Unit은 “돌려줄 값이 없다“를 뜻하는 타입입니다.
어떤 함수는 값을 돌려주지 않고
그냥 일만 하고 끝납니다.
예를 들어 화면에 출력만 하는 함수가 그렇습니다.
이런 함수의 반환 타입이 Unit입니다.
fun printHello(): Unit {
println("Hello")
}
사실 Unit은 거의 생략합니다.
안 적으면 자동으로 Unit으로 취급됩니다.
fun printHello() { // 반환 타입 생략 = Unit
println("Hello")
}
자바의 void와 비슷한 자리라고
이해하면 쉽습니다.
Nothing
Nothing은 “정상적으로 끝나지 않는다“를 뜻합니다.
값을 절대 돌려주지 못하는 경우에 쓰입니다.
대표적으로 항상 예외를 던지는 함수입니다.
fun fail(): Nothing {
throw RuntimeException("실패")
}
이 함수는 값을 돌려주는 대신
항상 오류를 발생시키고 끝납니다.
지금은 “그런 타입도 있다” 정도로 충분합니다.
예외는 8부에서 자세히 배웁니다.
Any?
앞에서 본 Any에 물음표(?)가 붙었습니다.
이 물음표는 아주 중요합니다.
“값이 없을 수도 있다(null일 수 있다)“는 표시입니다.
val a: Any = "값" // null이 될 수 없음
val b: Any? = null // null이 될 수 있음
이 물음표 하나가
코틀린의 가장 큰 특징으로 이어집니다.
바로 null 안전성(Null Safety)입니다.
이 내용은 3부에서 집중적으로 다룹니다.
지금은 이렇게만 기억해 둡시다.
타입 뒤의
?는
“null이 들어올 수 있음“을 뜻한다.
2.5 값의 비교와 객체의 비교
값을 비교하는 방법에는 두 종류가 있습니다.
코틀린은 이 둘을 명확히 구분합니다.
==와 !=
==는 “값이 같은가“를 비교합니다.!=는 “값이 다른가“를 비교합니다.
val a = 10
val b = 10
println(a == b) // true (값이 같다)
println(a != b) // false (다르지 않다)
문자열도 마찬가지입니다.
val x = "kotlin"
val y = "kotlin"
println(x == y) // true
우리가 일상적으로 “같다“고 말할 때의 의미가
바로 이 ==입니다.
===와 !==
등호가 세 개인 ===는 조금 다릅니다.
이것은 “완전히 같은 객체인가“를 비교합니다.
여기서 잠깐 개념 하나를 짚겠습니다.
- 값이 같다 : 내용물이 같다
- 같은 객체다 : 메모리에서 같은 존재다
비유하자면 이렇습니다.
내용이 똑같은 두 장의 복사본은
“값은 같지만” “같은 종이는 아닙니다”.
===는 “같은 종이인가“를 묻는 것입니다.
val a = "kotlin"
val b = "kotlin"
println(a == b) // true (값이 같다)
println(a === b) // 상황에 따라 다름 (같은 객체인지)
실무에서는 대부분 ==만 씁니다.===는 아주 특별한 경우에만 필요합니다.
지금은 이렇게만 구분해 두면 됩니다.
==: 값이 같은가 (거의 이것만 씀)===: 같은 객체인가 (드물게 사용)
Java의 equals()와 비교하기
자바에서는 값 비교가 늘 헷갈리는 지점이었습니다.
자바에서 ==는
“같은 객체인가“를 비교합니다.
그래서 문자열 값을 비교하려면
반드시 equals()를 써야 했습니다.
// 자바
String a = "kotlin";
String b = "kotlin";
a == b; // 같은 객체인지 (헷갈림의 원인)
a.equals(b); // 값이 같은지 (이걸 써야 함)
코틀린은 이 혼란을 정리했습니다.
- 코틀린의
=== 자바의equals()(값 비교) - 코틀린의
==== 자바의==(객체 비교)
즉, 코틀린에서는
평소에 그냥 ==만 쓰면 됩니다.
값을 비교하려다 실수하는 일이
크게 줄어드는 것입니다.
2장을 마치며
이 장에서 우리는 다음을 배웠습니다.
val은 바꿀 수 없는 값,var은 바꿀 수 있는 값이라는 점- 되도록
val을 써서
불변성을 기본으로 삼는다는 생각 - 타입을 직접 적을 수도 있고,
코틀린이 추론하게 둘 수도 있다는 점 - 문자열 템플릿(
$)으로
값을 깔끔하게 끼워 넣는 방법 Any,Unit,Nothing, 그리고?의 의미- 값 비교(
==)와 객체 비교(===)의 차이
특히 두 가지는 앞으로 계속 나옵니다.
“일단 val”, 그리고 “타입 뒤의 ?“입니다.
다음 장에서는
조건과 반복을 다뤄 보겠습니다.