kotlin 提供 operator overloading 機制,可以覆寫 operator 的行為,例如提供了 plus method,就可以將物件以 +
進行運算。
Overloading arithmetic operators
overloading binary arithmetic operations
a + b -> a.plus(b)
data class Point(val x: Int, val y: Int) {
// 覆寫 plus 方法
operator fun plus(other: 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)
// + 就是呼叫 plus
println(p1 + p2)
}
Expression |
function name |
a*b |
times |
a/b |
div |
a%b |
mod |
a+b |
plus |
a-b |
minus |
如果是 Java 的 class,也可以用 operator fun
的方式定義 plus
data class Point(val x: Int, val y: Int)
// extension function,附加到 Point
operator fun Point.plus(other: 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)
}
兩個 operands 不需要有相同的 type,Point 可以乘上 Double
data class Point(val x: Int, val y: Int)
// 兩個 operands 不需要有相同的 type
operator fun Point.times(scale: Double): Point {
return Point((x * scale).toInt(), (y * scale).toInt())
}
fun main(args: Array<String>) {
val p = Point(10, 20)
println(p * 1.5)
}
可改變 return data type
operator fun Char.times(count: Int): String {
return toString().repeat(count)
}
fun main(args: Array<String>) {
println('a' * 3)
}
kotlin 沒有定義 bitwise operators,改以 function 並用 index call syntax 處理
function |
meaning |
shl |
signed shift left |
shr |
signed shift right |
ushr |
unsigned shift right |
and |
|
or |
|
xor |
|
inv |
bitwise inversion |
fun main(args: Array<String>) {
println(0x0F and 0xF0)
println(0x0F or 0xF0)
println(0x1 shl 4)
}
Overloading compound assignment operators
除了 + 也能使用 +=
data class Point(val x: Int, val y: Int)
operator fun Point.plus(other: Point): Point {
return Point(x + other.x, y + other.y)
}
fun main(args: Array<String>) {
var point = Point(1, 2)
point += Point(3, 4)
println(point)
}
有時只需要用 += 不需要 +,可以提供 plusAssign 另外加上 Unit return type,類似的 method 為 minusAssign, timesAssign
operator fun <T> MutableCollection<T>.plusAssign(element: T) {
this.add(element)
}
如果在程式裡面呼叫 a+=b
,其實會先呼叫 a = a.plus(b)
然後是 a.plusAssign(b)
standard libary 的 +, - 都會產生新的 collection,因此 +=, -= 可同時用在 read-only, mutable collection
fun main(args: Array<String>) {
val list = arrayListOf(1, 2)
/// 改變了原本的 list
list += 3
// 產生新的 list,但原本的 list 不變
val newList = list + listOf(4, 5)
println(list)
println(newList)
}
Overloading unary operators
+a 就是 a.unaryPlus(), -a 是 a.unaryMinus
data class Point(val x: Int, val y: Int)
operator fun Point.unaryMinus(): Point {
return Point(-x, -y)
}
fun main(args: Array<String>) {
val p = Point(10, 20)
println(-p)
}
expression |
function name |
+a |
unaryPlus |
-a |
unaryMinus |
!a |
not |
++a, a++ |
inc |
--a, a-- |
dec |
import java.math.BigDecimal
// 覆寫 ++
operator fun BigDecimal.inc() = this + BigDecimal.ONE
fun main(args: Array<String>) {
var bd = BigDecimal.ZERO
// 在 println 後,呼叫 inc
println(bd++)
// 在 println 前,呼叫 inc
println(++bd)
}
Overloading comparison operators
comparison operators: ==, !=, >, <
euqals ==
a==b
會轉換為 equals 及 null check a?.equals(b) ?: b ==null
class Point(val x: Int, val y: Int) {
override fun equals(obj: Any?): Boolean {
if (obj === this) return true
if (obj !is Point) return false
return obj.x == x && obj.y == y
}
}
fun main(args: Array<String>) {
println(Point(10, 20) == Point(10, 20))
println(Point(10, 20) != Point(5, 5))
println(null == Point(1, 2))
}
Ordering operators: compareTo
java 的 class 實作 Comparable 介面,就可以比較大小
kotlin 也有支援 Comparable 介面,但是 compareTo method 是用在 call by convention
a>=b
會轉換為 a.compareTo(b) >=0
import kotlin.comparisons.compareValuesBy
class Person(
val firstName: String, val lastName: String
) : Comparable<Person> {
override fun compareTo(other: Person): Int {
return compareValuesBy(this, other,
Person::lastName, Person::firstName)
}
}
fun main(args: Array<String>) {
val p1 = Person("Alice", "Smith")
val p2 = Person("Bob", "Johnson")
println(p1 < p2)
}
Conventions used for collections and ranges
Accessing elements by index: get and set
kotlin 要使用 map,是用 map[index] 取得某個 element,類似 java 的 array
如果定義 get function,並標記為 operator fun,就可以用 [] 呼叫 get
x[a,b] -> x.get(a,b)
data class Point(val x: Int, val y: Int)
operator fun Point.get(index: Int): Int {
return when(index) {
0 -> x
1 -> y
else ->
throw IndexOutOfBoundsException("Invalid coordinate $index")
}
}
fun main(args: Array<String>) {
val p = Point(10, 20)
println(p[1])
}
如果定義 operator fun get(rowIndex: Int,
colIndex: Int)
就可以用 matrix[row, col]
set 跟 get 類似
x[a,b] = c 會轉換為 x.set(a, b, c)
data class MutablePoint(var x: Int, var y: Int)
operator fun MutablePoint.set(index: Int, value: Int) {
when(index) {
0 -> x = value
1 -> y = value
else ->
throw IndexOutOfBoundsException("Invalid coordinate $index")
}
}
fun main(args: Array<String>) {
val p = MutablePoint(10, 20)
p[1] = 42
println(p)
}
"in" convention
in 是呼叫 contains
a in c -> c.contains(a)
data class Point(val x: Int, val y: Int)
data class Rectangle(val upperLeft: Point, val lowerRight: Point)
operator fun Rectangle.contains(p: Point): Boolean {
return p.x in upperLeft.x until lowerRight.x &&
p.y in upperLeft.y until lowerRight.y
}
fun main(args: Array<String>) {
val rect = Rectangle(Point(10, 20), Point(50, 50))
println(Point(20, 30) in rect)
println(Point(5, 5) in rect)
}
rangeTo
start..end
轉換為 start.rangeTo(end)
standard library 有定義一個 rangeTo,可被任何 comparable element 呼叫
operator fun <T: Comparable<T>> T.rangeTo(that: T): ClosedRange<T>
import java.time.LocalDate
fun main(args: Array<String>) {
// 產生十天
val now = LocalDate.now()
val vacation = now..now.plusDays(10)
val n = 9
// 0..10
println(0..(n + 1))
// 加上 () 再呼叫 forEach
(0..n).forEach { print(it) }
}
"iterator" convention for "for" loop
iterator method
import java.util.Date
import java.time.LocalDate
operator fun ClosedRange<LocalDate>.iterator(): Iterator<LocalDate> =
// 實作 Iterator
object : Iterator<LocalDate> {
var current = start
// 使用 compareTo convention
override fun hasNext() =
current <= endInclusive
// 修改前會回傳 current date
override fun next() = current.apply {
current = plusDays(1)
}
}
fun main(args: Array<String>) {
val newYear = LocalDate.ofYearDay(2017, 1)
val daysOff = newYear.minusDays(1)..newYear
for (dayOff in daysOff) {
println(dayOff)
}
//2016-12-31
//2017-01-01
}
Destructuring declarations and component functions
destructuring declarations 可以 unpacke composite values,儲存在不同的變數
val p = Point(10, 20)
val (x, y) = p
val (x, y) = p
等同
val a = p.component1()
val b = p.component2()
data class 會自動為每一個 property 產生 componentN 的 function
class Point(val x: Int, val y: Int) {
operator fun component1() = x
operator fun component2() = y
}
但也能自己處理
import java.io.File
data class NameComponents(val name: String,
val extension: String)
fun splitFilename(fullName: String): NameComponents {
val result = fullName.split('.', limit = 2)
return NameComponents(result[0], result[1])
}
fun main(args: Array<String>) {
val (name, ext) = splitFilename("example.kt")
println(name)
println(ext)
}
splitFilename 也可以寫成
fun splitFilename(fullName: String): NameComponents {
val (name, extension) = fullName.split('.', limit = 2)
return NameComponents(name, extension)
}
for ((key, value) in map)
可 iterate map
fun printEntries(map: Map<String, String>) {
for ((key, value) in map) {
println("$key -> $value")
}
}
>>> val map = mapOf("Oracle" to "Java", "JetBrains" to "Kotlin")
>>> printEntries(map)
Oracle -> Java
JetBrains -> Kotlin
Reusing property accessor logic: delegated properties
delegated properties 可實作更複雜的 perperties
Delegated properties: the basics
delegated property 語法如下: p delegates the logic to an instance of Delegate class
class Foo {
var p: Type by Delegate()
}
實際上 compiler 會產生以下的 code
class Foo {
private val delegate = Delegate()
var p: Type
set(value: Type) = delegate.setValue(..., value)
get() = delegate.getValue(...)
}
搭配 Delegate 的實作,完整的範例如下
class Delegate {
operator fun getValue(...) { ... }
operator fun setValue(..., value: Type) { ... }
}
class Foo {
var p: Type by Delegate()
}
>>> val foo = Foo()
// 使用 property,會呼叫 delegate.get
>>> val oldValue = foo.p
// 修改 property value 會呼叫 delegate.set
>>> foo.p = newValue
使用 delegated properties: lazy initialization and by lazy()
lazy initialization
例如 Person 可提供多個 emails,但emails 存在 DB 裡面,需要一段時間才能取得,我們需要在第一次使用 emails 時,載入 email 並將 email 暫存。
使用 backing property _emails
的方式,提供這樣的功能
class Email { /*...*/ }
fun loadEmails(person: Person): List<Email> {
println("Load emails for ${person.name}")
return listOf(/*...*/)
}
class Person(val name: String) {
private var _emails: List<Email>? = null
val emails: List<Email>
get() {
if (_emails == null) {
_emails = loadEmails(this)
}
return _emails!!
}
}
fun main(args: Array<String>) {
val p = Person("Alice")
p.emails
p.emails
}
如果用 lazy initialization 的方式
class Email { /*...*/ }
fun loadEmails(person: Person): List<Email> {
println("Load emails for ${person.name}")
return listOf(/*...*/)
}
class Person(val name: String) {
val emails by lazy { loadEmails(this) }
}
fun main(args: Array<String>) {
val p = Person("Alice")
p.emails
p.emails
}
implementing delegated properties
notifying listeners when a property of an object changes
Java 提供 PropertyChangeSupport, PropertyChangeEvent classes
在 kotlin 使用 PropertyChangeSupport, PropertyChangeEvent 的範例
import java.beans.PropertyChangeSupport
import java.beans.PropertyChangeListener
open class PropertyChangeAware {
// 儲存 list of listeners, dispatch PropertyChangeEvent events to them
protected val changeSupport = PropertyChangeSupport(this)
fun addPropertyChangeListener(listener: PropertyChangeListener) {
changeSupport.addPropertyChangeListener(listener)
}
fun removePropertyChangeListener(listener: PropertyChangeListener) {
changeSupport.removePropertyChangeListener(listener)
}
}
class Person(
val name: String, age: Int, salary: Int
) : PropertyChangeAware() {
var age: Int = age
set(newValue) {
// field 可取得 property backing field
val oldValue = field
field = newValue
// 通知 listeners
changeSupport.firePropertyChange(
"age", oldValue, newValue)
}
var salary: Int = salary
set(newValue) {
val oldValue = field
field = newValue
changeSupport.firePropertyChange(
"salary", oldValue, newValue)
}
}
fun main(args: Array<String>) {
val p = Person("Dmitry", 34, 2000)
// 將 property change listener 加入 p
p.addPropertyChangeListener(
PropertyChangeListener { event ->
println("Property ${event.propertyName} changed " +
"from ${event.oldValue} to ${event.newValue}")
}
)
p.age = 35
p.salary = 2100
}
以 ObservableProperty 修改
為每一個 property 都產生 ObservableProperty instance,並 delegate getter, setter
import java.beans.PropertyChangeSupport
import java.beans.PropertyChangeListener
open class PropertyChangeAware {
protected val changeSupport = PropertyChangeSupport(this)
fun addPropertyChangeListener(listener: PropertyChangeListener) {
changeSupport.addPropertyChangeListener(listener)
}
fun removePropertyChangeListener(listener: PropertyChangeListener) {
changeSupport.removePropertyChangeListener(listener)
}
}
class ObservableProperty(
val propName: String, var propValue: Int,
val changeSupport: PropertyChangeSupport
) {
fun getValue(): Int = propValue
fun setValue(newValue: Int) {
val oldValue = propValue
propValue = newValue
changeSupport.firePropertyChange(propName, oldValue, newValue)
}
}
class Person(
val name: String, age: Int, salary: Int
) : PropertyChangeAware() {
val _age = ObservableProperty("age", age, changeSupport)
var age: Int
get() = _age.getValue()
set(value) { _age.setValue(value) }
val _salary = ObservableProperty("salary", salary, changeSupport)
var salary: Int
get() = _salary.getValue()
set(value) { _salary.setValue(value) }
}
fun main(args: Array<String>) {
val p = Person("Dmitry", 34, 2000)
p.addPropertyChangeListener(
PropertyChangeListener { event ->
println("Property ${event.propertyName} changed " +
"from ${event.oldValue} to ${event.newValue}")
}
)
p.age = 35
p.salary = 2100
}
調整 ObservableProperty,將 getValue, setValue 改為 operator fun
去掉 name property,改為使用 KProperty.name
class ObservableProperty(
var propValue: Int, val changeSupport: PropertyChangeSupport
) {
operator fun getValue(p: Person, prop: KProperty<*>): Int = propValue
operator fun setValue(p: Person, prop: KProperty<*>, newValue: Int) {
val oldValue = propValue
propValue = newValue
changeSupport.firePropertyChange(prop.name, oldValue, newValue)
}
}
最精簡的版本
import java.beans.PropertyChangeSupport
import java.beans.PropertyChangeListener
import kotlin.properties.Delegates
import kotlin.reflect.KProperty
open class PropertyChangeAware {
protected val changeSupport = PropertyChangeSupport(this)
fun addPropertyChangeListener(listener: PropertyChangeListener) {
changeSupport.addPropertyChangeListener(listener)
}
fun removePropertyChangeListener(listener: PropertyChangeListener) {
changeSupport.removePropertyChangeListener(listener)
}
}
class Person(
val name: String, age: Int, salary: Int
) : PropertyChangeAware() {
private val observer = {
prop: KProperty<*>, oldValue: Int, newValue: Int ->
changeSupport.firePropertyChange(prop.name, 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 { event ->
println("Property ${event.propertyName} changed " +
"from ${event.oldValue} to ${event.newValue}")
}
)
p.age = 35
p.salary = 2100
}
Delegated-property translation rules
整理 delegated properties 的運作方式
以下是 class with delegated property
class Foo {
var c: Type by MyDelegate()
}
val foo = Foo()
實際上 compiler 會產生以下的 code
class Foo {
private val <delegate> = MyDelegate()
var c: Type
set(value: Type) = <delegate>.setValue(c, <property>, value)
get() = <delegate>.getValue(c, <property>)
}
使用 property 時,會呼叫
val x=c.prop
就是 val x = <delegate>.getValue(c, <property>)
c.prop=x
就是 <delegate>.setValue(c, <property>, x)
Storing property values in a map
expando objects: 有動態定義的 set of attributes
Person 沒有固定的 properties,將 property 放在 hashMap 裡面
class Person {
private val _attributes = hashMapOf<String, String>()
fun setAttribute(attrName: String, value: String) {
_attributes[attrName] = value
}
val name: String
get() = _attributes["name"]!!
}
fun main(args: Array<String>) {
val p = Person()
val data = mapOf("name" to "Dmitry", "company" to "JetBrains")
for ((attrName, value) in data)
p.setAttribute(attrName, value)
println(p.name)
}
可以使用 delegated property
class Person {
private val _attributes = hashMapOf<String, String>()
fun setAttribute(attrName: String, value: String) {
_attributes[attrName] = value
}
val name: String by _attributes
}
Delegated properties in frameworks
Database framework
// 將 object 關聯至 table in the database
object Users : IdTable() {
// property 為 columns
val name = varchar("name", length = 50).index()
val age = integer("age")
}
// 每一個 User instance 都有特定的 id
class User(id: EntityID) : Entity(id) {
// name 對應到 DB 的 name
var name: String by Users.name
var age: Int by Users.age
}
另一種方式,是定義 Column 類別
object Users : IdTable() {
val name: Column<String> = varchar("name", 50).index()
val age: Column<Int> = integer("age")
}
framework 為 Column class 定義了 getValue, setValue
operator fun <T> Column<T>.getValue(o: Entity, desc: KProperty<*>): T {
// retrieve the value from the database
}
operator fun <T> Column<T>.setValue(o: Entity, desc: KProperty<*>, value: T) {
// update the value in the database
}
References
Kotlin in Action
Redis是一套基於key-value的in-memory database,常見的用途之一,是拿來作為Application Server的快取系統。
而Lettuce則是一個強調thread-safe的Java Redis Client。
Redis參數設定
可以使用XML或Annotation兩種方式,來設定Redis的IP、Port。
方法(一):建立XML設定檔
以下假設專案使用webapp,並將XML檔案放在src/main/resource底下,取名為lettuce.xml
完整XML檔案範例如下:
Sentinel設定
若要連線至3個Sentinel:
範例如下:
Connection設定
Java Redis Client使用lettuce,如下:
RedisTemplate設定
方法(二):Annotation
建立一個類別,並於class前加上 @Configuration
Connection Pool 設定
Sentinel設定
若要連線至3個Sentinel:
範例如下:
Connection設定
Java Redis Client使用lettuce,如下:
RedisTemplate設定
設定KeySerializer使用StringRedisSerializer
在Java中使用RedisTemplate操作Redis
由於XML檔案位於src/main/resource/lettuce.xml,此處先使用ClassPathXmlApplicationContext取得RedisTemplate物件:
設定KeySerializer
設定KeySerializer為StringRedisSerializer
因為無論是Key還是Value,預設都使用JdkSerializationRedisSerializer;如此一來,假設原本程式中寫入的key為mykey,若沒有將KeySerializer為StringRedisSerializer,寫入資料後,進console查看keys資料如下:
若有將KeySerializer為StringRedisSerializer時,寫入資料後,進console查看keys資料如下
兩筆資料將會是不同的key,因此KeySerializer應一開始就設定好,並保持一致。
此外,Value也可以設定使用StringRedisSerializer:
不過,如果需要將序列化的物件存入,那就不應將ValueSerializer設為StringRedisSerializer。
透過Operations操作Redis
ValueOperations
取得ValueOperations物件
寫入字串
讀取字串
寫入/讀取物件
如同之前提到,寫入的Value預設是使用JdkSerializationRedisSerializer。因此也可以寫入有implements Serializable的物件
TestUser需implements Serializable,如下:
ListOperations
建立ListOperations物件
寫入list
讀取list
不難發現,多數函數名稱都與原本的Redis指令很接近;若已熟悉Redis的話,使用Lettuce也能很快就上手。
刪除key
刪除資料時,直接使用redisTemplate的delete即可。