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' 내용을 기반으로 작성하였습니다.
'[GDSC Ewha 5th] Android Session' 카테고리의 다른 글
[GDSC Android] Chapter 4. 안드로이드 앱 만들기 (Build your first Android app) (1) (1) | 2023.11.08 |
---|---|
[GDSC Android] Chapter 3. 클래스와 객체 (Classes and Objects) (1) | 2023.11.04 |
[GDSC Android] Chapter 2. Functions 함수 (0) | 2023.10.07 |
[GDSC Android] Chapter 0. Kotlin을 사용한 Android 개발 (0) | 2023.09.28 |
[GDSC Android] 섹션 0. Android를 공부하고 싶은 이유 / 교육 세션 소개 (0) | 2023.09.20 |