一、基础
1. 基础语法
1. 变量
var表示一个变量
val表示不可变的变量(不是常量)
如果编译器可以识别出变量类型,那么变量的类型可以不用写
1 | var age: Int = 18 |
kotlin具有空安全类型的,如果不确定是否为空,需要在类型后面加上?
String 和String?是完全不同的两种类型
1 | val name2: String? = null |
如果确定不会为空,需要将带问号的类型赋值给不带问号的类型,则需要进行强转,加!!可以进行强转
1 | name = name2!! |
如果是不带问号的类型赋值给带问号的类型,不需要强转,直接赋值就行
1 | name2 = name |
2. 函数
通过fun 进行申明函数,kotlin中如果方法可以写在类外部,通过包名.方法名调用,要注意同一个包中,方法名字防止重名
1 | fun [方法名] ( [参数名] : [参数类型] ) : [返回类型]{ |
参数后面跟参数的类型
如果函数有返回值,在方法后面跟返回值类型
1
2
3
4
5
6
7
8
9fun printLen(str: String): String {
println("这个字符串是:$str")
return str
}
//如果只有一行执行代码可以这样写
fun getLen1(str: String): Int = str.length
//或者换成lambda写法
var getLen2 = {str: String -> str.length}如果函数没有返回值,类型用Unit代替,一般可以省去
1
2
3
4
5
6
7fun printStr(str: String): Unit {
println("这个字符串是:$str")
}
//可以省略Unit
fun printStr1(str: String) {
println("这个字符串是:$str")
}普通方法:通过对象.方法名调用
1
2
3
4
5
6
7
8
9fun main(args: Array<String>) {
Test().method()
}
class Test{
fun method(){
print("hello")
}
}静态方法:通过类名.方法名调用
1
2
3
4
5
6
7
8
9
10
11fun main(args: Array<String>) {
Test.method()
}
class Test{
companion object{
fun method(){
print("hello")
}
}
}顶级方法:通过包名.方法名调用
1
2
3
4
5
6
7
8
9package com.xumao.kotlindemo
fun method(){
print("method")
}
fun main(args: Array<String>) {
com.xumao.kotlindemo.method()
}
3. 条件和循环控制(if、when、for、while、return、break、continue)
if:用法雷同java,与java不同的是可以有返回值
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// 传统用法
var max = a
if (a < b) max = b
// 使用 else
var max: Int
if (a > b) {
max = a
} else {
max = b
}
// 作为表达式
val max = if (a > b) a else b
// 有返回值的用法
val max = if (a > b) {
print("Choose a")
// 返回值需要放到最后
a
} else {
print("Choose b")
b
}
// 实现三元操作符
val c = if (condition) a else b
// 用in运算符来检测数字是否在区间内,区间格式为x..y
if (a in 1..8) {
println("a 在区间内")
}when:类似于java的switch
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25when (x) {
1 -> print("x == 1")
2 -> print("x == 2")
// 多个分支需要相同处理时,可以合并条件,用,隔开
3, 4 - > print("x == 3 or x == 4")
in 5..10 -> print("x is in the range")
in validNumbers -> print("x is valid")
!in 10..20 -> print("x is outside the range")
is String -> print("x is String")
// when中,else等同于switch中的default
else -> { // 如果只有一行可以不用{},直接把语句写后面
print("x 不是 1 ,也不是 2")
}
}
// 可以用when实现if,else的效果
when {
a > b -> print("a>b")
b > c -> print("b>c")
else -> print("else")
}for:可以对任何提供迭代器(iterator)的对象进行遍历
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
33for (item: Type in collection) {
// ......
}
var list=arrayListOf("a","b","c")
for(item in list){
println(item)
}
// 根据下标遍历
for (i in list.indices) {
println(list[i])
}
// 用区间遍历
for (i in 0..list.size - 1) {
println(list[i])
}
//从1到10(不包括10),步长为1
for (i in 1 until 10) {
println(i)
}
//从1到10,步长为2
for (i in 1..10 step 2) {
println(i)
}
//从10到0倒数,步长为3
for (i in 10 downTo(0) step 3) {
println(i)
}while:和java中用法相同
1
2
3
4
5
6
7
8while( 布尔表达式 ) {
//TODO
}
// do...while循环至少会执行一次
do {
//TODO
}while(布尔表达式);返回和跳转:kotlin有三种结构化跳转表达式
- return:默认从最直接包围它的函数或者匿名函数返回
- break:终止最直接包围它的循环
- continue:继续下一次最直接包围它的循环
1
2
3
4
5
6
7fun main(args: Array<String>) {
for (i in 1..10) {
if (i == 3) continue // i 为 3 时跳过当前循环,继续下一次循环
println(i)
if (i > 5) break // i 为 6 时 跳出循环
}
}多重循环用@标记:在多重循环中可用 “标记名+@”在循环体外做标记,跳转时添加“@+标记名”跳到对应的位置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21fun main(args: Array<String>) {
flag1@
for (i in 0..3) {
println()
flag2@
for (j in 0..3) {
if (i > 2) {
break@flag1
}
if (j % 2 == 0) {
continue@flag2
}
print("[$i : $j] ")
}
}
}
//输出结果为
[0 : 1] [0 : 3]
[1 : 1] [1 : 3]
[2 : 1] [2 : 3]
4.复合符号
?. foo?.bar:对象不为空,返回相应的成员变量,对象为空,返回null
1
2
3
4
5
6//等同于下面代码
if (foo != null) {
return foo.bar()
} else {
return null
}?: foo?:bar:对象不为空,返回对象,为空,返回冒号后面的
1
2
3
4
5
6//等同于下面代码
if (foo != null) {
return foo
} else {
return bar
}as? foo as? Type
1
2
3
4
5
6//等同于下面代码
if (foo is Type) {
return foo as Type
} else {
return null
}!! foo!!
1
2
3
4
5
6//等同于下面代码
if (foo != null) {
return foo
} else {
throw NullPointerException
}
2. 与Java代码互调
kotlin里面的函数可以直接写在文件里面,不需要写在类里
1
2
3
4//Utils.kt
fun echo(name: String){
println("$name")
}在Java中调用这个函数时,在文件名后面加Kt,然后就可以直接调用函数
1
2
3public static void main(String[] args){
UtilsKt.echo("hello");
}匿名内部类(单例)
1
2
3
4
5
6
7
8
9
10
11object Test{
fun sayMessage(msg: String){
println(msg)
}
}
//在kotlin中调用
Test.sayMessage("hello")
//在java中调用
Test.INSTANCE.sayMessage("hello")kotlin中class文件是KClass,例如有JavaMain和KotlinMain两个类
1
2
3
4
5
6
7
8
9
10
11
12fun main(args: Array<String>){
testClass(JavaMain::class.java)
testClass(KotlinMain::class)
}
fun testClass(clazz: Class<JavaMain>){
println(clazz.simpleName)
}
fun testClass(clazz: KClass<KotlinMain>){
println(clazz.simpleName)
}如果java中变量和kotlin关键字发生冲突,引用该变量时,用两个反引号`转义
1
2
3class JavaMain{
public static final String in = "in";
}1
2//在kotlin中引用的的时候,因为in在kotlin中是关键字,需要用反引号转义
println(JavaMain.`in`)kotlin中没有封装类
kotlin调用java返回值可能为空的方法时,尽量用空安全类型,在类型后面加?
1
2
3
4
5
6// java中方法
class A{
public static String format(String str){
return str.isEmpty()?null:str;
}
}1
val fmt: String? = A.format("")
kotlin没有静态变量和静态方法,可以通过使用注解@JvmStatic
1
2
3
4
5
6
7
8
9object Test{
fun sayMessage(msg: String){
println(msg)
}
}
//在java中调用
Test.sayMessage("hello")
3.函数与Lambda
1. 函数参数–默认参数和可变参数
vararg用于修饰可变参数
1 | fun print(name: String = "Sun Jie"): String{ |
2. 如果一个函数只有一个语句,可以直接将语句赋值给函数
1 | fun echo(name: String) = println("$name") |
3. 函数可以嵌套(不推荐使用)
用于在某些条件下触发递归的函数或者不希望被外部函数访问到的函数
1 | fun function(){ |
4. 扩展函数
为三方库,或者不能控制的类中添加函数,可以使用扩展函数
1 | // 在FileReadWrite.kt中为File添加一个函数readText |
5. Lambda闭包
Kotlin中lambda参数最多只能有22个参数,不过Kotlin1.3后参数没有限制了
1 | //java中lambda语法 |
1 | val thread = Thread({ |
6. 高阶函数:函数的参数也是函数
1 | // 函数作为参数时候,Unit不能省略 |
lambda在编译时候,会编译成匿名内部类对象,可以通过在lambda函数前面加上inline,这样当方法在编译时会拆解方法的调用为语句调用,减少创建不必要的对象
inline通常只会用于修饰高阶函数
1 | inline fun onlyif(isDebug: Boolean,block: () -> Unit) { |
4. 类与对象
1. 父类与继承
kotlin中所有的类都继承Any类,它是所有类的超类
继承类的写法
1
class MainActivity : AppCompatActivity()
如果当前类需要能够被继承,那么需要添加open关键字,kotlin默认给类添加public final关键字,方法如果需要被重写也需要添加open关键字
1
open class MainActivity : AppCompatActivity()
实现接口可以直接跟着类名后面,和继承的父类没有先后关系顺序
1
2
3
4class MainActivity : AppCompatActivity(),OnClickListener
// 没有先后顺序的要求,也可以用如下写法
class MainActivity : OnClickListener,AppCompatActivity()
2. 构造函数
如果需要给Kotlin的主构造函数添加参数,需要在类名后面添加,如果需要在主构造函数中执行语句,需要在init里面执行
类内部的init模块和变量的初始化顺序按照他们出现的顺序进行,并且都在次构造函数之前执行
成员变量和init模块在初始化时可以直接使用主构造方法中的参数
1
2
3
4
5
6open class MainActivity(var int: Int) : AppCompatActivity(), View.OnClickListener {
init {
// TODO
println("===log===")
}
}主构造函数通过再类名后面添加constructor和参数实现,如果没有注解和可见的修饰符,constructor关键字可以省略
1
2
3class User private constructor(name: String) {}
class User(name: String) {}如果在kotlin的类中有多个构造函数的话,需要显式声明次级构造函数,次级构造函数必须直接或者间接的继承主构造函数,构造函数关键字constructor
1
2
3
4
5
6
7
8
9class TestView : View {
constructor(context: Context) : super(context) {
println("constructor")
}
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
}
3. 访问修饰符
- private:表示类的成员都是这个类私有的
- protected:表示只有这个类和它的继承类才能访问
- public:表示这个类和所有其他类都能访问
- internal:表示一个模块中的类都可以访问到这个对象,跨模块则不能访问
4. 伴生对象
伴生对象通过companion object两个关键字组合起来声明
1 | public class StringUtils { |
用kotlin伴生对象实现如下
1 | class StringUtils{ |
如果在java中调用,需要类名后面调用.Companion,然后再调用方法
1 | StringUtils.Companion.isEmpty("aabc"); |
用伴生对象可以声明一个单例,常用写法如下
1 | class Single private constructor() { |
5. 动态代理
1 | interface Developer { |
如果在动态代理中重写了方法,则会直接输出重写的结果
1 | class Company(developer: Developer) : Developer by developer { |
6. 数据类
数据类会将该类中的成员变量自动生成getter()和setter()方法,以及toString(),hashCode(),equals(),copy(),数据类是final类型的,不能用open,也不能被继承
数据类前面用data修饰,如果变量是val的,则只会生成get方法
getter/setter方法无法被直接调用,不过对它的操作本质上都是通过调用方法实现的
1 | data class User(var id: Int, var name: String) |
7.密闭类–kotlin中更加强大的枚举类
枚举类
1
2
3
4
5
6
7
8
9
10
11enum class Command {
A, B, C, D
}
//如果有成员属性时
enum class Color(val rgb: Int) {
RED(0xFF0000),
GREEN(0x00FF00),
BLUE(0x0000FF)
}
print(Color.RED.rgb)密闭类:通过sealed关键字修饰,sealed类自身是抽象类,子类不能是抽象类,子类必须和它在同一个文件中,与when搭配使用非常方便
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22sealed class SuperCommand {
object A : SuperCommand()
object B : SuperCommand()
class C(var id: Int) : SuperCommand()
}
fun exec(superCommand: SuperCommand) = when (superCommand) {
SuperCommand.A -> {}
SuperCommand.B -> {}
is SuperCommand.C -> {}
}
sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
fun eval(expr: Expr): Double = when(expr) {
is Const -> expr.number
is Sum -> eval(expr.e1) + eval(expr.e2)
NotANumber -> Double.NaN
}
8. object
可以用来实现单例模式
1
object A
1
2
3
4
5
6
7
8// 可以反编译得到的java代码如下
public final class A {
public static final A INSTANCE;
static {
A a = new A();
INSTANCE = a;
}
}简单的用法如下
1
2
3
4
5
6
7
8
9
10
11fun main(args: Array<String>) {
O.test()
O.name = "hello"
}
object O {
var name = "mao"
fun test() {
print("test")
}
}可以用来实现匿名内部类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24fun main(args: Array<String>) {
var btn = Btn()
btn.onClickLsn = object : Btn.OnClickLsn {
override fun click() {
print("click")
}
}
btn.callClick()
}
class Btn() {
var onClickLsn: OnClickLsn? = null
fun callClick() {
onClickLsn?.click()
}
interface OnClickLsn {
fun click()
}
}可以继承一个类和多个接口,当父类有构造方法时,应传入对应的参数。
1
2
3
4
5
6interface A
open class B(age: Int) {
var mAge = age
}
var c: A = object : B(18), A {}