Kotlin学习记录

可Null性

java类型系统缺陷

例如String类型的变量可以持有两种值,分别是String的实例和 null,但两种值完全不一样(instanceof 运算符null不是String)。类型系统的缺陷导致即使变量拥有声明的类型 String你依然无法知道能对该变量的值做些什么,除非做额外的检查。

Kotlin解决NullPointerException之道:把运行时的错误转变成编译期的错误

  • 编译器强制检测变量实参,所有常见类型默认都是非空的,可以在任何类型的后面加?来表示这个类型的变量可以存储 null 引用。但是对可null参数进行的操作也会受到限制,需要对可null参数进行null处理(判断,安全符…)才可以编译通过。
  • Java 中的类型在Kotlin中被解释成平台类型,允许开发者把它们当作可空或非空来对待。
fun strLen(s: String?) = s?.length
  • 安全运算符 ?. 和 ?: 安全Null运算符
/**
 * ?.把一次 null 检查和一次方法调用合并成一个操作
 * 下面两个函数是等效的
 */
fun strLen(s: String?) = s?.length

fun strLenOld(s: String?) = {
    if (s != null) s.length else null
}

/**
 * Elvis 运算符 代替 null 的默认值,也可配合throw食用
 */
val t: String = s ?: ""

val t: String = s ?: throw IllegalArgumentException("String is null")

/**
 * 链式安全调用
 */
class Address(val streetAddress: String, val zipCode: Int, val city: String, val country: String)
class Company(val name: String, val address: Address ? )
class Person(val name: String, val company: Company?)

fun Person.countryName() :String{
    val companyStr = this.company?.address?.country
    return companyStr?:"UnKnow"
}

as? 安全类型转换运算符

/**
 * as? 尝试把值转换成指定的类型, 如果值不是合适的类型就返回 null
 * 配合Elvis 运算符
 */
val otherPerson = o as? Person?: return false

非Null断言!!

/**
 * !!可以把任何值转换成非空类型。如果对 null 值做非空断言,则会抛出异常。
 */
fun ignoreNulls(s: String?) {
    val sNotNullVal = s!! //如果s为null,此处抛出KotlinNullPointerException异常
    println(sNotNullVal)
}

let

/**
 * let:允许你对表达式求值,检查求值结果是否为 null ,并把结果保存为一个变量
 * 只在表达式不为 null 时执行 lambda,相当于
 * if (email != null)  sendEmailTo(email)
 */
email?.let { email -> sendEmailTo(email) }

lateinit 延迟初始化

/**
 * 不需要在构造方法中初始化它。如果在属性被初始化之前就访问了它,会抛出异常
 */
private lateinit var myService: MyService
  • Kotlin与Java Null操作 Java 的类型系统是不支持可空性的

Kotlin注解转换:Java 中的@ Nullable String 被 Kotlin 当作 String?,而@NotNull String 就是String

Kotlin编译器无法对Java的可Null类型检测,开发者负责正确处理来自 Java 的值。

数据类型

  • 对比Java

整数类型 Byte Short Int Long 浮点数类型 Float Double 字符类型 Char 布尔类型 Boolean

对比Java,Kotlin不区分基本数据类型和它们的包装类。对于变量、属性、参数和返回类型Kotlin编译器会尽可能编译为基本类型,除了泛型。

Kotiin不会自动地把数字从一种类型转换成另外一种,即便是转换成范围更大的类型。可以使用支持双向转换的函数,toByte(),toShort()… Kotlin 要求转换必须是显式的,可以避免转换数值的问题。

  • 根类型’Any’ ‘Any?’ Any类型是Kotiin所有非空类型的超类型,包括基础类型,区别于Java Object 只是所有引用类型的超类型,非基础类型

  • Unit 类型 和 Nothing 类型 Unit是Kotlin 的 ‘void’ ,区别是Unit是一个完备的类型,可以作为类型参数。只存在一个值是 Unit 类型,这个值也叫作 Unit,并且(在函数中)会被隐式地返回。

