Kotlin官方参考整理——01.开始

前言

这是前段时间学习Kotlin官方参考( / )时所做的笔记,相比官方参考,这些笔记:

  1. 为了方便根据笔记去查阅官方参考,笔记的整体结构与官方参考基本保持一致
  2. 对官方参考中一些零散分布的知识点进行了整合
  3. 对官方参考中个别讲的比较笼统的知识点做了详细展开
  4. 将官方参考中一些比较晦涩的知识点,使用个人认为更容易理解的方式进行了阐述
  5. 笔记并未100%覆盖官方参考中的内容

这份笔记基本涵盖了Kotlin的大部分知识点,可以作为 Kotlin快速入门 或是 官方参考的导读 来使用。

另外,因为个人也正处于学习Kotlin的过程中,因此笔记中的理解和论述难免会有错误,希望读者朋友不吝指正,大家共同学习进步。

1开始

1.1 基本语法

1.1.1 定义包

package foo.bar

//kotlin中语句结尾不需要写分号
var aaa = 1

fun baz() {

}

class Goo {

}
……

包的声明应处于源文件顶部。不同于Java,Kotlin中包名可以不用与源文件所在的目录结构对应,也就是说源文件可以随便移动而不用修改其包名。

Kotlin源文件的结构与C++类似,即变量、函数、类可以同级(如上面代码所示),而Java中,变量和函数都在类中。

源文件所有内容都包含在声明的包内。所以上例中 baz() 的全名是 foo.bar.baz 、Goo 的全名是foo.bar.Goo。如果没有指明包,该文件的内容属于无名字的默认包。

1.1.2 访问权限修饰符(又称为可见性修饰符)

Kotlin中变量、函数、类有三种位置,分别是:

  • 顶层位置:即在源文件中顶层
  • 类中
  • 局部位置:即在代码块中或函数中

Kotlin中有四种可见性修饰符,分别为public(默认)、internal、protected、private。可以修饰顶层位置和类中的变量、函数、类,不能修饰局部位置的变量、函数、类。

顶层位置的可见性修饰符:

  • public:随处可见,默认。
  • internal:本模块内可见。
  • protected:不可用于顶层位置的变量、函数、类。
  • private:本文件内可见。

类中的可见性修饰符:

  • public:能见到类的任何人都可见。
  • internal:在本模块内,能见到类的任何人都可见。
  • protected:本类和子类中可见。
  • private:本类中可见。

关于可见性的应用,举一个简单直观的例子:

这里写图片描述

/*
 *文件File1.kt
 */
package cn.szx.kotlindemo

//默认可见性为public
fun function1(){
    print("function1 in File1")
}

//---------------------------------------------------------------------------------------

/*
 *文件File2.kt
 */
package cn.szx.kotlindemo

fun function2(){
    //File2中可以访问File1中定义的函数function1()
    //并且因为File2和File1在同一个包内,因此访问function1()时不用加包名前缀
    function1()
}

//编译报错,不能在同一个包内再定义一个function1()了
//因为此处的function1()和File1中的function1(),其全名都是cn.szx.kotlindemo.function1()
//系统无法区分二者
//fun function1(){
//
//}

//function1(a:Int)与File1中的function1()形参列表不同,可以区分,因此可以定义
fun function1(a:Int){
    print("function1 in File2"+a)
}

//---------------------------------------------------------------------------------------

/*
 *文件File3.kt
 */
package cn.szx.kotlindemo2

//import cn.szx.kotlindemo.function1

fun function3(){
    //File3和File1不在同一个包内,因此访问function1()时需要加包名前缀
    cn.szx.kotlindemo.function1()

    //使用“import cn.szx.kotlindemo.function1”将function1导入后,则可以省略前缀
    //“import cn.szx.kotlindemo.function1”代表将cn.szx.kotlindemo包内所有名为function1的函数都导入了
    //function1()
}

//允许定义function1()。与File1中的function1()的包名前缀不同,因此系统可以区分
fun function1(){
    print("function1 in File3")
}

1.1.3 定义函数

基本规则:使用fun关键字来定义函数,形参的定义格式为“形参名:形参类型”。

//带有两个 Int 参数、返回 Int 的函数
fun sum(a: Int, b: Int): Int {
    return a + b
}

//将表达式作为函数体、返回值类型⾃动推断的函数
fun sum(a: Int, b: Int) = a + b

//没有返回值的函数
fun printSum(a: Int, b: Int) {
    println("sum of $a and $b is ${a + b}")
}

1.1.4 定义变量

注意:abstract只能修饰成员位置(即类中)的变量和函数,不能修饰顶层位置和局部位置的变量和函数。

定义变量的基本格式:

var a:Int = 1//可变变量
val b:Int = 2//不可变变量
var c = 3//根据等号右边的值自动推断c的类型
var d:Int//定义时不进行初始化,则Int不能省略

var定义的是可变变量,即一般的变量,val定义的是不可变变量,即符号常量,类似于Java中的final变量或者C++中的const变量。

