logo头像

我有一个梦想

Kotlin初探

本文于 909 天之前发表,文中内容可能已经过时。

[TOC]

概述

以.kt结尾的程序文件

优点:

  • 简洁:大大减少样板代码的数量
  • 安全:避免空指针异常等整个类的错误
  • 互操作性:充分利用JVM、Android和游览器的现有库
  • 工具友好:可用任何Java IDE或者使用命令行构建

创建方法

1
2
3
4
5
6
7
8
9
/**
* a:入参 b:入参
* 返回:Int
*/
fun sum(a: Int, b: Int): Int { // Int 参数,返回值 Int
return a + b
}

fun sum2(a: Int, b: Int): Int = a + b

可变长参数函数(vararg)

1
2
3
4
5
6
7
8
9
10
11
/**
* 多个Int参数
*/
fun vars(vararg v:Int){
for(vt in v){
print("----$vt")
}
}

main :
vars(1,2,3,4,5)

lambda

1
2
val sumLambda:(Int,Int) -> Int = {a,b ->a+b}
println(sumLambda(1,2)) // 输出 3

val

1
2
//修饰不可变的变量,类似于 final,只可被赋值一次
val <标识符> :<类型> = <初始化值>

var

1
2
//修饰可变的变量,声明时可不指定类型,由编译器判断
var <标识符> :<类型> = <初始化值>
1
2
3
4
5
6
7
8
val a: Int = 1
val b = 1 // 系统自动推断变量类型为Int
val c: Int // 如果不在声明时初始化则必须提供变量类型
c = 1 // 明确赋值


var x = 5 // 系统自动推断变量类型为Int
x += 1 // 变量可修改

注释

1
2
3
4
5
6
7
8
9
10
11
// kotlin中块注释的级联使用,其实个人觉得块注释的嵌套使用的意义不大,不过从视觉上确实能给人一种层次感
/*
第一层块注释
/*
第二层块注释
/*
第三层快注释
这种注释方式在java中是不支持的,但是在kotlin中是支持的。算是一个亮点吧(貌似意义不大)。
*/
*/
*/

字符串模版

$ 表示一个变量名或者变量值
$varName 表示变量值
${varName.fun()} 表示变量的方法返回值:

1
2
3
4
5
6
7
var a = 1
// 模板中的简单名称:
val s1 = "a is $a"

a = 2
// 模板中的任意表达式:
val s2 = "${s1.replace("is", "was")}, but now is $a"

NULL检查机制

1
2
3
4
5
6
7
8
9
10
11
12
//类型后面加?表示可为空
var age: String? = "23"
//抛出空指针异常
val ages = age!!.toInt()
//不做处理返回 null
val ages1 = age?.toInt()
//age为空返回-1
val ages2 = age?.toInt() ?: -1
//当 str 中的字符串内容不是一个整数时, 返回 null
fun parseInt(str: String): Int? {
// ...
}

类型检测及自动类型转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fun getStringLength(obj: Any): Int? {
if (obj is String) {
// 做过类型判断以后,obj会被系统自动转换为String类型
return obj.length
}

//在这里还有一种方法,与Java中instanceof不同,使用!is
// if (obj !is String){
// // XXX
// }

// 这里的obj仍然是Any类型的引用
return null
}

区间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
for (i in 1..4) print(i) // 输出“1234”

for (i in 4..1) print(i) // 什么都不输出

if (i in 1..10) { // 等同于 1 <= i && i <= 10
println(i)
}

// 使用 step 指定步长
for (i in 1..4 step 2) print(i) // 输出“13”

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


// 使用 until 函数排除结束元素
for (i in 1 until 10) { // i in [1, 10) 排除了 10
println(i)
}

基本数据类型

类型 位宽度
Double 64
Float 32
Long 64
Int 32
Short 16
Byte 8
  • 十进制:123
  • 长整型以大写的 L 结尾:123L
  • 16 进制以 0x 开头:0x0F
  • 2 进制以 0b 开头:0b00001011
  • Doubles 默认写法: 123.5, 123.5e10
  • Floats 使用 f 或者 F 后缀:123.5f

注意:8进制不支持

可以使用下划线使数字常量更易读:

1
2
3
4
5
val oneMillion = 1_000_000
val creditCardNumber = 1234_5678_9012_3456L
val socialSecurityNumber = 999_99_9999L
val hexBytes = 0xFF_EC_DE_5E
val bytes = 0b11010010_01101001_10010100_10010010

比较两个数字

Kotlin 中没有基础数据类型,只有封装的数字类型,你每定义的一个变量,其实 Kotlin 帮你封装了一个对象,这样可以保证不会出现空指针。

三个等号 === 表示比较对象地址,两个 == 表示比较两个值大小

类型转换

