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) }
}