Kotlin大纲
Kotlin基础、类、对象:
一些链接、资源
Kotlin参考文档
https://www.cnblogs.com/Jetictors/p/9227498.html
《Kotlin实战》
《Kotlin从零到精通Android开发》
kotlin的活动等信息公布网站:cn.kotlin.tips
Java与Kotlin互转
(借助Android Studio)
Java转Kotlin
打开要转的文件
- Tools>Kotlin>Show Kotlin Bytecode
- Decompile
Kotlin特性
- 空判断,空安全检查
==
和===
,==
判断值相等(等价于equals),===
判断引用(等价于判断内存地址)- 构造函数,也是个函数,跟普通函数调用一样,调用后构造一个对象
inline
,函数内联,在编译时就进行内联优化,而不是运行时sealed class
(“密封类”)同时包含了两个优势–抽象类表示的灵活性和枚举里集合的受限性- 互操作性:Kotlin与所有基于Java的框架完全兼容,而无需将所有代码迁移到Kotlin
- 支持多平台开发:不仅可以使用Kotlin开发Android,还可以开发iOS、后端与Web应用程序。享受在平台之间共享公共代码的好处
开发环境
Kotlin与Android开发的关系
Kotlin开发工具
- IntelliJ IDEA 可以运行
- 在线运行 Kotlin 代码
- 使用 Android Studio
SDK安装与插件升级
Kotlin简单配置
Kotlin相关技术
【Hencoder】
变量和函数
Kotlin的变量、函数和类型
1 | //声明变量。这样写会报Property must be initialized or be abstract |
kotlin实现java中的“静态”
在kotlin中有三种方式可以实现java中静态函数的声明
直接在kotlin文件中直接声明fun函数(不是在class内)
会根据文件编译生成属于这个文件的静态函数。叫顶层函数
、包级函数
在java中调用kotlin的顶层函数:文件名Kt.顶层函数名(参数)
在kotlin中调用java的静态方法:文件名.静态函数(参数)
这种直接在kotlin文件中写的方式,函数不知道归于哪个类。
可在首行添加@file:JvmName("KotlinUtils")
来规定类名,那么在java中调用:KotlinUtils.函数(参数)
@file
表示注解使用处的目标,告诉注解它的作用对象是文件object 类名{}
里面所有函数、变量都会是静态的
在java中调用:类名.INSTANCE.函数(参数)
在kotlin中调用:类名.函数(参数)
使用object修饰的类,它会
自动
创建一个单例,所以在java中调用的话要通过它的单例来调用。伴生对象
(在内部会维护一个内部类的单例对象)【Application(是由Android的Framework来创建)不能使用第2点的自动生成,得用这种伴生对象的方式】1
2
3
4
5
6
7
8
9
10
11
12
13
14
15class BaseApplication : Application(){
companion object{
private lateinit var currentApplication: Context
fun currentApplication(): Context{
return currentApplication
}
}
override fun onCreate(){
super.onCreate()
currentApplication = this
}
}不加
@JvmStatic
那么它只是个普通函数在java中调用:
BaseApplication.Companion.currentApplication();
在kotlin中调用:
java中的匿名内部类
java中的匿名内部类:就是创建一个类的对象,在kotlin中可用object的方式来实现这个效果。
1 | object HttpClient: OkHttpClient(){ |
kotlin中没有强制捕获异常
Java
中某个sdk的类在库中可以相互引用,但不想被外面的调用类看到,在这个类的注释中添加{@hide}
Kotlin
中想实现上述的功能,在类声明时添加internal
如internal class 类名{}
Kotlin
中加abstract
(类)、open
(方法)、override
(方法)才可以被继承或重写,普通类默认是final
不可以被继承主构造器,把构造器放到类名后面。原来的次构造器方法名改成
init
kotlin
会按代码顺序把init
、成员变量
放入类中简化:主构造器
contract(var data: String?)
其中加var
会默认生成一个data
的成员变量【不加var就不是成员属性】enum class 是写枚举类; data class 写数据类(就不用重复写hashcode、equals)
解构:
val(构造参数1, 构造参数2, 构造参数3)=execute()
一次性得到三个值【顺序跟构造器中的参数是一一对应的】如果某参数不想要使用结构得重写对应的 Component1()、Component2()、Component3()
在java中要这样:
val response = execute()
val body1 = response.body
val code1 = response.code
val user1 = response.user
在kotlin中用解构可一次性得到三个值 val(body,code,user) = execute()
自定义操作符
加 operator
遍历写法简化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36//原来
val playbackLessons = ArrayList<Lesson>()
for(lesson in lessons){
if(lesson.state === Lesson.State.PLAYBACK){
playbackLessons.add(lesson)
}
}
//简化
lessons.forEach({lesson: Lesson ->
if(lesson.state === Lesson.State.PLAYBACK){
playbackLessons.add(lesson)
}})
//最后一个传入参数是lambda,那么大括号可以挪外面
lessons.forEach(){lesson: Lesson ->
if(lesson.state === Lesson.State.PLAYBACK){
playbackLessons.add(lesson)
}}
//传入参数只有一个lambda,那么小括号可以省略
lessons.forEach{lesson: Lesson ->
if(lesson.state === Lesson.State.PLAYBACK){
playbackLessons.add(lesson)
}}
//类型可以推导,简化
lessons.forEach{lesson ->
if(lesson.state === Lesson.State.PLAYBACK){
playbackLessons.add(lesson)
}}
//kotlin中lambda只有一个参数,那么参数可以省略
lessons.forEach{
if(it.state === Lesson.State.PLAYBACK){
playbackLessons.add(it)
}
}
//如果学过rxjava,看到forEach会联想到filter操作符,简化
val filter = lessons.filter{it.state === Lesson.State.PLAYBACK}循环
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18//循环100次,输出0..99
fun main(){
//传入两个参数,最后一个参数是lambda所以可以提取到小括号外,lambda只有一个参数所以省略成it
repeat(100){
println(it)
}
//java中的fori,在kotlin中有in的写法
val array = arrayOf(1,23,452,213,23,1)
for (i in 0..(array.size-1)){}
//这种写法不优雅,改成
for (i in 0 until array.size){}
//实际是 for(i in 0.until(array.size)){},点until跳过去可以看到是加infix的扩展函数。使得可以写成上面的形式让代码可读性更高点
//此处array自带一个方法,可以返回一个区间,简化
for (i in array.indices){}
}函数不想被别的地方调用,函数可以写在另一个函数中,只能被外部函数访问到
函数嵌套会在外面函数每次被调用的时候生成一个额外对象。所以要考虑外面函数是不是被频繁调用,如果被频繁调用那么不适合这种写法(会额外生成很多临时对象)
不想让外面更改某个变量,加
private set
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47//原来:
class BaseApplication : Application(){
companion object{
private lateinit var currentApplication: Context
fun currentApplication(): Context{
return currentApplication
}
}
override fun onCreate(){
super.onCreate()
currentApplication = this
}
}
//更改成
class BaseApplication : Application(){
companion object{
lateinit var currentApplication = Context
private set
}
override onCteate(){
currentApplication = this
}
}
//kotlin中调用
BaseApplication.currentApplication
//java中调用
BaseApplication.Companion.getCurrentApplication()
//如果java中调用不想加Companion,那么加@JvmStatic
class BaseApplication: Application(){
companion object{
lateinit var currentApplication = Context
private set
}
override onCreate(){
currentApplication = this
}
}
//java中调用
BaseApplication.getCurrentApplication()
Kotlin知识体系图
基础
基本数据类型
整型
Byte(8)
Short(16)
Int(32)
Long(64)
默认整数类型为Int,需要在后面标记L或l
浮点型
Float(32)
默认浮点数类型为Double,需要在浮点数后标记f或F
Double(64)
布尔型
Boolean
字符型
Char
字符用 Char 表示,不能直接当做数字
显式转换
toByte(): Byte
toShort(): Short
toInt(): Int
toLong(): Long
toFloat(): Float
toDouble(): Double
toChar(): Char
支持的进制
二进制
十进制
十六进制
注意:不支持八进制
注意
不显式转换的情况下不能把较小类型的变量赋值给较大类型
===:内存地址值相等
==:值相等
当需要可空引用时,像数字、字符会被装箱。装箱操作不会保留同一性。
基本数据类型名称 | Kotlin的数据类型 | Java的数据类型 |
---|---|---|
字节型 | Byte(8) | byte和Byte(8) |
整型 | Int(32) | int和Integer(32) |
长整型 | Long(64) | long和Long(64) |
浮点型 | Float(32) | float和Float(32) |
双精度 | Double(64) | double和Double(64) |
布尔型 | Boolean(8) | boolean和Boolean(8) |
字符型 | Char | char(16) |
字符串 | String | String |
包
声明:
package package.name
如果没有显示的声明包名,则使用默认的命名空间
和 Java 不同的是:目录与包的结构无需匹配:源代码可以在文件系统的任意位置
如果两个文件的包名一致,无需导入可以直接使用对方的类和顶层函数、属性
import 的使用方式
import package.name.*
导入包下的所有顶层的类、函数、属性
import package.name.funname
导入包下的具体某个顶层函数
import package.name.propertyname
导入包下具体的某个顶层属性
import package.name.classname
导入包下具体的某个类
import package.name.classname.*
导入某个类下的所有的成员
注意
在同一个包下不能存在相同的函数、属性、类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19//A.kt
package cn.intozhou.bean
val a = 1
fun sayHello(){
}
class person1
//B.kt
package cn.intozhou.bean
val a = 2
fun sayHello(){
}
class person1
//上面的写法是错误的,因为在package相同的情况下函数、类、属性不能相同。这里会编译时会抛出:sayHello a person1 已经在 A.kt 文件中定义如果在一个文件中导入不同目录下的相同的类、函数、属性,可以使用关键字 as 为不同命名空间下的类、函数、属性起别名
函数
无返回值的函数
1
2
3
4
5
6fun main(args:Array<string>){
}
//等同
fun main(args:Array<string>):Unit{
}有返回值的函数
1
2
3fun add(x:Int,y:Int):Int{
return x + y;
}返回值可为空的函数
1
2
3fun parseInt(str:String):Int?{
return str.toIntOrNull()
}表达式主体函数
1
fun test(x:Int,y:Int) = if(x > y) x else y
有默认参数值的函数
有默认值的参数必须在无默认值参数的右边1
2
3fun test(name:String,age:Int = 18):String{
return "姓名:$name,年龄:$age";
}
变量
var(可变变量)
在其初始化赋值后可再次赋值1
2
3
4
5
6
7
8
9fun main(args:Array<string>){
var x = 0;//根据值自动推断变量类型
x = 1;//再次赋值Int类型的值,ok
x = "3"//错误。只有在初始赋值的时候会自动推断变量的类型,在此之后的所有赋值的数据类型必须与第一次赋值的类型相同
var y :String = ""//显示声明变量的类型并赋值
var b :Boolean//如果声明变量但不赋初始值则必须显示声明变量的类型
b = true
}val(不可变变量)
在初始化赋值后不可再赋值的的变量1
2
3
4fun main(args:Array<string>){
val PI = 3.141596253f
PI = 4//错误,不可再次赋值
}1
2
3
4
5
6
7class lessonPresenter{
//使用伴生对象的形式,让常量是静态的
companion object{
//加const让常量变成是编译期常量
const val LESSON_PATH = ‘lessons
}
}
字符串模板
1 | fun main(args:Array<string>){ |
表达式
if表达式
1
2
3fun test3(age:Int){
println(if(age < 18) "未成年" else if (age >= 18 && age < 30) "青年" else "中年、老年")
}if 既可以作为语句来使用,也可以作为表达式来使用
如果你使用if作为表达式来使用,那么 else 分支是必须要存在的
when表达式
1
2
3
4
5
6
7
8fun describe(obj: Any): String =
when (obj) {
1 -> "One"
"Hello" -> "Greeting"
is Long -> "Long"
!is String -> "Not a string"
else -> "Unknown"
}使用方式
有参数
1
2
3
4
5
6
7
8
9
10fun getMnemonic(color: Color) =
when(color){
Color.RED -> "Richard"
Color.ORANGE -> "Of"
Color.YELLOW -> "York"
Color.GREEN -> "Gave"
Color.BLUE -> "Battle"
Color.INDIGO -> "In"
Color.VIOLET -> "Vain"
}无参数
1
2
3
4
5
6
7
8//不带参数的when
fun mixOptimized(c1 : Color, c2 : Color) =
when{
(c1 == RED && c2 == YELLOW) || (c1 == YELLOW && c2 == RED) -> ORANGE
(c1 == YELLOW && c2 == BLUE) || (c1 == BLUE && c2 == YELLOW) -> GREEN
(c1 == BLUE && c2 == VIOLET) || (c1 == VIOLET && c2 == BLUE) -> INDIGO
else -> throw Exception("Dirty color")
}一条分支多个选项
1
2
3
4
5
6
7//在一个when分支上合并多个选项
fun getWarmth(color: Color) =
when(color){
RED,ORANGE,YELLOW -> "warm"
GREEN -> "neutral"
BLUE,INDIGO,VIOLET -> "cold"
}任意参数
1
2
3
4
5
6
7
8//when 表达式的判断条件可以是任何对象
fun mix(c1:Color,c2:Color) =
when(setOf(c1,c2)){
setOf(RED,YELLOW) -> ORANGE
setOf(YELLOW,BLUE) -> GREEN
setOf(BLUE,VIOLET) -> INDIGO
else -> throw Exception("Dirty color")
}取代了 switch 操作符,比switch更强大
when 可以接受任意对象为参数,switch 只支持 常量:枚举常量、字符串常量、数字常量when 既可以作为语句来使用,也可以作为表达式来使用
如果你使用 when 作为表达式来使用,那么 else 分支是必须要存在的,除非编译器确认分支已经包含所有的可能性
替代 if…else if…else…,可以不提供参数,所有的分支条件都是简单的布尔表达式
1
2
3
4
5
6
7when{
x % 2 == 0 -> println("是偶数")
x % 2 == 1 -> println("是奇数")
else ->{
println("不是奇数页不是偶数")
}
}
在Kotlin中不支持三元表达式
在kotlin中不支持switch
空值和null检查
1 | fun getStringLength(obj: Any): Int? { |
类型检查和智能类型转换
循环
for循环
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17fun main(args: Array<string>) {
//sampleStart
val items = listOf("apple", "banana", "kiwi")
for (item in items) {
println(item)
}
//sampleEnd
}
fun main(args: Array<string>) {
//sampleStart
val items = listOf("apple", "banana", "kiwi")
for (index in items.indices) {
println("item at $index is ${items[index]}")
}
//sampleEnd
}while循环
1
2
3
4
5
6
7
8
9
10fun main(args: Array<string>) {
//sampleStart
val items = listOf("apple", "banana", "kiwi")
var index = 0
while (index < items.size) {
println("item at $index is ${items[index]}")
index++
}
//sampleEnd
}do…while()循环
break、continue
break: 跳出循环
continue: 跳出本次循环,执行下次循环
标签处返回
- label@
区间
是否在区间
1
2
3
4
5
6
7
8
9
10
11
12
13val x = 9
val y = 10
//是否在区间内
if(x in 0..y){
println("x 在区间内")
}
println("--------")
//是否不在区间内
if(x !in 0..y){
println("x 不在区间内")
}if(x in x..y)
迭代区间
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18fun main(args: Array<string>) {
//sampleStart
for (x in 1..5) {
print(x)
}
//sampleEnd
}
fun main(args: Array<string>) {
//sampleStart
for (x in 1..10 step 2) {
print(x)
}
for (x in 9 downTo 0 step 3) {
print(x)
}
//sampleEnd
}for(x in items)
集合
List
1
val list = listOf("A","B","D")
Set
1
val set = setOf("A","B","D","E")
类和对象
类与继承
类
类声明
- 可是使用 class 关键字来声明类。类声明由类名、类头(指定其类型参数、主构造函数等)和由大括号包围的类体构成。类头和类体都是可选的; 如果一个类没有类体,可以省略花括号。
类成员
属性
函数
嵌套类和内部类
对象声明
构造函数
构造函数的可见性是
public
的,如果不希望外部创建改类的实例则需要显式的使用可见性修饰符来修饰构造函数主构造函数
只能有一个
主构造函数是类头的一部分:它跟在类名(和可选的类型参数)后。
使用
constructor
关键字来声明主构造函数。如果主构造函数没有任何注解或者可见性修饰符,可以省略这个constructor
关键字。主构造函数不能包含任何的代码。初始化的代码可以放到以
init
关键字作为前缀的初始化块主构造的参数可以在初始化块中使用。它们也可以在类体内声明的属性初始化器中使用
主构造函数中声明的属性可以是可变的(
var
)或只读的(val
)在
JVM
上,如果主构造函数的所有的参数都有默认值,编译器会生成 一个额外的无参构造函数,它将使用默认值如果构造函数有注解或可见性修饰符,这个
constructor
关键字是必需的,并且这些修饰符在它前面
次构造函数
可以有一个或多个
如果类有一个主构造函数,每个次构造函数需要委托给主构造函数, 可以直接委托或者通过别的次构造函数间接委托。委托到同一个类的另一个构造函数用
this
关键字即可如果一个类没有主构造函数和次构造函数,它会有一个不带参数的主构造函数
继承
在
Kotlin
中所有类都有一个共同的超类Any
,这对于没有超类型声明的类是默认超类如果该基类有一个主构造函数,其子类型可以(并且必须) 用(基类型的)主构造函数参数就地初始化。如果类没有主构造函数,那么每个次构造函数必须使用
super
关键字初始化其基类型,或委托给另一个构造函数做到这一点类上的
open
标注与Java
中final
相反,它允许其他类从这个类继承。默认情况下,在Kotlin
中所有的类都是final
-要么为继承而设计,并提供文档说明,要么就禁止继承覆盖方法
与
Java
不同,Kotlin
需要显式标注可覆盖的成员(我们称之为开放)和覆盖后的成员首先只能覆盖标注了
open
的成员,其次如果覆盖则必须显式地使用override
标注覆盖后的成员覆盖成员的前提是类是开放的
属性与字段
接口
可见性修饰符
扩展
数据类
密封类
泛型
嵌套类
枚举类
使用
enum class
来定义一个枚举类1
enum class Color
enum
是软关键字,只有在和class
一起使用时才具有特殊含义枚举常量和方法之间要使用分号(
;
)分割,这是必须的如果枚举类定义了主构造函数,那么每个枚举常量在创建时必须指定起初始值
1
2
3
4
5
6
7enum class Color(var r :Int,var g:Int,var b:Int){
RED(255,0,0),ORANGE(255,165,0),
YELLOW(255,255,0),GREEN(0,255,0),
BLUE(0,0,255),INDIGO(75,0,130),
VIOLET(238,130,238);//这里的分号是必须的,用于划分常量列表和方法
fun rgb() = (r * 256 + g) * 256 + b
}