1
2
3
4
5
6
7
8
9
10
val b: Byte = 1 // OK, 字面值是静态检测的
val i: Int = b.toInt() // OK

toByte(): Byte
toShort(): Short
toInt(): Int
toLong(): Long
toFloat(): Float
toDouble(): Double
toChar(): Char

位操作符

1
2
3
4
5
6
7
shl(bits) – 左移位 (Java’s <<)
shr(bits) – 右移位 (Java’s >>)
ushr(bits) – 无符号右移位 (Java’s >>>)
and(bits) – 与
or(bits) – 或
xor(bits) – 异或
inv() – 反向

字符

Char:使用单引号'包含起来的

布尔

Boolean:true和false

1
2
3
|| – 短路逻辑或
&& – 短路逻辑与
! - 逻辑非

数组

注意: 与 Java 不同的是,Kotlin 中数组是不协变的(invariant)。

1
2
3
4
5
6
7
8
9
10
fun main(args: Array<String>) {
//[1,2,3]
val a = arrayOf(1, 2, 3)
//[0,2,4]
val b = Array(3, { i -> (i * 2) })

//读取数组内容
println(a[0]) // 输出结果:1
println(b[1]) // 输出结果:2
}

除了类Array,还有ByteArray, ShortArray, IntArray,用来表示各个类型的数组,省去了装箱操作,因此效率更高,其用法同Array一样

字符串

和 Java 一样,String 是不可变的

1
2
3
for (c in str) {
println(c)
}

Kotlin 支持三个引号 “”” 扩起来的字符串,支持多行字符串,比如:

1
2
3
4
5
6
7
fun main(args: Array<String>) {
val text = """
多行字符串
多行字符串
"""
println(text) // 输出有一些前置空格
}

String 可以通过 trimMargin() 方法来删除多余的空白。

1
2
3
4
5
6
7
8
9
10
fun main(args: Array<String>) {
val text = """
|多行字符串
|菜鸟教程
|多行字符串
|Runoob
""".trimMargin()
println(text) // 前置空格删除了
}
//默认 | 用作边界前缀,但你可以选择其他字符并作为参数传入,比如 trimMargin(">")。

字符串模版表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fun main(args: Array<String>) {
val i = 10
val s = "i = $i" // 求值结果为 "i = 10"
println(s)
}

fun main(args: Array<String>) {
val s = "runoob"
val str = "$s.length is ${s.length}" // 求值结果为 "runoob.length is 6"
println(str)
}

fun main(args: Array<String>) {
val price = """
${'$'}9.99
"""
println(price) // 求值结果为 $9.99
}

When表达式

类似于其他语言的switch,else类似于default

1
2
3
4
5
6
7
when (x) {
1 -> print("x == 1")
2 -> print("x == 2")
else -> { // 注意这个块
print("x 不是 1 ,也不是 2")
}
}

1
val site = Runoob() // Kotlin 中没有 new 关键字

主构造器中不能包含任何代码,初始化代码可以放在初始化代码段中,初始化代码段使用 init 关键字作为前缀。

类也可以有二级构造函数,需要加前缀 constructor

类默认是不可变的,即是被final修饰的,使用open后,去除final

内部类使用 inner 关键字来表示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
abstract    // 抽象类  
final // 类不可继承,默认属性
enum // 枚举类
open // 类可继承,类默认是final的
annotation // 注解类

private // 仅在同一个文件中可见
protected // 同一个文件中或子类可见
public // 所有调用的地方都可见
internal // 同一个模块中可见

模块:
一个 IntelliJ IDEA 模块;
一个 Maven 项目;
一个 Gradle 源集(例外是 test 源集可以访问 main 的 internal 声明);
一次 <kotlinc> Ant 任务执行所编译的一套文件。

扩展函数

1
2
3
4
5
6
7
8
9
10
11
class User(var name:String)

/**扩展函数**/
fun User.Print(){
print("用户名 $name")
}

fun main(arg:Array<String>){
var user = User("Runoob")
user.Print()
}

伴生对象

关键字整理

img

  1. lateinit:延迟初始化

​ 作用:具体来讲,这个关键字告诉编译器,我无法声明的时候就初始化,但是我保证我在使用前一定会初始化,你就别给我检查了。

  1. apply:用于对象配置,类似于构造者模式(Builder),调用某对象的apply函数,在函数范围内,可以任意调用该对象的任意方法,并返回该对象
  2. with:返回是最后一行,然后可以直接调用对象的方法,感觉像是let和apply的结合。
  3. let:默认当前这个对象作为闭包的it参数,返回值是函数里面最后一行,或者指定return
  4. also:调用某对象的also函数,则该对象为函数的参数。在函数块内可以通过 it 指代该对象。返回值为该对象自己。
  5. run:run函数和apply函数很像,只不过run函数是使用最后一行的返回,apply返回当前自己的对象。