我是靠谱客的博主 任性西装,最近开发中收集的这篇文章主要介绍Kotlin学习笔记之剩余部分1. 解构声明2. 类型检测与类型转换:“is”与“as”3. This 表达式4. 相等性5. 操作符重载6. 空安全7. 异常8. 注解9. 反射10. 作用域函数11 总结,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

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. 操作符重载

首先是一元函数,很简单他就是用操作符来代替函数。

表达式翻译为
+aa.unaryPlus()
-aa.unaryMinus()
!aa.not()

二元函数的话

表达式翻译为
a++a.inc()
a--a.dec()

这边要注意下++a和a++的区别。

++a是先计算然后结果,而a++是先返回a值再对a进行inc()操作。

二元操作

表达式翻译为
a + ba.plus(b)
a - ba.minus(b)
a * ba.times(b)
a / ba.div(b)
a % ba.rem(b)a.mod(b) (已弃用)
a..ba.rangeTo(b)

“In”操作符

表达式翻译为
a in bb.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] = ba.set(i, b)
a[i, j] = ba.set(i, j, b)
a[i_1, ……, i_n] = ba.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 += ba.plusAssign(b)
a -= ba.minusAssign(b)
a *= ba.timesAssign(b)
a /= ba.divAssign(b)
a %= ba.remAssign(b)

这边有个要注意,就是如果plusAssign和plus都定义了那么会冲突,就会报告错误。

相等与不等操作符

表达式翻译为
a == ba?.equals(b) ?: (b === null)
a != b!(a?.equals(b) ?: (b === null))

比较操作符

表达式翻译为
a > ba.compareTo(b) > 0
a < ba.compareTo(b) < 0
a >= ba.compareTo(b) >= 0
a <= ba.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. 作用域函数

作用域函数一共有五种:letrunwithapply 以及 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 函数选择

函数选择的话,官方给出了一系列表格和详细参数,这边直接拷贝过来:

函数对象引用返回值是否是扩展函数
letitLambda 表达式结果
runthisLambda 表达式结果
run-Lambda 表达式结果不是:调用无需上下文对象
withthisLambda 表达式结果不是:把上下文对象当做参数
applythis上下文对象
alsoit上下文对象

以下是根据预期目的选择作用域函数的简短指南:

  • 对一个非空(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 总结所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(51)

评论列表共有 0 条评论

立即
投稿
返回
顶部