Kotlin 기본편

생성일
Nov 13, 2024 09:47 AM
최종 편집 일시
Last updated November 17, 2024
태그
Kotlin

코틀린에서 변수를 다루는 방법

1. 변수 선언 키워드 - var과 val의 차이점

가변은 var, 불변은 val
즉, 수정 가능 여부에 다라 나뉜다.
또한 변수의 타입을 컴파일러가 자동으로 추론해주기 때문에 타입을 명시해주지 않아도 된다.
타입을 명시하고 싶다면 var number1: Long = 10L 과 같이 작성할 수 있다.
val 컬렉션이 있다면 코틀린도 자바와 똑같이 add(), remove()를 이용해 내부의 원소를 추가, 제거할 수 있다.

2. 코틀린에서의 원시 타입

숫자, 문자, 불리언과 같은 몇몇 타입은 내부적으로 특별한 표현을 갖는다. 이 타입들은 실행 시에 Primitive Value로 표현되지만, 코드에서는 평범한 클래스처럼 보인다.
Long이라고 명시해도 코틀린에서 내부적으로 필요한 경우에 primitive type으로 변경해서 사용한다.

3. 코틀린에서의 nullable 변수

코틀린에서 null이 들어갈 수 있는 변수라면 타입에 ?를 붙여서 사용해야 한다.
코틀린은 기본적으로 변수에 null이 들어갈 수 없게끔 설계되었기 때문이다.

4. 객체의 인스턴스화

코틀린에서는 객체 인스턴스화를 할 때에는 new 키워드를 쓰지 않는다.
 

코틀린에서 null을 다루는 방법

1. 코틀린에서의 null체크

fun startsWithA1(str: String?): Boolean { if (str == null) { throw IllegalArgumentException("null이 들어왔습니다.") } return str.startsWith("A"); } fun startWithA2(str: String?): Boolean? { if (str == null) { return null } return str.startsWith("A"); } fun startsWithA3(str: String?): Boolean { if (str == null) { return false } return str.startsWith("A") }
위와 같이 null이 가능한 타입을 아예 다르게 취급한다.

2. Safe Call과 Elvis연산자

Safe Call
  • null이 아니면 실행하고, null이면 실행하지 않는다.
    • val str: String? = "ABC" println(str?.length)
Elvis 연산자
  • 앞의 연산 결과가 null이면 뒤의 값을 사용
    • val str: String? = null println(str?.length ?: 0)
 
1번의 예제를 아래와 같이 바꿀 수 있다.
fun startsWithA1(str: String?): Boolean { return str?.startsWith("A") ?: throw IllegalArgumentException("null이 들어왔습니다.") } fun startsWithA2(str: String?): Boolean? { return str?.startsWith("A") } fun startsWithA3(str: String?): Booleaen { return str?.startsWith("A") ?: false }

3. null 아님 단언!!

nullable type이지만, 아무리 생각해도 null이 될 수 없는 경우
fun startsWith(str: String?): Boolean { return str!!.startsWith("A") }
이 경우 혹시나 nul이 들어오면 런타임시에 NPE가 뜬다.

4. 플랫폼 타입

코틀린과 자바를 같이 사용하는 경우가 있다.
코틀린에서 자바 코드를 가져다 쓸 때는 어떻게 처리될까?
// 자바 코드 public class Person { private final String name; public Person(String namee) { this.name = name; } @Nullable public String getName() { return name; } }
위의 경우 @Nullable, @NotNull이 붙어있는 경우 코틀린이 이해한다.
만약 이게 없는 경우에는 런타임 시에 코틀린에서 NPE를 띄운다.
 

코틀린에서 타입을 다루는 방법

1. 기본 타입

val number1 = 4 val number2: Long = number1 // Type mismatch
위와 같이 작성하면 코틀린에서는 컴파일 단에서 에러가 난다.
이 때 .toType()을 사용해서 명시적으로 캐스팅을 해주어야 한다.
val number1 = 4 val number2: Long = number1.toLong()

2. 타입 캐스팅

기본 타입이 아닌 일반 타입에 대한 캐스팅은 어떻게 해야 할까?
public static void printAgeIfPerson(Object obj) { if (obj instanceof Person) { Person person = (Person) obj; System.out.println(person.getAge()); } }
// 위의 자바 코드를 코틀린으로 바꾸면 fun printAgeIfPerson(obj: Any) { if (obj is Person) { val person = obj as Person // 스마트 캐스트 기능으로 인해 위 라인은 생략해도 된다. // 생략하고 obj.age를 사용할 수 있다. println(person.age) } }
instanceof의 반대는 !is 를 사용할 수 있다.
만약 as로 변환하려면 타입이 nullable이라면 as? 를 사용해야 한다.

