[GDSC Ewha 5th] Android Session

[GDSC Android] Chapter 1. Kotlin 기본 문법

wlalsu_u 2023. 10. 7. 14:25

1.0 Kotlin basics 학습 목표

 

 

 

 

먼저 아래의 목차를 따라 Kotlin의 기본적인 문법을 배워보자.

 

 

 

1) 연산자
2) 데이터 타입
3) 변수
4) 조건문
5) List 와 Array
6) Null 안전성

 

 

 

 

모든 실습은 IntelliJ 에서 진행하였으며,

 

REPL (Read-Eval-Print-Loop) 사용법은 앞선 블로그에 작성되어있다.

 

 

 

 

 


 

 

 

1.1 연산자 (Operators)

 

 

 

연산자는 다른 언어와 매우 유사하므로 간단하게 정리하고 넘어가자.

 

 

연산자는 '기본연산자' 와 '연산자 함수' 로 나뉜다.

 

 

 

 

 

 

1) 기본 연산자

 

 

 

- 기본 수학 연산 / 증감 연산 / 비교 연산 / 동등 연산 등이 속함

 

- ++ / -- / >= / == / != 등등

 

 

 

[ 기본 연산자 사용 예시 ]


1) 1 + 1 : kotlin.Int = 2 ( Int 의 첫 대문자는 객체임을 의미)

2) 1.0 / 2.0 : kotlin.Double = 0.5

3) 2.0 * 3.5 : kotlin.Double = 7.0

 

 

 

 

 

 

2) 비교 연산자

 

 

- 기본 연산자로도 수행할 수 있지만, 해당 연산을 더 강조하고 싶을 때 사용

 

 

 

[ 비교 연산자 사용 예시 ]


1) 2.times(3) : kotlin.Int = 6

2) 3.5.plut(4) : kotlin.Double = 7.5

3) 2.4.div(2) = kotlin.Double = 1.2

 

 

 

 

 


 

 

 

 

1.2 데이터 타입 (Data types)

 

 

 

 

먼저 정수형, 실수형, 문자, 논리형 타입을 살펴보자.

 

여기서 1 byte = 8bits 를 의미한다.

 

 

 

 

1) 정수형 타입 (Integer types)

 

 

 

- Long 타입 (64 bits)

 

- Int 타입 (32 bits) 기본 값

 

- Short 타입 (16 bits)

 

- Byte 타입 (8 bits)

 

 

 

 

 

2) 실수형 타입 (Floating-point)

 

 

 

- Double 타입 (64 bits) 기본 값

 

- Float 타입 (32 bits)

 

 

 

 

3) 그 외 타입들 (numeric types)

 

 

 

- Char 타입 (16 bits) : Unicode character

 

- Boolean 타입 (8 bits) : True of False

 

 

 

 

 

 

 

[ 데이터타입에 따른 연산 예시 ]


1) 6 * 50 : kotlin.Int = 300 

2) 6.0 * 50 : kotlin.Double = 300.0 (Int 타입보다 Double 타입이 더 크므로 실수형이 됨)

3) 1 / 2 : kotlin.Int = 0 (정수형)

4) 1.0 / 2.0 : kotlin.Double = 0.5 (실수형)

 

 

[ 참고 ]

Kotlin은 긴 숫자 상수를 더 읽기 편하기 위해 underscore "_" 사용을 허용한다.

ex) val oneMillion = 1_000_000

 

 

 

 

 

 

 

이때 Kotlin은 정적타입 언어이므로, 

 

한번 타입을 설정하면 타입 변환이 불가능하다!

 

 

 

예를 들어 name = "지민" 으로 설정한 이후에

 

name = 5 로 바꿀 수 없다.

 

 

 

따라서 이에 대한 해결책으로 Type casting 을 사용한다!

 

 

 

 

 

 

 

타입캐스팅 (Type Casting)

 

 

 

- i.toByte() 를 이용하여 타입 캐스팅

 

 

 

 

 

먼저 아래와 같이 타입 캐스팅을 이용하지 않고 타입변화를 시도하면,

 

type mismatch 오류가 난다.

 

 

 

 

 

 

 

 

 

 

아래와 같이 타입캐스팅을 명시적으로 보여주면

 

오류가 해결되는 것을 확인할 수 있다.

 

 

 

 

 

 

 

 

 

 

 

 

 

다음으로 String 타입에서

 

