kotlin 之 杂谈上
in关键字的使用
//如果存在于区间(1,Y-1),则打印OK 【判断区间】 if (x in 1..y-1) print("OK") //如果x不存在于array中,则输出Out 【用于判断】 if (x !in 0..array.lastIndex) print("Out") //打印1到5 【判断区间】 for (x in 1..5) print(x) //遍历集合(类似于Java中的for(String name : names)) for (name in names) println(name) //如果names集合中包含text对象则打印yes 【判断包含】 if (text in names) print("yes")
when表达式
在Kotlin所有类中,有一个公共的superclass,名字就叫:Any。 所有没有显式声明基类的class,
它的默认父类就是***Any***
fun cases(obj: Any) { when (obj) { 1 -> print("第一项") "hello" -> print("这个是字符串hello") is Long -> print("这是一个Long类型数据") !is String -> print("这不是String类型的数据") else -> print("else类似于Java中的default") } }
is关键字
判断一个对象是否为一个类的实例,可以使用is关键字
与 Java 中的instanceof关键字类似,但在 Kotlin 中如果已经确定了一个对象的类型,可以在接下来的代码块中直接作为这个确定类型使用。
fun getStringLength(obj: Any): Int? { if (obj is String) { // 做过类型判断以后,obj会被系统自动转换为String类型 return obj.length } //同时还可以使用!is,来取反 if (obj !is String){ } // 代码块外部的obj仍然是Any类型的引用 return null }
**?. 与 ?: !! **
Kotlin 是空指针安全的,也就意味着你不会再看到那恼人的空指针异常。
例如这句代码 println(files?.size),只会在files不为空时执行。
?.就是当前面的变量!= nuil 时正常调用,如果为null就为null,!!就是当变量为null时,抛出空指针异常 (建议不要使用!! 使用就失去空安全的优点了)
?:操作符,elvis操作符,这个其实和可空类型没啥关系,这个也不是Java中的三目运算符,但是容易混淆
//当data不为空的时候,执行语句块 data?.let{ //... } //相反的,以下代码当data为空时才会执行 data?:let{ //... }
open 修饰符
Kotlin 默认会为每个变量和方法添加 final 修饰符。这么做的目的是为了程序运行的性能,其实在 Java 程序中,你也应该尽可能为每个类添加final 修饰符( 见 Effective Java 第四章 17 条)。
为每个类加了final也就是说,在 Kotlin 中默认每个类都是不可被继承的。如果你确定这个类是会被继承的,那么你需要给这个类添加 open 修饰符。
internal 修饰符
Java 有三种访问修饰符,public/private/protected,还有一个默认的包级别访问权限没有修饰符。
1、private
2、protected:一个包成员不能被定义为 protected.
3、internal:如果一个定义为 internal 的包成员的话,对所在的整个 module 可见。如果它是一个其他领域的成员,它就需要依赖那个领域的可见性。如有一个 private 类,那么它的 internal 修饰的函数的可见性会限制与它所在的该类的可见性。可以访问同一个 module 中的 internal 修饰的类,但不能访问其他 module的。
4、public: 仅受限于它的领域。一个定义为 public 的成员被包含在一个 privaet 修饰的勒种,这个成员在这个类之外也是不可见的。
枚举类
在 Kotlin 中,每个枚举常量都是一个对象。枚举常量用逗号分隔
其实在 Kotlin 中,枚举的本质是一个实现了Comparable的 class,其排序就是按照字段在枚举类中定义的顺序来的。
enum class Programer { JAVA, KOTLIN, C, CPP, ANDROID; }
sealed 密封类
sealed 修饰的类称为密封类,用来表示受限的类层次结构。例如当一个值为有限集中的 类型、而不能有任何其他类型时。在某种意义上,他们是枚举类的扩展:枚举类型的值集合也是受限的,但每个枚举常量只存在一个实例,而密封类的一个子类可以有可包含状态的多个实例。
data 数据类
data 修饰的类称之为数据类。它通常用在我们写的一些 POJO 类上。
当 data 修饰后,会自动将所有成员用operator声明,即为这些成员生成类似 Java 的 getter/setter 方法。
operator 操作符重载
Kotlin 有一些固定数量象征性的操作符,可以在任何类中很容易地使用。方法是创建一个方法,方法名为保留的操作符关键字,这样就可以让这个操作符的行为映射到这个方法。
inline (内联函数) 内联函数与普通的函数有点不同。一个内联函数会在编译的时候被替换掉,而不是真正的方法调用。这在译写情况下可以减少内存分配和运行时开销。例如,有一函数只接收一个函数作为它的参数。如果是普通函数,内部会创建一个含有那个函数的对象。而内联函数会把我们调用这个函数的地方替换掉,所以它不需要为此生成一个内部的对象。 // 例一、创建代码块只提供 Lollipop 或更高版本来执行 inline fun supportsLollipop(code: () -> Unit) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { code() } } // usage supportsLollipop { window.setStatusBarColor(Color.BLACK) }
lazy、lateinit 用于延迟初始化,第一次使用时再实例化
1、包含一个 lambda,当第一次执行 getValue 时该 lambda 会被调用,所以该属性可以被延迟初始化。之后的调用都只会返回同一个值。
2、lazy 操作符是线程安全的。
3、如果不担心多线程问题或想提高更多的性能,可以使用 lazy(LazyThreadSafeMode.NONE) { … }
4、一般 lazy 委托的代码块可以阻止在多个不同的线程中创建多个对象。
两者区别:
by lazy 修饰val的变量
lateinit 修饰var的变量,且变量是非空的类型
class App : Application() { val database: SQLiteOpenHelper by lazy { MyDatabaseHelper(applicationContext) } override fun onCreate() { super.onCreate() val db = database.writeableDatabase } }
“by” 关键字
在扩展一个类并重写某些方法时,你的代码就变得依赖你自己继承的那个类的实现细节了。当你的基类的实现被修改或者新方法被添加进去,你做出的类行为的假设会失效,所以你的代码也许最后就会以不真确的行为告终。
因为有以上原因,kotlin的默认类都被修饰 final 不可更改的。
实现:by 委托,它的实现思想其实跟设计模式中的装饰器模式一样
// 定义一个接口,和一个方法 show() interface Base { fun show() } // 定义类实现 Base 接口, 并实现 show 方法 open class BaseImpl : Base { override fun show() { print("BaseImpl::show()") } } // 将Base中所有的实现都委托给base 对象,这个类本身就不需要去实现Base接口中的方法 class BaseProxy(base: Base) : Base by base // main 方法 fun main(args: Array<String>) { val base = BaseImpl() BaseProxy(base).show() } 分析:每次在实现接口时,都需要实现接口中的所有方法,by也就是将 Base 接口中需要实现的 方法都委托给BaseImpl 类去实现,所以这里这样写 :Base by base 其实也就是说,我实现了接口 Base,但我本身不实现Base接口中的方法,委托给base帮我实现,那么在调用的时候就传入有具体实现了Base接口的类,方便我们的委托。 这里: // 传入了BaseImpl类的引用,BaseImpl 必须是实现了Base接口的类 val base = BaseImpl() // 传入了BaseImpl类的引用,这里在调用的时候也是调用BaseImpl中的方法 BaseProxy(base).show() /* 在这里就把整个接口的实现委托给 持有 base 引用的类去实现接口Base中具体的方法 不代表Base接口在其他地方一定要有被其他实体类实现了才可以使用by 委托,即使在其他地方没 有实现Base 接口也可以使用 Base接口的对象去委托 */ class BaseProxy(base: Base) : Base by base
let
默认当前这个对象作为闭包的it参数,返回值是函数里面最后一行
fun testLet(){ "Rocker".let { print(it) print(it) } }
apply
调用对象的apply函数,在函数范围内,可以任意调用该对象的任意方法,并返回该对象
fun testApply(){ mutableListOf<String>().apply { add("jim") add("lili") add("Rocker") }.add("To test whether returns this object") }
Observable
1、该委托可以检测希望观察的属性变化。当被观察属性的 set 方法被调用时,它就会自动执行我们指定的 lambda 表达式。所以一旦该属性被赋予了新值,则可以收到被委托的属性、旧值和新值。
class ViewModel(val db: MyDatabase) { var myProperty by Delegates.observable("") { d,old,new -> db.saveChanges(this,new) } }
Vetoable
一个特殊的 observable, 可以来决定是否保存这个值。在真正保存之前进行一些条件判断。
var positiveNumber = Delegates.vetoable(0) { d, old, new -> new >= 0 } // 上面这个委托只允许在新的值是正数时执行保存。在 lambda 中,最后一行表示返回值。不需要使用 return 关键字(实质上不能被编译)
Not Null
1、场景1:需要在某些地方初始化该属性,但不能在构造函数中确定,或不能在构造函数中做任何事。
2、场景2:在 Activity fragment service receivers…中,一个非抽象的属性在构造函数执行之前需要被赋值。
3、解决方案1:使用可 null 类型并且赋值为 null,直到真正去赋值。氮素,在使用时就需要不停的进行 not null 判断。
4、解决方案2:使用 notnull 委托。含有一个可 null 的变量并会在设置该属性时分配一个真实的值。如果该值在被获取之前没有被分配,它就会抛出一个异常。
class App : Application() { companion object { var instance: App by Delegates.notnull() } override fun onCreate() { super.onCreate() instance = this } }
Companion
官方描述到这是 伴生 对象, companion 本身也是伴侣的意思. 外围内会持有 companion 对象的一个静态常年字段, 伴随着外围类 的加载而诞生
关于在 compain 中定义的变量的理解:
companion 中定义的变量通通都会在 外围类 定义成一个 私有 静态字段.
如果变量为 val , 那么 对应的 java 便是 static final.
变量为 private , 也就意味着 companion 中 不会为这个变量生成对应的访问方法,
我们可以看到, 其实 companion 就是个 空壳子, 字段不会位于 companion 类中, 只有方法会定义在 companion 内部.
companion 中的方法都不是静态方法, 其实我们访问任何 companion 中的字段都是通过 外围类持有的 companion 实例对象来访问的, 方法也是如此, 而且 companion 的构造方法也是 private 的
总结: companion 其实和 java 中的静态方法和字段很像, 只是我们访问这些字段和方法时需要通过 companion 实例对象来绕一下, 而外围类永远只会持有一个 companion 实例对象, 所以这和静态的结果没什么太大的区别.
从 Map 中映射值
另一种委托方式,属性的值会从一个map中获取 value,属性的名字对应这个map 中的 key。
import kotlin.properties.getValue class Configuration(map: Map<String,Any?>) { val width: Int by map val height: Int by map val dp: Int by map val deviceName: String by map } // usage conf = Configuration(mapof( "width" to 1080, "height" to 720, "dp" to 240, "deviceName" to "myDecive" ))
custom delegate
自定义委托需要实现 ReadOonlyProperty / ReadWriteProperty 两个类,具体取决于被委托的对象是 val 还是 var
// step1 private class NotNullSingleValueVar<T>() : ReadWriteProperty<Any?, T> { private var value: T? = null override fun getValue(thisRef: Any?, property: KProperty<*>): T { return value ?: throw IllegalStateException("${desc.name not initialized}") } override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { this.value = if (this.value == null) value else throw IllegalStateException("${desc.name} already initialized") } } // step2: usage object DelegatesExt { fun notNullSingleValue<T>(): ReadWriteProperty<Any?, T> = NotNullSingleValueVar() }
集合和函数操作
1、Iterable: 父类。可以遍历一系列的都是实现这个接口
2、MutableIterable: 一个支持便利的同时可以执行删除的 Iterables
3、Collection:
4、MutableCollection:支持增加删除item 的 collection。提供了额外的函数,如 add、remove、clear等
5、List: 范性有序集合。
6、MutableList: 支持增删item 的 List
7、Set: 无序并不支持重复 item 的 集合
8、MutableSet: 支持增删item 的 Set
9、 Map:
10、MutableMap: 支持增删 item 的 map