3. Kotlin의 특이한 타입 3가지

  1. Any
      • Java의 Object 역할 (모든 객체의 최상위 타입)
      • 모든 Primitive Type의 최상위 타입
      • Any 자체로는 null을 포함할 수 없어 null을 포함하고 싶다면 Any? 로 표현해야 한다.
      • Any에 equals, hashCode, toString이 존재한다.
  1. Unit
    1. Unit은 Java의 void와 동일한 역할
    2. void와 다르게 Unit은 그 자체로 타입 인자로 사용 가능하다.
    3. 함수형 프로그래밍에서 Unit은 단 하나의 인스턴스만 갖는 타입을 의미한다. 즉, 코틀린의 Unit은 실제 존재하는 타입이라는 것을 표현한다.
  1. Nothing
    1. 함수가 정상적으로 끝나지 않아싸는 사실을 표현하는 역할
    2. 무조건 예외를 반환하는 함수 / 무한 루프 함수 등

4. String interpolation / String indexing

val person = Person("pure", 20) println("이름: ${person.name}, 나이: ${person.age}")
이런식으로 ${변수} 를 사용할 수 있다.
fun main() { val namee = "AAAA" val str = """ ABC EFG ${name} """.trimIndent() println(str) }
위와 같이 """ ... """.trimIndent() 를 사용해서 줄바꿈 문자열을 넣을 수 있다.
String str = "ABCDE" val ch1 = str[1] val ch3 = str[2]
위와 같은 방식으로 인덱싱을 사용해 문자열에서 특정 문자를 가져올 수 있다.
 

4. 코틀린에서 연산자를 다루는 방법

1. 단항 연산자 / 산술 연산자

자바와 완전히 동일하다

2. 비교 연산자와 동등성, 동일성

비교 연산자를 사용하면 똑같이 동작하기는 하는데 비교 연산자를 사용하면 compareTo를 호출한다.
동등성(Equality): 두 객체의 값이 같은가? (==)
동일성(Identity): 완전히 동일한 객체인가? 즉 메모리 주소가 같은가 이다. (===)

3. 논리 연산자와 코틀린에 있는 특이한 연산자

&&, ||, ! 의 논리연산자는 자바와 완전히 동일하고 자바처럼 Lazy 연산을 수행한다.
fun main() { if (fun1() || fun2()) { println("본문") } } fun fun1(): Boolean { println("fun 1") return true } fun fun2(): Boolean { println("fun 2") return false }
위 코드에서 실행결과는 fun1, 본문이다. if 안의 연산 조건에서 앞의 것이 true이므로 무조건 true이기 때문에 뒤에 것을 볼 필요도 없기 때문에 fun2()는 실행되지 않는다.
마찬가지로 fun2() && fun1()인 경우 앞에 것이 false라 뒤에 것을 확인하지 않는다.
이게 Lazy 연산이다.

in / !in

  • 컬렉션이나 범위에 포함되어 있다. 포함되어 있지 않다.

a..b

  • a 부터 b 까지의 범위 객체를 생성한다.
  • 반복문에서 자주 쓰인다.

a[i]

  • a에서 특정 Index i로 값을 가져온다.

a[i] = b

  • a의 특정 index i에 b를 넣는다.

4. 연산자 오버로딩

코틀린에서는 객체마다 연산자를 직접 정의할 수 있다.
(심화편에서 정리)

5. 코틀린에서 조건문을 다루는 방법

1. if

fun abc(num: Int) { if (num < 0) { throw IllegalArgumentException("${num}은 음수일 수 없다.") } }
void대신 Unit이고 throw new 대신 throw만 썼다는 점이 자바와의 차이이다.

2. Expression과 Statement

Statement: 프로그램의 문장, 하나의 값으로 도출되지 않는다.
Expression: 하나의 값으로 도출되는 문장.
Java에서는 if-else는 Statement지만 Kotlin에서는 Expression이다.
이 때문에 코틀린에서는 삼항연산자가 없다.
fun getPassOrFail(score: Int): String { return if (score >= 50) { "P" } else { "F" } }
범위를 조건으로 주고 싶을 때 자바에서는 ≥ && ≤ 같은 표현을 썼는데 코틀린에는 in이라는 키워드가 있다.
fun validateScore(score: Int) { if (score !in 0..100) { throw IllegalArgumentException("점수는 0에서 100사이여야 한다.") } }