此外,处于不同位置的变量,其初始化要求是不同的:

  • 顶层变量必须进行初始化。(除非其没有幕后字段,详见《03类和对象1.md》->“属性”)
  • 类中的变量要么进行初始化(可以直接后接等号进行初始化,也可以在构造函数或初始化块(init块)中初始化),要么声明为abstract。(除非其没有幕后字段,详见《03类和对象1.md》->“属性”)
  • 局部变量可以不初始化。
package cn.szx.kotlindemo

var a: Int = 1              //顶层变量,必须进行初始化

private class Test{
    //abstract var a:Int    //类中的变量,声明为抽象
    var b: Int = 2          //类中的变量,初始化
}


private fun baz() {
    var a: Int              //局部变量,可以不初始化
}

1.1.5 字符串

字符串用String类型表示。字符串是不可变的。字符串的元素——字符可以使用索引运算符访问: s[i] 。可以用for循环迭代字符串:

for (c in str) {
    println(c)
}

Kotlin有两种类型的字符串字面值: 转义字符串和原生字符串。

转义字符串支持转义字符,很像Java中的字符串:val s = "Hello, world!\n"

原生字符串使用三个引号( “”” )分界符括起来,不支持转义,任何字符都会被原样展示(包括换行、空格、tab等):

val text = """
    for (c in "foo")
        print(c)
    """

字符串模板

字符串可以包含模板表达式,使用时会用模板表达式的值来替换掉模板表达式。模板表达式以美元符( $ )开头,由一个简单的名字构成,如:

val i = 10
val s = "i = $i" // s为"i = 10"

或者用花括号扩起来的任意表达式:

val s = "abc"
val str = "$s.length is ${s.length}" // str为"abc.length is 3"

转义字符串和原生字符串都支持模板表达式,那如果我们想要的就是$怎么办呢——使用${'$'}

1.1.6 条件表达式

Java中的很多语句,在kotlin中都既是语句,也是表达式(即本身可以代表一个值,可以放在等号右边)。例如if表达式:


//if的分支可以是代码块,最后的表达式的值作为该块的值
val max = if (a > b) {
    print("Choose a")
    a
} else {
    print("Choose b")
    b
}

//也可以像下面这样,类似于Java中的?:表达式
val max = if (a > b) a else b

1.1.7 空安全

1.1.7.1 非空类型与可空类型

默认情况下,所有类型都是非空的,即我们告诉编译系统,该类型变量的值不可能是null,因此我们也不能将null赋值给该类型的变量:

var a: String = null    //非空类型取值不能为null,编译报错
var b: String = "hello"
val l = b.length        //因为b是非空类型的,所以b.length必然是安全的,不可能报出NPE(Null Pointer Exception)

在类型关键字后加上,那么就得到了一个可空类型,如String?

var b: String? = null   //可空类型,取值可以为空
val l = b.length        //编译报错,因为b是可空类型,这样调用是不安全的

那么我们想要获得可空的b的length属性该怎么办呢?有两种方法:

(1) 先进行空检测

val l = if (b != null) b.length else -1

//也支持更复杂(更智能的检测)
if (b != null && b.length > 0) {
    print("String of length ${b.length}")
} else {
    print("Empty string")
}

(2) 使用安全调用操作符“?.”

val l = b?.length

如果b非空,就返回b.length,否则返回null,这个表达式的类型是Int?。

安全调用操作符在链式调用中很有用,例如:

bob?.department?.head?.name

只要任何一个环节为null,那么该链式调用就会返回null。

如果要只对非空值执行某个操作,可以将安全调用操作符与let一起使用:

val listWithNulls: List<String?> = listOf("A", null)
for (item in listWithNulls) {
    item?.let { println(it) }   //输出A并忽略null
}

1.1.7.2 Elvis操作符

表达式1 ?: 表达式2

含义:如果表达式1不是null,那么就返回表达式1的值,否则就返回表达式2的值。注意,当且仅当表达式1的值为null时,才会对表达式2进行求值。

throw和return在Kotlin中也都是表达式,所以它们也可以用在elvis操作符右侧。这可能会非常方便:

fun foo(node: Node): String? {
    val parent = node.getParent() ?: return null
    val name = node.getName() ?: throw IllegalArgumentException("name expected")
    ……
}

1.1.7.3 !!操作符

这是为NPE爱好者准备的:

var a: String? = null   //a可空
val l = a!!.length      //如果引用a为null,则抛出NPE,如果不为null,则返回a.length

1.1.7.4 过滤集合中的非空元素

如果你有一个可空类型元素的集合,并且想要过滤得到非空元素,你可以使用filterNotNull来实现:

val nullableList: List<Int?> = listOf(1, 2, null, 4)
val intList: List<Int> = nullableList.filterNotNull()

1.1.8 类型检测与转换

1.1.8.1 类型检测与智能类型转换

类型检测使用is或!is操作符。在多数情况下,不需要在Kotlin中使用显式转换操作符,因为编译器会跟踪不可变值的is检查,并在需要时自动进行类型转换:

if (obj is String) {
    print(obj.length)   //obj被自动转换为String
}

