그러나 그 외의 클래스에 대해서도 산술 연산이 가능한 편이 좋은데 코틀린에서는 가능!
/**
* x, y 한 좌표가 정의 된 Point data class
*/
data class Point(val x: Int, val y: Int) {
/**
* "plus (+와 대응)라는 이름의 연산자 함수를 정의
* operator(연산자) 함수라는 것을 표시
*/
operator fun plus(other: Point): Point {
//반환타입이 Point고 파라미터도 Point로 현재는 타입이 동일하지만
//반환타입과 파라미터타입이 불일치 해도 상관 없다
return Point(x + other.x, y + other.y) //좌표를 성분별로 더한 새로운 점을 반환한다.
}
}
fun main(args: Array<String>) {
val p1 = Point(10, 20)
val p2 = Point(30, 40)
println(p1 + p2) // + 로 계산하면 "plus" 함수가 호출 // Point(x=40, y=60)
}
var point = Point(1,2)
point += Point(3,4) // point = point + Point(3,4)
//-> 변수가 변경 가능한 경우에만(var) += 가능
println(point) //>> Point(x=4, y=6)
val numbers = ArrayList<Int>()
numbers += 42 //numbers.add(42)
println(numbers[0]) //get
point += Point(3,4) // point = point + Point(3,4)
//-> 변수가 변경 가능한 경우에만(var) += 가능
println(point) //>> Point(x=4, y=6)
위 예제는 Unit타입인 plusAssign
연산자 함수 사용
operator fun <T> MutableCollection<T>.plusAssign(element:T) {
this.add(element)
}
+a
: unaryPlus -a
: unaryMinus!a
: not++a, a++
: inc--a, a--
: decoperator fun Point.unaryMinus(): Point { //단항 minus(음수) 함수는 파라미터가 없다.
//본인안에 있는 값에서 -만 하면 되니까 파라미터가 없쥬
return Point(-x, -y) //좌표에서 각 성분의 음수를 취한 새 점을 반환
}
val p = Point(10, 20)
println(-p) //>> Point(-10, -20)
++a
| a++
이 헷갈린다면?var num :Int = 0
println(num++) //>> 0 //출력 후 계산
println(++num) //>> 2 //선출력 후 계산
class Point(val x: Int, val y: Int) {
override fun equals(obj: Any?): Boolean { //Any에 정의된 메서드를 오버라이딩 한다.
//equals는 Any에 이미 operator로 정의되어져 있어서 오버라이드 시에는 명시하지 않아도 된다.
if (obj === this) return true //파라미터가 수신객체와 같은지를 비교할때 ===을 사용
if (obj !is Point) return false //파라미터 타입 검사
return obj.x == x && obj.y == y //Point로 스마트 캐스트하여(위 if에서 is 사용) x,y 프로퍼티 접근
}
}
operator
를 써야 하지만 이미 작성된 클래스 상속시 상위클래스에 해당 연산자 함수가 이미 정의돼있다면 operator 명시 필요 Xa >= b
는 a.compareTo(b) >= 0
과 동일a < b
는 a.compareTo(b) < 0
과 동일 return a[0]
: get
a[0] = 1
: set
in
: contains
와 대응 : a 1 until 10
: (1<=a && a<=9) in
은 찾고자 하는 값이 있니? 에 대한 결과 도출 a 1 .. 10
(1<=a && a<=10) : rangeTo
in
과 달리 for의 in
은 hasNext, next 호출을 반복in
은 끝날때까지 반복 val map = mapOf("Oracle" to "Java", "Jetbrains" to "Kotlin") //key to value 형태
for ((key, value) in map) { //루프 변수에 구조 분해 선언 사용
println("$key -> $value") //Oracle -> Java ...
}
(어렵다..한국말이 참 어렵다..이해해보자..)
프로퍼티 본인이 본인이 해야 하는 작업을 직접 수행하는 것이 아닌 by해서 위임한 객체에게 일을 맡기는 고런 것?0?
위임 객체를 위임한 프로퍼티가 위임 프로퍼티..
뭔가 프로퍼티들의 중복된 행위들을 직접 작성하지 않고 이미 구현된 도우미 클래스에게 이것좀 해줘! 라고 맡기는 느낌
중복코드를 방지할 수 있을 듯 하다.
class Email { /*...*/ }
fun loadEmails(person: Person): List<Email> {
println("Load emails for ${person.name}")
return listOf(/*...*/)
}
/**
* lazy 를 사용하지 않는다면 아래와 같이 긴 코드를 작성해야 한다.
* private 변경가능 변수와 변경 불가능 변수를 이용해서 null일때 초기화하는 코드를 작성해야함
* -> 매우 긴거 보이시쥬?
*/
/*class Person(val name: String) {
private var _emails: List<ch07.LazyEmails.Email>? = null
//_emails 보이시쥬?
val emails: List<ch07.LazyEmails.Email>
get() {
if (_emails == null) { //getter에서 해당 값이 없는 경우에만 loadEmails 함수 호출
_emails = ch07.LazyEmails.loadEmails(this)
}
return _emails!!
}
}*/
class Person(val name: String) {
val emails by lazy { loadEmails(this) } //굉장히 짧은 코드로 초기화 가능🌝
}
fun main(args: Array<String>) {
val p = Person("Alice")
//아래 코드 실행시에는 emails가 초기화 전 상태이기 때문에 lazy 함수를 탄다.
p.emails //Load emails for Alice
p.emails
}
(혼자이해해보기..) 변수 초기화시에 private var(getter&setter) & 실제 변수 val 두개를 사용해서 값이 초기화 됐는지 여부 판단후 초기화되지 않았다면 값을 초기화해야하는데 ..
그걸 직접 작성하지 않고by lazy
를 하면 초기화 되지 않았을 때 lazy 내부 람다식을 실행 가능하다.
두개의 변수를 사용하지 않아도 되고 코드 또한 한줄로 줄어든다..
아주 좋군..
Delegates 관련 내용이 책에서 넘넘 어려운데 마지막 예제를 보면 좀 더 이해하기 쉬운 것 같다!
너무너무 어려워서 예제에 다 설명을 달아뒀다.
/**
* 프로퍼티 변경 리스터를 추적하는 도우미 클래스 PropertyChangeAware
*/
open class PropertyChangeAware {
protected val changeSupport = PropertyChangeSupport(this)
//PropertyChangeSupport 는 프로퍼티 변경 지원 클래스
//프로퍼티 변경 리스너 PropertyChangeSupport클래스의 함수를 오버라이드 하는 형식
fun addPropertyChangeListener(listener: PropertyChangeListener) {
changeSupport.addPropertyChangeListener(listener)
}
fun removePropertyChangeListener(listener: PropertyChangeListener) {
changeSupport.removePropertyChangeListener(listener)
}
}
/**
* 위에서 추가한 PropertyChangeAware(프로퍼티 변경 추적 도우미 클래스)를 상속받는 Person 클래스
* Person 클래스에 존재하는 프로퍼티(name, age, salary) 변경 여부를 판단 가능하다
*/
class Person(
val name: String, age: Int, salary: Int
) : PropertyChangeAware() {
private val observer = { //observer 는 어떤 값의 변경을 관찰하는 관찰자
//값 변경시마다 해당 람다가 실행됨
prop: KProperty<*>, oldValue: Int, newValue: Int ->
println("observer exist")
//firePropertyChange 함수에서 new PropertyChangeEvent(this.source, propertyName, oldValue, newValue)
//하면서 변경된 값에 따른 새로운 객체를 생성해준다.
changeSupport.firePropertyChange(prop.name, oldValue, newValue)
//changeSupport 는 상속받은 PropertyChangeAware 클래스에 존재하는 값임
}
/**
* by 보이시쥬?
* 옵저버 구현 객체를 위임하는 것이죵
* 그러면 age 와 salary 는 값 변경시에 대한 행위를 직접 하지 않고 위임한 놈에게 해당 행위를 맡기는 것이쥬!
*/
//by 로 위임객체를 지정해주지 않으면 아래와 같이 길고 긴 코드를 작성해야 합니다!
//한개라면 괜찮겠지만 두개 이상의 프로퍼티를 사용하는 클래스라면 중복이 굉장하겠쥬?
/*var salary: Int = salary
set(newValue) {
val oldValue = field
field = newValue
changeSupport.firePropertyChange(
"salary", oldValue, newValue)
}*/
var age: Int by Delegates.observable(age, observer)
var salary: Int by Delegates.observable(salary, observer)
}
fun main(args: Array<String>) {
val p = Person("Dmitry", 34, 2000)
p.addPropertyChangeListener(
//PropertyChangeListener 는 속성이 변경될때마다 읽어드리는 리스너임
PropertyChangeListener { event ->
println("Property ${event.propertyName} changed " +
"from ${event.oldValue} to ${event.newValue}")
}
)
p.age = 35 //값 변경
// observer exist
// Property age changed from 34 to 35
p.salary = 2100 //값 변경
// observer exist
// Property salary changed from 2000 to 2100
p.salary = 21002 //값 변경
// observer exist
// Property salary changed from 2100 to 21002
}
책에서는 by옆에 있는 우항에 있는 식(Delegates.observable(age,observer)
)을 계산한 결과인 객체는 컴파일러가 호출할 수 있는 올바른 타입의 getValue와 setValue를 반드시 제공해야 한다 라고 명시돼있다.
머선 말이냐 하묜..(아래이미지 참고)
이미 힘든데 마지막 하나가 더 남았다..🥺
class Person {
private val _attributes = hashMapOf<String, String>()
//Map, MutableMap 자체에서 setValue와 getValue 확장 함수를 제공하기 때문에 by가 먹히는 것
//(by를 하기 위한 위임 객체에서는 위 두 메서드가 정의돼있어야 함)
fun setAttribute(attrName: String, value: String) {
_attributes[attrName] = value
}
//위임 프로퍼티로 맵을 사용한다.
val name: String by _attributes //hashMapOf 위임 객체
}
fun main(args: Array<String>) {
val p = Person() //hashMapOf 위임객체인 _attributes 를 사용하는 프로퍼티 2개를 갖고있는 Person 클래스
val data = mapOf("name" to "Dmitry", "company" to "JetBrains")
for ((attrName, value) in data)
p.setAttribute(attrName, value)
println(p.name) //Dmitry
//p.name을 호출하면 _attributes.getValue(p, prop)을 대신 호출하고 ,
// _attributes.getValue(p, prop) 가 _attributes[prop.name] 을 통해 구현이 된다.
// 맵에 값을 넣고 꺼내오는 과정을 name 프로퍼티가 직접하지 않고 이를 위임객체인 _attributes 에게 맡긴다.
}
무수히 많은 키가 존재하는 맵 데이터가 있고
어떤 키에 해당하는 값을 간편하게 갖고 오고자 할때 맵 위임 프로퍼티를 사용하면 p.name 과 같은 식으로 호출할 때 해당 맵에 있는 name 키에 해당 하는 값을 간편하게 얻어올 수 있다!
(뭔가 코드 자체가 되게 길어져서 이게 진짜 유용한가 라는 생각이 들긴하지만 해당 클래스에 대한 인스턴스의 어떤 키가 자주 사용된다면 유용하겠지? 라고 꾸역꾸역 이해해본다.😭)