Chapter 1 ์์ ์ฑ
Item 1 ๊ฐ๋ณ์ฑ์ ์ ํํ๋ผ
์ฝํ๋ฆฐ์ ๋ชจ๋๋ก ํ๋ก๊ทธ๋จ์ ์ค๊ณํฉ๋๋ค.
๋ชจ๋์ ํด๋์ค, ๊ฐ์ฒด, ํจ์, ํ์ ๋ณ์นญ (type alias), ํฑ๋ ๋ฒจ (top-level) ํ๋กํผํฐ๋ฑ ๋ค์ํ ์์๋ก ๊ตฌ์ฑ๋ฉ๋๋ค.
์ผ๋ถ๋ ์ํ (state) ๋ฅผ ๊ฐ์ง ์๋๋ฐ, ์ฝ๊ณ ์ธ ์ ์๋ ํ๋กํผํฐ (read-write property)
var
๋ฅผ ์ฌ์ฉํ๊ฑฐ๋, mutable
๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ๋ฉด ์ํ๋ฅผ ๊ฐ์ง ์ ์๋ค.
var a = 0
var list: MutableList<Int> = mutableListOf()
์์๊ฐ ์ํ๋ฅผ ๊ฐ๋ ๊ฒฝ์ฐ, ํด๋น ์์์ ๋์์ ์ฌ์ฉ ๋ฐฉ๋ฒ๋ฟ๋ง ์๋๋ผ ๊ทธ ์ด๋ ฅ (history)์๋ ์์กดํ๊ฒ ๋๋ค.
class BankAccount {
var balence = 0.0
private set
fun deposit(depositAmount: Double) {
balance += depositAmount
}
@Throw(InsufficientFunds::class)
fun withdraw(withdrawAmount: Double) {
if (balance < withdrawAmount) {
throw InsufficientFunds()
}
balance -= withdrawAmount
}
}
class InsufficientFunds: Exception()
val account = BankAccount()
val expectInitialAmount = 0.0
val expectDepositAmount = 100.0
val expectWithdrawAmount = 50.0
assertThat(account.balance)
.`as`("์ด๊ธฐ๊ฐ์ $expectInitialAmount ์ผ๊ฒ์ด๋ค.")
.isEqualTo(expectInitialAmount)
// ๊ณ์ข ์์ก์ ์ํ๋ฅผ ๋ณ๊ฒฝ
account.deposit(expectDepositAmount)
assertThat(account.balance)
.`as`("$expectDepositAmount ์ ์ ์ฌํ์์ผ๋ ํ์ฌ ๊ฐ์ $expectDepositAmount ์ผ๊ฒ์ด๋ค.")
.isEqualTo(expectDepositAmount)
// ๊ณ์ข ์์ก์ ์ํ๋ฅผ ๋ณ๊ฒฝ
account.withdraw(expectWithdrawAmount)
assertThat(account.balance)
.`as`("$expectDepositAmount ๊ฐ์์ $expectWithdrawAmount ์ ์ ์ธํ์์ผ๋ ${expectDepositAmount - expectWithdrawAmount} ์ผ๊ฒ์ด๋ค.")
.isEqualTo(expectDepositAmount - expectWithdrawAmount)
์ํ๋ฅผ ๊ฐ๊ฒ ํ๋ ๊ฒ์ ์๋ ์ ๊ฒ์ ๋๋ค.
์๊ฐ์ ๋ณํ์ ๋ฐ๋ผ์ ๋ณํ๋ ์์๋ฅผ ํํํ ์ ์๋ค๋ ๊ฒ์๋ ์ ์ฉํ์ง๋ง, ์ํ๋ฅผ ์ ์ ํ๊ฒ ๊ด๋ฆฌํ๋ ๊ฒ์ด ๋งค์ฐ ์ด๋ ต์ต๋๋ค.
ํ๋ก๊ทธ๋จ์ ์ดํดํ๊ณ ๋๋ฒ๊ทธํ๊ธฐ ํ๋ค์ด์ง๋ค.
- ์ํ๋ฅผ ์ถ์ ํ๋ฉด์ ๋๋ฒ๊น ํ๊ธฐ ์ํํ์ง ์๋ค.
- ์ฝ๋๋ฅผ ์ถ๊ฐํ๊ฑฐ๋ ์์ ํ๊ธฐ ์ด๋ ต๋ค.
- ์์์น ๋ชปํ ์ํฉ ๋๋ ์ค๋ฅ๋ฅผ ๋ฐ์์์ผฐ์๋ ํด๊ฒฐ์ด ์ฝ์ง ์๋ค.
๊ฐ๋ณ์ฑ(mutableility)์ด ์์ผ๋ฉด, ์ฝ๋์ ์คํ์ ์ถ๋ก ํ๊ธฐ ์ด๋ ต๋ค.
- ์คํํ๋ ์์ ์ ๊ฐ์ด ๋ฌ๋ผ์ง ์ ์๋ค.
- ์ฝ๋ ์คํ์ ์์ธกํ๊ธฐ ์ํด ๊ฐ์ ์ถ์ ํด์ผ ํ๋ค.
- ๋๋ฒ๊น ํ ๋ ๋ง๋ค ๋์ผํ ๊ฐ์ ์ ์งํ๋ค๊ณ ํ์ ํ ์ ์๋ค.
๋ฉํฐ์ค๋ ๋ ํ๋ก๊ทธ๋จ์ผ ๋ ์ ์ ํ ๋๊ธฐํ๊ฐ ํ์ํ๋ค.
- ๋ณ๊ฒฝ์ด ์ผ์ด๋๋ ๋ชจ๋ ๋ถ๋ถ์ ๋ํด์ ์ถฉ๋์ด ๋ฐ์ํ ์ ์๋ค.
ํ ์คํธํ๊ธฐ ์ด๋ ต๋ค.
- ๋ชจ๋ ์ํ์ ๋ํด์ ํ ์คํธ๊ฐ ํ์ํ์ฌ ๋ณ๊ฒฝ์ด ๋ง์ผ๋ฉด ๋ ๋ง์ ํ ์คํธ ์กฐํฉ์ด ํ์ํ๋ค.
์ํ ๋ณ๊ฒฝ์ด ์ผ์ด๋ ๋, ๋ณ๊ฒฝ์ ๋ค๋ฅธ ๋ถ๋ถ์ ์๋ ค์ค์ผ ํ๋ ์ผ์ด์ค๊ฐ ์๋ค.
- ์ ๋ ฌ๋์ด ์๋ ๋ฆฌ์คํธ์ ๊ฐ๋ณ์์๋ฅผ ์ถ๊ฐํ๋ค๋ฉด, ์์์ ๋ณ๊ฒฝ์ด ์ผ์ด๋ ๋ ์ฌ ์ ๋ ฌ์ด ํ์ํ๋ค.
๋ค์ ์ฝ๋์ println
๋๋ ๊ฐ์ 1000 ์ด ์๋ ํ๋ฅ ์ด ๋งค์ฐ ๋๋ค.
var num = 0
for (i in 1..1000) {
thread {
Thread.sleep(10)
num += 1
}
}
Thread.sleep(5000)
println(num)
์ฝ๋ฃจํด์ ์ฌ์ฉํ๋ค๊ณ ํด์ ์ผ๋ถ ํด์๊ฐ ๊ฐ๋ฅํ์ง๋ง, ๋ฌธ์ ๊ฐ ์ฌ๋ผ์ง๋ ๊ฒ์ ์๋๋ค.
suspend fun main() {
var num = 0
coroutineScope {
for (i in 1..1000) {
launch {
delay(10)
num += 1
}
}
}
println(num)
}
์ ์ฝ๋๋ ์คํํ ๋๋ง๋ค ๋ค๋ฅธ ์ซ์๊ฐ ๋์จ๋ค.
synchronized
๋ธ๋ญ์ ์ฌ์ฉํ์ฌ๋ ๋ฌธ์ ๋ ํด๊ฒฐ๋์ง ์๋๋ค.
๋ณํ ์ ์๋ ์ง์ ์ ์ค์ผ์๋ก ์ข๋ค. (๋ณ๊ฒฝ์ด ์ผ์ด๋์ผ ํ๋ ๋ถ๋ถ์ ํ์คํ๊ฒ ๊ฒฐ์ ํ๊ณ ์ฌ์ฉํ๋๋ก ํ์)
์ฝํ๋ฆฐ์์ ๊ฐ๋ณ์ฑ ์ ํํ๊ธฐ
์ฝํ๋ฆฐ์ ๊ฐ๋ณ์ฑ์ ์ ํํ ์ ์๊ฒ ์ค๊ณ๋์ด ์๋ค.
์ฝ๊ธฐ ์ ์ฉ ํ๋กํผํฐ(val)
์ฝํ๋ฆฐ์ val
์ ์ฌ์ฉํด ์ฝ๊ธฐ ์ ์ฉ ํ๋กํผํฐ๋ก ๋ง๋ค ์ ์๋ค.
val a = 10
a = 20 // Error
์ฝ๊ธฐ ์ ์ฉ ํ๋กํผํฐ๊ฐ ์์ ํ ๋ถ๊ฐ๋ฅํ๊ฒ์ ์๋๋ค.
์ฝ๊ธฐ ์ ์ฉ ํ๋กํผํฐ๊ฐ mutable
๊ฐ์ฒด๋ฅผ ๋ด๊ณ ์๋ค๋ฉด ๋ด๋ถ์ ์ผ๋ก ๋ณํ ์ ์๋ค.
val list = mutableListOf(1, 2, 3)
list.add(4)
println(list) // [1, 2, 3, 4]
์ฝ๊ธฐ ์ ์ฉ ํ๋กํผํฐ๋ ๋ค๋ฅธ ํ๋กํผํฐ๋ฅผ ํ์ฉํ๋ ์ฌ์ฉ์ ์ ์ getter
๋ก๋ ์ ์ํ ์ ์์ต๋๋ค.
var name: String = "Marcin"
var surname: String = "Moskala"
val fullName
get() = "$name $surname"
fun main() {
println(fullName) // Marcin Moskala
name = "Maja"
println(fullName) // Maja Moskala
}
์ด๋ ๊ฒ var
ํ๋กํผํฐ๋ฅผ ์ฌ์ฉํ๋ val
ํ๋กํผํฐ๋ var
ํ๋กํผํฐ๊ฐ ๋ณํ ๋ ๋ณํ ์ ์์ต๋๋ค.
๊ฐ์ ์ถ์ถํ ๋๋ง๋ค ์ฌ์ฉ์ ์ ์ ๊ฒํฐ๊ฐ ํธ์ถ๋๋ฏ๋ก ์ด๋ฌํ ์ฝ๋๋ฅผ ์ฌ์ฉํ ์ ์๋ค.
fun calculate(): Int {
println("Calculating... ")
return 42
}
val fizz = calculate()
val buzz
get() = calculate()
@Test
fun `์ฝ๊ธฐ ์ ์ฉ ํ๋กํผํฐ(val)`() {
println(fizz)
println(fizz)
println(buzz)
println(buzz)
}
42
42
Calculating...
42
Calculating...
42
์ฝํ๋ฆฐ์ ํ๋กํผํฐ๋ ๊ธฐ๋ณธ์ ์ผ๋ก ์บก์ํ๋์ด ์๊ณ , ์ถ๊ฐ์ ์ผ๋ก ์ฌ์ฉ์ ์ ์ ์ ๊ทผ์ (getter
, setter
) ๋ฅผ ๊ฐ์ง ์ ์์ต๋๋ค.
var
๋ getter
์ setter
๋ฅผ ์ ๊ณตํ์ง๋ง val
์ ๋ณ๊ฒฝ์ด ๋ถ๊ฐ๋ฅํ๋ฏ๋ก getter
๋ง ์ ๊ณตํฉ๋๋ค. ๊ทธ๋์ val
์ var
๋ก ์ค๋ฒ๋ผ์ด๋ ํ ์ ์์ต๋๋ค.
interface Element {
val active: Boolean
}
class ActualElement: Element {
override var active: Boolean = false
}
val
์ ์ฝ๊ธฐ ์ ์ฉ ํ๋กํผํฐ์ง๋ง, ๋ณ๊ฒฝํ ์ ์์ (๋ถ๋ณ: immutable) ์ ์๋ฏธํ๋ ๊ฒ์ ์๋๋ค.
val name: String? = "Marton"
val surname: String = "Braun"
val fullName: String?
get() = name?.let { "$it $surname" }
val fullName2: String? = name?.let { "$it $surname" }
fun main() {
if (fullName != null) {
println(fullName.length) // ERROR
}
if (fullName2 != null) {
println(fullName2.length)
}
}
fullName
์ getter
๋ก ์ ์ํ์ผ๋ฏ๋ก ์ค๋งํธ ์บ์คํธ (smart-cast) ๋ฅผ ํ ์ ์๋ค.
์ค๋งํธ ์บ์คํธ (smart-cast)
์ปดํ์ผ๋ฌ๊ฐ ์๋์ผ๋ก ํ์ ์ ํ์ธํด์ฃผ๊ฑฐ๋null
์ฒดํฌ ๋ฑ์ ์ง์ํด์ค๋ค.
๊ฐ๋ณ ์ปฌ๋ ์ ๊ณผ ์ฝ๊ธฐ ์ ์ฉ ์ปฌ๋ ์ ๊ตฌ๋ถํ๊ธฐ
Iterable
, Collection
, Set
, List
์ธํฐํ์ด์ค๋ ์ฝ๊ธฐ ์ ์ฉ์ด๋ค.
๋๋ฌธ์ ๋ณ๊ฒฝ์ ์ํ ๋ฉ์๋๋ ์๋ค.
๋ฐ๋ฉด์ MutableIterable
, MutableCollection
, MutableSet
, MutableList
์ธํฐํ์ด์ค๋ ์ฝ๊ณ ์ธ ์ ์๋ ์ปฌ๋ ์
์ด๋ค.
์ด์ฒ๋ผ mutable ์ด ๋ถ์ ์ธํฐํ์ด์ค๋ ๋์๋๋ ์ฝ๊ธฐ ์ ์ฉ ์ธํฐํ์ด์ค๋ฅผ ์์๋ฐ์ ๋ณ๊ฒฝ์ ์ํ ๋ฉ์๋๋ฅผ ์ถ๊ฐํ ๊ฒ์ด๋ค.
์๋์ ๊ฐ์ด ์ปฌ๋ ์ ์ธํฐํ์ด์ค์ ๋ค์ด์บ์คํ ์ ์ฌ์ฉํ๋ฉด ์๋๋ค.
์ด๋ ์ถ์ํ๋ฅผ ๋ฌด์ํ๋ ํ์์ด๋ฉฐ, ์์ธก ๋ถ๊ฐ๋ฅํ ๊ฒฐ๊ณผ๋ฅผ ์ด๋ํ๋ค.
val list = listOf(1, 2, 3)
if (list is MutableList) {
list.add(4)
}
ํ์ง๋ง ์ ์ฝ๋์ ์คํ ๊ฒฐ๊ณผ๋ ํ๋ซํผ์ ๋ฐ๋ผ ๋ค๋ฅธ ๊ฒฐ๊ณผ๋ฅผ ๋ณด์ฌ์ค๋ค.
๋ง์ฝ ์ฝ๊ธฐ ์ ์ฉ์์ mutable ๋ก ๋ณ๊ฒฝํด์ผ ํ๋ค๋ฉด, ๋ณต์ (copy) ๋ฅผ ํตํด์ ์๋ก์ด mutable ์ปฌ๋ ์
์ผ๋ก ๋ง๋๋ list.toMutableList
๋ฅผ ํ์ฉํด์ผ ํ๋ค.
val list = listOf(1, 2, 3)
val mutableList = list.toMutableList()
mutableList.add(4)
์์ ๊ฐ์ด ๋ณต์ (copy) ๋ฅผ ํตํด์ ์์ฑํ ์ฝ๋๋ ๊ธฐ์กด์ ๊ฐ์ฒด์ ์ด๋ ํ ์ํฅ์ ์ฃผ์ง ์์ผ๋ฉฐ ์ฌ์ ํ immutable ์ด๋ผ ์์ ํ ์ ์์ผ๋ฏ๋ก, ์์ ํ๋ค๊ณ ํ ์ ์๋ค.
๋ฐ์ดํฐ ํด๋์ค์ copy
immutable ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ๋ฉด ๋ค์๊ณผ ๊ฐ์ ์ฅ์ ์ด ์๋ค.
- ํ ๋ฒ ์ ์๋ ์ํ๊ฐ ์ ์ง๋๋ฏ๋ก, ์ฝ๋๋ฅผ ์ดํดํ๊ธฐ ์ฝ๋ค.
- immutable ๊ฐ์ฒด๋ ๊ณต์ ํ์ ๋๋ ์ถฉ๋์ด ๋ฐ๋ก ์ด๋ฃจ์ด์ง์ง ์์ผํ๋ก, ๋ณ๋ ฌ ์ฒ๋ฆฌ๋ฅผ ์์ ํ๊ฒ ํ ์ ์๋ค.
- immutable ๊ฐ์ฒด์ ๋ํ ์ฐธ์กฐ๋ ๋ณ๊ฒฝ๋์ง ์์ผ๋ฏ๋ก, ์ฝ๊ฒ ์บ์ํ ์ ์๋ค.
- immutable ๊ฐ์ฒด๋ ๋ฐฉ์ด์ ๋ณต์ฌ๋ณธ (defensive copy) ๋ฅผ ๋ง๋ค ํ์๊ฐ ์๋ค. (๊น์ ๋ณต์ฌ๋ ํ์ ์์)
- immutable ๊ฐ์ฒด๋ ๋ค๋ฅธ ๊ฐ์ฒด๋ฅผ ๋ง๋ค๋ ํ์ฉํ๊ธฐ ์ข์ผ๋ฉฐ, ์์ธกํ๊ธฐ ์ฝ๋ค.
- immutable ๊ฐ์ฒด๋ ์ (Set) ํน์ ๋งต (Map) ์ ํค๋ก ํ์ฉํ ์ ์๋ค.
์ (Set) ํน์ ๋งต (Map) ์ ๋ด๋ถ์ ์ผ๋ก ํด์ ํ ์ด๋ธ (Hash-table) ์ ์ฌ์ฉํ๊ณ
์ฒ์ ์์๋ฅผ ๋ฃ์๋ ์์์ ๊ฐ์ ๊ธฐ๋ฐ์ผ๋ก ๋ฒํท์ ๊ฒฐ์ ํ๊ธฐ ๋๋ฌธ์ด๋ค.
๋ง์ฝ immutable ํ ๊ฐ์ฒด์ ๋ณ๊ฒฝ ๊ฐ๋ฅํ ๊ธฐ๋ฅ์ ๋ฃ์ด์ผ ํ๋ค๊ณ ๊ฐ์ ํ์๋ ๋ค์๊ณผ ๊ฐ์ด immutable ํ๊ฒ ์๋ํด์ผ ํ๋ค.
class User(
val name: String,
val surname: String
) {
fun withSurname(surname: String) = User(name, surname)
override fun toString(): String {
return """
User (
name: $name
surname: $surname
)
""".trimIndent()
}
}
var user = User("Blue", "Berry")
user = user.withSurname("Verry")
println(user)
User (
name: Blue
surname: Verry
)
withSurname
์ ๊ฐ์ด ๋ณ๊ฒฝ ๊ฐ๋ฅํ ๋ถ๋ถ ์ ๋ํด ๋ฐฉ์ด์ ๋ณต์ฌ๋ณธ์ ์ ๊ณตํ๊ธฐ ์ด๋ ต๋ค๋ฉด data
ํ์ ์๋ฅผ ์ฌ์ฉํ์
data
ํ์ ์์์ ์ ๊ณตํ๋ copy
๋ผ๋ ์ด๋ฆ์ ๋ฉ์๋๋ฅผ ๋ง๋ค์ด ์ค๋ค.
copy
๋ฉ์๋๋ฅผ ํ์ฉํ๋ฉด, ๋ชจ๋ ๊ธฐ๋ณธ ์์ฑ์ ํ๋กํผํฐ๊ฐ ๊ฐ์ ์๋ก์ด ๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด ๋ผ ์ ์๋ค.
data class User(
val name: String,
val surname: String
) {
override fun toString(): String {
return """
User (
name: $name
surname: $surname
)
""".trimIndent()
}
}
var user = User("Blue", "Berry")
user = user.copy(surname = "Verry")
println("user")
User (
name: Blue
surname: Verry
)
๋ค๋ฅธ ์ข ๋ฅ์ ๋ณ๊ฒฝ ๊ฐ๋ฅ ์ง์
๋ณ๊ฒฝ ๊ฐ๋ฅํ ์ง์ ์ ๋ง๋ค์ด์ผ ํ๋ค๊ณ ํ ๋ ๋๊ฐ์ง ๋ฐฉ๋ฒ์ด ์๋ค.
์๋ ์ฝ๋์ ๊ฐ์ด ๋๊ฐ์ง ์ ํ์ง๊ฐ ์๋ค.
list1
์ mutable ์ปฌ๋ ์
์ ๋ง๋๋ ๊ฒ์ด๊ณ
list2
์ var
๋ก ์ฝ๊ณ ์ฐ๋ ํ๋กํผํฐ๋ฅผ ๋ง๋๋๊ฒ ์ด๋ค.
val list1: MutableList<Int> = mutableListOf()
var list2: List<Int> = listOf()
์๋์ ๊ฐ์ด ๋ ๊ฐ์ฒด์ ๋ณ๊ฒฝ์ ์ํ ๋ ๋๊ฐ์ง ๋ชจ๋ ๋ณ๊ฒฝ ๊ฐ๋ฅ ์ง์ (mutating point) ์ด ์์ง๋ง,
๊ทธ ์์น๊ฐ ๋ค๋ฅด๋ค.
list1.add(1)
list2 = list2 + 1
์ฒซ๋ฒ์งธ list1.add(1)
์ ๊ตฌ์ฒด์ ์ธ ๊ตฌํ์ด ๋ฆฌ์คํธ ๋ด๋ถ์ ์์ง๋ง
๋๋ฒ์งธ list2 = list2 + 1
๋ ํ๋กํผํฐ ์์ฒด๊ฐ ๋ณ๊ฒฝ ๊ฐ๋ฅ ์ง์ ์ด๋ค.
๋ฐ๋ผ์ ๋ณ๊ฒฝ ๊ฐ๋ฅ์ง์ ์ ์ง์ ์ ์ผ๋ก ์ ์ด๊ฐ๋ฅํ ๋๋ฒ์งธ๊ฐ ๋ฉํฐ์ค๋ ๋ ๊ธฐ๋ฐ์์๋ ๋ ์์ ์ฑ์ด ์ข๋ค๊ณ ํ ์ ์๋ค.
๋ง์ฝ mutable ๋ฆฌ์คํธ ๋์ mutable ํ๋กํผํฐ๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ ์ฌ์ฉ์ ์ ์ setter
๋ฅผ ํ์ฉ (ํน์ ์ด๋ฅผ ์ฌ์ฉํ๋ Delegates) ํ์ฌ ๋ณ๊ฒฝ์ ์ถ์ ํ ์ ์๋ค.
var names by Delegates.observable(listOf<String>()) { _, old, new ->
println("Names changed form $old to $new")
}
names += "Fabio"
println(names)
names += "Bill"
println(names)
Names changed form [] to [Fabio]
[Fabio]
Names changed form [Fabio] to [Fabio, Bill]
[Fabio, Bill]
mutable ์ปฌ๋ ์ ๋ ์ด์ฒ๋ผ ๊ด์ฐฐ (observe) ํ ์ ์๊ฒ ๋ง๋๋ ค๋ฉด, ์ด์ฒ๋ผ ์ถ๊ฐ์ ์ธ ๊ตฌํ์ด ํ์ํ๋ค.
mutable ํ๋กํผํฐ์ ์ฝ๊ธฐ ์ ์ฉ ์ปฌ๋ ์ ์ ๋ฃ์ด ์ฌ์ฉํ๋๊ฒ์ด ๊ฐ์ฅ ์ฝ๋ค.
private
์ผ๋ก ์ ํํ ์ ์๊ธฐ ๋๋ฌธ์ด๋ค.
var announcements = listOf<Announcement>()
private set
์ํ๋ฅผ ๋ณ๊ฒฝํ๋ ๋ชจ๋ ๋ฐฉ๋ฒ์ ์ฝ๋๋ฅผ ์ดํดํ๊ณ ์ ์งํด์ผ ํ๋ฏ๋ก ๋น์ฉ์ด ๋ฐ์ํ๋ค.
๋ฐ๋ผ์ ๊ฐ๋ณ์ฑ์ ์ ํํ๋๊ฒ์ด ๊ฐ์ฅ ์ข๋ค.
๋ณ๊ฒฝ ๊ฐ๋ฅ ์ง์ ๋ ธ์ถํ์ง ์๊ธฐ
data class User(val name: String)
class UserRepository {
private val storedUser: MutableMap<Int, String> = mutableMapOf()
fun loadAll(): MutableMap<Int, String> {
return storedUser
}
}
loadAll
์ ์ฌ์ฉํ์ฌ private
์ํ์ธ UserRepository
๋ฅผ ์์ ํ ์ ์๋ค.
val userRepository = UserRepository()
val storedUsers = userRepository.loadAll()
storedUsers[4] = "Kirill"
println(userRepository.loadAll()) // {4=Kirill}
์ ์ฝ๋๋ ๊ฐ์์ค๋ฝ๊ฒ ์์ ์ด ์ผ์ด๋๋ฉด ์ํํ ์ ์๋ค.
์ด๋ ๋ค์ ๋๊ฐ์ง ๋ฐฉ๋ฒ์ผ๋ก ํด๊ฒฐ์ด ๊ฐ๋ฅํ๋ค.
๋ฐฉ์ด์ ๋ณต์
๋ค์๊ณผ ๊ฐ์ด ๋ฆฌํด๋๋ mutable ๊ฐ์ฒด๋ฅผ ๋ณต์ ํ์ฌ ๋ฐฉ์ด์ ๋ณต์ (defensive coping) ๋ฅผ ์ํํ๋ค.
์ด ๋ data
ํ์ ์๋ก ๋ง๋ค์ด์ง๋ copy
๋ฉ์๋๋ฅผ ํ์ฉํ๋ฉด ์ข๋ค.
class UserHolder {
private val user: MutableUser()
fun get(): MutableUser {
return user.copy()
}
}
๊ฐ๋ณ์ฑ ์ ์ด
์ปฌ๋ ์ ์ ๊ฐ์ฒด๋ฅผ ์ฝ๊ธฐ ์ ์ฉ ์ํผํ์ ์ผ๋ก ์ ์บ์คํธ ํ์ฌ ๊ฐ๋ณ์ฑ์ ์ ํํ ์ ์๋ค.
data class User(val name: String)
class UserRepository {
private val storedUsers: MutableMap<Int, String> =
mutableMapOf()
fun loadAll(): Map<Int, String> {
return storedUsers
}
}
์ ๋ฆฌ
๊ฐ๋ณ์ฑ์ ์ ํํ immutable ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ์ข์ ์ด์ ๋ฅผ ์ ๋ฆฌํด๋ณด๋ฉด ๋ค์๊ณผ ๊ฐ๋ค.
var
๋ณด๋ค๋val
์ ์ฌ์ฉํ๋ผ- mutable ํ๋กํผํฐ ๋ณด๋ค๋ immutable ํ๋กํผํฐ๋ฅผ ์ฌ์ฉํ๋ผ
- mutable ๊ฐ์ฒด๋ ํด๋์ค ๋ณด๋ค๋ immutable ๊ฐ์ฒด๋ ํด๋์ค๋ฅผ ์ฌ์ฉํ๋ผ
- ๋ณ๊ฒฝ์ด ํ์ํ ๋์์ ๋ง๋ค์ด์ผ ํ๋ค๋ฉด, immutable data ํด๋์ค๋ก ๋ง๋ค๊ณ copy ๋ฉ์๋๋ฅผ ํ์ฉํ๋ผ
- ์ปฌ๋ ์ ์ ์ํ๋ฅผ ์ ์ฅํด์ผ ํ๋ค๋ฉด, ์ฝ๊ธฐ ์ ์ฉ ์ปฌ๋ ์ ์ ์ฌ์ฉํ๋ผ
- ๋ณ์ด ์ง์ ์ ์ ์ ํ๊ฒ ์ค๊ณํ๊ณ , ๋ถํ์ํ ๋ณ์ด ์ง์ ์ ๋ง๋ค์ง ๋ง๋ผ
- mutable ๊ฐ์ฒด๋ฅผ ์ธ๋ถ์ ๋ ธ์ถํ์ง ๋ง๋ผ
immutable ๊ฐ์ฒด์ mutable ๊ฐ์ฒด๋ฅผ ๊ตฌ๋ถํ๋ ๊ธฐ์ค์ ๊ฐ๋ณ์ฑ์ด๋ค.
Item 2 ๋ณ์์ ์ค์ฝํ๋ฅผ ์ต์ํ ํ๋ผ
์ํ๋ฅผ ์ ์ํ ๋๋ ๋ณ์์ ํ๋กํผํฐ์ ์ค์ฝํ๋ฅผ ์ต์ํ ํ๋๊ฒ์ด ์ข๋ค.
- ํ๋กํผํฐ ๋ณด๋ค๋ ์ง์ญ๋ณ์๋ฅผ ์ฌ์ฉํ๋๊ฒ์ด ์ข๋ค.
- ์ต๋ํ ์ข์ ์ค์ฝํ๋ฅผ ๊ฐ๊ฒ ๋ณ์๋ฅผ ์ฌ์ฉํ๋ค.
์๋ฅผ ๋ค๋ฉด ๋ฐ๋ณต๋ฌธ ๊ฐ์ ๊ฒฝ์ฐ ๋ณ์๋ฅผ ๋ด๋ถ์์๋ง ์ฌ์ฉ๊ฐ๋ฅํ๊ฒ ์์ฑํ๋๊ฒ์ด ์ข๋ค.
val a = 1
fun fizz() {
val b = 2
println(a + b)
}
fun buzz() {
val c = 3
println(a + c)
}
์์ ์์ ์ธ ๊ฒฝ์ฐ fizz
์ buzz
ํจ์์ ์ค์ฝํ์์ ์ธ๋ถ ์ค์ฝํ์ ์๋ ๋ณ์์ ์ ๊ทผ ๊ฐ๋ฅํ์ง๋ง ์ธ๋ถ์์ ๋ด๋ถ์ ์ค์ฝํ์๋ ์ ๊ทผ์ด ๋ถ๊ฐ๋ฅํ๋ค.
/**
* BAD
**/
var user: User
for (i in users.indices) {
user = users[i]
println("User at $i is $user")
}
/**
* Good
**/
for (i in user.indices) {
val user = users[i]
println("User at $i is $user")
}
/**
* BEST
**/
for ((i, user) in users.withIndex()) {
println("User at $i is $user")
}
์ค์ฝํ๋ฅผ ์ข๊ฒ ๋ง๋๋ ๊ฒ์ด ์ข์ ์ด์ ๋ ํ๋ก๊ทธ๋จ์ ์ถ์ ํ๊ณ ๊ด๋ฆฌํ๊ธฐ ์ฝ๊ธฐ ๋๋ฌธ์ด๋ค.
๋ณ์๋ ์ฝ๊ธฐ ์ ์ฉ ๋๋ ์ฝ๊ณ ์ฐ๊ธฐ ์ ์ฉ ์ฌ๋ถ์ ์๊ด์์ด, ๋ณ์๋ฅผ ์ ์ํ ๋ ์ด๊ธฐํ๋๋ ๊ฒ์ด ์ข์ต๋๋ค.
/**
* BAD
**/
val user: User
if (hasValue) {
user = getValue()
} else {
user = User()
}
/**
* Good
**/
val user: User = if (hasValue) {
getValue()
} else {
User()
}
์ฌ๋ฌ ํ๋กํผํฐ๋ฅผ ํ๊บผ๋ฒ์ ์ค์ ํด์ผ ํ๋ ๊ฒฝ์ฐ์๋ ๊ตฌ์กฐ๋ถํด ์ ์ธ (destructuring declaration) ์ ํ์ฉํ๋๊ฒ์ด ์ข๋ค.
/**
* BAD
**/
fun updateWeather(degrees: Int) {
val description: String
val color: Int
if (degrees < 5) {
description = "cold"
color = Color.BLUE
} else if (degrees < 23) {
description = "mild"
color = Color.YELLOW
} else {
description = "hot"
color = Color.RED
}
// other codes...
}
/**
* Good
**/
fun updateWeather(degrees: Int) {
val (description, color) = when {
degrees < 5 -> "cold" to Color.BLUE
degrees < 23 -> "mild" to Color.YELLOW
else -> "hot" to Color.RED
}
// other codes...
}
๊ฒฐ๋ก ์ ์ผ๋ก ๋ณ์์ ์ค์ฝํ๊ฐ ๋์ผ๋ฉด ๋งค์ฐ ์ํํ๋ค.
์บก์ฒ๋ง
๋ง์ฝ ๋ณ๊ฒฝ ๊ฐ๋ฅํ ๋ณ์์ ์ ํจ๋ฒ์๊ฐ ๋์ผ๋ฉด ์ด๋ฅผ ์ฐธ์กฐํ๋ ์ธ๋ถ ๋ก์ง์์ ํด๋น ๋ณ์์ ์ํ๊ฐ์ ์บก์ฒํ์ฌ ๋ณ๊ฒฝํ๊ธฐ ๋๋ฌธ์ ์๋ํ์ง ์์ ๊ฒฐ๊ณผ๊ฐ ๋์ค๊ฒ ๋๋ฒ๋ฆฐ๋ค.
๋ค์์ ์์๋ฅผ ๊ตฌํ๋ ์๊ณ ๋ฆฌ์ฆ์ ๊ตฌํ์์ ์ด๋ค.
val primes: Sequence<Int> = sequence {
var numbers = generateSequence(2) { it + 1 }
var prime: Int
while (true) {
prime = number.first()
yield(prime)
numbers = numbers.drop(1)
.filter { it % prime != 0 }
}
}
์ ์ฝ๋๋ฅผ ์คํํ๋ฉด ๋ค์ ๊ฒฐ๊ณผ๊ฐ ๋์จ๋ค.
print(primes.take(10).toList())
// [2, 3, 5, 6, 7, 8, 10, 11, 12]
์ด๋ฌํ ์๋ชป๋ ๊ฒฐ๊ณผ๋ 4๋ฒ์งธ ๋ผ์ธ์ prime
์ด๋ผ๋ ๋ณ์๋ฅผ ์บก์ฒํ๊ธฐ ๋๋ฌธ์ด๋ค.
๋๋ฌธ์ ๋ณ์์ ์ ํจ๋ฒ์๊ฐ ๋์ผ๋ฉด ํญ์ ์ ์ฌ์ ์ธ ์บก์ฒ๋ฌธ์ ๋ฅผ ์ฃผ์ํด์ผ ํ๋ค.
๊ฐ๋ณ์ฑ์ ์ต๋ํ ํผํ๊ณ ์ค์ฝํ ๋ฒ์๋ฅผ ์ข๊ฒ ๋ง๋ค๋ฉด ์ด๋ฌํ ๋ฌธ์ ๋ฅผ ๊ฐ๋จํ ํผํ ์ ์๋ค.
์ ๋ฆฌ
์ฌ๋ฌ๊ฐ์ง ์ด์ ๋ก ๋ณ์์ ์ค์ฝํ๋ ์ข๊ฒ ๋ง๋ค์ด์ ํ์ฉํ๋ ๊ฒ์ด ์ข๋ค.
๋๋ค๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ ๋ณ์๋ฅผ ์บก์ณํ๋ค๋ ๊ฒ์ ๊ธฐ์ตํด์ผ ํ๋ค.
Item 3 ์ต๋ํ ํ๋ซํผ ํ์ ์ ์ฌ์ฉํ์ง ๋ง๋ผ
์๋ฐ์์ String
์ nullable ํ์
(null
์ ํ์ฉํ๋ ํ์
) ์ด๊ธฐ ๋๋ฌธ์
์ฝํ๋ฆฐ์์์ ์๋ฐ ์ฝ๋๋ฅผ ์ฌ์ฉํ ๊ฒฝ์ฐ์๋ ํ๋ํผ ํ์ (platform type) ์ผ๋ก ๋ณํํ๋ ๊ณผ์ ์ ๊ฑฐ์น๋ค.
ํ๋ซํผ ํ์ (platform type)
๋ค๋ฅธ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด์์ ์ ๋ฌ๋์ดnullable
์ธ์ง ์๋์ง ์ ์ ์๋ ํ์
๋๋ฌธ์ null
์ด ์๋๋ผ๊ณ ์๊ฐ ๋๋๊ฒ์ด null
์ผ ๊ฐ๋ฅ์ฑ์ด ์๊ธฐ ๋๋ฌธ์ ์ํํ๋ค๋ ๊ฒ์ด๋ค.
์ค๊ณ์๊ฐ ๋ช ์์ ์ผ๋ก ์ด๋ ธํ ์ด์ ์ผ๋ก ํ์ํ๊ฑฐ๋, ์ฃผ์์ผ๋ก ๋ฌ์๋์ง ์์ผ๋ฉด, ์ธ์ ๋ ์ง ๋์์ด ๋ณ๊ฒฝ๋ ๊ฐ๋ฅ์ฑ์ด ์๋ค.
์๋ฐ๋ฅผ ์ฝํ๋ฆฐ๊ณผ ๊ฐ์ด ์ฌ์ฉํ ๋, ์๋ฐ ์ฝ๋๋ฅผ ์ง์ ์กฐ์ํ ์ ์๋ค๋ฉด, ๊ฐ๋ฅํ @Nullable
๊ณผ @NotNull
์ด๋
ธํ
์ด์
์ ๋ถ์ฌ์ ์ฌ์ฉํ๋๋ก ํ์
import org.jetbrains.annotations.NotNull;
public class UserRepo {
public @NotNull User getUser() {
// codes...
}
}
๊ฐ ํ๋ซํผ ๋ณ๋ก ์ง์๋๋ ๊ด๋ จ ์ด๋ ธํ ์ด์ ์ ๋ค์๊ณผ ๊ฐ๋ค.
Platform | Library | Annotation |
---|---|---|
JetBrains | org.jetbraions.annotations | @Nullable @NotNull |
Android | androidx.annotation com.android.annotations android.support.annotations | @Nullable @NonNull |
JSR-305 | javax.annotation | @Nullable @CheckForNull @Nonnull |
JavaX | javax.annotation | @Nullable @CheckForNull @Nonnull |
FindBugs | edu.umd.cs.findbugs.annotations | @Nullable @CheckForNull @PossiblyNull @Nonnull |
ReactiveX | io.reactivex.annotations | @Nullable @CheckForNull @Nonnull |
Eclipse | org.eclipse.jdt.annotation | @Nullable @Nonnull |
Lombock | lombock.NonNull | @NonNull |
๋ํ ํ๋ซํผ ํ์ ์ ์ฌ์ฉํ ๊ฒฝ์ฐ, ์ด๋ฅผ ์ฌ์ฉํ๋ ๋ค๋ฅธ ์ฝ๋๊น์ง ์ ํ์ํ์ฑ์ด ์๊ธฐ ๋๋ฌธ์ ๋ฌธ์ ๊ฐ ๋ ์ ์๋ค.
class RepoImpl: UserRepo {
overrid fun getUserName(): String? {
return null
}
}
fun main() {
val repo: UserRepo = RepoImpl()
val text: String = repo.getUserName()
println("User name length is ${text.length}")
}
์ ์ฝ๋์์๋ 9๋ฒ์งธ ๋ผ์ธ์์ Runtime ์์ NPE ์๋ฌ๊ฐ ๋ฐ์ ํ๋ค.
์ ๋ฆฌ
ํ๋ซํผ ํ์ ์ ์ฌ์ฉํ๋ ์ฝ๋๋ ํด๋น ๋ถ๋ถ๋ง ์ํํ ๋ฟ ์๋๋ผ, ์ด๋ฅผ ์ฌ์ฉํ๋ ๊ณณ๊น์ง ์ํฅ์ ์ค ์ ์๋ ์ํํ ์ฝ๋์ด๋ค.
Item 4 inferred ํ์ ์ผ๋ก ๋ฆฌํดํ์ง ๋ง๋ผ
์ฝํ๋ฆฐ์ ํ์ ์ถ๋ก (type inference)์ JVM ์ธ๊ณ์์ ๊ฐ์ฅ ๋๋ฆฌ ์๋ ค์ง ์ฝํ๋ฆฐ์ ํน์ง์ด๋ค.
ํ์
์ ํ ๋นํ ๋ inferred
ํ์
์ ์ค๋ฅธ์ชฝ์ ์๋ ํผ ์ฐ์ฐ์์ ๋ง๊ฒ ์ค์ ํด์ผ ํ๋ค.
open class Animal
class Zebra: Animal()
fun main() {
var animal = Zebra()
animal = Animal() // ์ค๋ฅ: Type mismatch
}
์ด๋ด ๊ฒฝ์ฐ ํ์ ์ ๋ช ์์ ์ผ๋ก ์ง์ ํ์ฌ ๋ฌธ์ ํด๊ฒฐ์ด ๊ฐ๋ฅํ๋ค.
animal
๊ฐ์ฒด๋ Zebra
ํ์
์ผ๋ก ์ ํ (bounded) ๋๊ธฐ ๋๋ฌธ์ ๋ถ๋ชจ ํ์
์ ๊ฐ์ฒด๋ฅผ ์์ฉํ ์ ์๋ค.
open class Animal
class Zebra: Animal()
fun main() {
var animal: Animal = Zebra()
animal = Animal()
}
๋ชจ๋ (ํน์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ) ๋ฅผ ์ง์ ์ ์ผ๋ก ์กฐ์ํ ์ ์๋ ๊ฒฝ์ฐ ๋ฌธ์ ํด๊ฒฐ์ด ๋ถ๊ฐ๋ฅํ๋ค.
๋ฆฌํด ํ์ ์ ์ธ๋ถ์์ ํ์ธํ ์ ์๊ฒ ๋ช ์์ ์ผ๋ก ์ง์ ํด ์ฃผ๋ ๊ฒ์ด ์ข๋ค.
์ ๋ฆฌ
ํ์ ์ ํ์คํ๊ฒ ์ง์ ํด์ผ ํ๋ ๊ฒฝ์ฐ์๋ ๋ช ์์ ์ผ๋ก ํ์ ์ ์ง์ ํด์ผ ํ๋ค๋ ์์น๋ง ๊ฐ๊ณ ์์ผ๋ฉด ๋๋ค.
๋๋ฌธ์ ์ด๋ ์ค์ํ ์ ๋ณด๋ก์จ ์จ๊ธฐ์ง ์์๋ ๋๋ค.
์ธ๋ถ์์ ์ฐธ์กฐํ๋ API ๋ฅผ ๋ง๋ค ๊ฒฝ์ฐ ๋ฐ๋์ ํ์ ์ ์ง์ ํ๋ผ.
inferred
ํ์
์ ํ๋ก์ ํธ ์งํ ์, ์ ํ์ด ๋๋ฌด ๋ง์์ง๊ฑฐ๋ ์์ธกํ์ง ๋ชปํ ๊ฒฐ๊ณผ๋ฅผ ๋ผ ์ ์๋ค๋ ๊ฒ์ ๋ช
์ฌํ๋ผ.
Item 5 ์์ธ๋ฅผ ํ์ฉํ์ฌ ์ฝ๋์ ์ ํ์ ๊ฑธ์ด๋ผ
์ฝํ๋ฆฐ์์๋ ๋์์ ์ ํ์ ๊ฑธ ๋ ๋ค์๊ณผ ๊ฐ์ ๋ฐฉ๋ฒ์ ์ฌ์ฉํ ์ ์๋ค.
- require ๋ธ๋ญ : Arguments ๋ฅผ ์ ํํ ์ ์๋ค.
- check ๋ธ๋ญ : ์ํ์ ๊ด๋ จ๋ ๋์์ ์ ํํ ์ ์๋ค.
- assert ๋ธ๋ญ : ํ ์คํธ ๋ชจ๋์์๋ง ์๋ํ๋ฉฐ, ๊ฐ์ ์ง์์ฌ๋ถ ํ๋จ์ ํ ์ ์๋ค.
- return ๋๋ throw ์ ํจ๊ปํ๋ Elvis ์ฐ์ฐ์
์ฌ์ฉ์์๋ ๋ค์๊ณผ ๊ฐ๋ค.
fun pop(num: Int = 1): List<T> {
require(num <= size) {
"Can't remove more elements than current size"
}
check(isOpen) {
"Can't pop from close stack"
}
val ret = collection.take(num)
collection = collection.drop(num)
assert(ret.size == num)
return ret
}
์์ ๊ฐ์ด ์ฝ๋์์์ ์ ํ์ ๊ฑธ์ด์ฃผ๋ฉด ๋ค์๊ณผ ๊ฐ์ ์ฅ์ ์๋ค.
- ์ ํ์ ๊ฑธ๋ฉด ๋ฌธ์๋ฅผ ์ฝ์ง ์๋ ๊ฐ๋ฐ์๋ ๋ฌธ์ ๋ฅผ ํ์ธํ ์ ์๋ค.
- ๋ฌธ์ ๊ฐ ์๋ ๊ฒฝ์ฐ ํจ์๊ฐ ์์ํ์ง ๋ชปํ ๋์์ ํ์ง ์๊ณ ์์ธ๋ฅผ
throw
ํ๋ค. - ์ฝ๋๊ฐ ์ด๋์ ๋ ์์ฒด์ ์ผ๋ก ๊ฒ์ฌ๊ฐ ๋๋ค.
- ์ค๋งํธ ์ผ์คํธ๋ฅผ ํ์ฉํ ์ ์๊ณ , ์บ์คํ (ํ์ ๋ณํ) ์ ์ ๊ฒ ํ ์ ์๋ค.
์๊ท๋จผํธ
ํจ์๋ฅผ ์ ์ํ ๋ ํ์ ์์คํ ์ ์ด์ฉํด์ Arguments ์ ์ ํ์ ๊ฑฐ๋ ์ฝ๋๋ฅผ ๋ง์ด ์ฌ์ฉ
/**
* ์ซ์๋ฅผ ์๊ท๋จผํธ๋ก ๋ฐ์ ํฉํ ๋ฆฌ์ผ์ ๊ณ์ฐ
* @param n ์ซ์(์์ ์ ์)
**/
fun factorial(n: Int): Long {
require(n >= 0)
return if (n <= 1) 1
else factorial(n - 1) * n
}
/**
* ์ขํ ๋ชฉ๋ก์ ๊ตฌํ๋ค.
* @param points ์ขํ ๋ชฉ๋ก(๋น ๊ฐ์ด ์๋)
**/
fun findClusters(points: List<Point>): List<Cluster> {
require(points.isNotEmpty())
/**
* Other codes...
**/
}
/**
* ๋ฉ์ผ ๋ณด๋ด๊ธฐ
* @param user ์ฌ์ฉ์ ์ ๋ณด (์ด๋ฉ์ผ ์ ๋ณด๊ฐ ํฌํจ๋์ด์ผ ํจ)
* @param message ๋ฉ์ธ์ง
**/
fun sendEmail(user: User, message: String) {
requireNotNull(user.email)
require(isValidEmail(user.email))
/**
* Other codes...
**/
}
์์ ๊ฐ์ด ์ ํจ์ฑ ์ฝ๋๋ ๋งจ ์์ ๋ฐฐ์น๋์ด์ผ ํ๋ค.
require
ํจ์๋ ์กฐ๊ฑด์ ๋ง์กฑํ์ง ๋ชปํ ๋, ๋ฌด์กฐ๊ฑด์ ์ผ๋ก IllegalArgumentException
์ ๋ฐ์
๋ค์๊ณผ ๊ฐ์ด ๋๋ค ํํ๋ก ๋ฉ์ธ์ง๋ฅผ ์ ์ํ ์ ์๋ค.
fun factorial(n: Int): Long {
require(n >= 0) {
"Can't calculate factorial of $n because it is smaller than 0"
}
return if (n <= 1) 1 else factorial(n - 1) * n
}
์ํ
์ด๋ค ๊ตฌ์ฒด์ ์ธ ์กฐ๊ฑด์ ๋ง์กฑํ ๋๋ง ํจ์๋ฅผ ์ฌ์ฉํ ์ ์๊ฒ ํด์ผ ํ ๋๊ฐ ์๋ค.
์๋ฅผ ๋ค๋ฉด ๋ค์๊ณผ ๊ฐ์ ๊ฒฝ์ฐ
์์ ๊ฐ์ ํน์ ์ํ๋ฅผ ๋ง์กฑํ์๋์๋ check
ํจ์๋ฅผ ์ฌ์ฉํฉ๋๋ค.
/**
* ๊ฐ์ฒด๋ฅผ ๋ฏธ๋ฆฌ ์ด๊ธฐํ๋์ด ์๋ ์ํ์๋ง ์คํํ๊ณ ์ถ์ ํจ์
**/
fun speak(text: String) {
check(isInitialized)
/**
* Other codes...
**/
}
/**
* ์ฌ์ฉ์๊ฐ ๋ก๊ทธ์ธ๋์ด ์์๋์๋ง ์ฒ๋ฆฌํ๊ณ ์ถ์ ํจ์
**/
fun getUserInfo(text: String) {
checkNotNull(token)
/**
* Other codes...
**/
}
/**
* ๊ฐ์ฒด์ ์ฌ์ฉ ์ค๋น๊ฐ ์๋ฃ๋์ด ์์๋๋ง ์ฌ์ฉํ๊ณ ์ถ์ ํจ์
**/
fun next(): T {
check(isOpen)
/**
* Other codes...
**/
}
check
ํจ์๋ require ์ ๋น์ทํ์ง๋ง, ์กฐ๊ฑด์ ๋ง์กฑํ์ง ๋ชปํ ๋ IllegalStateException
์ ๋ฐ์์ํจ๋ค.
์ผ๋ฐ์ ์ผ๋ก require
๋ธ๋ญ์ ๋ค์ ๋ฐฐ์น ์ํจ๋ค.
Assert ๊ณ์ด์ ํจ์ ์ฌ์ฉ
ํจ์์ ์ฌ๋ฐ๋ฅธ ๊ตฌํ ํ์ธ์ ์ํด ๊ฒ์ฆํ๋ ํจ์
๊ตฌํ๋ฌธ์ ๋ก ๋ฐ์ํ ์ ์๋ ์ถ๊ฐ์ ์ธ ๋ฌธ์ ๋ฅผ ์๋ฐฉํ๋ ค๋ฉด, ๋จ์ํ ์คํธ๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ์ข๋ค.
@Test
fun `Stack pops correct number of elements`() {
val stack = Stack(20) { it }
val ret = stack.pop(10)
assertEquals(10, ret.size)
}
ํ
์คํธ ์ฝ๋๊ฐ ์๋ ์คํ ์ฝ๋์์ assert
๋ฅผ ์ฌ์ฉํ ์๋ ์์ผ๋ฉฐ ๋ค์๊ณผ ๊ฐ์ ์ฅ์ ์ด ์๋ค.
- Assert ๊ณ์ด์ ํจ์๋ ์ฝ๋ ์์ฒด๋ฅผ ์ ๊ฒํ๋ฉฐ, ๋ ํจ์จ์ ์ผ๋ก ํ ์คํธํ ์ ์๋ค.
- ํน์ ์ํฉ์ด ์๋ ๋ชจ๋ ์ํฉ์ ๋ํด ํ ์คํธ ํ ์ ์๋ค.
- ์คํ ์์ ์ ์ ํํ๊ฒ ํ์ธํ ์ ์๋ค.
- ์ค์ ์ฝ๋๊ฐ ๋ ๋น ๋ฅธ ์์ ์ ์คํจํ๊ธฐ ๋ง๋ค๋ฉฐ, ์ธ์ ์ด๋์ ์คํํ๋์ง ์ฝ๊ฒ ์ฐพ์์ ์๋ค.
์คํ์์ ์ assert
๋ ์์ธ๋ฅผ throw
ํ์ง ์๋๋ค๋ ๊ฒ์ ๋ช
์ฌ
nullability์ ์ค๋งํธ ์บ์คํ
์ฝํ๋ฆฐ์์๋ require
์ check
๋ธ๋ญ์ผ๋ก ํน์ ์กฐ๊ฑด์ ํ์ธํด์ true
๊ฐ ๋์๋ค๋ฉด, ํด๋น ์กฐ๊ฑด์ ์ดํ๋ก๋ true
์ผ๊บผ๋ผ๊ณ ๊ฐ์ ํ๋ค.
@kotlin.internal.InlineOnly
public inlin fun require(value: Boolean): Unit {
contract {
returns() implies value
}
require(value) { "Failed requirement." }
}
์ด๋ฅผ ํ์ฉํด์ ํ์ ๋น๊ต๋ฅผ ํ๋ค๋ฉด, ์ค๋งํธ ์บ์คํธ ๊ฐ ์๋๋๋ค.
fun changeDress(person: Person) {
require(person.outfit is Dress)
val dress: Dress = person.outfit
}
์ด๋ฌํ ํน์ง์ ๋์์ด null
์ธ์ง ํ์ธํ ๋ ๋งค์ฐ ์ ์ฉํ๋ค.
class Person(val email: String?)
fun sendEmail(person: Person, message: String) {
require(person.email != null)
val email: String = person.email
/**
* Other codes...
**/
}
์ด๋ ๊ฒ null ์ธ์ง ์๋์ง ํ์ธํ๋ ์ผ์ด์ค๋ requireNotNull
, checkNotNull
์ด๋ผ๋ ํน์ํ ํจ์๋ฅผ ์ฌ์ฉํ๋ ๊ด์ฐฎ๋ค.
๋๋ค ์ค๋งํธ ์บ์คํธ๋ฅผ ์ง์ํ๋ฏ๋ก, ๋ณ์๋ฅผ ์ธํฉ (unpack) ํ๋ ์ฉ๋๋ก ์ฌ์ฉํ ์ ์๋ค.
class Person(val email: String?)
fun validateEmail(email: String) { /*...*/ }
fun sendEmail(person: Person, text: String) {
val email = requireNotNull(person.email)
validateEmail(email)
/**
* Other codes...
**/
}
fun sendEmail(person: Person, text: String) {
requireNotNull(person.email)
validateEmail(person.email)
/**
* Other codes...
**/
}
nullability
๋ฅผ ๋ชฉ์ ์ผ๋ก throw ๋๋ return ์ ๋๊ณ Elvis ์ฐ์ฐ์ (?๐ ๋ฅผ ํ์ฉํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง๋ค.
fun sendEmail(person: Person, text: String) {
val email: String = person.email ?: return
}
์์ ๊ฐ์ด ์ค๋ฅ๋ฅผ ๋ฐ์์ํค์ง ์๊ณ ๋จ์ํ๊ฒ ํจ์๋ฅผ ์ค์ง ์ํค๊ฑฐ๋
์๋์ ๊ฐ์ด ๋ก๊ทธ๋ฅผ ๋ ธ์ถ ์ํจํ์ ์ข ๋ฃ์ํจ๋ค.
fun sendEmail(person: Person, text: String) {
val email: String = person.email ?: run {
log("Email not sent, no email address")
return
}
}
Elvis ์ฐ์ฐ์๋ nullable
์ ํ์ธํ ๋ ๊ต์ฅํ ๋ง์ด ์ฌ์ฉ๋๋ ๊ด์ฉ์ ์ธ ๋ฐฉ๋ฒ์ด๋ค.
์ ๋ฆฌ
์์ธ๋ฅผ ์ฌ์ฉํ์ฌ ์ฝ๋์ ์ ํ์ ๊ฑฐ๋ ๋ฐฉ๋ฒ์ ๋ค์๊ณผ ๊ฐ์ ์ด๋์ ์ป์์ ์๋ค.
- ์ ํ์ ๋ ์ฝ๊ฒ ๊ฑธ ์ ์๋ค.
- ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ ์์ ์ ์ผ๋ก ์งํฌ ์ ์๋ค.
- ์ฝ๋๋ฅผ ์๋ชป ์ฐ๋ ์ํฉ์ ๋ง์ ์ ์๋ค.
- ์ค๋งํธ ์บ์คํ ์ ํ์ฉํ ์ ์๋ค.
๋ํ ์ด๋ฅผ ์ํ ํ์ฉ๋ฐฉ์์ ๋งค์ปค๋์ฆ์ ๋ค์๊ณผ ๊ฐ๋ค.
require
๋ธ๋ญ : ์๊ท๋จผํธ์ ๊ด๋ จ๋ ์์ธก์ ์ ์ํ ๋ ์ฌ์ฉํ๋ ๋ฒ์ฉ์ ์ธ ๋ฐฉ๋ฒcheck
๋ธ๋ญ : ์ํ์ ๊ด๋ จ๋ ์์ธก์ ์ ์ํ ๋ ์ฌ์ฉํ๋ ๋ฒ์ฉ์ ์ธ ๋ฐฉ๋ฒassert
๋ธ๋ญ : ํ ์คํธ ๋ชจ๋์์ ํ ์คํธํ ๋ ์ฌ์ฉํ๋ ๋ฒ์ฉ์ ์ธ ๋ฐฉ๋ฒ- ๊ทธ ์ธ :
return
ํน์throw
์ ํจ๊ปElvis
์ฐ์ฐ์์ ์ฌ์ฉํ๊ธฐ
์ด์ธ์๋ ๋ค๋ฅธ ์ค๋ฅ๋ค์ ๋ฐ์ ์ํฌ๋ throw
๋ฅผ ํ์ฉํ ์ ์๋ค.
Item 6 ์ฌ์ฉ์ ์ ์ ์ค๋ฅ๋ณด๋ค๋ ํ์ค ์ค๋ฅ๋ฅผ ์ฌ์ฉํ๋ผ
Item 5 ์์ ์์๋ณด์๋ (require, check, assert) ๋ฅผ ์ฌ์ฉํ๋๋ผ๋ ์์์น ๋ชปํ ์ค๋ฅ๋ฅผ ๋ง๋ ์ ์๋ค.
์๋ฅผ ๋ค๋ฉด ์๋์ ๊ฐ์ด JSON ์ ํ์ฑํ๋ค ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค๋ฉด JsonParsingException
์ ๋ฐ์์ํค๋๊ฒ์ด ์ข๋ค.
inline fun <reified T> String.readObject(): T {
//...
if (incorrectSign) {
throw JsonParsingException()
}
//...
}
์ง์ ์ค๋ฅ๋ฅผ ์ ์ ํ๋๊ฒ ๋ณด๋ค๋ ์ต๋ํ ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์ค๋ฅ๋ฅผ ์ฌ์ฉํ๋๊ฒ์ด ์ข๋ค.
ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์ค๋ฅ๋ ๋ง์ ๊ฐ๋ฐ์๊ฐ ์๊ณ ์์ผ๋ฏ๋ก, ์ด๋ฅผ ์ฌ์ฌ์ฉํ๋ ๊ฒ์ด ์ข๋ค.
์ผ๋ฐ์ ์ผ๋ก ์ฌ์ฉ๋๋ ์์ธ๋ฅผ ๋ช๊ฐ์ง ์ ๋ฆฌํด๋ณด๋ฉด ๋ค์๊ณผ ๊ฐ๋ค.
์์ธ ์ข ๋ฅ | ์ค๋ช |
---|---|
IllegalArgumentException IllegalStateException | require ์ check๋ฅผ ์ฌ์ฉํ์ฌ throw ํ ์ ์๋ ์์ธ |
IndexOutOfBoundsException | ์ธ๋ฑ์ค ํ๋ผ๋ฏธํฐ์ ๊ฐ์ ๋ฒ์๋ฅผ ๋ฒ์ด๋ฌ๋ค๋๊ฒ์ ์๋ ค์ค |
ConcurrentModificationException | ๋์ ์์ (concurrent modification) ์ ๊ธ์งํ์์ผ๋, ๋ฐ์ํจ |
UnsupportedOperationException | ์ฌ์ฉ์๊ฐ ์ฌ์ฉํ๋ ค๊ณ ํ๋ ๋ฉ์๋๊ฐ ํ์ฌ ๊ฐ์ฒด์์๋ ์ฌ์ฉํ ์ ์๋ค๋ ๊ฒ์ ์๋ ค์ค ๊ธฐ๋ณธ์ ์ผ๋ก ์ฌ์ฉํ ์ ์๋ ๋ฉ์๋๋ ํด๋์ค์ ์๋ ๊ฒ์ด ์ข๋ค. |
NoSuchElementException | ์กด์ฌํ์ง ์๋ ๋ฉ์๋๋ฅผ ํธ์ถ |
Item 7 ๊ฒฐ๊ณผ ๋ถ์กฑ์ด ๋ฐ์ํ ๊ฒฝ์ฐ null ๊ณผ Failure ๋ฅผ ์ฌ์ฉํ๋ผ
ํจ์๊ฐ ์ํ๋ ๊ฒฐ๊ณผ๋ฅผ ๋ด๋์ ์ ์์๋
- ์๋ฒ๋ก๋ถํฐ ๋ฐ์ดํฐ๋ฅผ ์ฝ์ด ๋ค์ด๋ ค๊ณ ํ๋๋ฐ, ๋คํธ์ํฌ ๋ฌธ์ ๋ก ์ด์๊ฐ ์๊ธด๊ฒฝ์ฐ
- ์กฐ๊ฑด์ ๋ง๋ ์ฒซ๋ฒ์งธ ์์๋ฅผ ์ฐพ์ผ๋ ค ํ๋๋ฐ, ์์๊ฐ ์๋ ๊ฒฝ์ฐ
- ํ ์คํธ๋ฅผ ํ์ฑํด์ ๊ฐ์ฒด๋ฅผ ๋ง๋๋ ค๊ณ ํ๋๋ฐ, ํ์์ด ๋ง์ง ์๋ ๊ฒฝ์ฐ
์ด๋ฌํ ์ค๋ฅ ์ํฉ์ ์ฒ๋ฆฌ ๊ฐ๋ฅํ ๋ฉ์ปค๋์ฆ์ ๋ค์ ๋๊ฐ์ง๊ฐ ์๋ค.
null
๋๋ ์คํจ๋ฅผ ๋ํ๋ด๋sealed
ํด๋์ค๋ฅผ ๋ฆฌํด- ์์ธ๋ฅผ
throw
ํ๋ค.
์ ๋๊ฐ์ง๋ ์ค์ํ ์ฐจ์ด์ ์ด ์๋ค.
์์ธ๋ ์ ๋ณด๋ฅผ ์ ๋ฌํ๋ ๋ฐฉ๋ฒ์ผ๋ก ์ฌ์ฉํด์๋ ์๋๋ค. ์์ธ๋ ์๋ชป๋ ํน์ ํ ์ํฉ์ ๋ํ๋ด์ผ ํ๋ฉฐ, ์ด๋ฅผ ์ฒ๋ฆฌํ ์ ์์ด์ผ ํ๋ค.
์์ธ๋ ์์ธ์ ์ธ ์ํฉ์ด ๋ฐ์ํ์ ๋ ์ฌ์ฉํ๋ ๊ฒ์ด ์ข๋ค.
์ด๋ฌํ ์ด์ ๋ฅผ ์ ๋ฆฌํ๋ฉด ๋ค์๊ณผ ๊ฐ๋ค.
- ๋ง์ ๊ฐ๋ฐ์๋ค์ ์์ธ๊ฐ ์ ํ๋๋ ๊ณผ์ ์ ์ ๋๋ก ์ถ์ ํ์ง ๋ชปํ๋ค.
- ์ฝํ๋ฆฐ์ ๋ชจ๋ ์์ธ๋ unchecked ์์ธ์ด๋ค.
- ์์ธ์ ๊ด๋ จ๋ ์ฌํญ์ ๋จ์ํ๊ฒ ๋ฉ์๋๋ฑ์ผ๋ก ์ฌ์ฉํ๋ฉด์ ํ์ ํ๊ธฐ ํ๋ค๋ค
- ์์ธ๋ ์์ธ์ ์ธ ์ํฉ์ ์ฒ๋ฆฌํ๊ธฐ ์ํด ๋ง๋ค์ด์ก์ผ๋ฉฐ, ๋ช ์์ ์ธ ํ ์คํธ๋งํผ ๋น ๋ฅด๊ฒ ๋์ํ์ง ์๋๋ค.
try-catch
๋ธ๋ญ ๋ด๋ถ์ ์ฝ๋๋ฅผ ๋ฐฐ์นํ๋ฉด, ์ปดํ์ผ๋ฌ๊ฐ ํ ์ ์๋ ์ต์ ํ๊ฐ ์ ํ๋๋ค.
์ 1๋ฒ ์์ null
๊ณผ Failure
์ ์์๋๋ ์ค๋ฅ๋ฅผ ํํํ ๋ ๊ต์ฅํ ์ข๋ค.
์ถฉ๋ถํ ์์ธก๊ฐ๋ฅํ ๋ฒ์์ ์ค๋ฅ๋ null
๊ณผ Failure
๋ฅผ ์ฌ์ฉํ๊ณ , ์์ธกํ๊ธฐ ์ด๋ ค์ด ์์ธ์ ์ธ ๋ฒ์์ ์ค๋ฅ๋ ์์ธ๋ฅผ throw ํด์ ์ฒ๋ฆฌํ๋๊ฒ์ด ์ข๋ค.
inline fun <reified T> String.readObjectOrNull(): T? {
//...
if (incorrectSign) {
return null
}
//...
return result
}
inline fun <reified T> String.readObject(): Result<T> {
//...
if (incorrectSign) {
return Failure(JsonParsingException())
}
//...
return Success(result)
}
sealed class Result<out T>
class Success<out T>(val result: T): Result<T>()
class Failure(val throwable: Throwable): Result<Noting>()
class JsonParsingException: Exception()
์ด๋ ๊ฒ ํ์๋๋ ์ค๋ฅ๋ ๋ค๋ฃจ๊ธฐ ์ฌ์ฐ๋ฉฐ ๋์น๊ธฐ ์ด๋ ต๋ค.
null
์ ์ฒ๋ฆฌํด์ผ ํ๋ค๋ฉด, ์ฌ์ฉ์๋ ์์ ํ ํธ์ถ(safety-call) ๋๋ Elvis ์ฐ์ฐ์๋ฅผ ์ด์ฉํ ๋ ์์ ์ฑ(null-safety) ๊ธฐ๋ฅ์ ํ์ฉํ๋ค.
val age = userText.readObjectOrNull<Person>()?.age ?: -1
Result
์ ๊ฐ์ ๊ณต์ฉ์ฒด (union type) ์ ๋ฆฌํดํ๊ธฐ๋ก ํ๋ค๋ฉด, when
ํํ์์ ์ฌ์ฉํด์ ์ฒ๋ฆฌํ์
val person = userText.readObjectOrNull<Person>()
val age = when(person) {
is Success -> person.age
is Failure -> -1
}
์ด๋ฌํ ์ค๋ฅ ์ฒ๋ฆฌ ๋ฐฉ์์ try-catch
๋ธ๋ก๋ณด๋ค ํจ์จ์ ์ด๋ฉฐ, ๋ ์ฝ๊ณ ๋ช
ํํฉ๋๋ค.
Sealed Class (๋ด์ธ ํด๋์ค)
๊ฐ์ด ์ ํ๋ ์งํฉ์ ์ ํ์ค ํ๋๋ฅผ ๊ฐ์ง์ ์๋ ์ ํ๋ ํด๋์ค ๊ณ์ธต ๊ตฌ์กฐ๋ฅผ ๋ํ๋ด๋๋ฐ ์ฌ์ฉํ๋ค.
์ถ์ ํด๋์ค์ ์ผ์ข ์ด์ง๋ง ์ง์ ์ ์ผ๋ก ์ธ์คํด์คํ ํ ์ ์๊ณ , ์ถ์ ๋ฉค๋ฒ๋ฅผ ๊ฐ์ง ์ ์๋ค.
Sealed
ํด๋์ค์ ์ด์ ์ enum ํด๋์ค์ ์ ์ฌํ๊ฒwhen
์ ์ฌ์ฉ์else
์ ์ด ํ์๊ฐ ์๋ค.
๊ฐ๋ฐ์๋ ํญ์ ์์ ์ด ์์๋ฅผ ์์ ํ๊ฒ ์ถ์ถํ ๊ฑฐ๋ผ ์๊ฐํ๊ธฐ ๋๋ฌธ์ nullable
๋ฅผ ๋ฆฌํดํ๋ฉด ์๋๋ค.
๊ฐ๋ฐ์์๊ฒ null
์ด ๋ฐ์ํ ์ ์๋ค๋ ๊ฒฝ๊ณ ๋ฅผ ์ฃผ๋ ค๋ฉด getOrNull
์ ์ฌ์ฉํด์ ๋ฐํ๋๋ ๊ฐ์ ๋ฒ์๋ฅผ ๋ช
์์ ์ผ๋ก ์๋ ค์ฃผ๋ ๊ฒ์ด ์ข๋ค.
Item 8 ์ ์ ํ๊ฒ null์ ์ฒ๋ฆฌํ๋ผ
null
์ '๊ฐ์ด ๋ถ์กฑํ๋ค(lack of value)'๋ผ๋ ๊ฒ์ ๋ํ๋ธ๋ค.
null
์ ๋ฐํํ๋ค๋ ๊ฒ์ ์ฌ๋ฌ ์๋ฏธ๋ฅผ ๊ฐ์ง ์ ์๋ค.
String.toIntOrNull()
์String
์Int
๋ก ์ ์ ํ๊ฒ ๋ณํํ ์ ์๋ ๊ฒฝ์ฐnull
์ ๋ฆฌํดํ๋ค.Iterable<T>.firstOrNull(() -> Boolean)
์ ์ฃผ์ด์ง ์กฐ๊ฑด์ ๋ง๋ ์์๊ฐ ์๋ ๊ฒฝ์ฐnull
์ ๋ฆฌํดํ๋ค.
์ด์ฒ๋ผ null
์ ์ต๋ํ ๋ช
ํํ ์๋ฏธ๋ฅผ ๊ฐ๋ ๊ฒ์ด ์ข๋ค.
null์ ์์ ํ๊ฒ ์ฒ๋ฆฌํ๊ธฐ
null ์ ์์ ํ๊ฒ ์ฒ๋ฆฌํ๋ ๋ฐฉ๋ฒ ์ค ๋๋ฆฌ ์ฌ์ฉ๋๋ ๋ฐฉ๋ฒ์ผ๋ก๋ ์์ ํธ์ถ (safety-call) ์ค๋งํธ ์บ์คํ (smart casting) ์ด ์์ต๋๋ค.
printer?.print() // ์์ ํธ์ถ
if (printer != null)
printer.print() // ์ค๋งํธ ์บ์คํ
์ ๋๊ฐ์ง ๋ชจ๋ printer
๊ฐ null
์ด ์๋๋ print
ํจ์๋ฅผ ํธ์ถํ๋ค.
nullable ๋ณ์๋ฅผ ์ฒ๋ฆฌํ๋ ๋ํ์ ์ธ ๋ฐฉ๋ฒ์ผ๋ก๋ Elvis ์ฐ์ฐ์๋ฅผ ์ด์ฉํ์ฌ ์ฒ๋ฆฌํ๋ ๋ฐฉ๋ฒ์ด๋ค.
Elvis ์ฐ์ฐ์๋ ์ค๋ฅธ์ชฝ์ return
๋๋ throw
๋ฅผ ํฌํจํ ๋ชจ๋ ํํ์์ด ํ์ฉ๋๋ค.
val printerName1 = printer?.name ?: "Unnamed"
val printerName2 = printer?.name ?: return
val printerName3 = printer?.name ?:
throw Error("Printer must be named")
return
๊ณผthrow
๋ชจ๋Noting
(๋ชจ๋ ํ์ ์ ์๋ธํ์ ) ์ ๋ฆฌํดํ๊ฒ ์ค๊ณ๋์ด ๊ฐ๋ฅํ๋ค. (link)
null ์ ์ ์ ํ๊ฒ ์ฒ๋ฆฌํ๊ธฐ ์ํ์ฌ ์ฒ๋ฆฌํ๊ธฐ ์ํ ๋ฐฉ๋ฒ๋ค์ ๋ค์๊ณผ ๊ฐ๋ค.
๋ฐฉ์ด์ ํ๋ก๊ทธ๋๋ฐ (defensive programming)
- ๋ชจ๋ ๊ฐ๋ฅ์ฑ์ ์ฌ๋ฐ๋ฅธ ๋ฐฉ์์ผ๋ก ์ฒ๋ฆฌํ๋๋ก ๋ฐฉ์ดํ๋ ๋ฐฉ์
๊ณต๊ฒฉ์ ํ๋ก๊ทธ๋๋ฐ (offensive programming)
- ์์์น ๋ชปํ ์ํฉ์ด ๋ฐ์ํ์ ๋ ๊ฐ๋ฐ์๊ฐ ์์ ํ๊ฒ ์ ๋ํ๋ ๋ฐฉ์
์ค๋ฅ throw ํ๊ธฐ
๊ฐ๋ฐ์๋ ์์ฑํ ์ฝ๋๋ ์์ ๊ฐ๋ฅํ ํํ๋ก ๋์๋๊ธธ ์ํ๋ฉฐ, ๋ค๋ฅธ ๊ฐ๋ฐ์๊ฐ ๊ทธ ์ฝ๋๋ฅผ ๋ณด๊ณ ์ ์ ๊ฒฌ์ฒ๋ผ '๋น์ฐํ ๊ทธ๋ด๊ฒ์ด๋ค' ๋ผ๊ณ ์๊ฐํ๋ ๋ถ๋ถ์ด ์๋ค.
๊ทธ ๋ถ๋ถ์์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ๊ฒฝ์ฐ ๊ฐ๋ฐ์์๊ฒ ์ค๋ฅ๋ฅผ ๊ฐ์ ๋ก ๋ฐ์์์ผ ์ฃผ๋ ๊ฒ์ด ์ข๋ค.
์ค๋ฅ๋ฅผ ๊ฐ์ ๋ก ๋ฐ์์ํฌ ๋๋ throw
, !!
, requireNotNull
, checkNotNull
๋ฑ์ ํ์ฉํด์ ์ฒ๋ฆฌํ๋ ๊ฒ์ด ์ข๋ค.
not-null assertion(!!) ๊ณผ ๊ด๋ จ๋ ๋ฌธ์
nullable ์ ์ฒ๋ฆฌํ ๋ ๊ฐ์ฅ ๊ฐ๋จํ ๋ฐฉ๋ฒ์ not-null assertion(!!) ์ ์ฌ์ฉํ๋ ๊ฒ ์ด๋ค.
๋ง์ฝ ์ด ๊ฐ์ด null ์๋๋ผ๊ณ ์๊ฐํ๊ณ ๋ค๋ฃจ๋ฉด, NPE(Null pointer exception) ์ด ๋ฐ์ํ๋ค.
!!
์ ์ฌ์ฉํ๊ธฐ ์ฝ์ง๋ง ์ข์ ํด๊ฒฐ๋ฐฉ๋ฒ์ ์๋๋ฉฐ, ์์ธ๊ฐ ๋ฐ์ํ ๋, ์ด๋ ํ ์ค๋ช
๋ ์๋ ์ ๋ค๋ฆญ ์์ธ (generic excpetion) ๊ฐ ๋ฐ์ํ๋ค.
fun largestOf(vararg nums: Int): Int =
nums.max()!!
largestOf() // NPE
๋๋ฌธ์ ์ด๋ฐ ์ ์ฌ์ ์ธ ์ํ์ ํผํ๊ธฐ ์ํด !!
์ฐ์ฐ์์ ์ฌ์ฉ์ ์ง์ํด์ผ ํ๋ค.
TIP
max
ํจ์๋ kotlin 1.4 ๋ถํฐ deprecate ๋๋ฉฐ, kotlin 1.5 ์ด์๋ถํฐ๋ error๋ก ํ๊ธฐ๋๋ค.
์ด๋ฅผ ๋์ ํ maxOrNull
์ ์ง์ํ๋ค (link)
์๋ฏธ ์๋ nullability ํผํ๊ธฐ
nullability ๋ ์ ์ ํ๊ฒ ์ฒ๋ฆฌํ๊ธฐ ์ํด ์ถ๊ฐ๋น์ฉ์ด ๋ฐ์ํ๋ค.
๋ฐ๋ผ์ ํ์ํ ๊ฒฝ์ฐ๊ฐ ์๋๋ผ๋ฉด, nullability ์์ฒด๋ฅผ ํผํ๋ ๊ฒ์ด ์ข๋ค.
null ๊ฐ ์์ฒด์ ์ด๋ ํ ์๋ฏธ๋ฅผ ๋ถ์ฌํด์ ์ฒ๋ฆฌํ ์ ์์ผ๋ฉฐ ์๋ฏธ๊ฐ ์์ ๊ฒฝ์ฐ์๋ null ์ ์ฌ์ฉํ์ง ์๋๊ฒ์ด ์ข๋ค.
lateinit ํ๋กํผํฐ์ notNull ๋ธ๋ฆฌ๊ฒ์ดํธ
lateinit ํ์ ์๋ ํ๋กํผํฐ๊ฐ ์ดํ์ ์ค์ ๋ ๊ฒ์์ ๋ช ์ํ๋ ํ์ ์์ด๋ค.
lateinit ์ฌ์ฉ์๋ ๋น์ฉ์ด ๋ฐ์ํ๋ฉฐ, ์ด๊ธฐํ ์ ์ ์ฌ์ฉํ ๊ฒฝ์ฐ ์์ธ๊ฐ ๋ฐ์ํ๋ค.
lateinit ๊ณผ nullable ์ ๋น๊ตํ๋ฉด ๋ค์๊ณผ ๊ฐ์ ์ฐจ์ด์ ์ด ์๋ค.
!!
์ฐ์ฐ์๋ก ์ธํฉ(unpack)ํ์ง ์์๋ ๋๋ค.- ์ด๋ ํ ์๋ฏธ๋ฅผ ๊ฐ๋ null ์ ์ฌ์ฉํ๊ณ ์ถ์๋ nullable ๋ก ๋ง๋ค ์ ์๋ค.
- ํ๋กํผํฐ๊ฐ ์ด๊ธฐํ๋ ์ดํ์๋ ์ด๊ธฐํ๋์ง ์๋ ์ํ๋ก ๋์๊ฐ ์ ์๋ค.
lateinit ์ฌ์ฉ์์๋ ๋ฐ๋์ ์ด๊ธฐํ ๋ ๊ฑฐ๋ผ๊ณ ์์ํ ๊ฒฝ์ฐ์ ์ํฉ์์ ์ฌ์ฉํ๋ค.
JVM ์ Int
, Long
, Double
, Boolean
๊ฐ์ด ๊ธฐ๋ณธํ์
๊ณผ ์ฐ๊ฒฐ๋ ํ์
์ lateinit ์ ์ฌ์ฉํ ์ ์๋ค.
์ด๋ฌํ ๊ฒฝ์ฐ์๋ lateinit ๋ณด๋ค ๋๋ฆฌ์ง๋ง Delegates.notNull ์ ์ฌ์ฉํ๋ค.
class DoctorActivity: Activity() {
private var doctorId: Int by arg(DOCTOR_ID_ARG)
private var fromNotification: Boolean by arg(FROM_NOTIFICATION_ARG)
}
Item 9 use ๋ฅผ ์ฌ์ฉํ์ฌ ๋ฆฌ์์ค๋ฅผ ๋ซ์๋ผ
๋ ์ด์ ๋ฆฌ์์ค๊ฐ ํ์ํ์ง ์์๋ close
๋ฉ์๋๋ฅผ ์ฌ์ฉํด์ ๋ช
์์ ์ผ๋ก ๋ซ์์ผ ํ๋ ๋ฆฌ์์ค์ ์ฌ์ฉ
Kotlin/JVM ์์ ์ฌ์ฉํ๋ ์๋ฐ ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์ ํด๋นํ๋ ๋ฆฌ์์ค๋ ๋ค์๊ณผ ๊ฐ๋ค.
- InputStream
- OutputStream
- java.sql.Connection
- java.io.Reader(FileReader, BufferedReader, CSSParser)
- java.net.Socket
- java.util.Scanner
์ ๋ฆฌ์์ค๋ค์ AutoCloseable
์ ์์๋ฐ๋ Closeable
์ธํฐํ์ด์ค๋ฅผ ๊ตฌํ (implement) ํ๊ณ ์๋ค.
๋ชจ๋ ๋ฆฌ์์ค๋ค์ ์ต์ข ์ ์ผ๋ก ๋ฆฌ์์ค์ ๋ํ ๋ ํผ๋ฐ์ค๊ฐ ์์ด์ง ๋, GC๊ฐ ์ฒ๋ฆฌํ๋ค.
ํ์ง๋ง ์ฒ๋ฆฌ๊ฐ ์๋ฃ๋ ๋๊น์ง์ ๋งค์ฐ ๋ง์ ๋ฆฌ์์ค์ ๋น์ฉ๊ณผ ๊ธด ์ฒ๋ฆฌ์๊ฐ์ ์ํ๋ค.
๋ฐ๋ผ์ ๋ช
์์ ์ผ๋ก close
๋ฉ์๋๋ฅผ ํธ์ถํด์ฃผ๋ ๊ฒ์ด ์ข๋ค.
Java ์์๋ ์ ํต์ ์ผ๋ก try-finally
๋ธ๋ญ์ ์ฌ์ฉํ์ฌ ์ฒ๋ฆฌ
fun countCharactersInFile(path: String): Int {
val reader = BufferedReader(FileReader(path))
try {
return reader.lineSequence().sumBy { it.length }
} finally {
reader.close()
}
}
JDK7 ๋ถํฐ๋ try-with-resource ์ง์
int countCharactersInFile(String path) throws IOException {
try (
BufferedReader reader = BufferedReader(FileReader(path))
) {
return reader.lineSequence().sumBy { it.length }
}
}
Closeable
๊ฐ์ฒด์ use
ํจ์๋ฅผ ์ฌ์ฉํ ์ ์๋ค.
fun countCharactersInFile(path: String): Int {
val reader = BufferedReader(FileReader(path))
render.use {
return render.lineSequence().sumBy { it.length }
}
}
๋๋ค ๋งค๊ฐ๋ณ์๋ก ๋ฆฌ์๋ฒ๊ฐ ์ ๋ฌ๋๋ ํํ๋ ์์ผ๋ฏ๋ก ๋ค์๊ณผ ๊ฐ์ด ์ค์ฌ์ ์ฌ์ฉ๋ ๊ฐ๋ฅํ๋ค.
fun countCharactersInFile(path: String): Int {
BufferedReader(FileReader(path)).use { reader ->
return render.lineSequence().sumBy { it.length }
}
}
ํ์ผ ๋ฆฌ์์ค๋ฅผ ํ์ค์ฉ ์ฝ์ด์ ์ฒ๋ฆฌํ๋ ๊ฒฝ์ฐ์๋ useLine ํจ์๋ฅผ ์ด์ฉํ์
fun countCharactersInFile(path: String): Int {
File(path).useLines { lines ->
lines.sumBy { it.length }
}
}
์์ ๊ฐ์ด ์ฒ๋ฆฌํ๋ฉด ๋ฉ๋ชจ๋ฆฌ์ ํ์ผ์ ๋ด์ฉ์ ํ์ค์ฉ๋ง ์ ์งํ๋ฏ๋ก, ๋์ฉ๋ ํ์ผ๋ ์ ์ ํ๊ฒ ์ฒ๋ฆฌํ ์ ์๋ค.
์ ๋ฆฌ
use
ํจ์๋ฅผ ์ฌ์ฉํ๋ฉด Closeable
, AutoCloseable
์ ๊ตฌํํ ๊ฐ์ฒด๋ฅผ ์ฝ๊ณ ์์ ํ๊ฒ ์ฒ๋ฆฌ ๊ฐ๋ฅํ๋ค.
Item 10 ๋จ์ ํ ์คํธ๋ฅผ ๋ง๋ค์ด๋ผ
๋จ์ ํ ์คํธ๋ ๊ฐ๋ฐ์๊ฐ ๋ง๋ค๊ณ ์๋ ์์๊ฐ ์ ๋๋ก ๋์ํ๋์ง ๋น ๋ฅด๊ฒ ํผ๋๋ฐฑ ๊ฐ๋ฅํ๋ฏ๋ก ๊ฐ๋ฐํ๋๋์ ํฐ ๋์์ด ๋๋ค.
- ์ฅ์
- ํ ์คํธ๊ฐ ์ ๋ ์์๋ ์ ๋ขฐ ๊ฐ๋ฅํ๋ค.
- ํ ์คํธ๊ฐ ์ ๋ง๋ค์ด์ ธ ์์ผ๋ฉด ๋ฆฌํํ ๋ง ํ๊ธฐ ์ฝ๋ค.
- ์๋์ผ๋ก ํ ์คํธํ๋ ๊ฒ ๋ณด๋ค ๋จ์ ํ ์คํธ๋ก ํ์ธํ๋ ๊ฒ์ด ๋น ๋ฅด๋ค.
- ๋จ์
- ํ ์คํธ ์์ฑ์ ์๊ฐ์ด ๊ฑธ๋ฆฐ๋ค.
- ํ ์คํธ ํ์ฉ์ด ์ฉ์ดํ๊ฒ ์ฝ๋๋ฅผ ์กฐ์ ํด์ผ ํ๋ค.
- ์ข์ ํ ์คํธ๋ฅผ ๋ง๋ค๊ธฐ ์ํด ๋ง์ ๋ ธ๋ ฅ์ ๊ธฐ์ธ์ฌ์ผ ํ๋ค.
๋จ์ ํ ์คํธ๊ฐ ํ์ํ ์ฝ๋์ ์ข ๋ฃ
- ๋ณต์กํ ์ฝ๋ ๋ญ์น
- ์์ ์ด ๋น๋ฒํ๊ณ ๋ฆฌํํ ๋ง์ด ๋น๋ฒํ๊ฒ ์ผ์ด๋ ์ ์๋ ๋ถ๋ถ
- ๋น์ง๋์ค ๋ก์ง
- ๊ณต์ฉ API ๋ฅผ ํธ์ถํ๋ ์ ํฉ๋ถ
- ๋ง์ ๋ฌธ์ ๋ฅผ ์ ์ฌ์ ์ผ๋ก ๋ดํฌํ๊ณ ์๋ ์ฝ๋
- ์์ ์ด ํ์ํ ํ๋ก๋์ ๋ฒ๊ทธ
์ ๋ฆฌ
๋น์ง๋์ค ์ ํ๋ฆฌ์ผ์ด์ ์์๋ ์ต์ํ ๋ช๊ฐ์ ๋จ์ ํ ์คํธ๊ฐ ๊ผญ ํ์ํ๋ค.