특수문자 사용 방법, 문자열 결합 방법, String template 사용 방법을 알아보자.

 

 

 

 

 

 

1) String 타입에서 특수문자 사용 방법

 

 

방법1) 이스케이프 문자 (\) 사용

 

 

// Hello World I'm "Jimin" 출력
val s1 = "Hello World I'm \"Jimin\""

 

 

 

방법 2)  트리플 쿼트(""") 사용

 

 

 

val text = """var bikes = 50"""

 

 

 

 

 

 

 

2) 문자열 결합 (String concatenation) 방법

 

 

 

- "+ "를 사용하여 결합 가능

 

 

 

 

 

 

 

 

 

 

 

3) 문자열 템플릿 (String templates) 사용방법

 

 

 

 

- 달러 사인 ($) 을 이용하여 표현식을 나타냄

 

- 중괄호 ( {} ) 안에 입력한 연산을 수행하며 함수 호출도 가능

 

 

 

 

 

 [ 참고 ]

여기서 변수 이름을 지정할 때,

유니코드(영어, 한글 모두 포함)는 모두 변수 이름으로 설정됨을 주의하자!

(이에 대한 자세한 설명은 Chapter0. Kotlin을 사용한 안드로이드 개발 하단 실습 부분에 설명 https://wlalsu.tistory.com/112)

 

 

 

 

 

 

 

 

 

 

이 실습을 진행하면서

 

마침표 ( . ) 도 유니코드 일 것 같은데, 왜 컴파일 오류가 안나는지 궁금했다.

 

 

찾아보니 점(.)이나 쉼표(,) 같은 기호는 변수 이름으로 처리하지 않는다고 한다!

 

 

 

 

 

 

 

 

 

위의 실습은 String template expressions(식) 에서

 

중괄호를 통해 연산을 수행하는 예제이다.

 

 

 

 

 

 

[ 참고 ]

String은 불변 (immutable) 이므로 저장한 String 값을 변경할 수 없다.

예를 들어 str1 = "abcde" 로 설정한 후, str1[3] = "f" 를 통해 d를 f로 변환하지 못한다.

 

 

 

 

 

 

 


 

 

 

 

1.3 변수 (Variables)

 

 

 

 

변수에서 타입추론과 가변변수 / 불변변수에 대해 알아보자.

 

 

 

 

 

1) 타입추론

 

 

 

- Kotlin은 강력한 타입 추론 제공

 

- 컴파일 타임에 타입이 결정됨

 

- 원래는 명시적으로 써주어야 하지만, 충분히 명확하다면 타입 생략 가능

 

 

 

 

 

 

먼저 타입을 명시적으로 적는 방법을 살펴보자.

 

 

콜론 (:) 을 이용하여 타입을 명시하고,

 

콜론 뒤의 띄어쓰기는 관습적인 방법이니 지켜주자!

 

 

 

 

 

// 타입을 명시적으로 적는 방법
var width: Int = 12
val length: Double = 2.5

 

 

 

 

 

하지만 아래와 같이 타입을 적어주지 않아도,

 

자동으로 타입 추론이 가능하다. (타입이 명확한 경우이므로)

 

 

 

 

 

// 타입 추론 예시
var width = 12
val length = 2.5

 

 

 

 

 

 [ 참고 ]

Kotlin은 정적타입 언어이므로 타입이 컴파일 타임에 결정되며,

이후에 타입을 변경하려고 하면 오류가 발생한다.

 

 

 

 

 

 

 

 

2) 가변변수 (Mutable) 와 불변변수 (Immutable)

 

 

 

- 가변변수는 var을 이용하여 선언

 

- 불변변수는 val을 이용하여 선언 (권장되는 방법)

  (프로그램이 복잡해지면 가변일때 추적하기 힘들기 때문)

 

 

 

 

 

먼저 가변변수의 예를 살펴보자.

 

 

 

// 가변변수 예시
var score = 10

 

 

 

위의 예시에서 score 변수를 가변(var) 으로 설정하였기 때문에,

 

score의 값을 5,10,100 등등 다른 값으로 변경 가능하다.

 

 

 

 

 

// 불변변수 예시
val name = "Jennifer"

 

 

 

 

하지만 다음과 같이 name 변수를 불변(val)으로 설정할 경우,

 

name = "Jimin" 과 같이 변수 값 변경이 불가능하다.

 

 

 

 

 

 

 

 

 

 

 

 

 

실제로 불변변수 값을 변경하려고 하는 경우,

 

"Val annot be reassigned" 오류가 뜨는 것을 확인할 수 있다. 

 

 

 

 

 

 

 

 


 

 

 

 

1.4 조건문 (Conditionals)

 

 

 

 

조건문은 정상적인 흐름에서 벗어난 경우를 처리하는 방법이다.

 

아래의 5가지 조건문들을 하나씩 살펴보자.

 

 

 

 

 

1) if / else 조건문

 

 

 

- if 안의 조건문이 true 일 때 if 문 수행

 

- if 안의 조건문이 false일 때 else 문 수행

 

- 조건이 3개 이상인 경우, else if 사용

 

 

 

 

 

fun main() {
    val numberOfCups = 30
    val numberOfPlates = 50

    if (numberOfCups > numberOfPlates) {
        println("Too many cups!")
    } else {
        println("Not enough cups!")
    }
}

 

 

 

 

 

 

 

 

 

 

 

조건이 3개 이상인 경우, else if를 사용하는 예제는

 

다음과 같다.

 

 

 

 

 

 

 

fun main() {
    val guests = 30
    if (guests == 0){
        println("No guests")
    } else if (guests < 20) {
        println("Small group of people")
    } else {
        println("Large group of people")
    }
}

 

 

 

 

 

 

 

 

 

[ 참고 ] if 문 vs if 식 차이점이 뭘까? (뒤에서 자세히 설명)

1) if 문 (statement) : else 문을 생략 가능 / 결과값이 없어도 됨

2) if 식 (expression) : else 문이 필수 / 결과값이 있어야 함

 

 

[ 추가 ] 범위 (Range) 에 대해 알아보자!


- Kotlin 에서 범위는 양 끝 값을 포함

- 점 2개 ( . . ) 를 이용하여 범위 표현

// 1부터 100까지 실행
if (numberOfStudents in 1..100) { }​


- IntRange 뿐만 아닐 CharRange('a' .. 'z'), LongRange 등 다양한 범위 지원

for (i in 'd' .. 'g') print(i)​


- step 을 사용하여 범위를 건너뛸 수 있음

// 2개씩 건너뛰도록
for (i in 3..6 step 2) print(i)​


- downTo 를 이용하여 내림차순 (  5 .. 1 로 적을 경우 컴파일 오류! )

for (i in 5 downTo 1) print(i)​


 

 

 

 

 

 

 

2) when 조건문

 

 

 

- 선택지가 3개 이상인 경우 사용 권장

 

- 선택지가 많은 경우 if / else 문 보다 가독성이 높음

 

- swith 문과 유사

 

 

 

 

 

 

fun main() {
    val results = 30
    when (results) {
        0 -> println("No results")
        in 1..39 -> println("Got results!")
        else -> println("That's a lot of results!")
    }
}

 

 

 

 

 

 

 

 

 

 

3) for loop

 

 

 

- for 문의 조건에 해당하는 값을 반복하고 싶은 경우 사용

 

- withIndex() 를 이용하여 인덱스를 받아올 수 있음

 

- 앞서 다루어던 range 적용 가능

 

 

 

 

 

fun main() {
    val pets = arrayOf("dog", "cat", "canary")
    for (element in pets) {
        print(element + " ")
    }
}

 

 

 

 

 

 

 

 

다음으로 withIndex() 를 이용하여 인덱스를 받아오는 실습도 진행해보자.

 

여기서 ( index, element ) 순서로 값이 받아와지는데, 이 순서는 반드시 지켜야 한다!

 

 

 

 

 

fun main() {
    val pets = arrayOf("dog", "cat", "canary")
    for ((index, element) in pets.withIndex()) {
        println("Item at $index is $element\n")
    }
}

 

 

 

 

 

 

 

 

 

 

4) while loop

 

 

 

- while 문의 조건에 해당될 때 까지 while 문을 수행

 

- while 문과 do while 문 가능

 

 

 

 

 

먼저 while 문을 사용한 예시는 아래와 같다.

 

 

 

 

fun main() {
    // 변수 값을 변경할 것이기 때문에 가변 처리
    var bicycles = 0
    while (bicycles < 50) {
        bicycles++
    }
    println("$bicycles bicycles in the bicycle rack\n")
}

 

 

 

 

 

 

 

 

do while 문을 사용한 예시는 아래와 같다.

 

 

 

 

 

fun main() {
    // 변수 값을 변경할 것이기 때문에 가변 처리
    var bicycles = 50
    do {
        bicycles--
    } while (bicycles > 50)
    println(" $bicycles bicycles in the bicycle rack\n")
}

 

 

 

 

 

 

 

 

 

 

5) repeat loops

 

 

 

- 단순반복을 원할 때 사용

 

- iterate 이나 증감을 하지 않을 경우 사용

 

- repeat ( ) 의 괄호 안에 반복 횟수를 작성

 

 

 

 

 

fun main() {
    repeat(2) {
        print("Hello!")
    }
}

 

 

 

 

 

 

 

 

 

 

 


 

 

 

 

1.5 리스트(List) 와 배열(Array)

 

 

 

 

리스트와 배열은 매우 유사하지만,

 

어떤 차이점이 있는지 알아보자.

 

 

 

 

 

1) 리스트 (List)

 

 

 

- 리스트는 순서가 있는 요소들의 집합

 

- 순서가 있으므로 인덱스로 접근 가능

 

- 리스트는 중복을 허용 (집합이 아니기 때문에)

 

- listOf() 를 이용하여 리스트 사용

 

- 불변 (Immutable) 이 기본값이므로 가변을 원할 경우 mutableListOf() 로 표현

 

- 요소의 추가나 삭제가 가능

 

 

 

 

 

 

fun main() {
    val instruments = listOf("trumpet", "piano", "violin")
    println(instruments)
}

 

 

 

 

 

 

 

 

다음으로 리스트를 가변으로 바꾸기 위해

 

mutableListOf() 를 사용하는 경우를 살펴보자.

 

(기본적으로 리스트는 불변이므로, 가변 리스트를 원할경우 꼭 명시적으로 작성해야 함을 유의하자!)

 

 

 

 

 

 

fun main() {
    val myList = mutableListOf("trumpet", "piano", "violin")
    myList.remove("violin")
    println(myList)
}

 

 

 

 

 

 

 

 

 

 

여기서 한가지 의문점이 생긴다.

 

 

myList 라는 리스트를 mutableListOf 를 이용하여 가변리스트로 설정하였는데,

 

왜 앞에 val 을 이용하여 불변 변수로 설정하였을까? 

 

 

 

 

 

이를 이해하기 위해 var/val 과 mutable/immutable 의 차이점을 알아보자.

 

 

 

 

 

[ 💡 val 과 immutable 의 차이점! ]


1) val 의 불변

- 객체가 변경되면 안됨

- 객체가 메모리 상에 똑같은 위치에 있어야 하지만, 내부 구현은 변경 가능

- 즉 새로운 List로 바뀌지 않는다면, List 의 세부정보는 바뀌어도 됨

- 위의 예시에서는 myList 가 yourList로 바뀌지만 않는다면, myList 내부 값은 변경 가능


2) immutable 의 불변

- 객체 내부 정보가 바뀔 수 없음

- 위의 예시에서는 myList 값이 ("trumpet", "piano", "violin") 이므로 이를 수정 불가능

 

 

 

[ 참고 ]

- 리스트를 문장(sentence) 으로 비유하면 이해가 쉽다!

- 문장은 순서가 있는 word의 집합

- 같은 단어가 반복해서 나올 수 있음

 

 

 

 

 

 

 

 

2) 배열 (Array)

 

 

 

- 다양한 요소들을 포함 가능 (List와 유사)

 

- arrayOf() 를 사용하여 array 사용

 

- 인덱스를 이용하여 접근 가능

 

- Array 는 가변! (불변과 가변 설정이 가능한 list와 차이점)

 

- 가변(mutable) 이므로 내부 변경 가능

 

- Array 의 사이즈는 고정되어 있어서, 요소의 추가(add) 나 제거(remove) 가 불가능

 

- 서로 다른 타입도 함께 사용 가능 (ex. arrayOf("hats", 2))

 

- 더하기 (+) 를 이용하여 array 를 결합한 새로운 array 생성 가능

 

 

 

 

 

 

여기서 Array 의 출력은 다음과 같은 3가지 방식 모두 가능하다. 

 

 

 

 

fun main() {

    // Array 출력 방법 3가지
    val pets = arrayOf("dog", "cat", "canary")
    println(java.util.Arrays.toString(pets)) // 정석적인 방법
    println(Arrays.toString(pets)) // import 필요
    println(pets.contentToString()) // intelliJ 추천

}

 

 

 

 

 

 

 

 

 

 

그렇다면 List 는 print 에서 그냥 println(pets) 사용이 가능한데, 

 

Array 의 경우 오류가 나는 이유가 무엇일까?

 

 

 

이는 List 와 Array 의 print 구현이 다르기 때문이다.

 

 

 

 

일반적으로 List 를 사용하는 경우

 

List의 내용이 더 중요하고, 메모리 주소를 알 필요가 없기 때문에

 

메모리 주소 값을 println 으로 제공하지 않는다.

 

 

 

 

하지만 Array 의 경우

 

메모리 주소 값을 사용해야 하는 경우가 있으므로

 

println 시에는 @ 뒤에 메모리 값을 확인할 수 있고

 

array의 내용값을 확인하고 싶다면 위의 3가지 방법을 사용하면 된다.

 

 

 

 

 

 

실제로 아래와 같이 array 를 단순히 println한 경우

 

해당 값이 저장된 메모리 위치가 @ 뒤에 나오는 것을 확인할 수 있다. 

 

 

 

 

 

 

 

 

 

 

 

 

[ 💡 List 와 Array 가 다른 이유! ]


List와 Array 가 다른 이유는 기본적으로 메모리 상에 저장되는 방식이 다르기 때문이다.


1) List 의 메모리 저장 방식

- 메모리 내에 자리가 비어있는 곳에 저장

- 따라서 값이 연속되어 저장되지 않고 linked list 를 이용하여 저장

- 요소들이 한번에 저장되어 있지 않기 때문에 add, remove 가 가능


2) Array 의 메모리 저장 방식

- 요소를 한번에 연속하여 저장

- 접근이 쉽기 때문에 고사양 데이터 작업시 권장

- 하지만 요소의 추가, 삭제가 어려움



 

 

 

 

 

 


 

 

 

 

1.6 널 안전성 (Null safety)

 

 

 

 

- Kotlin은 기본적으로 null 을 허용하지 않음 (java NPE 과 차이점)

 

- null 발생시 컴파일 타임에서 오류가 발생하므로, 런타임에 에러가 발생하는 java 보다 안전성이 높음

 

- null을 사용해야 하는 경우 safe call operator (?) 을 사용하여 명시적으로 표현

 

- null 이 아님을 강조하기 위해 ! ! 연산자 사용

 

- elvis (? :) 연산자를 사용하여 null 사용을 테스트 가능

 

 

 

 

 

 

 

 

1) null safe call operator (?) : 널 사용

 

 

 

Kotlin 은 기본적으로 null 을 허용하지 않기 때문에,

 

아래와 같이 null 값을 입력하면 오류가 발생하는 것을 확인할 수 있다.

 

 

 

 

 

 

 

 

 

 

 

 

 

만약 Java 와의 상호운용성 등의 이유로 null을 써야 하는 경우

 

safe call operator (?) 를 사용하여 null 을 허용할 수 있다.

 

 

 

 

 

fun main() {
    // null 허용
    var numberOfBooks: Int? = null
}

 

 

 

 

 

 

 

 

 

2) not-null assertion operator (!!) : 널 아님 강조

 

 

 

Kotlin 은 기본적으로 null 을 허용하지 않지만,

 

널 사용이 불가능함을 강조하기 위해 !! 연산자를 사용할 수 있다.

 

 

이때 null 이 사용되면 NPE(Null Point Exception) 이 발생한다.

 

 

 

 

 

 

 

 

 

 

 

 

3) Elvis operator (?:) : 널 사용 확인

 

 

 

- null 인 경우 0 출력

 

- null이 아닌 경우 값 출력

 

- 엘비스 프레슬리 머리 모양을 따서 지었다는데..ㅎㅎ

 

 

 

 

 

 

 

 

 

 

 

 

fun main() {
    // null 인 경우 -> 0 반환
    // null이 아닌 경우 -> 5 반환
    var numberOfBooks = 6
    numberOfBooks = numberOfBooks?.dec() ?: 0
    println(numberOfBooks)
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

'GDSC Android 교육 세션 자료 및 강의 내용' 과 'Codelab 1강 : Kotlin basics' 내용을 기반으로 작성하였습니다.