ref : Udemy [Android 12 및 Kotlin 개발 완전 정복] 섹션 5
Collection
단순히 같은 데이터 타입 또는 다른 데이터 타입의 묶음이다.
collection 의 예시
IntArray - Integer
BooleanArray - Boolean
DoubleArray - Double
ByteArray - Byte
LongArray - Long
ShortAray - Short
FloatArray - Float
다른 타입을 함께 저장할 수 있는 collection
arrayOf<String>
arrayOf<1,2,"dawon",0.5,Fruit())
immutable collection
다음은 값을 변경할 수 없다.
List - listOf
Set - setOf
Map - mapOf
mutable collection
Mutable List - ArrayList, arrayListOf, mutableListOf
Mutable Set - mutableSetOf, hashSetOf
Mutable Map - HashMap, hashMapOf, mutableMapOf
배열
한개의 변수에 여러 데이터를 저장할 수 있다.
//arrayOf 은 다양한 데이터 타입의 요소를 넣을 수 있다.
val numbers = arrayOf(1,2,3,4,5,6)
val numbersD:DoubleArray = doubleArrayOf(1.0,2.0,3.0,4.0,5.0,6.0)
print(numbers) //array는 print method로 출력할 수 없다.
println()
print(numbers.contentToString())
println()
먼저 arrayOf 로 배열을 만들어보았다.
arrayOf로 만들면 꼭 요소들의 타입이 다 같지 않아도 된다.(다양한 데이터 타입이 허용된다.)
doubleArrayOf 로 만들었다면 double 타입만 들어가야하겠다.
print로 배열을 출력할 때에는 contentToString() 함수를 통해 출력해주었다.
//for 문으로 접근하기
for(element in numbers){
print(element)
}
println()
// 2 를 더해서 출력 (not overriding)
for(element in numbers){
print("${element + 2}")
}
println()
// 인덱스를 통해 접근
print(numbers[0])
println()
// 요소 수정
numbers[0] = 6
print(numbers.contentToString())
println()
배열의 요소들을 for 문을 활용해 하나씩 출력해보았다.
각각의 요소에 2를 더해서 출력해보기도 했는데 저건 오버라이딩이 아니라 단순 디스플레이 이기 때문에
원래의 배열에는 영향을 주지 않는다.
// 배열의 크기는 정해져 있으므로 벗어나는 범위의 인덱스에 값 추가하지 못함.
// 객체를 담는 배열
val fruits = arrayOf(Fruit("Apple",2.5), Fruit("Grape", 3.5))
print(fruits.contentToString())
println()
//indices
for (index in fruits.indices){
print("${fruits[index].name} is in index $index")
println()
}
for(fruit in fruits){
print("${fruit.name}")
println()
}
//main 함수 외부에
data class Fruit(val name:String, val price:Double)
fruits 라고 하는 배열을 만들어보았다.
이 배열에는 class Fruit 의 객체들이 들어간다.
이 배열의 요소들에 접근할 때에 .indices 를 활용하여 접근하였다.
indices 를 쓰면 인덱스를 활용할 수 있다.
아니면 단순히 fruit 처럼 객체 자체로 반복문을 돌면서
fruit.name 식으로 속성에 접근할 수도 있다.
리스트
리스트에는 하나의 데이터 타입과 커스텀 객체 또는 클래스를 모두 저장할 수 있다.
새로운 값이 추가되면서 크기가 바뀔 수 있고
수정가능한 mutable 타입
수정 불가능한 immutable 타입이 있다.
val months = listOf("1월", "2월", "3월")
val anyTypes = listOf(1,2,3,true, false, "String")
print(anyTypes.size)
println()
print(months[1])
months 라고 하는 리스트를 listOf 로 만들어보았다.
listOf 에는 아무 타입이나 사용할 수 있다.
아이템의 사이즈를 출력하려면 .size 를 입력하면 된다.
아이템에는 [ ]를 통해 인덱스로 접근할 수도 있다.
val months = listOf("1월", "2월", "3월")
val anyTypes = listOf(1,2,3,true, false, "String")
//print(anyTypes.size)
//println()
//print(months[1])
for (month in months){
println(month)
}
for 문을 이용해 아이템에 접근해보았다.
val months = listOf("1월", "2월", "3월")
val anyTypes = listOf(1,2,3,true, false, "String")
//mutable list 로 전환하기
val additionalMonths = months.toMutableList()
val newMonths = arrayOf("4월", "5월", "6월")
additionalMonths.addAll(newMonths)
print(additionalMonths)
println()
additionalMonths.add("7월")
print(additionalMonths)
println()
months 가 기존에는 immutable 리스트 였는데 mutable 리스트로 전환해보았다.
toMutableList() 를 사용하면 mutable 리스트로 전환이 가능하다.
mutable 리스트가 되었으므로 아이템을 추가할 수 있다.
newMonths 라는 배열을 만들어서 addAll 함수로 추가해보았다.
add() 를 통해서 원하는 아이템을 하나씩 추가할 수도 있다.
//mutable type list 만들기
val days = mutableListOf<String>("월", "화", "수")
days.add("목")
print(days)
println()
days[2] = "일"
print(days)
println()
//아이템 삭제하기
days.removeAt(3)
print(days)
println()
val removeList = mutableListOf<String>("월","화")
days.removeAll(removeList) //지우고 싶은 list filter
print(days)
println()
처음부터 mutable 리스트를 만들려면 mutableListOf 로 만들어주면 된다.
days 라는 뮤터블 리스트를 만들어보았다.
인덱스를 [ ] 안에 넣음으로써 접근과 수정이 가능하다.
아이템을 삭제하려면 removeAt 이나 removeAll 등을 사용할 수 있다.
removeAt 에는 원하는 인덱스를 넣어 삭제할 수도 있고
removeList 와 같은 필터링을 위한 리스트를 만들어서 removeAll 함수를 써서 삭제할 수도 있다.
set
중복되는 데이터를 제거해주는 collection 이다.
val fruits = setOf("orange", "apple", "grape", "apple")
print(fruits.size) //중복된 아이템은 무시된다.
println()
print(fruits)
println()
print(fruits.toSortedSet()) //알파벳 순 정렬
println()
//mutable list
val newFruits = fruits.toMutableList()
newFruits.add("수박")
newFruits.add("배")
print(newFruits)
println()
print(newFruits.elementAt(3)) //인덱스로 요소에 접근
println()
fruits 라고 하는 immutable 한 set 을 만들어보았다. setOf로 생성한다.
size 를 출력해보면 4개가 아닌 3개로 출력이 된다. 중복된 아이템은 무시하는 것이다.
전체를 출력해보아도 중복된 apple 은 하나만 나오는 것을 확인할 수 있다.
toMutableList() 로 mutable 한 리스트로 전환시켜보았다.
이제 새로운 요소를 추가가능하다.
add() 를 통해 새로운 요소들을 출력하고, elementAt 을 통해 인덱스로 요소에 접근할 수 있다.
map
key와 value를 짝 형태로 데이터를 저장하는 collection
key는 고유해야한다.
fun main(){
//map
val days = mapOf(1 to "mon", 2 to "tue", 3 to "wed")
print(days[2]) //key 값을 통한 접근
println()
for (key in days.keys){
print("$key is to ${days[key]}")
println()
}
println()
val fruitsMap = mapOf("favorite" to Fruit("Grape", 2.5),
"ok" to Fruit("apple", 1.5))
val newdays = days.toMutableMap()
newdays[4] = "thu"
newdays[5] = "fri"
print(newdays.toSortedMap())
println()
}
data class Fruit(val name:String, val price:Double)
mapOf로 map 을 생성할 수 있다.
key와 value로 이루어 지는데 이 사이는 to로 연결지어주면 된다.
[ ] 를 통해 요소에 접근할 수 있는데 이 대괄호 안에 들어가는 값은 인덱스가 아닌 key값이다.
Fruit 라고 하는 data class 를 만들어서 이 클래스의 객체를 value 값으로 가지는 fruitsMap 을 만들어보았다.
mutable 한 map으로 만들기 위해 toMutalbeMap() 을 사용할 수 있다.
Map을 정렬하기 위해선 toSortedMap() 함수를 사용할 수 있다.
Array List
동적 배열을 만들기 위함이다.
Array List 의 크기는 필요에 따라 증가 또는 감소할 수 있다.
읽기용 쓰기용 모두 가능하고
insertion 시퀀스를 따른다.
무언가를 추가할 때에 array list 는 동기화를 하지 않는다.
따라서 중복요소를 포함할 수 있다.
생성자
ArrayList<E>()
ArrayList(capacity : Int))
ArrayList(elements : Collection<E>)
함수
add
clear -> 모든 요소 제거
get
remove
람다 표현식
이름이 없는 함수이다.
val sum : (Int, Int) -> Int = {a:Int, b:Int -> a + b}
println(sum(10,5))
//even shorter
//바로 출력 구문을 사용
val sum = {a:Int, b:Int -> println(a + b)}
sum(10,5)
함수 body 는 변수 뒤에 화살표와 연산자와 함께 쓰인다.
위는 두개의 값을 받아 합을 출력하는 sum 함수를 람다식으로 표현해본 것이다.
정리하자면 다음과 같다.
//전체표현
val multi:(Int, Int) -> Int = {x:Int, y:lnt -> x*y}
//선언 타입 생략
val multi = {x:Int, y:Int -> x*y}
//매개변수 타입 생략
val multi: (Int, Int) -> Int = {x, y -> x*y}
//추론불가 -> 에러
val multi: {x, y -> x*y}
접근 제한자
클래스, 인터페이스, 속성 을 제한하는데 사용하는 키워드
클래스의 헤더나 메서드의 바디 등 여러곳에 사용됨.
종류는 public, private, protected, internal.
public
public 은 프로젝트 어디서든 접근 가능하다
코틀린의 기본(default) 제한자이고
접근제한자로 따로 명시된 게 없으면 public scope 이다.
모든 public 선어은 파일의 맨 위에 위치한다.
private
private 은 속성, 필드 등이 선언되어있는 블록에서만 요소에 접근할 수 있게 해준다.
private 이 선언되어있는 곳에서만 사용 가능 한 것이다.
-> scope 밖으로의 접근을 막는다.
Internal
Internal은 자바에 없는 코틀린 만의 기능이다.
Internal은 시행된 모듈 안에서만 필드가 보이도록 한다.
모든 필드는 internal로 선언되고 시행된 모듈 안에서만 접근 가능하다.
open keyword
코틀린에서 모든 클래스는 자동으로 최종값이다. 자동으로 상속 받을 수 없다
그래서 다른 클래스에서 상속받게 하려면 키워드 open 을 사용해야한다.
protected
protected 는 그 클래스나 서브 클래스에서만 보일 수 있게 한다.
서브 클래스 안의 오버라이딩한 protected 선언은 변경을 명시하지 않는한 보호받는다.
protected는 최상위에 선언될 수 없다.
중첩 클래스 내부 클래스
중첩 클래스 : 클래스 안에 생성된 클래스
내부 클래스 : 키워드 inner 를 사용한 클래스 안의 클래스
한 클래스 안에 다른 클래스를 정의하면 기본적으로는 중첩클래스가 되고
inner 키워드를 사용하면 내부 클래스가 된다.
내부 클래스는 기본적으로 외부클래스를 참조하지만
중첩 클래스는 그렇지 않다.
// nested class 중첩 클래스
class Outer {
private val bar: Int = 1
class Nested {
fun foo() = 2
}
}
val demo = Outer.Nested().foo() // == 2
중첩 클래스의 예시이다. 외부 클래스를 참조하지 않아 Outer.Nested().foo()의 값이 2가된다.
// inner class 내부 클래스
class Outer {
private val bar: Int = 1
inner class Inner {
fun foo() = bar
}
}
val demo = Outer().Inner().foo() // == 1
하지만 내부 클래스에서는 외부 클래스를 항상 참조하고 있어서 Outer.Nested().foo()의 값이 1이된다.
내부 클래스를 사용하면 항상 외부 클래스를 참조하기 때문에
객체를 삭제할때 객체가 적절한 시점에 삭제되지 못하고 메모리 누수가 발생한다고 한다.
그래서 특별한 경우가 아니라면 내부클래스 사용을 지양하고 중첩클래스를 사용하는게 권장된다고 한다.
safe cast, unsafe cast
unsafe cast : as 연산자를 이용한 캐스트
safe cast : as? 연산자를 이용한 캐스트
as? 는 safe cast 연산자이다. (nullable type cast 연산자)
해당 타입이면 수행을 하고, 해당타입으로 캐스팅 할 수 없어도
Exception(ClassCastException)을 발생 시키지 않고 null을 반환해준다.
val location:Any = "kotlin"
val safeString:String? = location as? String
val safeInt:Int? = location as? Int
println(safeString)
println(safeInt)
//output : kotlin
null
location 이라고 하는 Any 타입의 스트링을 받는다.
Any 타입이지만 String 타입이다.
safeString 에는 안전한 캐스트가 되어서 "kotlin" 이 String 으로 잘 들어간다.
safeInt 에는 location을 Int 로 받으려고한다. 하지만 해당 타입으로 캐스팅할 수 없기 때문에 null을 반환한다.
예외 처리하기
try, catch, finally, throw 키워드 4개를 알아가면 된다.
try블록에서 발생한 예외를 catch 블록에서 잡아서 처리한다.
try{
// 예외 발생 가능성이 있는 문장
} catch (e: 예외 처리 클래스) {
// 예외 처리
} finally {
// 반드시 실행되어야 하는 문장
}
finally 블록은 try 블록의 예외 발생여부에 상관없이 반드시 처리할 코드를 작성하면 된다.
반드시 해야할 작업이 없다면 생략이 가능하다.
fun main(){
val a = 10
val b = 0
val c : Int
try{
c = a / b // 0으로 나눔
} catch (e : Exception) {
// 예외 처리
}
}
위는 0으로 나누었을 때 발생하는 예외상황을 처리하는 코드이다.
그리고 try catch 블록으로 예외를 처리했다면, throw 키워드로 의도적으로 예외를 발생시킬 수 있다.
fun main() {
var a = 10
var b = 0
try {
Divide(a, b)
} catch (e: Exception) {
println(e.message)
}
}
fun Divide (a: Int, b: Int): Int {
if (a == 0 || b == 0)
throw Exception("0으로는 나눌 수 없습니다.")
else
return a / b
}
//output: 0으로는 나눌 수 없습니다.
a나 b 중에 하나라도 0이면 throw 키워드를 사용해서 예외를 발생시킨다.