part1看完了HelloWorld和BasicSyntax咱们接着往后面看(依然是跟着Try Kotlin进行~)
Destructuring declarations and Data classes
基本语法的demo看完就迎接更有意思的东西了,构造声明和数!据!类!
Destructuring declarations
/**
* This example introduces a concept that we call destructuring declarations.
* It creates multiple variable at once. Anything can be on the right-hand
* side of a destructuring declaration, as long as the required number of component
* functions can be called on it.
* See http://kotlinlang.org/docs/reference/multi-declarations.html#multi-declarations
*/
fun main(args: Array<String>) {
val pair = Pair(1, "one")
val (num, name) = pair
println("num = $num, name = $name")
}
class Pair<K, V>(val first: K, val second: V) {
operator fun component1(): K {
return first
}
operator fun component2(): V {
return second
}
}
这个小例子介绍了一个叫做"解构声明"的概念,它是在干嘛呢?它呀,能一次创建多个变量;
任何对象都能放在一个解构声明的右边,只要可以调用到所需数量的组件函数即可.
例如例子中的Pair类,类中有两个componentX function,X对应的就是解构创建变量的位置
在kotlin中,data class是隐含component function的,例如:
fun main(args: Array<String>) {
val person1 = Person1("zhangsan", 3)
val (name1, age1) = person1
println("name1 =$name1 and age = $age1")
}
data class Person1(val name: String, val age: Int)
控制台输出:name1 =zhangsan and age = 3
如果想自己的类也可以使用解构声明,那就需要写出componentX function,例如:
fun main(args: Array<String>) {
val person2 = Person2("lisi", 4)
val (name2, age2) = person2
println("name1 = $name2 and age1 = $age2")
}
class Person2(val name: String, val age: Int) {
operator fun component2(): String {
return name
}
operator fun component1(): Int {
return age
}
}
//
控制台输出:name1 = 4 and age1 = lisi
可以看到componentX中的X的的确确就是解构时返回值的角标
Data classes
/**
* Data class gets component functions, one for each property declared
* in the primary constructor, generated automatically, same for all the
* other goodies common for data: toString(), equals(), hashCode() and copy().
* See http://kotlinlang.org/docs/reference/data-classes.html#data-classes
*/
data class User(val name: String, val id: Int)
fun getUser(): User {
return User("Alex", 1)
}
fun main(args: Array<String>) {
val user = getUser()
println("name = ${user.name}, id = ${user.id}")
// or
val (name, id) = getUser()
println("name = $name, id = $id")
// or
println("name = ${getUser().component1()}, id = ${getUser().component2()}")
}
data class会根据主构造函数的参数自动生成component functions,同样的也会生成诸如:toString(),equals(),hashCode(),copy()等针对数据的常用函数
上面的三个pirntln会输出同样的结果:name = Alex, id = 1
Traversing a map
/**
* Kotlin Standard Library provide component functions for Map.Entry
*/
fun main(args: Array<String>) {
val map = hashMapOf<String, Int>()
map.put("one", 1)
map.put("two", 2)
for ((key, value) in map) {
println("key = $key, value = $value")
}
}
如果想遍历一个map,kotlin的标准库已经给Map.Entry提供了component function,我们就可以直接使用解构函数输出它啦!
Autogenerated functions
/**
* Data class gets next functions, generated automatically:
* component functions, toString(), equals(), hashCode() and copy().
* See http://kotlinlang.org/docs/reference/data-classes.html#data-classes
*/
data class User(val name: String, val id: Int)
fun main(args: Array<String>) {
val user = User("Alex", 1)
println(user) // toString()
val secondUser = User("Alex", 1)
val thirdUser = User("Max", 2)
println("user == secondUser: ${user == secondUser}")
println("user == thirdUser: ${user == thirdUser}")
// copy() function
println(user.copy())
println(user.copy("Max"))
println(user.copy(id = 2))
println(user.copy("Max", 2))
}
data class会自动成component functions, toString(), equals(), hashCode() , copy().这个我已经在上面提到,就不赘述了
建议大家敲一手两手看看结果感受感受(这里的copy函数能自动识别函数来进行copy,我觉得很神奇,看.class文件也没出现copy函数,不知道怎么实现的TODO)0
Delegated properties
Custom delegate
/**
* There's some new syntax: you can say `val 'property name': 'Type' by 'expression'`.
* The expression after by is the delegate, because get() and set() methods
* corresponding to the property will be delegated to it.
* Property delegates don't have to implement any interface, but they have
* to provide methods named getValue() and setValue() to be called.</p>
*/
import kotlin.reflect.KProperty
class Example {
//声明一个成员变量,委派给了Delegate()
var p: String by Delegate()
//重写toString,返回值为"Example Class"
override fun toString() = "Example Class"
}
class Delegate {
operator fun getValue(thisRef: Any?, prop: KProperty<*>): String {
return "$thisRef, thank you for delegating '${prop.name}' to me!"
}
operator fun setValue(thisRef: Any?, prop: KProperty<*>, value: String) {
println("$value has been assigned to ${prop.name} in $thisRef")
}
}
fun main(args: Array<String>) {
val e = Example()
/**
打印e.p,输出结果是"Example Class, thank you for delegating 'p' to me!"
即调用toString的时候将toString返回的字符串放到了Delegate() 的 getValue()的第一个参数中,然后返回的是getValue的值
*/
println(e.p)
/**
* 给p赋值的时候调用了Delegate()的setValue(),会执行setValue()函数中的打印语句,输出:"NEW has been assigned to p in Example Class"
*/
e.p = "NEW"
}
kotlin中有像 val 'property name': 'Type' by 'expression' 这样的新语法 ,冒号后面如果是表达式,那这就被称作委派,因为关联到该属性的get()/set()函数会被委派出去;属性委派是不需要实现任何接口的,但他们需要被提供名为getValue()和setValue()的函数
基本用法是这样,还想不到怎么运用起来,毕竟入门嘛,先把概念稍微理清再说
Lazy property
/**
* Delegates.lazy() is a function that returns a delegate that implements a lazy property:
* the first call to get() executes the lambda expression passed to lazy() as an argument
* and remembers the result, subsequent calls to get() simply return the remembered result.
* If you want thread safety, use blockingLazy() instead: it guarantees that the values will
* be computed only in one thread, and that all threads will see the same value.
*/
class LazySample {
val lazy: String by lazy {
println("computed!")
"my lazy"
}
}
fun main(args: Array<String>) {
val sample = LazySample()
println("lazy = ${sample.lazy}")
println("lazy = ${sample.lazy}")
}
Delegates.lazy()是一个返回实现延迟属性的委托的函数:
第一次调用get()函数时执行的lambda表达式会传一个参数给lazy()函数,然后lazy()会记住这个结果,后面再调用get(),就会很轻松的得到第一次调用记录的结果.
注释里关于线程安全的内容好像已经过时了,找了个官方说法是:默认的lazy就是线程安全的,级别是LazyThreadSafetyMode.SYNCHRONIZED,它会确保如果我们的属性是第一次初始化,那它只会在单一线程进行;
如果我们已经确保线程是安全的,不需要Delegates去处理线程安全问题,那么可以对lazy函数更改线程安全级别为LazyThreadSafetyMode.NONE,例如下面的
val lazy: String by lazy(LazyThreadSafetyMode.NONE) {
println("computed!")
"my lazy"
}
除了还有一个级别是LazyThreadSafetyMode.PUBLICATION:如果多个线程同时进行初始化,那么lazy()会将第一个返回的结果存起来,以后拿来用
Observable property
/**
* The observable() function takes two arguments: initial value and a handler for modifications.
* The handler gets called every time we assign to `name`, it has three parameters:
* a property being assigned to, the old value and the new one. If you want to be able to veto
* the assignment, use vetoable() instead of observable().
*/
import kotlin.properties.Delegates
class User {
var name: String by Delegates.observable("no name") {
d, old, new ->
println("$old - $new")
}
}
fun main(args: Array<String>) {
val user = User()
user.name = "Carl"
}
observable()需要两个参数:初始值和一个处理改变的handler.
这个handler能在我们每次给name赋值的时候调用,它有三个参数:①被赋值的参数②它的旧值③它的新值
如果你想否决(取消?)这次赋值,用vetoable()代替observable()
注释看完没太明白vetoable()是在干嘛,那就写两句结合着输出理解吧
class Child {
/**
vetoable方法里的参数是初始值,后面表达式中的desc是该参数,old是旧值,new是新值;该表达式需要返回一个boolean;
这个boolean值为true时才能对该属性赋值,如果为false则不赋值,详细可见下面的例子
*/
var height:Int by Delegates.vetoable(100){ desc, old, new ->
new>old
}
}
fun main(args: Array<String>) {
val child=Child()
child.height=90
println("小孩的身高,第一次:${child.height}")//小孩初始身高是100,90>100? ->false,所以不赋值
child.height=110
println("小孩的身高,第二次:${child.height}")//小孩当前身高还是100,110>100? ->true,所以赋值,下面两个以此类推
child.height=130
println("小孩的身高,第三次:${child.height}")
child.height=80
println("小孩的身高,第四次:${child.height}")
}
输出的结果:
小孩的身高,第一次:100
小孩的身高,第二次:110
小孩的身高,第三次:130
小孩的身高,第四次:130
所以委派的vetoable()的使用大概就是这么回事啦
NotNull property
/**
* Users frequently ask what to do when you have a non-null var, but you don't have an
* appropriate value to assign to it in constructor (i.e. it must be assigned later)?
* You can't have an uninitialized non-abstract property in Kotlin. You could initialize it
* with null, but then you'd have to check every time you access it. Now you have a delegate
* to handle this. If you read from this property before writing to it, it throws an exception,
* after the first assignment it works as expected.
*/
import kotlin.properties.Delegates
class User {
var name: String by Delegates.notNull()
fun init(name: String) {
this.name = name
}
}
fun main(args: Array<String>) {
val user = User()
// user.name -> IllegalStateException
user.init("Carl")
println(user.name)
}
咱们在Kotlin中不能有未初始化的非抽象属性,我们可以将其初始化为null,但这之后当我们需要用它时,我们每次都要检查它,我们现在可以通过委托来处理它;
当咱们在它没赋值的时候访问它,就会抛一个异常,如果是赋值后再访问,他就能像我们期望的那样工作啦
notNull()委托也是针对于Kotlin的np-check而出现的,有了这个委托就不用每次进行一个非null判断,当然也可以通过声明该变量肯定不为null的方式来处理(在一个变量后加两个感叹号),就看情况选择了
Properties in map
/**
* Properties stored in a map. This comes up a lot in applications like parsing JSON
* or doing other "dynamic" stuff. Delegates take values from this map (by the string keys -
* names of properties). Of course, you can have var's as well,
* that will modify the map upon assignment (note that you'd need MutableMap instead of read-only Map).
*/
class User(val map: Map<String, Any?>) {
val name: String by map
val age: Int by map
}
fun main(args: Array<String>) {
val user = User(mapOf(
"name" to "John Doe",
"age" to 25
))
println("name = ${user.name}, age = ${user.age}")
}
参数存储在map中这种情况有很多,例如解析JSON或者别的动态玩意儿,委托能从这些map中获得值.
你当然也可以在map里存取变量,这会在分配变量时更改这个map(所以记住如果需要在map里存取变量,得使用一个可变的Map)
(这个章节的知识我想不到使用场景)