코틀린 고급1 (컬렉션, 데이터클래스, 싱글톤)

김소희·2023년 12월 8일
1

컬렉션 타입

  • 코틀린은 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("원화","엔화")

// 방법1 for문
for(currency in currencyList) {
	println(currency)
}


// 방법2 이터레이터
val iterator = currencyList.iterator() 
while (iterator.hasNest()) {
	println(iterator.next())
}


// 방법3 Stream API
currencyList.forEach { 
	println(it)
}

//결과 원화, 엔화 동알
  • for loop -> map 변환을 사용하면 더 쉽게 작업할 수 있다.
// map을 사용하지 않고 소문자를 대문자로 변환
val lowerList = listOf("a","b","c")
val upperList = mutableListOf<String>()

for (lowerCase in lowerList) {
	upperList.add(lowerCase.uppercase())
}

println(upperList)
//결과 [A, B, C]


// map을 사용하여 소문자를 대문자로 변환
val lowerList = listOf("a","b","c")
val upperList = lowerList.map { it.uppercase() }
println(upperList)
//결과 [A, B, C]
  • filter 사용시 map 활용 방법
// 방법1
val filteredList = mutableListOf<String>()
for (lowerCase in lowerList) {
	if(upperCase == "A" || upperCase == "C") {
    	filteredList.add(upperCase)
    }
}
println(filteredList)


// 방법2
val filteredList = upperList.filter { it == "A" || it == "C" }
println(filteredList)
  • 이외에도 다양한 내장함수를 이용할 수 있는데 자바8의 스트림과 흡사하지만 동작방식이 다르다.
  • 내장함수를 1번 사용할 때 마다 컬렉션이 1개 생성되어 쓰이므로 대량의 데이터를 다룰때 내장함수를 사용하면 메모리를 낭비하거나 아웃오브메모리가 발생할 수 있으므로 체인이 많아질 경우에는 터미널 오퍼레이터인 시퀀스API를 사용하는 것이 좋다.
// 시퀀스 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())
}
//결과 true, true, Person(name=tony, age=12)
//일반클래스였다면 결과 false, false, person@1d251891
  • 아무데서나 변경 가능한 코드를 추척하는 것이 어렵기 때문에 객체를 직접 수정하는 것 보다 새로운 객체로 복사해서 사용하는 것이 객체 불변성 유지할 수 있다.
data class Person(val name: String, val age: Int)

fun main() {
	val person1 = Person(name = "tony", age = 12)
    val person2 = person1.copy(name="tom")
}
// 결과 false

싱글톤과 동반객체

  • 싱글톤 패턴은 인스턴스를 하나의 단일 인스턴스로 제한하는 디자인 패턴이다.
  • 싱글톤 패턴을 구현할때는 몇가지 제약사항을 통해 구현한다.
  • 제약 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()
}
//결과 1234,1234
  • 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())
}
//결과 1234, MyClass@2d209079
profile
백엔드 자바 개발자 소희의 노트

0개의 댓글