//下面两个函数等效
fun f () : Unit { . . . }
fun f () { .. }

interface Processor<T> {
    fun process(): T
}

class NoResultProcessor : Processor<Unit>{
    override fun process() {
        //不需要显示return 会隐式返回Unit
    }
}

Nothing 类型没有任何值,只有被当作函数返回值使用,表示函数永不返回

  • 集合数组

集合

List<Int?> : 列表中的单个值是可Null的 List<Int>? : 集合本身是可Null的,但列表中的元素保证是非空的 List<Int?>? :集合本身是可Null的,列表中的单个值是可Null的

可以使用filterNotNull()函数过滤集合

只读集合,可变集合

对比Java,Kotlin把访问集合数据的接口和修改集合数据的接口分开了。Collection接口(读),MutableCollection接口(写)

优势:让程序 中的数据发生的事情更容易理解,根据入参类型是可读或者可写的,很容易实现防御性编程,

fun<T> copyElements(source:Collection<T>,target:MutableCollection<T>){
    for (item in source){
        target.add(item)
    }
}

数组

Kotiin 中的一个数组是一个带有类型参数的类Array,其元素类型被指定为相应的类型参数。基本数据类型的数组使用像 IntArray 这样的特殊类来表示 。

语言特性-约定

重载

  • 二元运算符重载
表达式 函数名
a * b times
a / b div
a % b mod
a + b plus
a - b minus
/**
 * 定义一个名为plus的方法,operator重载+号运算符(扩展函数)
 */
operator fun Point.plus(other: Point): Point {
    return Point(x + other.x, y + other.y)
}

data class Point(val x: Int, val y: Int)

fun main(args: Array<String>) {
    val p1 = Point(10, 20)
    val p2 = Point(30, 40)
    println(p1 + p2)
}
    
  • 一元运算符重载

用于重载一元运算符的函数,没有任何参数。

表达式 函数名
+a unaryPlus
-a unaryMinus
!a not
++a,a++ inc
–a,a– dec
  • 比较运算符重载 如果在 Kotiin 中使用==(!=)运算符,它将被转换成 equals(!equals) 方法的调用。===恒等于等效于Java的==
data 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
    }
}
  • 重载(下标访问get/set),in,rangeTo,iterator
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 p1 = Point(10, 20)
    println("p1 x-> ${p1[0]}y-> ${p1[1]}")
}

解构声明和组件函数

解构声明允许你展开单个复合值,并使用它来初始化多个单独的变量。

/**
 * 数据类持有类型
 */
data class NamesComponents(val name: String, val ext: String)

fun splitFilename(fullName: String): NamesComponents {
    val reslut = fullName.split(".", limit = 2)
    return NamesComponents(reslut[0], reslut[1])
}

fun main(args: Array<String>) {
    //解构声明展开类
    val (name, ext) = splitFilename("ext.so")
    println("name:$name ext $ext")
}

解构声明可用于集合循环

for (entry in map.entries) {
val key = entry. cornponentl()
val value= entry.cornponent2()
}

委托属性

委托模式,这个模式应用于一个属性时,它也可以将访问器的逻辑委托给一个辅助对象。Kotlin中关键宇by可以用于任何符合属性委托约定规则的对象。

用处:Observable,Listener(编译器自动实现代码)Delegates.observable 函数可以用来添加属性更改的观察者

  • 委托属性

class Foo {
// 对p的get/set操作调用对应Delegate的get/setValue
var p: Type by Delegate ()
}

//委托属性应用:存储到MySql/Map

class Person {
    private val _attributes = hashMapOf<String, String> ()
    
    fun setAttribute(attrName: String, value: String) {
        _attributes[attrName] = value
    }
    //将map(_attributes) 作为委托属性 set/get name自动存储到Map
    val name: String by _attributes 
}
  • 惰性初始化 相比java的if(obj==null){//初始化并返回}else{//直接返回},Kotlin可以更简单做到

class Person(val name:String){
    val email by lazy { loadEmail(this) }

}