概述
1. 解构声明
首先看下写法:val (name, age) = person。其中name以及age可以单独使用。
println(name)
println(age)
解构声明也可以用在for循环中:
for ((a, b) in collection) { …… }
如果解构出来的变量如果有某个参数不会使用,那么可以使用_来替换。
val (_, status) = getResult()
解构也可以用在lambda中的参数:
map.mapValues { entry -> "${entry.value}!" }
map.mapValues { (key, value) -> "$value!" }
map.mapValues { (_, value) -> "$value!" }
2. 类型检测与类型转换:“is”与“as”
2.1 is
与 !is
操作符
is
与 !is
操作符用来判断某个实例是否属于某种类型。而且kotlin在判断之后会对该转换的结果做记录以便后续操作。
if (obj is String) {
print(obj.length)// x 自动转换为字符串
}
if (obj !is String) { // 与 !(obj is String) 相同
print("Not a String")
}
else {
print(obj.length)
}
有一点需要注意的是,在检测和使用之间如果有可能被改变的时候,该类型转换就不能用。
2.2 as转换操作符
as转换操作的话可分为安全和不安全的,如下所示:
val x: String = y as String//如果y不能转换为String,那么该方法报错
val x: String? = y as? String
2.3 类型擦除与泛型检测
由于类型擦除,所以泛型检测以及类型转换都是被禁止的。泛型检测的时候,可以检测星投影。
if (something is List<*>) {
something.forEach { println(it) } // 这些项的类型都是 `Any?`
}
也可以带有具体化的类型参数的内联函数,那么就可以进行arg is T检测。但如果arg是泛型实例的话,还是会被擦除的。
inline fun <reified A, reified B> Pair<*, *>.asPairOf(): Pair<A, B>? {
if (first !is A || second !is B) return null
return first as A to second as B
}
val somePair: Pair<Any?, Any?> = "items" to listOf(1, 2, 3)
val stringToSomething = somePair.asPairOf<String, Any>()
val stringToInt = somePair.asPairOf<String, Int>()
val stringToList = somePair.asPairOf<String, List<*>>()
val stringToStringList = somePair.asPairOf<String, List<String>>() // List<String>是泛型实例
3. This 表达式
this有三种方式
-
在类的成员中,this 指的是该类的当前对象。
-
在扩展函数或者中, this 表示在点左侧传递的 接收者 参数。
-
限定的 this。使用
this@label
,其中@label
是一个代指 this 来源的标签。
4. 相等性
Kotlin 中有两种类型的相等性:
- 结构相等(用
equals()
检测); - 引用相等(两个引用指向同一对象)
5. 操作符重载
首先是一元函数,很简单他就是用操作符来代替函数。
表达式 | 翻译为 |
---|---|
+a | a.unaryPlus() |
-a | a.unaryMinus() |
!a | a.not() |
二元函数的话
表达式 | 翻译为 |
---|---|
a++ | a.inc() |
a-- | a.dec() |
这边要注意下++a和a++的区别。
++a是先计算然后结果,而a++是先返回a值再对a进行inc()操作。
二元操作
表达式 | 翻译为 |
---|---|
a + b | a.plus(b) |
a - b | a.minus(b) |
a * b | a.times(b) |
a / b | a.div(b) |
a % b | a.rem(b) 、 a.mod(b) (已弃用) |
a..b | a.rangeTo(b) |
“In”操作符
表达式 | 翻译为 |
---|---|
a in b | b.contains(a) |
a !in b | !b.contains(a) |
索引访问操作符
表达式 | 翻译为 |
---|---|
a[i] | a.get(i) |
a[i, j] | a.get(i, j) |
a[i_1, ……, i_n] | a.get(i_1, ……, i_n) |
a[i] = b | a.set(i, b) |
a[i, j] = b | a.set(i, j, b) |
a[i_1, ……, i_n] = b | a.set(i_1, ……, i_n, b) |
调用操作符
表达式 | 翻译为 |
---|---|
a() | a.invoke() |
a(i) | a.invoke(i) |
a(i, j) | a.invoke(i, j) |
a(i_1, ……, i_n) | a.invoke(i_1, ……, i_n) |
广义赋值
表达式 | 翻译为 |
---|---|
a += b | a.plusAssign(b) |
a -= b | a.minusAssign(b) |
a *= b | a.timesAssign(b) |
a /= b | a.divAssign(b) |
a %= b | a.remAssign(b) |
这边有个要注意,就是如果plusAssign和plus都定义了那么会冲突,就会报告错误。
相等与不等操作符
表达式 | 翻译为 |
---|---|
a == b | a?.equals(b) ?: (b === null) |
a != b | !(a?.equals(b) ?: (b === null)) |
比较操作符
表达式 | 翻译为 |
---|---|
a > b | a.compareTo(b) > 0 |
a < b | a.compareTo(b) < 0 |
a >= b | a.compareTo(b) >= 0 |
a <= b | a.compareTo(b) <= 0 |
6. 空安全
先说说?这个符号。默认情况下,常规初始化意味着非。如果允许为空,我们可以声明一个变量为可空字符串,可以在变量右侧加上?。那么使用的时候,如果碰到不能为空的地方就会报错。
var a: String = "abc" // 默认情况下,常规初始化意味着非空
a = null // 编译错误
var b: String? = "abc" // 可以设置为空
b = null // ok
print(b)
如果想安全的调用,可以在变量的右侧加上?符号。那么表示如果为空的时候返回null。
安全调用也可以出现在赋值的左侧。这样,如果调用链中的任何一个接收者为空都会跳过赋值,而右侧的表达式根本不会求值:
6.1 Elvis 操作符
如果加上?之后碰到空值,此时不想发挥null。怎么办呢?可以使用Elvis 操作符
val name = node.getName() ?: throw IllegalArgumentException("name expected")//如果空操作符,那么抛出遗产
6.2 !!
操作符
如果想要为空的时候抛出遗产,那么可以在变量右侧加上!!
val aInt: Int? = a as? Int
7. 异常
其他和java是差不多的。这边主要是以下几点:
- Kotlin 没有受检的异常。java可以写成Appendable append(CharSequence csq) throws IOException,而java却不行。
- Nothing 类型。Nothing类型有两种情况的时候。第一种是
throw
是表达式,throw表达式的类型是特殊类型
Nothing。在throw表达式之后不会执行。第二种是用null来初始化一个变量,那么它就是一个Nothing类型。
val x = null // “x”具有类型 `Nothing?`
val l = listOf(null) // “l”具有类型 `List<Nothing?>
8. 注解
注解是将元数据附加到代码的方法。注解的声明就是将annotation放在所要注解的对象前面。如下所示:
annotation class Fancy
注解的附加属性可以通过用元注解标注注解类来指定:
-
@Target
指定可以用该注解标注的元素的可能的类型(类、函数、属性、表达式等); -
@Retention
指定该注解是否存储在编译后的 class 文件中,以及它在运行时能否通过反射可见 (默认都是 true); -
@Repeatable
允许在单个元素上多次使用相同的该注解; -
@MustBeDocumented
指定该注解是公有 API 的一部分,并且应该包含在生成的 API 文档中显示的类或方法的签名中。
如果是构造函数的时候,那么需要将constructor写出来
class Foo @Inject constructor(dependency: MyDependency) { …… }
也可以标注属性访问器。
class Foo {
var x: MyDependency? = null
@Inject set
}
可以使用构造函数作为注解,此时需要加入参数。
annotation class Special(val why: String)
@Special("example") class Foo {}
允许的参数类型有:
-
对应于 Java 原生类型的类型(Int、 Long等);
-
字符串;
-
类(
Foo::class
); -
枚举;
-
其他注解(使用其他注解时,可以省略@);
-
上面已列类型的数组。
Lambda 表达式也可以使用注解。
8.2 注解使用处目标
在使用的时候可以指定如何生成该注解。
class Example(@field:Ann val foo, // 标注 Java 字段
@get:Ann val bar, // 标注 Java getter
@param:Ann val quux) // 标注 Java 构造函数参数
可以使用注解来标注整个文件,不过得把注解放在顶层:
@file:JvmName("Foo")
package org.jetbrains.demo
如果对同一目标有多个注解,可以使用方括号并将所有注解放在方括号内:
class Example {
@set:[Inject VisibleForTesting]
var collaborator: Collaborator
}
支持的使用处目标的完整列表为:
file
;property
(具有此目标的注解对 Java 不可见);field
;get
(属性 getter);set
(属性 setter);receiver
(扩展函数或属性的接收者参数);param
(构造函数参数);setparam
(属性 setter 参数);delegate
(为委托属性存储其委托实例的字段)。
要标注扩展函数的接收者参数,请使用以下语法:
fun @receiver:Fancy String.myExtension() { ... }
如果没有用Target来指定目标的话,可以在以下目标中找出合适的目标:
param
property
field
9. 反射
反射的话主要有三种类型的引用:类引用、函数引用以及属性引用
9.1 类引用
类引用最简单,可以使用 类字面值 语法:
val c = MyClass::class
9.2 函数引用
如果一个具名的函数如下所示:
fun isOdd(x: Int) = x % 2 != 0
那么我们可以使用::isOdd来使用该函数。
如果你是用类的成员函数或者扩展函数,比如说你可以使用String::toCharArray。
即使以扩展函数的引用初始化一个变量,其推断出的函数类型也会没有接收者(它会有一个接受接收者对象的额外参数)。如需改为带有接收者的函数类型,请明确指定其类型:
val isEmptyStringList: List<String>.() -> Boolean = List<String>::isEmpty
9.3 属性引用
属性引用的话也可以使用::来获取
val x = 1
fun main() {
println(::x.get())
println(::x.name)
}
要访问属于类的成员的属性,我们这样限定它:
class A(val p: Int)
val prop = A::p
println(prop.get(A(1)))
对于扩展属性:
val String.lastChar: Char
get() = this[length - 1]
fun main() {
println(String::lastChar.get("abc"))
}
10. 作用域函数
作用域函数一共有五种:let
、run
、with
、apply
以及 also
。
这五种的函数非常相似,主要是从两个方面加以区分:
- 引用上下文对象的方式
- 返回值
10.1 引用上下文对象的方式有两种:it和this
当你主要是对上下文对象成员进行操作的时候,比较适合使用this。具体例子如下所示:
val adam = Person("Adam").apply {
age = 20 // 和 this.age = 20 或者 adam.age = 20 一样
city = "London"
}
println(adam)
当你把上下文当作参数的使用的时候,那就比较适合用it。若在代码块中使用多个变量,则 it
也更好。当然也可以不用it而使用自定义的名称。
fun getRandomInt(): Int {
return Random.nextInt(100).also {
writeToLog("getRandomInt() generated value $it")
}
}
val i = getRandomInt()
10.2 返回值
同样也有两种。一种是返回上下文对象,另外一种是返回Lambda 表达式结果。
10.3 函数选择
函数选择的话,官方给出了一系列表格和详细参数,这边直接拷贝过来:
函数 | 对象引用 | 返回值 | 是否是扩展函数 |
---|---|---|---|
let | it | Lambda 表达式结果 | 是 |
run | this | Lambda 表达式结果 | 是 |
run | - | Lambda 表达式结果 | 不是:调用无需上下文对象 |
with | this | Lambda 表达式结果 | 不是:把上下文对象当做参数 |
apply | this | 上下文对象 | 是 |
also | it | 上下文对象 | 是 |
以下是根据预期目的选择作用域函数的简短指南:
-
对一个非空(non-null)对象执行 lambda 表达式:
let
-
将表达式作为变量引入为局部作用域中:
let
-
对象配置:
apply
-
对象配置并且计算结果:
run
-
在需要表达式的地方运行语句:非扩展的
run
-
附加效果:
also
-
一个对象的一组函数调用:
with
10.4 takeIf 与 takeUnless
在作用域函数之前也可以加一个过滤,这也就是takeIf或者takeUnless。逻辑也很简单,看个例子就够了:
fun displaySubstringPosition(input: String, sub: String) {
input.indexOf(sub).takeIf { it >= 0 }?.let {
println("The substring $sub is found in $input.")
println("Its start position is $it.")
}
}
displaySubstringPosition("010000011", "11")//有执行结果,因为takeIf已经做了过滤通过
displaySubstringPosition("010000011", "12")//没有执行结果,以为已经在执行let之前已经被踢掉了
11 总结
到此kotlin学习告一段落。重点还是实践中去巩固知识以及深入理解。
最后
以上就是任性西装为你收集整理的Kotlin学习笔记之剩余部分1. 解构声明2. 类型检测与类型转换:“is”与“as”3. This 表达式4. 相等性5. 操作符重载6. 空安全7. 异常8. 注解9. 反射10. 作用域函数11 总结的全部内容,希望文章能够帮你解决Kotlin学习笔记之剩余部分1. 解构声明2. 类型检测与类型转换:“is”与“as”3. This 表达式4. 相等性5. 操作符重载6. 空安全7. 异常8. 注解9. 反射10. 作用域函数11 总结所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复