Kotlin知识点整理

208 阅读8分钟

一、基础

1. 基础语法

1. 变量

var表示一个变量

val表示不可变的变量(不是常量)

如果编译器可以识别出变量类型,那么变量的类型可以不用写

var age: Int = 18
val name: String = "Sun Jie"

// 因为可以识别,也可以省略变量类型
var age = 18
val name = "Sun Jie"

// 如果声明的时候没有赋值,那么变量类型不能省略
val name: String

kotlin具有空安全类型的,如果不确定是否为空,需要在类型后面加上?

String 和String?是完全不同的两种类型

val name2: String? = null

如果确定不会为空,需要将带问号的类型赋值给不带问号的类型,则需要进行强转,加**!!**可以进行强转

name = name2!!

如果是不带问号的类型赋值给带问号的类型,不需要强转,直接赋值就行

name2 = name
2. 函数

通过fun 进行申明函数,kotlin中如果方法可以写在类外部,通过包名.方法名调用,要注意同一个包中,方法名字防止重名

fun  [方法名] ( [参数名] : [参数类型] ) : [返回类型]{
    ...
    return [返回值]
}
  • 参数后面跟参数的类型

  • 如果函数有返回值,在方法后面跟返回值类型

    fun printLen(str: String): String {
        println("这个字符串是:$str")
        return str
    }
    
    //如果只有一行执行代码可以这样写
    fun getLen1(str: String): Int = str.length
    //或者换成lambda写法
    var getLen2 = {str: String -> str.length}
    
  • 如果函数没有返回值,类型用Unit代替,一般可以省去

    fun printStr(str: String): Unit {
      	println("这个字符串是:$str")
    }
    //可以省略Unit
    fun printStr1(str: String) {
      	println("这个字符串是:$str")
    }
    
  • 普通方法:通过对象.方法名调用

    fun main(args: Array<String>) {
        Test().method()
    }
    
    class Test{
        fun method(){
            print("hello")
        }
    }
    
  • 静态方法:通过类名.方法名调用

    fun main(args: Array<String>) {
        Test.method()
    }
    
    class Test{
        companion object{
            fun method(){
                print("hello")
            }
        }
    }
    
  • 顶级方法:通过包名.方法名调用

    package 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不同的是可以有返回值

    // 传统用法
    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

    when (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)的对象进行遍历

    for (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中用法相同

    while( 布尔表达式 ) {
    		//TODO
    }
    
    // do...while循环至少会执行一次
    do {
    		//TODO
    }while(布尔表达式);
    
  • 返回和跳转:kotlin有三种结构化跳转表达式

    • return:默认从最直接包围它的函数或者匿名函数返回
    • break:终止最直接包围它的循环
    • continue:继续下一次最直接包围它的循环
    fun main(args: Array<String>) {
        for (i in 1..10) {
            if (i == 3) continue  // i 为 3 时跳过当前循环,继续下一次循环
            println(i)
            if (i > 5) break   // i 为 6 时 跳出循环
        }
    }
    
  • 多重循环用@标记:在多重循环中可用 “标记名+@”在循环体外做标记,跳转时添加“@+标记名”跳到对应的位置

    fun 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

    //等同于下面代码
    if (foo != null) {
      	return foo.bar()
    } else {
      	return null
    }
    
  • ?: foo?:bar:对象不为空,返回对象,为空,返回冒号后面的

    //等同于下面代码
    if (foo != null) {
      	return foo
    } else {
      	return bar
    }
    
  • as? foo as? Type

    //等同于下面代码
    if (foo is Type) {
      	return foo as Type
    } else {
      	return null
    }
    
  • !! foo!!

    //等同于下面代码
    if (foo != null) {
      	return foo
    } else {
      	throw NullPointerException
    }
    

2. 与Java代码互调

  • kotlin里面的函数可以直接写在文件里面,不需要写在类里

    //Utils.kt
    fun echo(name: String){
        println("$name")
    }
    

    在Java中调用这个函数时,在文件名后面加Kt,然后就可以直接调用函数

    public static void main(String[] args){
        UtilsKt.echo("hello");
    }
    
  • 匿名内部类(单例)

    object Test{
        fun sayMessage(msg: String){
            println(msg)
        }
    }
    
    //在kotlin中调用
    Test.sayMessage("hello")
    
    //在java中调用
    Test.INSTANCE.sayMessage("hello")
    
  • kotlin中class文件是KClass,例如有JavaMain和KotlinMain两个类

    fun 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关键字发生冲突,引用该变量时,用两个反引号`转义

    class JavaMain{
        public static final String in = "in";
    }
    
    //在kotlin中引用的的时候,因为in在kotlin中是关键字,需要用反引号转义
    println(JavaMain.`in`)
    
  • kotlin中没有封装类

  • kotlin调用java返回值可能为空的方法时,尽量用空安全类型,在类型后面加?

    // java中方法
    class A{
        public static String format(String str){
            return str.isEmpty()?null:str;
        }
    }
    
    val fmt: String? = A.format("")
    
  • kotlin没有静态变量和静态方法,可以通过使用注解@JvmStatic

    object Test{
        @JvmStatic
        fun sayMessage(msg: String){
            println(msg)
        }
    }
    
    //在java中调用
    Test.sayMessage("hello")
    

3.函数与Lambda

1. 函数参数--默认参数和可变参数

vararg用于修饰可变参数

fun print(name: String = "Sun Jie"): String{
    println("$name")
    return name;
}

fun prints(vararg strs: String){
  	for (str in strs){
      	println("$str")
    }
}

fun main(args: Array<String>){
    print()//输出"Sun Jie"
    print("hello")//输出"hello"
  	prints("aaa","bbb","ccc")
}
2. 如果一个函数只有一个语句,可以直接将语句赋值给函数
fun echo(name: String) = println("$name")
3. 函数可以嵌套(不推荐使用)

用于在某些条件下触发递归的函数或者不希望被外部函数访问到的函数

fun function(){
    val str = "hello!"
    
    fun say(count: Int = 10){
        println(str)//内部函数可以直接访问外部函数的变量
        if (count > 0){
            say(count - 1)
        }
    }
    say()
}
4. 扩展函数

为三方库,或者不能控制的类中添加函数,可以使用扩展函数

// 在FileReadWrite.kt中为File添加一个函数readText
public fun File.readText(charset: Charset = Charsets.UTF_8): String = reader(charset).use { it.readText() }

// kotlin中调用时可以直接调用
fun main(args: Array<String>) {
    val file = File("KotlinDemo.iml")
    print(file.readText())
}

// java中调用
public static void main(String[] args) {
    File file = new File("KotlinDemo.iml");
  	// java调用方法时,第一参数需要传递本身对象,默认参数也不能省略
    String content = FilesKt.readText(file, Charsets.UTF_8);
    System.out.println(content);
}
5. Lambda闭包

Kotlin中lambda参数最多只能有22个参数,不过Kotlin1.3后参数没有限制了

//java中lambda语法
Thread thread = new Thread(() -> {
  	//TODO
});
val thread = Thread({
    //参数
    ->
	  //TODO
})

// 如果lambda没有参数可以省略->
val thread = Thread({
	  //TODO
})

// 如果lambda是函数的最后一个参数,可以将大括号放在小括号外面
val thread = Thread(){
	  //TODO
}

// 如果函数只有一个参数且这个参数是Lambda,则可以省略小括号
val thread = Thread{
    //TODO
}
6. 高阶函数:函数的参数也是函数
// 函数作为参数时候,Unit不能省略
fun onlyif(isDebug: Boolean,block: () -> Unit) {
    if (isDebug) block()
}

fun main(args: Array<String>) {
  	// 当lambda作为函数最后一个参数时,可以将大括号写到小括号外面
    onlyif(true){
        println("打印日志")
    }
}

val runnable = Runnable {
  	println("Runnable::run")
}

val function: () -> Unit
// ::后面跟方法表示引用这个函数,不执行
function = runnable::run

lambda在编译时候,会编译成匿名内部类对象,可以通过在lambda函数前面加上inline,这样当方法在编译时会拆解方法的调用为语句调用,减少创建不必要的对象

inline通常只会用于修饰高阶函数

inline fun onlyif(isDebug: Boolean,block: () -> Unit) {
    if (isDebug) block()
}

4. 类与对象

1. 父类与继承

kotlin中所有的类都继承Any类,它是所有类的超类

  • 继承类的写法

    class MainActivity : AppCompatActivity()
    
  • 如果当前类需要能够被继承,那么需要添加open关键字,kotlin默认给类添加public final关键字,方法如果需要被重写也需要添加open关键字

    open class MainActivity : AppCompatActivity()
    
  • 实现接口可以直接跟着类名后面,和继承的父类没有先后关系顺序

    class MainActivity : AppCompatActivity(),OnClickListener
    
    // 没有先后顺序的要求,也可以用如下写法
    class MainActivity : OnClickListener,AppCompatActivity()
    
2. 构造函数
  • 如果需要给Kotlin的主构造函数添加参数,需要在类名后面添加,如果需要在主构造函数中执行语句,需要在init里面执行

    类内部的init模块和变量的初始化顺序按照他们出现的顺序进行,并且都在次构造函数之前执行

    成员变量和init模块在初始化时可以直接使用主构造方法中的参数

    open class MainActivity(var int: Int) : AppCompatActivity(), View.OnClickListener {
        init {
          // TODO
          println("===log===")
        }
    }
    
  • 主构造函数通过再类名后面添加constructor和参数实现,如果没有注解和可见的修饰符,constructor关键字可以省略

    class User private constructor(name: String) {}
    
    class User(name: String) {}
    
  • 如果在kotlin的类中有多个构造函数的话,需要显式声明次级构造函数,次级构造函数必须直接或者间接的继承主构造函数,构造函数关键字constructor

    class 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两个关键字组合起来声明

public class StringUtils {
  	public static boolean isEmpty(String str) {
      	return "".equals(str);
    }
}

用kotlin伴生对象实现如下

class StringUtils{
  	companion object {
      	fun isEmpty(str: String): Boolean {
          	return "" == str
        }
    }
}

// 调用方法
fun main(args: Array<String>) {
  	StringUtils.isEmpty("abc")
}

如果在java中调用,需要类名后面调用**.Companion**,然后再调用方法

StringUtils.Companion.isEmpty("aabc");

用伴生对象可以声明一个单例,常用写法如下

class Single private constructor() {
  	companion object {
      	fun get(): Single {
          	return Holder.instance
        }
    }
  
  	private object Holder {
      	val instance = Single()
    }
}
5. 动态代理
interface Developer {
  	fun code()
}

class JavaDeveloper : Developer {
  	override fun code() {
      	println("javaDeveloper is coding")
    }
}

// by关键字表示该类中的方法直接调用developer对象的方法
class Company(developer: Developer) : Developer by developer

// 程序执行最后正常输出:javaDeveloper is coding
fun main(args: Array<String>) {
  	Company(JavaDeveloper()).code()
}

如果在动态代理中重写了方法,则会直接输出重写的结果

class Company(developer: Developer) : Developer by developer 	{
  	// 方法重写后不会再去调用代理的对象的方法
  	override fun code() {
        println("Company is working")
    }
}

// 程序执行最后正常输出:Company is working
fun main(args: Array<String>) {
  	Company(JavaDeveloper()).code()
}
6. 数据类

数据类会将该类中的成员变量自动生成getter()和setter()方法,以及toString(),hashCode(),equals(),copy(),数据类是final类型的,不能用open,也不能被继承

数据类前面用data修饰,如果变量是val的,则只会生成get方法

getter/setter方法无法被直接调用,不过对它的操作本质上都是通过调用方法实现的

data class User(var id: Int, var name: String)
7.密闭类--kotlin中更加强大的枚举类
  • 枚举类

    enum 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搭配使用非常方便

    sealed class SuperCommand { 
      object A : SuperCommand() 
      object B : SuperCommand() 
    }
    
    fun exec(superCommand: SuperCommand) = when (superCommand) { 
    		SuperCommand.A -> {}
    		SuperCommand.B -> {}
    }
    
    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
  • 可以用来实现单例模式

    object A
    
    // 可以反编译得到的java代码如下
    public final class A {
        public static final A INSTANCE;
        static {
            A a = new A();
            INSTANCE = a;
        }
    }
    

    简单的用法如下

    fun main(args: Array<String>) {
        O.test()
        O.name = "hello"
    }
    
    object O {
        var name = "mao"
        fun test() {
            print("test")
        }
    }
    
  • 可以用来实现匿名内部类

    fun 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()
        }
    }
    
  • 可以继承一个类和多个接口,当父类有构造方法时,应传入对应的参数。

    interface A
    open class B(age: Int) {
        var mAge = age
    }
    
    var c: A = object : B(18), A {}