3. switch, when

코틀린에서는 스위치 대신에 when을 쓴다.
값이나 범위 모두 조건부로 분기로 사용할 수 있다.
fun getGrade(score: Int): String { return when (score /10) { in 90..99 -> "A" in 80..89 -> "B" in 70..79 -> "C" else -> "D" } }
 
is 키워드를 통해 타입을 조건부로 쓸수도 있다.
fun startsWithA(obj: Any): Boolean { return when (obj) { is String -> obj.startsWith("A") else -> false } }
 
when() 안에 값을 안줄수도 있다. 자바의 early return 처럼 사용할 수 있다.
fun validateNum(num: Int) { when { num == 0 -> println("0이다.") num % 2 == 0 -> println("짝수이다.") else -> println("홀수이다.") } }

6. 코틀린에서 반복문을 다루는 방법

1. for-each(향상 for문)

val numbers = listOf(1L, 2L, 3L) for (number in numbers) { println(number) }

2. 전통적인 for문

// 숫자가 늘어날 때 for (i in 1..3) { println(i) } // 숫자가 줄어들 때 for (i in 3 downTo 1) { println(i) } // 간격을 지정할 때 for (i in 1..5 step 2) { println(i) }

3. Progression과 Range

1..3 이런 범위는 Progression을 사용하고 있다. 1에서 시작하고 3으로 끝나는 등차수열을 뜻한다.
downTo, step은 중위함수이다. (변수 함수이름 argument)

7. 코틀린에서 예외를 다루는 방법

1. try catch finally 구문

fun parseIntOrThrow(str: String): Int { try { return str.toInt() } catch (e: NumberFormatException) { throw IllegalArgumentException("주어진 ${str}는 숫자가 아니다.") } } fun parseIntOrThrowV2(str: String): Int? { return try { str.toInt() } catch (e: NumberFormatException) { null } }

2. Checked Exception과 Unchecked Exception

자바였으면 Checked Exception이어서 throws를 메서드 뒤에 붙여주겠지만 코틀린에서는 모두 Unchecked Exception이기 때문에 throws를 붙이지 않는다.
fun readFile() { val currentFile = File(".") val file = File(currentFile.absolutePath + "/a.txt") val reader = BuffereedReader(FileReader(file)) println(reader.readLine()) reader.close() }

3. try with resources

코틀린에는 try with resources가 없다.
대신 .use라는 inline 확장함수를 사용해야 한다.
fun readFile(path: String) { BufferedReader(FileReader(path)).use { reader -> println(reader.readLine()) } }

8. 코틀린에서 함수를 다루는 방법

1. 함수 선언 문법

함수가 하나의 결과값이면 block 대신 = 사용 가능
=을 사용하는 경우에는 값이기 때문에 반환 타입 생략도 가능하다.
함수는 클래스 안에 있을 수도, 파일 최상단에 있을 수도, 한 파일 안에 여러 함수들이 있을 수도 있다.
fun max(a: Int, b:Int): Int = if (a > b) { a } else { b } }

2. default parameter

파라미터에 기본값을 넣고 싶을 때는 아래와 같이 = 값 으로 작성해주면 된다.
fun repeat(str: String, num: Int = 3, useNewLine: Boolean = true) { for (i in 1..num) { if (useNewLine) { println(str) } else { print(str) } } }

3. named argument(parameter)

위의 repeat 함수의 파라미터를 호출하면서 특정매개변수를 직접 지정해서 값을 넣을 수 있다.
지정되지 않은 파라미터는 기본값을 사용한다.
이런 방식은 Builder 패턴을 사용하는 것과 비슷하다.
다만 자바코드를 불러다 쓸 때는 이 기능을 사용할 수 없다.
repeat("Hello World", useNewLine = false)

4. 같은 타입의 여러 파라미터 받기 (가변인자)

fun main() { printAll("A", "B", "C") //...strings val array = arrayOf("A", "B", "C") printAll(*array) // 스프레드 연산자. 배열 안의 것들을 ,를 붙여서 꺼내주는 역할 } fun printAll(vararg strings: String) { for (str in strings) { println(str) } }