컬렉션 타입
- 코틀린은 List, Set, Map 컬렉션 타입을 제공한다.
- 컬렉션은 읽기전용인 불변 컬렉션(Immutable)과 쓰기(삽입,수정,삭제)작업이 가능한 가변 컬렉션(Mutable)으로 나뉜다.
- 불변은 add함수를 사용할 수 없다.
- 자바에서는 가변형 컬렉션만 취급되므로 자바와 상호작용하는 코드에서는 주의해야 한다.
- apply함수 사용시 가독성이 증가한다.
- map컬렉션은 중위표현식(인픽스 노테이션 표현식)을 통해서 키와 벨류를 설정할 수 있다.
- map컬렉션을 사용할때 put함수가 아닌 리터럴 문법을 사용하는 것을 권장하고 있다.
val currencyList = listOf("원화","엔화")
val mutableCurrencyList = mutableListOf<String>()
mutableCurrencyList.add("원화")
mutableCurrencyList.add("엔화")
val mutableCurrencyList = mutableListOf<String>().apply {
add("원화")
add("엔화")
}
val numberSet = setOf(1, 2, 3, 4)
val mutableSet = mutableOf<Int>().apply {
add(1)
add(2)
}
val numberMap = mapOf("one" to 1, "two" to 2)
val mutableNumberMap = mutableMapOf<String, Int>()
mutableNumberMap["one"] = 1
mutableNumberMap["two"] = 2
- 빌더를 사용하면 더 쉽게 생성할 수 있다.
- 빌더 리스트를 사용하면 내부에서는 가변 리스트로(add로) 값을 할당하기 때문에 빌더 안에서만 값을 할당할수 있고, 결과는 불변리스트로 반환된다.
val numberList: List<Int> = buildList {
add(1)
add(2)
}
- arrayList, LinkedList를 사용하고 싶을 경우에는 해당 구현체의 생성자를 사용하면 된다.
val arrayList = ArrayList<Int>().apply {
add(1)
add(2)
}
val linkedList = LinkedList<Int>().apply {
addFirst(3)
add(2)
addLast(1)
}
- 컬렉션은 이터러블의 구현체이므로 반복문을 사용하여 순회할 수 있다.(자바와 동일)
val currencyList = listOf("원화","엔화")
for(currency in currencyList) {
println(currency)
}
val iterator = currencyList.iterator()
while (iterator.hasNest()) {
println(iterator.next())
}
currencyList.forEach {
println(it)
}
- for loop -> map 변환을 사용하면 더 쉽게 작업할 수 있다.
val lowerList = listOf("a","b","c")
val upperList = mutableListOf<String>()
for (lowerCase in lowerList) {
upperList.add(lowerCase.uppercase())
}
println(upperList)
val lowerList = listOf("a","b","c")
val upperList = lowerList.map { it.uppercase() }
println(upperList)
val filteredList = mutableListOf<String>()
for (lowerCase in lowerList) {
if(upperCase == "A" || upperCase == "C") {
filteredList.add(upperCase)
}
}
println(filteredList)
val filteredList = upperList.filter { it == "A" || it == "C" }
println(filteredList)
- 이외에도 다양한 내장함수를 이용할 수 있는데 자바8의 스트림과 흡사하지만 동작방식이 다르다.
- 내장함수를 1번 사용할 때 마다 컬렉션이 1개 생성되어 쓰이므로 대량의 데이터를 다룰때 내장함수를 사용하면 메모리를 낭비하거나 아웃오브메모리가 발생할 수 있으므로 체인이 많아질 경우에는 터미널 오퍼레이터인 시퀀스API를 사용하는 것이 좋다.
val filteredList = upperList
.asSequence()
.filter { it == "A" || it == "C" }
.filter { it == "C" }
.filter { it == "C" }
.filter { it == "C" }
.toList()
데이터 클래스
- 데이터 클래스란 데이터를 보관하거나 전달하는 목적을 가진 객체를 만들 때 사용한다.
(EX)자바의 DTO)
- data 키워드를 사용하여 데이터 클래스를 생성하면 equals(), hashcode(), toString(), ComponentN(), copy()와 같은 함수들을 코틀린 컴파일러가 자동으로 만들어준다.
data class Person(val name: String, val age: Int)
fun main() {
val person1 = Person(name = "tony", age = 12)
val person2 = Person(name = "tony", age = 12)
println(person1 == person2)
val set = hashSetOf(person1)
println(set.contains(person2))
println(person1.toString())
}
- 아무데서나 변경 가능한 코드를 추척하는 것이 어렵기 때문에 객체를 직접 수정하는 것 보다 새로운 객체로 복사해서 사용하는 것이 객체 불변성 유지할 수 있다.
data class Person(val name: String, val age: Int)
fun main() {
val person1 = Person(name = "tony", age = 12)
val person2 = person1.copy(name="tom")
}
싱글톤과 동반객체
- 싱글톤 패턴은 인스턴스를 하나의 단일 인스턴스로 제한하는 디자인 패턴이다.
- 싱글톤 패턴을 구현할때는 몇가지 제약사항을 통해 구현한다.
- 제약 1: 직접 인스턴스화 하지 못하도록 생성자를 private로 숨긴다.
- 제약 2: getInstance()라는 단일 인스턴스를 반환하는 static 메서드를 제공한다.
- 제약 3: 멀티-스레드 환경에서도 안전하게 유일한 인스턴스를 반환해야 한다.
- <싱글톤 구현 방법>
- 방법 1: 자바의 이른초기화 방식 - 생성자를 private로, getInstance()사용
puble class Java_Singleton {
private static final Java_Singleton INSTANCE = new Java_Singleton();
private Java_Singleton() {}
public Java_Singleton getInstance() {
return INSTANCE.INSTANCE;
}
}
- 방법 2: 자바의 지연초기화 방식 - 사용하지 않는 객체도 메모리에 로드되는 것을 개선한 방식
puble class Java_Singleton {
private Java_Singleton() {}
public Java_Singleton getInstance() {
return LazyHolder.INSTANCE;
}
private static class LazyHolder {
private static final Java_Singleton INSTANCE = new Java_Singleton();
}
}
- 방법 3: Enum 싱글톤-이펙티브 자바에서 소개된 방식이지만 실무에서 거의 사용하지 않는다.
- 방법 4: DCL(Double Check Locking)-자바에서 더블체크락킹으로 구현했을때 메모리 이슈가 있어서 JVM환경에서는 거의 사용하지 않는다.
- 코틀린에서는 객체 선언 문법을 통해 싱글톤을 지원하고 있다.
- object 키워드로 싱글톤 객체를 생성하고, 클래스 한정자를 사용하여 변수와 함수를 사용한다.
object Singleton {
val a = 1234
fun prontA() = println(a)
}
fun main() {
println(Singleton.a)
Singleton.printA()
}
- const는 상수변수를 나타내고 상수명은 대문자로 짓는다.
object DatetimeUtils {
val now : LocalDateTime
get() = LocalDateTime.now()
const val DEFAULT_FORMAT = "YYYY-MM-DD"
fun same(a: LocalDateTime, b: LocalDateTime) : Boolean {
return a == b
}
}
fun main() {
println(DatetimeUtils.now)
println(DatetimeUtils.now)
println(DatetimeUtils.DEFAULT_FORMAT)
val now = LocalDateTime.now()
println(DatetimeUtils.same(now, now))
}
2023-05-24T22:41:57.157697
2023-05-24T22:41:57.158962
YYYY-MM-DD
true
- companion object 키워드를 사용하면 동반객체를 만들 수 있다.
- 동반객체는 내부에 맴버와 함수를 가질 수 있다.
class MyClass {
companion object {
val a = 1234
fun newInstance() = MyClass()
}
}
fun main() {
println(MyClass.a)
println(MyClass.newInstance())
}