Hello world | Kotlin Documentation
코틀린 공식 문서를 기반으로 작성
자바언어와의 비교를 할 것
Kotlin
JetBrains에서 개발한 정적 타입 프로그래밍 언어
JVM 위에서 동작하며, 자바와의 완전한 상호운용성을 목표로 설계됨
특징
- 자바와 호환 가능
- 기존 자바 코드와 함께 사용할 수 있으며, 자바 라이브러리를 그대로 활용 가능
- JVM 기반 언어
- 자바처럼 바이트코드로 컴파일되어 JVM 위에서 실행되므로 플랫폼에 독립적
- 널 안정성 (Null Safety)을 언어 차원에서 지원한다.
- ? 타입으로 null 가능 여부를 명시하여 NullPointerException을 컴파일 타임에 방지
- 간결한 문법을 제공
- 데이터 클래스, 확장 함수, 스마트 캐스트 등으로 자바 대비 보일러플레이트 코드르 대폭 줄임
- 함수형 프로그래밍을 지원
- 람다, 고차함수, 컬렉션 함수형 API등을 기본 제공
- Garbage Collector를 사용
- JVM 위에서 동작하므로 자바와 동일하게 GC가 메모리를 자동으로 관리
시작 코드
class Main {
public static void main(String[] args) {
System.out.println("Hello world!");//전체 경로를 명시해야 한다.
}
}
기존 자바의 경우 클래스는 기본적인 단위로 모든 코드는 클래스 내에 작성해야한다.
fun main() {
println("Hello world!")//코틀린 표준 라이브러리에 미리 최상위 함수로 정의되어 있어 바로 접근 가능
}
하지만 코틀린의 경우 클래스 없이 파일 최상위 레벨에 함수 및 변수를 선언 가능하다.
가능한 이유는 내부적으로 코틀린 컴파일러가 자동으로 클래스를 만들어주기 때문
파일명이 Main.kt라면 컴파일 시 MainKt라는 클래스로 감싸져서 바이트 코드로 변환이 된다.
JVM입장에서는 클래스 내부에 있는걸로 인식이 되며 개발자 입장에서는 자유로운 코드 작성이된다.
또한 세미클론은 생략이 가능하며, IDE에서도 세미콜론 사용시 불필요한 세미클론 경고를 내보낸다.
사실상 안쓰는게 표준임
class Main {
fun hello() {
println("Hello")
}
}
fun topLevel() {
println("top")
}
파일명이 Main.kt인 파일에 이와 같이 작성된다면 Main.class 파일과 MainKt.class파일 두개가 별도로 생성된다.
둘은 별개의 클레스가 되며, 단지 같은 파일 내부에 있던 것뿐이다.
변수 선언
class Main {
public static void main(String[] args) {
final int popcorn = 5;
final int hotdog = 7;
int customers = 10;
customers = 8;
System.out.println(popcorn);
System.out.println(hotdog);
System.out.println(customers);
}
}
자바의 경우 final을 사용해서 재할당을 제한을 줄 수 있다.
또한 타입과 변수명으로 선언을 하게 된다.
fun main(){
val popcorn = 5
val hotdog = 7
var customers = 10
customers = 8
println(popcorn)
println(hotdog)
println(customers)
}
코틀린의 경우 val은 재할당할 수 없는 변수로, var은 재할당 가능한 변수로 선언 할 수 있다.
또한 따로 타입을 지정하지 않아도 추론적으로 알맞게 넣어준다.
하지만 문제는 선언후 나중에 할당하는 경우가 문제인데 이럴 땐 미리 타입을 지정할 수 있다.
fun main(){
var customers : Int
customers = 8
println(customers)
}
이렇게 타입을 지정하고 나중에 초기화 또한 가능하다. 주의할 점은 사용 전에 필수로 할당이 되어 있어야 한다.
템플릿 문자열
class Main {
public static void main(String[] args) {
int customers = 10;
System.out.println("There are "+customers +" customers");
System.out.println("There are "+(customers + 1) +" customers");
}
}
자바에서는 +를 사용해서 문자열 내부에 변수를 넣는 방법이 가능하다.
fun main() {
val customers = 10
println("There are $customers customers")
println("There are ${customers + 1} customers")
}
코틀린의 경우는 위와 같이 나타 낼 수 있는데 차이는 아래와 같다
- $
- 변수 값을 그대로 삽입
- ${}
- 중괄호 안에 표현식 사용 가능 (연산, 함수 호출 등)
기본형 데이터 타입
| 카테고리 | 기본 유형 | 예제코드 |
| 정수 | Byte, Short, Int, Long | val year: Int = 2020 |
| 부호 없는 정수 | UByte, UShort, UInt, ULong | val score: UInt = 100u |
| 부동소수점 | Float, Double | val currentTemp: Float = 24.5f val price: Double = 19.99 |
| 불리언 | Boolean | val isEnabled: Boolean = true |
| 문자 | Char | val separator: Char = ',' |
| 문자열 | String | val message: String = "Hello, world!" |
변수를 함수에 사용할때 값이 초기화 안될 경우 오류가 발생하며, 선언과 초기화가 동시에 이뤄지면 타입은 생략이 가능하다.
코틀린은 변수 선언 당시에 주어진 값으로 타입 추론이 가능하다.
Collection
데이터를 다음과 같은 형식으로 그룹화 하여 나중에 처리할 수 있다.
이런 목적을 위해 컬렉션을 제공한다.
- List
- 순서화된 아이템 컬렉션
- 중복 허용, 순서 보장
- Set
- 고유하고 순서가 없는 아이템 모음
- Map
- 키가 고유한 값을 가지고 하나의 값에만 대응 하는 키-값 쌍 집합
List
항목을 추가된 순서대로 저장하며, 중복 항목도 허용
- 읽기 전용 리스트(list) 생성
- listOf()
- 변경 가능한 리스트(MutableList) 생성
- mutableListOf()
list 생성시 Kotlin은 저장된 항목의 타입을 추론 가능하다.
타입을 명시적으로 선언하려면 리스트 선언 뒤에 <>안에 타입을 추가하면 된다.
fun main() {
val readOnlyShapes = listOf("triangle", "square", "circle")
// val readOnlyShapes : List<String> = listOf("triangle", "square", "circle") 타입 명시 버전
println(readOnlyShapes)
// val shapes = mutableListOf("triangle", "square", "circle") 타입 추론 버전
val shapes: MutableList<String> = mutableListOf("triangle", "square", "circle")
println(shapes)
}
또한 사용시에 수정을 목적으로 MutableList를 선언 했더라도 List로 제한이 가능하다.
fun main() {
val shapes: MutableList<String> = mutableListOf("triangle", "square", "circle")
val shapesLocked: List<String> = shapes // 업캐스팅 또한 가능
}
List가 가진 메소드는 다음과 같다
fun main() {
val shapes: MutableList<String> = mutableListOf("triangle", "square", "circle")
println("The second item in the list is: ${shapes[1]}")
println("The first item in the list is: ${shapes.first()}")
println("The last item in the list is: ${shapes.last()}")
println("This list has ${shapes.count()} items")
println("circle" in shapes)
shapes.add("pentagon") //mutableList만 가능
println(shapes)
shapes.remove("pentagon") //mutableList만 가능
println(shapes)
}
Set
순서가 없고 고유 아이템만 저장하는 자료 구조
- 읽기 전용 집합(Set) 생성
- setOf()
- 변경 가능한 집합(MutableSet) 생성
- mutableSetOf()
fun main() {
val readOnlyFruit = setOf("apple", "banana", "cherry", "cherry")
// val readOnlyFruit: Set<String> = setOf("apple", "banana", "cherry", "cherry") 타입 명시 예시
// val fruit = mutableSetOf("apple", "banana", "cherry", "cherry") 타입 추론 예시
val fruit: MutableSet<String> = mutableSetOf("apple", "banana", "cherry", "cherry")
val fruitLocked: Set<String> = fruit // 업캐스팅 가능
println(readOnlyFruit)
}
동일하게 타입을 추론할 수 있으며, 명시 또한 가능하고 Mutable에서 사용시 수정을 막고 싶다면 업캐스팅을 통해서 또한 가능하다.
set이 가지고 있는 메소드는 다음과 같다.
fun main() {
val fruit: MutableSet<String> = mutableSetOf("apple", "banana", "cherry", "cherry")
println("This set has ${fruit.count()} items")
println("banana" in fruit)
fruit.add("dragonfruit") //Mutable 타입만 가능
println(fruit)
fruit.remove("dragonfruit") //Mutable 타입만 가능
println(fruit)
}
순서를 보장하지 않기 때문에 index를 통한 접근이 불가능하다.
Map
키 - 값 쌍으로 저장하는 자료구조
맵의 특징은 모든 키는 고유해야 하며, 하나의 키에는 단 하나의 값만 대응된다. 단, 값은 중복될 수 있다.
- 읽기 전용 맵(Map) 생성
- mapOf()
- 변경 가능한 맵(MutableMap) 생성
- mutableMapOf()
map을 만들때 다른 collection들과 동일하게 타입을 추론 가능하다.
fun main() {
val readOnlyJuiceMenu = mapOf("apple" to 100, "kiwi" to 190, "orange" to 100)
// val readOnlyJuiceMenu: Map<String, Int> = mapOf("apple" to 100, "kiwi" to 190, "orange" to 100) // 타입 명시 예시
println(readOnlyJuiceMenu)
// val juiceMenu = mutableMapOf("apple" to 100, "kiwi" to 190, "orange" to 100) // 타입 추론 예시
val juiceMenu: MutableMap<String, Int> = mutableMapOf("apple" to 100, "kiwi" to 190, "orange" to 100)
println(juiceMenu)
val juiceMenuLocked: Map<String, Int> = juiceMenu // 업캐스팅 가능
}
map이 가진 함수는 다음과 같다.
fun main() {
val juiceMenu: MutableMap<String, Int> = mutableMapOf("apple" to 100, "kiwi" to 190, "orange" to 100)
println("The value of apple juice is: ${juiceMenu["apple"]}")
println("The value of pineapple juice is: ${juiceMenu["pineapple"]}") // 없는 키를 요청할 경우 null을 반환
juiceMenu["coconut"] = 150
println(juiceMenu)
juiceMenu.remove("orange")
println(juiceMenu)
println("This map has ${juiceMenu.count()} key-value pairs")
println(juiceMenu.containsKey("kiwi")) // 해당 키가 존재하는 지 확인
println(juiceMenu.keys) // 키의 배열
println(juiceMenu.values) // 값의 배열
println("orange" in juiceMenu.keys) // 키값에 존재하는지 확인
println("orange" in juiceMenu)
println(200 in juiceMenu.values)
}
여기서 비슷해보이는 로직이 존재하는데
다음과 같은 차이가 존재한다
- "orange" in juiceMenu.keys
- keys 컬렉션을 먼저 꺼낸 다음 탐색
- 즉 불필요한 단계가 존재 (사용 잘 안함)
- "orange" in juiceMenu
- 내부적으로 containsKey을 호출하는 것
- 즉, containsKey(Key)와 동일함
- juiceMenu.containsKey("kiwi")
- 명시적으로 키 존재 여부를 확인하는 것
조건부 표현
Kotlin은 조건부 표현식으로 if 문과 when문을 제공한다.
if문과 when문중에 하나를 선택해야한다면 when을 권장한다.
- 코드를 더 읽기 쉽게 만들어준다.
- 다른분기를 추가하기가 더 쉬워진다.
- 코드의 실수가 적어진다.
if 문
괄호 안에 조건부 표현식을 추가하고, 결과가 참일 경우 취할 로직을 중괄호안에 넣는다.
fun main() {
val d: Int
val check = true
if (check) {
d = 1
} else {
d = 2
}
println(d)
}
해당 로직은 check가 true를 가지기 때문에 d에는 1이 할당되는 로직이다.
또한 삼항 연산도 가능하다.
fun main() {
val a = 1
val b = 2
println(if (a > b) a else b) // 참과 거짓에 따른 로직이 한줄에 표현이 가능할때
//참과 거짓에 따른 로직이 한줄에 표현이 불가능할때
val condition = true
val result = if (condition) {
println("참이다")
10 // 마지막 줄이 반환값
} else {
println("거짓이다")
20 // 마지막 줄이 반환값
}
println("result = $result")
}
When 문
when문은 다른언어에 존재하는 switch문과 같은 방식으로 사용되며 괄호에 조건 변수를 넣어두고 해당 변수가 해당하는 로직을 ->을 통해 나타내며 한줄이 아닐경우 중괄호로 표현해서 만들수 있다.
fun main() {
var cnt = 0
val obj = "Hello"
when (obj) {
"1" -> {
println("One")
cnt++
}
"Hello" -> println("Greeting")
else -> println("Unknown")
}
}
또한 바로 print를 하지 않고 변수를 받아 올 수 있다.
fun main() {
var cnt = 0
val obj = "Hello"
val result = when (obj) {
"1" -> {
cnt++
"One" // 마지막 값이 반환됨
}
"Hello" -> "Greeting"
else -> "Unknown"
}
println(result)
}
지금 가지는 obj라는 지정 변수가 있었으나 없이도 가능하다.
fun main() {
val trafficLightState = "Red"
val trafficAction = when {
trafficLightState == "Green" -> "Go"
trafficLightState == "Yellow" -> "Slow down"
trafficLightState == "Red" -> "Stop"
else -> "Malfunction"
}
/* 위의 로직과 아래 로직은 같다
val trafficAction = when (trafficLightState) {
"Green" -> "Go"
"Yellow" -> "Slow down"
"Red" -> "Stop"
else -> "Malfunction"
}
*/
println(trafficAction)
}
Ranges
반복문 전에 반드시 사용되는 반복하는 루프의 범위를 구성하는 방법에 대해 설명한다.
kotlin에서 범위 생성의 가장 일반적인 방법은 연산자를 사용하는 것.
- 1..4
- 1,2,3,4를 의미함
- 1..<4
- 1,2,3을 의미함
- 4 downTo 1
- 4, 3, 2, 1을 의미함
- 1..5 step 2
- 1, 3, 5를 의미함
- 'a'..'d'
- 'a', 'b', 'c', 'd'
- 'z' downTo 's' step 2
- 'z', 'x', 'v', 't'
반복문
가장 흔한 반복문 구조는 for문과 while문이다
- 값을 반복해서 분석하고 행동을 수행
- for문
- 특정 조건이 충족될때 까지 행동을 계속 수행
- while문
for문
자바에서의 for문 예시
class Main {
public static void main(String[] args) {
for(int number = 1; number <=5 ; number++){
System.out.println(number);
}
ArrayList<String> cakes = new ArrayList<>(Arrays.asList("carrot", "cheese", "chocolate"));
for (String cake : cakes) {
System.out.println("Yummy, it's a "+cake+" cake!");
}
}
}
코틀린의 예시
fun main() {
for (number in 1..5) {
print(number)
}
val cakes = listOf("carrot", "cheese", "chocolate")
for (cake in cakes) {
println("Yummy, it's a $cake cake!")
}
}
While문
조건이 참일 경우 계속해서 반복
fun main() {
var cakesEaten = 0
while (cakesEaten < 3) {
println("Eat a cake")
cakesEaten++
}
}
혹은 do- while문도 가능하다
함수
Kotlin에서는 fun으로 함수를 선언한다.
fun hello(){
println("Hello world!")
}
fun main() {
hello()
}
코틀린에서 함수를 사용하는 방법
- 함수 매개변수는 괄호 안에 표시한다
- 각 매개변수는 타입을 가져야하며, 여러 매개변수는 쉼표로 구분이 되어야한다.
- 반환타입함수는 함수의 괄호 뒤에 콜론으로 구분한다.
- 만일 void 즉, 반환값이 없다면 생략이 가능하며 void 대신에 Unit을 사용한다.
- 반환값이 없으니 return또한 생략가능하다.
- 함수의 로직은 중괄호안에 표기
- return을 통해 종료하거나, 반환하는데 사용된다.
fun sum1(x: Int, y: Int): Int{
return x + y
}
fun sum2(x: Int, y: Int) = x + y
fun main() {
println(sum1(1,2))
println(sum2(1,2))
}
함수 사용은 위와 같이 사용가능하며, sum1처럼 return을 직접 선택 또한 가능하고, sum2처럼 return과 함수의 자료형을 지정하지 않아도 위와 같이 사용이 가능하다.
매개변수는 다음과 같은 방법으로 사용이된다.
fun printMessageWithPrefix(message: String, prefix: String = "Info") { //디폴트값을 미리지정
println("[$prefix] $message")
}
fun main() {
printMessageWithPrefix("Hello", "Log") // 순서에 맞게 매개변수가 지정됨
printMessageWithPrefix("Hello") // message는 "Hello"가 할당되며 prefix는 디폴트값이 지정됨
printMessageWithPrefix(prefix = "Log", message = "Hello")// 직접 어떤매개변수에 어떤값을 넣겠다고 지정이 가능함
}
함수내의 빠른 반환
일정 지점 이상으로 처리되는 것을 막으려면, if 키워드를 사용해서 막을 수있다.
val registeredUsernames = mutableListOf("john_doe", "jane_smith")
val registeredEmails = mutableListOf("john@example.com", "jane@example.com")
fun registerUser(username: String, email: String): String {
if (username in registeredUsernames) {
return "Username already taken. Please choose a different username."
}
if (email in registeredEmails) {
return "Email already registered. Please use a different email."
}
registeredUsernames.add(username)
registeredEmails.add(email)
return "User registered successfully: $username"
}
fun main() {
println(registerUser("john_doe", "newjohn@example.com")) // 사용중인 이름으로 첫번째 if문에서 반환됨
println(registerUser("new_user", "newuser@example.com")) // 새로운 이름으로 if문에 걸리지않고 마지막까지 내려감
}
Lambda
람다 표현식을 사용해 함수에 대해 더 간결한 코드를 작성 할 수 있다.
fun uppercaseString(text: String): String {
return text.uppercase()
}
fun main() {
println(uppercaseString("hello"))
}
해당 코드를 아래와 같이 변경이 가능하다.
fun main() {
val uppercaseString = { text: String -> text.uppercase() }
println(uppercaseString("hello"))
}
람다 표현식은 중괄호 내에 작성된다.
- ->의 앞부분
- 매개변수
- ->의 뒷부분
- 실행되는 로직
만일 매개변수가 없는 로직이라면 다음과 같이도 가능하다
fun main() {
val helloLogic = { println("Hello") }
helloLogic()
}
람다 표현식은 여러가지 방식으로 사용 가능하다
- 매개변수로 다른 함수에 전달
- 함수에서 람다 표현식 반환
- 단독으로 람다 표현식 호출
다른 함수로 전달하기
좋은 예로 filter()함수를 사용해 예시를 만들어보자
fun main() {
val numbers = listOf(1, -2, 3, -4, 5, -6)
val positives = numbers.filter({ x -> x > 0 })
val isNegative = { x: Int -> x < 0 }
val negatives = numbers.filter(isNegative)
println(positives)
println(negatives)
}
먼저 positives를 보면 numbers에 필터를 사용해서 값이 양수인것만 반환하는 로직을 만든 것
negatives를 보면 람다를 활용해 x가 음수인것만 return하는 isNegative를 만들고 그것을 활용해 filter에 담는것이다.
람다를 함수에 전달하는 방법은 두가지
- positives
- filter() 함수에 직접 람다를 추가함
- negatives
- isNegative에 람다 표현식을 할당하고 그 변수를 함수의 매개변수로 사용함
또 다른 예로 map을 사용할 수 있다.
여기서 말하는 map은 collection에서 사용한 자료구조 map이 아닌 배열에 각각의 값을 조절할때 사용하는 함수를 의미한다.
fun main() {
val numbers = listOf(1, -2, 3, -4, 5, -6)
val doubled = numbers.map { x -> x * 2 }
val isTripled = { x: Int -> x * 3 }
val tripled = numbers.map(isTripled)
println(doubled)
println(tripled)
}
람다식을 활용해서 각각 2배와 3배를 진행했으며 방금 방식과 같이 직접할당과 변수로 담은 후 매개변수로 써 할당한 방법이다.
주의점으로 함수 자체에도 타입이 존재한다.
타입 추론으로 매개변수 타입으로부터 함수의 타입을 추론 가능하나, 명시적으로 지정해야 할 때가 있을 수 있다.
컴파일러는 함수 타입이 필요해서 그 함수에 대해 허용되는 것과 허용되지 않은것을 알아야한다.
val upperCaseString: (String) -> String = { text -> text.uppercase() }
//(매개변수의 타입)-> 반환되는 타입
val test : () -> Unit = { println("test")}
// 매개변수가 없고, 반환이 없는 타입은 Unit으로 반환한다.
fun main() {
println(upperCaseString("hello"))
// HELLO
}
함수에서 람다 표현식 반환
람다 표현식은 함수에서 반환 가능하다.
이 경우에는 함수 타입을 선언해야한다.
fun toSeconds(time: String): (Int) -> Int = when (time) {
"hour" -> { value -> value * 60 * 60 }
"minute" -> { value -> value * 60 }
"second" -> { value -> value }
else -> { value -> value }
}
fun main() {
val timesInMinutes = listOf(2, 10, 15, 1)
val min2sec = toSeconds("minute")
val totalTimeInSeconds = timesInMinutes.map(min2sec).sum()
println("Total time is $totalTimeInSeconds secs")
}
toSeconds에서 when을 결정하고, 람다로 들어오게되는 값은 Int가 되고, 반환값도 Int임을 명시한 람다를 반환하는 식이다.
단독으로 람다 표현식 호출
람다 표현식은 괄호를 붙이고, 괄호 안에 모든 매개변수 넣는다면, 단독으로 호출이 가능하다.
println({ text: String -> text.uppercase() }("hello"))
매개변수로 필요한 "hello"가 주어지게 되어 람다를 실행하게 되고 HELLO를 반환하게 된다.
후행 람다
람다 표현식만이 함수 매개변수라면 괄호 안의 함수를 제거 가능하다.
람다식이 함수의 마지막 매개변수로 전달되면, 해당 표현식은 괄호 밖에서 쓸 수 있다.
이러한 경우 모두 트레일링 람다(trailing lambda)라고 한다.
println(listOf(1, 2, 3).fold(0, { x, item -> x + item }))//기본적인 람다
println(listOf(1, 2, 3).fold(0) { x, item -> x + item }) // 트레일링 람다
Class
kotlin은 클래스와 객체를 이용한 객체지향 프로그래밍을 지원한다.
객체는 프로그램에 데이터를 저장하는데 유용함.
또한 kotiln은 생성자와 필드를 미리 지정할 수 있음.
class Contact(val id: Int, var email: String)
//class Contact constructor(val id: Int, var email: String)//생성자 생략 안한 버전
fun main() {
val contact = Contact(1, "mary@gmail.com")
println(contact.email)
}
기본적으론 생성자 키워드를 넣으나 생략이 가능함
또한 매개변수에서 선언을 함으로써 필드의 역할이 가능한 변수가 된다.
변수접근 이외에도 함수 접근도 동일하게 접근이 가능하다.
class Contact(val id: Int, var email: String) {
fun printId() {
println(id)
}
}
fun main() {
val contact = Contact(1, "mary@gmail.com")
contact.printId()
}
Data Class
kotlin에는 데이터 저장에 유용한 데이터 클래스가 존재한다.
데이터 클래스는 일반 클래스와 동일한 기능을 가지고 있지만, 자동으로 추가적인 메소드가 제공된다.
- toString()
- 클래스 인스턴스와 그 속성의 읽기 가능한 문자열을 출력
- 디버깅이나 로그 생성시에 유용하다.
- equals() / ==
- 클래스의 인스턴스를 비교
- 모든 변수가 값이 같은지 확인하는 용도로 사용됨
- 주소값 비교를 원한다면 ===을 사용해야
- copy()
- 다른 클래스 인스턴스를 복사하여 생성하며, 일부 다른 속성을 가질 수도 있다.
- 일부 속성을 변경하려면, 인스턴스의 함수를 호출하고 속성값을 매개변수로 주어주면 된다.
data class User(val name: String, val id: Int)
fun main() {
val user = User("Alex", 1)
val secondUser = User("Alex", 1)
val thirdUser = User("Max", 2)
println("user == secondUser: ${user == secondUser}")
println("user == thirdUser: ${user == thirdUser}")
println(user.copy())
println(user.copy("Max"))
println(user.copy(id = 3))
}
Null Safety
뭔가 빠졌거나 아직 설정되지 않은 값들을 사용할때 null이 접근된다.
Null에 대한 처리 로직이 확실하지 않다면 NullPointException같은 문제를 일으키곤 한다.
코틀린은 이와 같은 문제를 해결하기 위해 null Safety를 적용한다.
실행 시점이 아닌 컴파일 단계에서 처리한다.
- 프로그램 내에서 언제 null 값을 허용하는지 명시적으로 선언한다
- null인지 확인을 한다
- null값이 포함될 수 있는 속성이나 함수에 대한 안전 호출을 사용한다
- null값이 감지되면 취할 행동을 선언한다
Nullable Type
코틀린은 선언된 타입에 null값을 가질 수 있는 타입을 지원한다.
기본적으로 타입은 null값을 받을 수 없지만 nullable타입은가능하며 명시적으로 타입선언후 ?를 선언하여야한다.
fun main() {
var neverNull: String = "This can't be null"
neverNull = null //error
var nullable: String? = "You can keep a null here"
nullable = null
var inferredNonNull = "The compiler assumes non-nullable"
inferredNonNull = null //error
fun strLength(notNull: String): Int {
return notNull.length
}
println(strLength(neverNull))
println(strLength(nullable)) //error
}
위와 같이 일반적인 String은 null을 가지면 오류를 반환하지만 nullable처럼 String?으로 선언하게 되면 널이 가능하므로 null을 넣어도 오류가 발생하지 않는다.
Null값 확인
fun describeString(maybeString: String?): String {
if (maybeString != null && maybeString.length > 0) {
return "String of length ${maybeString.length}"
} else {
return "Empty or null string"
}
}
fun main() {
val nullString: String? = null
println(describeString(nullString))
}
nullstring으로 매개변수를 넘겨 if문 내부에 != null이 거짓으로 되어 "Empty or null String을 반환한다.
만일 length가 먼저 나오게 된다면 null에 length를 접근하게 되어 error를 반환하게 된다.
안전하게 호출하기
fun lengthString(maybeString: String?): Int? = maybeString?.length
fun main() {
val nullString: String? = null
println(lengthString(nullString))
}
maybeString?.length를 하게되면 maybeString이 null일경우 null을 반환하게 되어 error를 반환하지 않는다.
또한 함수의 반환도 nullable이기때문에 error가 아닌 null을 반환 할 수 있다.
따라서 객체의 어떤 속성이든 값을 포함하면 오류 없이 반환되도록 연쇄 연결할 수 있다.
person.company?.address?.country
fun main() {
val nullString: String? = null
println(nullString?.uppercase())
}
이와같이 함수 또한 null로부터 안전하게 만든다면 null을 반환 할 수 있다.
엘비스 연산자 사용
엘비스 연산자를 사용하면 null을 감지했을때 기본적으로 반환하는 값을 제공할 수 있다.
fun main() {
val nullString: String? = null
println(nullString?.length ?: 0)
}
이와 같이 null이 감지된다면 0을 기본값으로 제공하게 된다.
'BackEnd > 코틀린 스프링' 카테고리의 다른 글
| [Kotlin] 코틀린 투어 중급 (0) | 2026.03.16 |
|---|---|
| 코틀린 스프링 스터디 일지 6 : 서버 빌드 및 JAVA_HOME 오류 해결 (0) | 2025.05.12 |
| 코틀린 스프링 스터디 일지 5 : 비밀번호 암호화 및 과제 내용 (1) | 2025.05.03 |
| 코틀린 스프링 스터디 일지 4 : 게시글 조회 (0) | 2025.04.25 |
| 코틀린 스프링 스터디 일지 3 : 게시글 작성 (2) (0) | 2025.04.17 |