와챠의 우당탕탕 코딩 일기장
[Android/Kotlin]Udemy 강의 정리: Kotlin Basics(2) 본문
목차
- Object
- Object 무명 객체
- class / interface생성 시의 Object
- companion object
- const
- inner class
- Extension functions
- Extensions properties
- lambda
- trailing lambda
- Scope function
- let
- with
- run
- apply
- also
- enum class
- sealed
1. Object
object: 싱글톤 패턴
언제 사용?: 언제나 같은 결과 반환을 기대할 때
주의해야할 점: 싱글톤 객체가 어느 위치에서 값을 수정했는지 파악 어려움
// 장바구니
object CartItems {
// private 제품 리스트, mutableKist: 수정, 추가 가능
private val mutableProducts = mutableListOf<Product>()
// public 제품 리스트, List 수정, 추가 불가능
val product: List<Product> = mutableProducts
// 제품 추가
fun addProduct(product: Product) { mutableProducts.add(product) }
}
1-1. Object 무명 객체
: 앞의 이름이 있는 object와는 달리 매번 새로운 객체를 만듦!!
val cartItems = object {
val products = mutableListOf(Product("전자기기", "갤럭시폰"))
override fun toString() = products.toString()
}
1-2. class / interface생성 시의 Object
val clickListener = object: ClickListener {
override fun onClick(view: View) { ~ }
}
1-3. companion object
- companion object 내부에 선언된 변수와 함수들은 java 의 static 이 아님
- 단, 아래 케이스들은 static
- const val 로 선언된 상수
- @JVMStatic 또는 @JVMField이 붙은 변수 + 함수
class Store private constructor(private val products: List<Product>) {
companion object {
fun create(storeId: String): String { return "~" }
}
}
val store = Store.create("electronics")
// 아래 2개 모두 같은 기능
Store.create("electronics") // 1
Store.Companion.create("electronics") // 2
// But, companion에 이름 붙일 시에는 2번으로는 호출 못 함
생성자에 private를 붙여 외부에서 인스턴스 생성 못 하게 함 + create()사용해서 인스턴스 반환해서 싱글톤 패턴 만들수도
2. const
: 상수값
ex) const val KEY = "1234"
3. inner clase
: inner 안 붙이면 외부에서 접근 불가능!
4. Extension functions
fun MutableList<Int>.swap(x: Int, y: Int) {
val temp = this.x
this[x] = this[y]
this[y] = temp
}
val list = mutableListOf(1, 2, 3)
list.swap(0, 2)
MutableList<Int>타입에서 호출할 수 있는 함수를 추가하는 것!
확장할 클래스.함수명() { ~ }<< 일케 쓰면 됨
너무너무 편하고 멋져보인다
5. Extensions properties
val <T> List<T>.lastIndex: Int
get() = size - 1
val list = listOf(1, 2, 3, 4)
println("${list.lastIndex}")
아까는 함수를 추가했다면 이번에는 프로퍼티 추가하는 방법
프로퍼티 이름 앞에 리시버 타입 선언하면 됨
대상 오브젝트에 추가 하는것이 아니기 때문에 backing field 를 가질 수 없어서(initializer X)
setter, getter를 써야 함
+) 프로퍼티: 변수 + getter / setter
+ field: 프로퍼티의 get(), set() 함수가 접근하는 곳
외부 프로퍼티 이용 시에는 프로퍼티의 get(), set() 함수가 호출되는데,
get(), set() 내부에서는 field 를 통해 프로퍼티가 가지고 있는 값에 접근함.
이렇게 뒤에 숨어있는 필드라는 의미라는 뜻으로 backing field 라고도 부름
함수 타입
- (파라미터) -> 리턴타입
- ex) (Int) -> Boolean
- 이름도 쓸 수 있음
- ex) (input: Int) -> Boolean
- 제너럴 타입도 사용 가능
- ex) (T) -> Boolean
- 여러 파라미터 사용 가능
- ex) (Int, Int) -> Int
ex) val isEven: (Int) -> Boolean = { it % 2 == 0 }
ex) val sum: (Int, Int) -> Int = { first: Int, second: Int ->
first + second
}
val resumt = sum(1, 2)
Int.() -> Boolean
T.() -> R 형식도 가능
ex) val isEven: Int.() -> Boolean = { this % 2 == 0 }
5-1. lambda
: 인자를 중괄호로 표현하는 식
val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y }
val sum = { x: Int, y: Int -> x + y }
5-2. trailing lambda
: 여러 파라미터 중 마지막 파라미터가 함수일 때 trailing lambda 지원
예시의 두번째 파라미터: 인덱스에 따라 초기화 어떻게 할 지 함수타입으로 전달하는 것
fun <T> MutableList(size: Int, init: (index: Int) -> T) : MutableList<T>
val list = MutableList(5) { index -> index }
// index로 list를 초기화 한 것 == [0, 1, 2, 3, 4]
6. Scope function
함수타입을 인자로 받는 고차함수
모두 제레닉 사용
함수명 앞에 T. 붙어있는 확장 함수도 있음
inline: 함수 호출의 부하 없이 호출
6-1: let
null 체크
// 1. let
inline fun <T, R> T.let(block: (T) -> R): R
// null아닌 경우에만 함수호출하는 용도로 사용
// ex
fun processNonNullString(str: String) {}
val str: String? = "hello" // String? 타입
processNonNullString(str) // error!! null일수고 있기 때문
val length = str.let { // str이 null이 아닐 때 let으로 전달
processNonNullString(it) // 가능
it.length // 반환!
}
6-2. with
(앞에서 받은 객체와) 추가로 함께 작업할 거 있을 때
// 2. with
inline fun <T, R> with(receiver: T, block: T.() -> R): R
// receiver객체의 참조를 블록으로 전달받아 추가로 처리할 작업이 있는 경우 주로 사용
// ex
val numbers = mutableListOf("one", "two", "three")
with(numbers) {
println($this) // numbers
println($size) // numbser.size
}
// let처럼 ?<와 함께 쓸 수 없음 << Null여부 알 수 없음!!
6-3. run
그냥 작업할 때
// 3. 확장함수 run
inline fun <T, R> T.run(block: T.() -> R): R
// T.()를 보니... 블록의 참조인 this 사용 가능
// ex
val products = mapOf(
"패션" to "겨울 패딩",
"전자기기" to "핸드폰",
)
// Key로 조회했을 때 Null일 수 있어서 Null 여부 확인
// let: it으로 참조
products["패션"]?.let{ println("상품명 $it") }
// run: this로 참조 + this의 프로퍼티 접근 시 this 생략 가능
products["패션"]?.let{ println("상품명 $this") }
// 4. 확장함수 아닌 run
inline fun <R> run(block: () -> R): R
// !!수신객체 없이!! 블록 안에서 특정 코드를 실행시켜야 하는 경우에 사용
// ex
products["패션"]?.run {
println("상품명: $this")
} ?: run { // 수신객체 없이~~ 값이 없는 경우에 실행됨(it, this 사용 불가 당연함)
println("등록된 상품이 없습니다.")
}
6-4. apply
생성 시점에 추가로 더 작업할 거 있을 때
// 5. apply
inline fun <T> T.apply(block: T.() -> Unit): T
// T.() << 객체 참조 this로 할 수 있겠다
// 블록의 반환타입이 Unit + 수신객체의 타입이 apply의 반환 타입이 됨
// 객체 생성 시점에 추가로 처리해야하는 작업이 있는 경우 사용
// ex
val adam = Person("Adam").apply {
age = 32
city = "London"
}
6-5. also
생성 이후에 작업할 거 있을 때
// 6. also
inline fun <T> T.also(block: (T) -> Unit): T
// 블록 안의 파라미터 타입이 수신객체 T와 동일, it 참조!
// 블록 반환 타입 Unit + 수신객체 타입인 T가 also의 반홤 타입
// 객체 생성 이후 추가로 처리해야 할 작업이 생기는 경우 사용
// ex
val numbers = mutableListOf("one", "two", "three")
numbers
.also ( println("추가되기 전 상태: #it")
.add("four")
7. enum class
같은 타입의 여러 상수 정의
enum class Color {
RED,
GREEN,
BLUE
}
// enum + when: else 없이도 모든 조건 평가 가능
val colorCode = when (color) {
Color.RED -> "red"
Color.GREEN -> "green"
Color.BLUE -> "blue"
}
// 생성자, 프로퍼티 선언 가능
enum class Color(val rgb: Int) {
RED(0xFF0000),
GREEN(0x00FF00),
BLUE(0x0000ff)
}
// 함수도 선언 가능
enum class ProtocolState {
WAITING { override fun signal() = TALKING },
TALKING { override fun signal() = WAITING };
abstract fun signal(): ProtocolState
}
8. sealed interface / class
: 클래스 간 계층 구조 생성 가능
class도 될 수 있고 object도 될 수 있음
같은 패키지 내에서만 subclass 선언 가능
sealed<<이거는 AndroidWeekly에서도 본 적이 있다.
그때도 이게 뭐지..하고 지나갔던 기억이,,다시 봐야지,,
이것도 상태 체크하는 거 같은데,,,!!!
코틀린 공부를 완전히 각잡고 책 봐가면서 제대로 해 본 적은,,, 없는데
그래도 쫌쫌따리 모르는 거 검색하고 공식문서 몇 개 보고 한 게 도움이 좀 된 거 같다.
그래도 코틀린 문법에 더 익숙해져야 할 필요성을 많이 느꼈다!!
글고 Scope function은 전에도 블로그에 정리한 적이 있지만 완벽한 이해는 아니었는데
이번에 또 공부하다보니까 이해가 더 잘 된 거 같다.
싱글톤 패턴이나 Scope function 사용해서 이전 플젝들을 조금씩 수정하고 싶어졌다ㅎ