자주 사용하는 기본적인 자료구조를 모아놓은 프레임워크
데이터 삭제, 저장마다 순서를 지키는 collection
val fruits= listOf<String?>("apple", "banana", "kiwi", "peach", null)
// val fruits= listOf("apple", "banana", "kiwi", "peach") -> 타입 생략 가능
println("fruits: $fruits")
println("fruits.size: ${fruits.size}")
println("fruits.get(2): ${fruits.get(2)}")
println("fruits[3]: ${fruits[3]}")
println("fruits.indexOf(\"peach\"): ${fruits.indexOf("peach")}")
listOf와 달리 추가 및 삭제 가능을 위해 mutableList 사용
val fruits= mutableListOf<String>("apple", "banana", "kiwi", "peach")
fruits.remove("apple")
fruits.add("grape")
println("fruits: $fruits")
fruits.addAll(listOf("melon", "cherry"))
println("fruits: $fruits")
fruits.removeAt(3)
println("fruits: $fruits")
순서 x, 중복 x
null 객체를 가질 수 있음
list와 같이 기본은 immutable, mutableSet 존재
@RequiresApi(Build.VERSION_CODES.N)
fun main() {
val numbers = mutableSetOf<Int>(33, 22, 11, 1, 22, 3)
println(numbers)
numbers.add(100)
numbers.remove(33)
println(numbers)
numbers.removeIf({ it < 10 }) // 10 이하의 숫자를 삭제
println(numbers)
}
[33, 22, 11, 1, 3]
.
[22, 11, 1, 3, 100]
.
[22, 11, 100]
removeIf
는 filter처럼 조건에 따라 요소들을 제거한다.
removeIf는 api버전 N부터 추가되었으므로 @RequiresApi 처리를 해준다.
List와 Set은 Collection을 상속받기 때문에 collection을 인자로 받는 함수 등에 사용할 수 있다.
fun main() {
fun printAll(strings: Collection<String>) {
for (s in strings) print("$s ")
println()
}
val stringList = listOf("one", "two", "one")
printAll(stringList)
val stringSet = setOf("one", "two", "three")
printAll(stringSet)
}
Map 선언
val numbersMap = mapOf<String, String>("1" to "one", "2" to "two", "3" to "three")
println("numbersMap: $numbersMap")
val numbersMap2 = mapOf(Pair("1", "one"), Pair("2", "two"), Pair("3", "three"))
println("numbersMap2: $numbersMap2")
key, val 가져오기
println("numbersMap keys:${numbersMap3.keys}")
println("numbersMap values:${numbersMap3.values}")
keys는 set으로, values는 collection으로 반환
val numbersMap = mutableMapOf<String, String>( "1" to "one", "2" to "two", "3" to "three" )
println("numbersMap: $numbersMap")
numbersMap.put("4", "four")
numbersMap["5"] = "five"
println("numbersMap: $numbersMap")
numbersMap.remove("1")
println("numbersMap: $numbersMap")
numbersMap.clear()
println("numbersMap: $numbersMap")
조건에 해당하는 데이터만으로 콜렉션 재구성,
콜랙션에 변화를 주는 것이 아니라 리스트로 리턴하기 때문에 사용하기 위해서는 필터된 리스트를 따로 저장해야 한다.
fun filterTest() {
/* 리스트에서 짝수만 뽑아내는 예제 */
val list = listOf(1, 2, 3, 4)
println(list.filter { it % 2 == 0 }) //짝수만 필터링
/* 나이가 30살 이상인 사람만 뽑아내는 예제 */
val people = listOf(Person("안드로이드", 29), Person("코틀린", 30))
val filtered = people.filter { it.age >= 30 }
filtered.forEach {
println(it) // Person(name=코틀린, age=30)
}
}
[2, 4]
Person(name=코틀린, age=30)
Map의 key, values를 필터를 주기 위해 filterkeys, filtervalues도 쓸 수 있다.
각각의 원소를 변환하는 기능을 하여 원본 리스트와 원소개수가 같은 새 컬렉션을 반환한다.
// 각 원소의 제곱으로 모인 리스트를 만드는 map 예제
val list = listOf(1, 2, 3, 4)
println(list.map { it * it }) //제곱 만들기 (1x1, 2x2, 3x3, 4x4)
// 사람 리스트 -> 이름 리스트 변환
val people = listOf(Person("안드로이드", 29), Person("코틀린", 30))
val mapped = people.map { it -> it.name }
mapped.forEach {
println(it)
}
// 안드로이드
// 코틀린
[1, 4, 9, 16]
안드로이드
코틀린
mapKeys, mapValues는 Map 객체의 key, value에 map을 쓰기 위해 사용한다.
val numbers = mapOf(1 to "zero", 2 to "one")
// key는 제곱수로, value는 대문자로 변환
val newMap = numbers.mapValues { it.value.uppercase() }.mapKeys { it.key * it.key }
newMap.forEach {
println("${it.key} - ${it.value}")
1 - ZERO
4 - ONE
조건을 모두 만족하는지 Boolean을 반환한다.
val under30 = { p: Person -> p.age < 30 }
//모든 원소가 만족하는지 판단하려면 all 함수를 사용
val people3 = listOf(Person("안드로이드", 25), Person("코틀린", 33))
println(people3.all(under30)) //false
false
any
조건을 하나라도 만족하는지 Boolean을 반환한다.
val under30 = { p: Person -> p.age < 30 }
//하나라도 만족하는 원소가 있는지 판단하려면 any 함수를 사용
println(people3.any(under30)) //true
true
조건에 만족하는 개수를 반환한다.
val under30 = { p: Person -> p.age < 30 }
val people5 = listOf(Person("안드로이드", 25), Person("코틀린", 33))
println(people5.count(under30)) // 1
1
조건에 만족하면 그 만족한 첫번째 값을 반환하고 찾는 것을 멈춘다.
찾지 못하면 null을 반환한다.
val under40 = { p: Person -> p.age < 40 }
val people7 = listOf(Person("안드로이드", 25), Person("코틀린", 33))
println(people7.find(under40)) //Person(name=안드로이드, age=25)
val under10 = { p: Person -> p.age < 10 }
println(people7.find(under10)) // null
Person(name=안드로이드, age=25)
null
데이터를 특정 조건으로 묶어 그 조건이 키가 되고 묶은 값들이 value인 Map 을 반환한다.
val people8 = listOf(
Person("안드로이드", 25),
Person("코틀린", 30),
Person("자바", 30)
)
val map = people8.groupBy { it.age }
map.forEach {
println("나이: ${it.key}, 구성원: ${it.value}")
}
나이: 25, 구성원: [Person(name=안드로이드, age=25)]
나이: 30, 구성원: [Person(name=코틀린, age=30), Person(name=자바, age=30)]
리스트의 요소들을 붙여주어 처리할 때 쓰는 함수
List<T>
와 List<T>
들의 리스트의 요소들을 합쳐 새로운 리스트로 만든다.
val strings = listOf("abc", "def")
val newMap = strings.flatMap{
it.toList()
}
println(newMap) //[a, b, c, d, e, f]
val nestedList = listOf(listOf("abc", "def"), listOf("hij", "klm"))
val newMap2 = nestedList.flatMap{ it.toList() }
println(newMap2) //[abc, def, hij, klm]
println(nestedList.flatMap{ it })
val newMap3 = nestedList.flatMap{ it }.flatMap { it.toList() }
println(newMap3) //[a, b, c, d, e, f, h, i, j, k, l, m]
[a, b, c, d, e, f][abc, def, hij, klm]
[abc, def, hij, klm][a, b, c, d, e, f, h, i, j, k, l, m]
리스트 안에 여러개의 리스트가 포함된 경우 하나의 리스트로 반환한다.
val strings = listOf(listOf("abc", "def"), listOf("hij", "klm"))
println(strings.flatten()) //[abc, def, hij, klm]
[abc, def, hij, klm]
코틀린은 자바와 달리 Checked Exception 개념이 없기 때문에
try~catch는 선택이고 로직에서 Exception을 처리하기를 권장함
fun readNumber(reader: BufferedReader): Int? {
try {
print("1 -->")
val line = reader.readLine()
return Integer.parseInt(line)
} catch (e: NumberFormatException) {
print("2 -->")
e.printStackTrace()
} finally {
print("3 --> ")
reader.close()
}
return null
}
fun main() {
var reader = BufferedReader(StringReader("239"))
print(readNumber(reader)) // 1 -->3 --> 239
println()
var reader2 = BufferedReader(StringReader("abc"))
print(readNumber(reader2)) // 1 -->2 -->3 --> null
println()
}
Closeable 인터페이스가 구현된 클래스에 한해 use 사용 가능
내부 구현을 보면 Exception 여부에 상관없이 close() 호출을 보장
어떤 클래스의 멤버 메소드인 것처럼 호출할 수 있지만, 그 클래스의 밖에 선언된 함수
예를 들어 String이나 Int에 새로운 메소드를 추가하고 싶으면
fun String.메소드명() : 리턴타입 {메소드 구현}
형식으로 구현하면 된다.
람다식으로도 선언할 수 있는데 이를 lambda with receiver라고 부른다.
Int.
과 같이 추가할 클래스에 .을 붙이고 람다식을 구현한다.
fun stringExtensionTest(){
// 문자열 확장 함수
fun String.lastChar(): Char {
return this.get(this.length - 1)
}
println("Hello World".lastChar()) // d
// 확장함수 추가 예제. lambda로 선언. lambda with receiver라고 부름.
val sum2 : Int.() -> Int = {
println("sum2 : this :${this.javaClass.name}")
this.plus(5)
}
println(100.sum2())
//----------------------------------
val stringToInt: String.() -> Int = {
println("String 확장함수 : $this")
toInt()
}
println("123".stringToInt().javaClass.name)
}
sum2 : this :int
105
String 확장함수 : 123
확장함수는 각 클래스에 정적 메서드로 생성되기 때문에 virtual method invocation이 발생하지 않는다.
fun staticExtension(){
open class MyView {
open fun click() = println("View clicked")
}
class MyButton: MyView() {
override fun click() = println("Button clicked")
}
// 기능 확장
fun MyView.showOff() = println("I'm a view!")
fun MyButton.showOff() = println("I'm a button!")
// 부모 타입으로 자식 객체 생성
val view: MyView = MyButton()
view.click() //Button clicked
view.showOff() // I'm a view!
}
위 예시에서 click()이라는 멤버함수는 자식 클래스가 오버라이딩하게 되면 자식클래스 구현체의 click는 오버라이딩된 메소드가 불리지만
확장함수는 static 영역의 정적 메소드로 생성되므로 MyView의 showOff를 가져온다.
제네릭 클래스도 확장함수를 추가할 수 있다.
fun List<Int>.getHigherThan(num: Int): List<Int> {
val result = arrayListOf<Int>()
for (item in this) {
if (item > num) {
result.add(item)
}
}
return result
}
fun main() {
val numbers: List<Int> = listOf(1, 2, 3, 4, 5, 6)
val filtered = numbers.getHigherThan(3).toString()
println(filtered)
}
[4, 5, 6]
class NewList : ArrayList<Int>() {
fun getHigherThan(num: Int): List<Int> {
val result = arrayListOf<Int>()
this.forEach { it->
if (it > num) {
result.add(it)
}
}
return result
}
}
fun main() {
val numbers1 = NewList()
numbers1.addAll(listOf(1,2,3,4,5,6))
val filtered = numbers1.getHigherThan(3).toString()
println(filtered)
}
val heroes = """
|D.Va
|Lucio
|Mercy
|Soldier: 76
""".trimMargin()
""" 으로 감싸면 모든 문자를 자유롭게 사용 가능
각줄의 시작을 구문할 문자 앞을 공백으로 채우고 trimMargin()
을 사용하면
D.Va
Lucio
Mercy
Soldier: 76
val heroes = """
D.va
Lucio
Mercy
Soldier: 76
""".trimIndent()
공백을 단순히 제거하는 trimIndent()
를 사용할수도 있다.
val items = listOf("D.Va", "Lucio", "Mercy", "Soldier: 76")
println(items.joinToString()) // "D.Va, Lucio, Mercy, Soldier: 76" 출력
println(items.joinToString("-")) // "D.Va-Lucio-Mercy-Soldier: 76" 출력
String을 합쳐준다. 인자를 넣으면 사이에 그 인자를 넣어준다.
val items2 = listOf(HeroName("Hana", "Song"), HeroName("Jack", "Morrison"))
println(items2.joinToString { "${it.firstName} ${it.lastName}" })
// "Hana Song, Jack Morrison" 출력
람다식으로 문자열로 변환하는 규칙을 추가로 지정하여 사용할 수도 있다. (joinToString의 마지막 인자가 transform 인데 여기에 문자열을 다르게 변환할 형식을 넣을 수 있다.)
자바는 연산자 오버로딩이 없으나 코틀린은 이를 지원한다.
대표적으로
plus, minues, times, div(/), rem(%)
data class Point(val x: Int, val y: Int) {
operator fun plus(other: Point): Point { //"plus" 라는 연산자 함수를 정의합니다.
return Point(x + other.x, y + other.y)
}
}
fun main() {
val point1 = Point(10, 20)
val point2 = Point(30, 40)
println(point1 + point2)
println(point1.plus(point2))
}
간단하게 확장 함수로 그때그때 구현할 수도 있다
/* 연산자를 확장 함수로 구현하기 */
operator fun Point.plus(other: Point): Point {
return Point(x + other.x, y + other.y)
}
/* 두 피연산자의 타입이 서로 다른 연산자 정의하기 */
operator fun Point.times(scale: Double): Point {
return Point((x * scale).toInt(), (y * scale).toInt())
}
/* 단항 연산자 정의하기 */
operator fun Point.unaryMinus(): Point { //단항연산자는 파라미터가 없습니다.
return Point(-x, -y) //각 좌표에 -(음수)를 취한 좌표를 반환
}
오해하지 말아햐 하는 것이 .plus, .times, .unaryMinus를 오버라이딩 한 것이지 새로운 함수명을 만들면
+, *, -를 쓸 때 이들이 불리지 않을것이다.
확장함수로 오버라이딩한 연산자 사용
//times test
val point = Point(10, 20)
println("times 호출 : ${point * 1.5}") // times 호출
//복합연산자
var point3 = Point(1, 2)
point3 += Point(3, 4) // point = point + Point(3, 4)와 동일
println("복합연산자 : ${point3}") // 복합연산자 : Point(x=4, y=6)
// 단항연산자 unaryMinus
val point6 = Point(10, 20)
println("단항 연산자(unaryMinus): ${-point6}") //// 단항 연산자(unaryMinus): Point(x=-10, y=-20)
times 호출 : Point(x=15, y=30)
복합연산자 : Point(x=4, y=6)
단항 연산자(unaryMinus): Point(x=-10, y=-20)
자바와 개념이 같은데 compareValuesBy를 사용하면 여러 기준으로 비교하기가 보다 더 수월하다.
compareValuesBy(비교대상 A, 비교대상 B, 비교값들 ...)
/* 비교연산자 overloading :compareTo */
data class Person55( val firstName: String, val lastName: String ) : Comparable<Person55> {
override fun compareTo(other: Person55): Int {
// 첫번째 비교에서 비교가 되면 리턴, 비교가 안되면 다음값 비교
return compareValuesBy(
this, other,
Person55::lastName, Person55::firstName
)
}
}