if (obj !is String) {
    print("Not a String")
}else {
    print(obj.length)   //obj被自动转换为String
}

if (x !is String) return
print(x.length)         //x被转换为字符串,可见编译器是十分智能的

//||右侧的x被自动转换为字符串
if (x !is String || x.length == 0) return
//&&右侧的x被自动转换为字符串
if (x is String && x.length > 0) {
    print(x.length)
}

但是需要注意,当编译器不能保证变量在检查和使用之间不可改变时,智能转换不能用(详见官方参考)。

1.1.8.2 类型转换操作符as

val b:String = a as String

当无法转换时会抛出类型转换异常。

1.1.8.3 安全的类型转换操作符as?

val b:String? = a as? String

当无法转换时不会抛出类型转换异常,而是返回null。

1.1.9 For循环

Java中有普通的for循环和foreach循环(也称为高级for循环),但kotlin中不支持普通的for循环,即你不能像这样使用for循环:for(var i=0;i<100;i++){}

kotlin中的for循环都是高级for循环。for循环可以对任何提供迭代器(iterator)的对象进行遍历,语法如下:

for(item in collection){
    print(item)
}

例如:

val strs = listOf("1", "2", "3")//List集合
val ints = arrayOf(1, 2, 3)//数组

for (item in strs) {
    print(item)
}
for (item in ints) {
    print(item)
}

/*
 * 也可以通过索引来访问
 */
for (index in strs.indices) {
    print(strs[index])
}
for (index in ints.indices) {
    print(ints[index])
}

1.1.10 while循环

while循环和do…while循环与Java中的用法一样

val items = listOf("apple", "banana", "kiwi")
var index = 0
while (index < items.size) {
    println("item at $index is ${items[index]}")
    index++
}

1.1.11 when

when即可以作为语句,也可以作为表达式,它的作用类似于C++和Java中的switch语句,不过使用起来更加灵活。

when (x) {
    1 -> print("x == 1")
    2 -> print("x == 2")
    else -> {
        print("x is neither 1 nor 2")
    }
}

//0,1两个分支执行一样的操作
when (x) {
    0, 1 -> print("x == 0 or x == 1")
    else -> print("otherwise")
}

//我们可以用任意表达式(而不只是常量)作为分支条件
when (x) {
    parseInt(s) -> print("s encodes x")//当x等于parseInt(s)时,执行此分支
    else -> print("s does not encode x")
}

//我们也可以检测一个值在(in)或者不在(!in)某个区间或者集合中
when (x) {
    in 1..10 -> print("x is in the range")
    in validNumbers -> print("x is valid")
    !in 10..20 -> print("x is outside the range")
    else -> print("none of the above")
}

//另一种可能性是检测某个值是(is)或者不是(!is)某个特定类型的值
val hasPrefix = when(x) {
    is String -> x.startsWith("prefix")//会将x智能转换为String类型
    else -> false
}

//when也可以用来取代if-else if链。如果不提供参数,所有的分支条件都是简单的布尔表达式,而当一个分支的条件为真时则执行该分支
when {
    x.isOdd() -> print("x is odd")
    x.isEven() -> print("x is even")
    else -> print("x is funny")
}

1.1.12 使用区间(range)

使用..操作符来定义一个区间,如1..10。任何可比较的类型都可以定义为一个区间。区间常与in和!in配合使用。

if (i in 1..10) { //检测i是否在区间[1,10]之内
    println(i)
}

整型区间(IntRange、LongRange、CharRange )有一个额外的特性:它们可以迭代。

前面讲for循环时提到过,kotlin中的for循环类似于Java中的高级for循环,那么如果我们非要使用Java中普通for循环那样的for循环呢,答案就是使用区间:当我们使用for循环来迭代区间时,编译器会将其转换为类似于Java中的普通for循环。

for (i in 1..4) print(i) //输出“1234”

等价于Java中的:

for(int i=1;i<=4;i++){...}

使用downTo操作符来定义一个逆向的区间for (i in 4 downTo 1) print(i),输出“4321”

指定迭代的步长

for (i in 1..4 step 2) print(i) // 输出“13”
for (i in 4 downTo 1 step 2) print(i) // 输出“42”

使用util操作符定义一个不含尾元素的区间

for (i in 1 until 10) { // i in [1, 10),排除了10
    println(i)
}

1.1.13 使用集合

对集合进行迭代:

for (item in items) {
    println(item)
}

使用in运算符来判断集合内是否包含某实例:

when {
    "orange" in items -> println("juicy")
    "apple" in items -> println("apple is fine too")
}

使用lambda表达式来过滤(filter)和映射(map)集合:

fruits
    .filter { it.startsWith("a") }
    .sortedBy { it }
    .map { it.toUpperCase() }
    .forEach { println(it) }

1.2 习惯用法

详见官方参考。建议暂时搁置,最后再看。

1.3 编码规范

详见官方参考。建议暂时搁置,最后再看。

发布了46 篇原创文章 · 获赞 38 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/al4fun/article/details/73928503
01.