Kotlinμ 곡λΆνλ©΄μ μμμ λν λΆλΆμ 곡λΆνκ³ μ 곡μ λ¬Έμλ₯Ό μ§μ λ²μνλ€.
ν΄λΉ λ¬Έμλ 2022λ
3μ 3μΌμ 곡μ λ¬Έμμ΄λ€.
μ€κ°μ€κ° π νμκ° μλ λ¬Έμ₯μ΄λ μμ λͺ©μ λ²μμ΄ λΆμ νν μ μμΌλ©° ꡬκΈμ΄λ ννκ³ λ²μκΈ°μ λμμ λ°μλ€.
Kotlin 곡μλ¬Έμ νμΈνκΈ° β‘οΈ
β» μμ΄ μ 곡μλ ν΄μΈ μ ννλ μλκΈ°μ λ²μμλ μμ, μ€μ, κ΅¬κΈ λ²μμ΄ λ¬΄μν λ§μ μ μμΌλ©°, νΌμ 곡μλ¬Έμλ₯Ό μ°Έμ‘°ν΄κ°λ©° λ²μνλ€ λ³΄λ μ€νλ λ§μ μ μλ€. μ νν λ΄μ©μ 곡μλ¬Έμλ₯Ό μ§μ μ΄ν΄λ³΄κ±°λ λ€λ₯Έ μ 보λ€μ λ μ°Ύμ보λ κ²μ μΆμ²νλ€.
(νμ§λ§ λκΈ νΌλλ°±λ νμν©λλ€π )
Declaring properties
Properties in Kotlin classes can be declared either as mutable, using the var keyword, or as read-only, using the val keyword.
μ½νλ¦° ν΄λμ€μ μμ±λ€μ var ν€μλλ₯Ό μ¬μ©νμ¬ κ°λ³μ μΌλ‘ μ μΈνκ±°λ val ν€μλλ₯Ό μ¬μ©νμ¬ μ½κΈ° μ μ©μΌλ‘λ§ μ μΈν μ μλ€.
class Address {
var name: String = "Holmes, Sherlock"
var street: String = "Baker"
var city: String = "London"
var state: String? = null
var zip: String = "123456"
}
To use a property, simply refer to it by its name:
μμ±μ μ¬μ©νλ €λ©΄ κ·Έ μ΄λ¦μΌλ‘ μ°Έμ‘°νλ©΄ λλ€:
fun copyAddress(address: Address): Address {
val result = Address() // there's no 'new' keyword in Kotlin
result.name = address.name // accessors are called
result.street = address.street
// ...
return result
}
Getters and setters
The full syntax for declaring a property is as follows:
μμ±μ μ μΈνκΈ° μν μ 체 λ¬Έλ²μ μλμ κ°λ€:
The initializer, getter, and setter are optional. The property type is optional if it can be inferred from the initializer or the getter’s return type, as shown below:
μ΄κΈ°ν, getter, setterμ μ νμ¬νμ΄λ€. μμ±μ νμ μ μ΄κΈ°ν κ°μ΄λ getterμ λ°ν νμ μΌλ‘ μλμ κ°μ΄ μΆλ‘ λ μ μμΌλ©΄ μ νμ¬νμ΄λ€:
var initialized = 1 // has type Int, default getter and setter
// var allByDefault // ERROR: explicit initializer required, default getter and setter implied
The full syntax of a read-only property declaration differs from a mutable one in two ways: it starts with val instead of var and does not allow a setter:
μ½κΈ° μ μ© μμ±μ μ μΈμ λ¬Έλ²μ κ°λ³νκ³Ό λ κ°μ§ μ μμ μ°¨μ΄κ° μλ€: var λμ valλ‘ μμνλ©° setterλ₯Ό νμ©νμ§ μλλ€:
val simple: Int? // has type Int, default getter, must be initialized in constructor
val inferredType = 1 // has type Int and a default getter
You can define custom accessors for a property. If you define a custom getter, it will be called every time you access the property (this way you can implement a computed property). Here's an example of a custom getter:
μμ±μ μν μ¬μ©μ μ§μ μ κ·Όμλ₯Ό μ μν μ μλ€. μ¬μ©μ μ§μ getterλ₯Ό μ μνλ©΄, ν΄λΉ μμ±μ μ κ·Όν λλ§λ€ νΈμΆλ κ²μ΄λ€(μ΄λ¬ν λ°©λ²μ κ³μ°λ μμ±μ ꡬνν μ μλ€). μ¬μ©μ μ§μ getterμ μμμ΄λ€:
class Rectangle(val width: Int, val height: Int) {
val area: Int // property type is optional since it can be inferred from the getter's return type
get() = this.width * this.height
}
fun main() {
val rectangle = Rectangle(3, 4)
println("Width=${rectangle.width}, height=${rectangle.height}, area=${rectangle.area}")
}
Width=3, height=4, area=12
You can omit the property type if it can be inferred from the getter:
getterλ‘λΆν° μΆλ‘ ν μ μμΌλ©΄ μμ± νμ μ μλ΅ν μ μλ€:
If you define a custom setter, it will be called every time you assign a value to the property, except its initialization. A custom setter looks like this:
μ¬μ©μ μ§μ setterλ₯Ό μ μΈνλ©΄ μ΄κΈ°νν λλ₯Ό μ μΈνκ³ μμ±μ κ°μ ν λΉν λλ§λ€ νΈμΆλ κ²μ΄λ€. μ¬μ©μ μ§μ setterλ μ΄μ κ°λ€:
var stringRepresentation: String
get() = this.toString()
set(value) {
setDataFromString(value) // parses the string and assigns values to other properties
}
By convention, the name of the setter parameter is value, but you can choose a different name if you prefer.
κ΄λ‘μ λ°λΌ setter νλΌλ―Έν°μ μ΄λ¦μ valueμ΄μ§λ§ μ νΈνλ μ΄λ¦μΌλ‘ λ³κ²½ν μλ μλ€.
If you need to annotate an accessor or change its visibility, but you don't need to change the default implementation, you can define the accessor without defining its body:
μ κ·Όμμ μ΄λ Έν μ΄μ μ λ¬μμΌνκ±°λ visibilityλ₯Ό λ³κ²½νκ³ μΆμΌλ©΄, κΈ°λ³Έ ꡬνμ λ³κ²½ν νμλ μκ³ λ°λλ₯Ό μ μνμ§ μλ μ κ·Όμλ₯Ό μ μν΄μ£Όλ©΄ λλ€:
var setterVisibility: String = "abc"
private set // the setter is private and has the default implementation
var setterWithAnnotation: Any? = null
@Inject set // annotate the setter with Inject
πBacking fields
In Kotlin, a field is only used as a part of a property to hold its value in memory. Fields cannot be declared directly. However, when a property needs a backing field, Kotlin provides it automatically. This backing field can be referenced in the accessors using the field identifier:
μ½νλ¦°μμ νλλ κ·Έ κ°μ λ©λͺ¨λ¦¬μ κ°μ§κ³ μκΈ° μν μμ±μ λΆλΆμΌλ‘λ§ μ¬μ©λλ€. νλλ μ§μ μ μΌλ‘ μ μΈλ μ μλ€. κ·Έλ¬λ μμ±μ backing νλκ° νμν κ²½μ°, μ½νλ¦°μ μλμΌλ‘ μ 곡ν΄μ€λ€. μ΄ backing νλλ νλ μλ³μλ₯Ό μ¬μ©νμ¬ μ κ·Όμμμ μ°Έμ‘°ν μ μλ€:
var counter = 0 // the initializer assigns the backing field directly
set(value) {
if (value >= 0)
field = value
// counter = value // ERROR StackOverflow: Using actual name 'counter' would make setter recursive
}
The field identifier can only be used in the accessors of the property.
νλ μλ³μλ μμ±μ μ κ·Όμμμλ§ μ¬μ©λ μ μλ€.
A backing field will be generated for a property if it uses the default implementation of at least one of the accessors, or if a custom accessor references it through the field identifier.
μ κ·Όμ μ€ νλ μ΄μμ κΈ°λ³Έ ꡬνμ μ¬μ©νκ±°λ μ¬μ©μ μ§μ μ κ·Όμκ° νλ μλ³μλ₯Ό ν΅ν΄ μ°Έμ‘°νλ κ²½μ° μμ±μ λν backing νλκ° μμ±λ κ²μ΄λ€.
For example, there would be no backing field in the following case:
μλ₯Ό λ€λ©΄, μλμ κ²½μ° backing νλκ° μλ€.
Backing properties
If you want to do something that does not fit into this implicit backing field scheme, you can always fall back to having a backing property:
λ§μ½ μ΄λ¬ν μμ backing νλ schemeμ΄ μ λ§μ§ μλ 무μΈκ°λ₯Ό νκΈΈ μν λ backing μμ±μ μ¬μ©ν μ μλ€:
private var _table: Map<String, Int>? = null
public val table: Map<String, Int>
get() {
if (_table == null) {
_table = HashMap() // Type parameters are inferred
}
return _table ?: throw AssertionError("Set to null by another thread")
}
On the JVM: Access to private properties with default getters and setters is optimized to avoid function call overhead.
JVMμμ: κΈ°λ³Έ getterμ setterκ° μλ private μμ±μ λν μ κ·Όμ ν¨μ νΈμΆ μ€λ²ν€λλ₯Ό λ°©μ§νκΈ° μν΄ μ΅μ νλμ΄ μλ€.
Compile-time constants
If the value of a read-only property is known at compile time, mark it as a compile time constant using the const modifier. Such a property needs to fulfil the following requirements:
μ½κΈ° μ μ© μμ±μ κ°μ΄ μ»΄νμΌ μκ°μ μλ €μ§ κ²½μ°, const modifierλ₯Ό μ¬μ©νμ¬ μ»΄νμΌ μκ° μμλ‘ νμνλ€. μ΄λ¬ν μμ±μ λ€μ μꡬμ¬νμ μΆ©μ‘±ν΄μΌνλ€:
- It must be a top-level property, or a member of an object declaration or a companion object.
- It must be initialized with a value of type String or a primitive type
- It cannot be a custom getter
- λ°λμ μ΅μμ λ 벨μ μμ±μ΄μ΄μΌ νκ±°λ κ°μ²΄ μ μΈμ νΉμ companion κ°μ²΄μ λ©€λ²μ¬μνλ€.
- String νμ μ΄λ κΈ°λ³Έ νμ μ κ°μΌλ‘ μ΄κΈ°ν λμ΄μΌλ§ νλ€.
- μ¬μ©μ μ§μ getterμΌ μ μλ€.
The compiler will inline usages of the constant, replacing the reference to the constant with its actual value. However, the field will not be removed and therefore can be interacted with using reflection.
μ»΄νμΌλ¬λ μμμ μ¬μ©μ μΈλΌμΈνμ¬ μμμ λν μ°Έμ‘°λ₯Ό μ€μ κ°μΌλ‘ λ°κΎΌλ€. νμ§λ§ νλλ μ κ±°λμ§ μμ κ²μ΄κ³ κ·Έλ κΈ° λλ¬Έμ reflectionμ μ¬μ©νμ¬ μνΈμμ©ν μ μλ€.
Such properties can also be used in annotations:
κ·Έλ¬ν μμ±λ€μ μ΄λ Έν μ΄μ μμλ μ¬μ©ν μ μλ€:
const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"
@Deprecated(SUBSYSTEM_DEPRECATED) fun foo() { ... }
Late-initialized properties and variables
Normally, properties declared as having a non-null type must be initialized in the constructor. However, it is often the case that doing so is not convenient. For example, properties can be initialized through dependency injection, or in the setup method of a unit test. In these cases, you cannot supply a non-null initializer in the constructor, but you still want to avoid null checks when referencing the property inside the body of a class.
λκ° μμ±λ€μ μμ±μμμ μ΄κΈ°νλλ non-null νμ μΌλ‘ μ μΈλλ€. νμ§λ§ μ΄κ²μ΄ κ·Έλ€μ§ νΈνμ§ μμ κ²½μ°κ° μ’ μ’ μλ€. μλ₯Ό λ€λ©΄, μμ±λ€μ dependency injectionμ΄λ μ λ ν μ€νΈμ setup λ©μλ λ΄μμ μ΄κΈ°νλ μ μλ€. μ΄λ¬ν κ²½μ°λ€μλ μμ±μμμ non-null μ΄κΈ°νλ₯Ό μ 곡ν μ μμ§λ§ ν΄λμ€ λ°λ λ΄μμ μμ±μ μ°Έμ‘°ν λ null 체ν¬λ₯Ό νΌνκ³ μΆμ κ²μ΄λ€.
To handle such cases, you can mark the property with the lateinit modifier:
μ΄λ¬ν κ²½μ°λ€μ μ²λ¦¬νκΈ° μν΄μ ν΄λΉ μμ±μ lateinit modifierλ‘ νμν μ μλ€:
public class MyTest {
lateinit var subject: TestSubject
@SetUp fun setup() {
subject = TestSubject()
}
@Test fun test() {
subject.method() // dereference directly
}
}
This modifier can be used on var properties declared inside the body of a class (not in the primary constructor, and only when the property does not have a custom getter or setter), as well as for top-level properties and local variables. The type of the property or variable must be non-null, and it must not be a primitive type.
μ΄ modifierλ ν΄λμ€μ λ°λ λ΄λΆμ(κΈ°λ³Έ μμ±μ λ΄λΆκ° μλκ³ μμ±μ΄ μ¬μ©μ μ μΈ getterλ setterκ° μλ κ²½μ°μλ§) μ μΈλ var μμ±κ³Ό μ΅μμ μμ±, μ§μ λ³μμ μ¬μ©λ μ μλ€. μμ±μ΄λ λ³μμ νμ μ non-nullμ΄μ΄μΌνκ³ κΈ°λ³Ένμ μ΄ μλμ΄μΌ νλ€.
Accessing a lateinit property before it has been initialized throws a special exception that clearly identifies the property being accessed and the fact that it hasn't been initialized.
lateinit μμ±μ μ΄κΈ°ν λκΈ° μ μ μ κ·Όνλ κ²μ μ κ·Ό μ€μΈ μμ±κ³Ό μ΄κΈ°νλμ§ μμλ€λ μ¬μ€μ λͺ ννκ² μλ³νλ νΉλ³ν μμΈλ₯Ό λμ§λ€.
Checking whether a lateinit var is initialized
To check whether a lateinit var has already been initialized, use .isInitialized on the reference to that property:
lateinit varμ΄ μ΄λ―Έ μ΄κΈ°ν λμλμ§ νμΈνκΈ° μν΄ ν΄λΉ μμ±μ λν μ°Έμ‘°μμ .isInitialized μ¬μ©νλΌ:
This check is only available for properties that are lexically accessible when declared in the same type, in one of the outer types, or at top level in the same file.
πμ΄λ¬ν νμΈμ λμΌν νμ , μΈλΆ νμ μ€ νλ λλ κ°μ νμΌμ μ΅μμ λ 벨μμ μ μΈ λ λ μ΄νμ μΌλ‘ μ κ·Ό κ°λ₯ν μμ±μ λν΄μλ§ κ°λ₯νλ€.
Overriding properties
πDelegated properties
The most common kind of property simply reads from (and maybe writes to) a backing field, but custom getters and setters allow you to use properties so one can implement any sort of behavior of a property. Somewhere in between the simplicity of the first kind and variety of the second, there are common patterns for what properties can do. A few examples: lazy values, reading from a map by a given key, accessing a database, notifying a listener on access.
πκ°μ₯ μΌλ°μ μΈ μ’ λ₯μ μμ±μ λ¨μν μ½κ±°λ νΉμ μΈ μ μμ§λ§ μ¬μ©μ μ§μ getterμ setterλ₯Ό μ¬μ©νλ©΄ μμ±μ μ¬μ©ν μ μκΈ° λλ¬Έμ μμ±μ λͺ¨λ μ’ λ₯μ λμλ ꡬνν μ μκ² ν΄μ€λ€. 첫 λ²μ§Έ μ’ λ₯μ λ¨μν¨κ³Ό λ λ²μ§Έ μ’ λ₯μ λ€μμ± μ¬μ΄μ μ΄λκ°μ μμ±μ΄ μνν μ μλ μμ μ λν κ³΅ν΅ ν¨ν΄μ΄ μλ€. λͺ κ°μ§ μμ: lazy values, μ£Όμ΄μ§ keyλ‘ mapμμ μ½μ΄μ€κΈ°, λ°μ΄ν°λ² μ΄μ€μ μ κ·ΌνκΈ°, μ κ·Ό μ 리μ€λμ μλ¦Ό.
Such common behaviors can be implemented as libraries using delegated properties.
μ΄λ¬ν 보ν΅μ λμλ€μ delegated μμ±μ μ¬μ©ν λΌμ΄λΈλ¬λ¦¬λ€μ ν΅ν΄ ꡬνλ μ μλ€.
'Android & Kotlin' μΉ΄ν κ³ λ¦¬μ λ€λ₯Έ κΈ
[λ΄λ³΄λ΄λ²] Interfaces | Kotlin (0) | 2022.03.03 |
---|---|
[λ΄λ³΄λ΄λ²] Classes | Kotlin (0) | 2022.03.03 |
[λ΄λ³΄λ΄λ²] Inheritance | Kotlin (0) | 2022.03.02 |
Overloading / Overriding (0) | 2022.03.02 |
Lesson 6: App Architecture (Persistence) (0) | 2022.02.23 |