Kotlin 语法基础总结

76 阅读11分钟

⚠️仅适合快速掌握,过一遍知识 .(自用复习)

基础

函数声明

声明函数要用 fun 关键字,就像声明类要用 class 关键字一样

「函数参数」的「参数类型」是在「参数名」的右边函数的

「返回值」在「函数参数」右边使用:分隔,没有返回值时可以省略

声明没有返回值的函数:

fun main() {
	//..
}

声明有返回值的参数:

fun sum(x: Int, y: Int): Int {
	return x+y
}

变量声明

  • var 可读可写变量
  • val 只读变量
  • 「类型」在「变量量名」的右边,用 : 分割,同时如果满足「类型推断」,类型可以省略创建对象直接调用构造器,不需要new关键字
val name:String = “Kotiln”.
和 Java 的 final 一样,只能声明,赋值一次

继承类 / 实现接口

继承类 / 实现接口 都是使用 : 如何类中没有构造器 constructor ,需要在父类类名后面加上 ():

classMainActivity : BaseActivity(),View.OnClickListener

空安全设计

Kotlin中的类型设计分为 可空类型 和 不可空类型

不可空类型:

val editText : EditText

  可空类型:(有可能为 null, 必须声明)

val editText : EditText? //?表示 判断是不是空,不是空才会被调用

调用符

!!  强行调调用符

?. 安全调用符号 //?表示 判断是不是空,非空才会被调用

lateinit 关键字

延迟初始化

lateinit 只能修饰var可读可写变量(思考下为什么) 因为val 只能赋值一次

lateinit 关键字声明的变量的类型必须是「不可空类型」,例如Android的 Edittetx

lateinit 声明的变量不能有「初始值」

lateinit 声明的变量不能是「基本数据类型」,

像 Android的 Edittext可以

在构造器中初始化的属性不需要 lateinit 关键字

类型判断

is 判断属于某类型

!is 判断不属于某类型

as 类型强转,失败时抛出类型强转失败异常

as? 类型强转,但失败时不会抛出异常而是返回null

Untitled.png

获取 Class 对象

使用类名 ::class 获取的是 Kotlin 的类型是 KClass

使用类名 ::class.java 获取的是 Java 的类型

eg.

startActivity(Intent(this, LessonActivity::class.java))

setter/getter

默认是这代码,不用写的, Kotlin 在声明的时候已将setter 和 getter 关联

Untitled 1.png

在 Kotlin 声明属性的时候(没有使用private修饰),会自动生成一个私有属性和一对公开的setter/getter函数。

在写setter/getter的时候使用field来代替内部的私有属性(防止递归栈溢出)。

为什么EditText.getText()的时候可以简化,但是 EditText.setText() 的时候不能和TextView.setText()一样简化?

因为EditText.getText()获得的类型是Editable,对应的如果EditText.setText()传入的参数也是Editable就可以简化了。

val newEditable = Editable.Factory.getInstance().newEditable("Kotlin")et_username.text=newEditable

类与对象

可见性修饰符:

  • 如果你不指定任何可见性修饰符,默认为 public,这意味着你的声明将随处可见;
  • 如果你声明为 private,它只会在声明它的文件内可见;
  • 如果你声明为 internal,它会在相同模块内随处可见;
  • protected 不适用于顶层声明。
  • 注意:要使用另一包中可见的顶层声明,仍需将其导入进来。

类和接口

对于类内部声明的成员:

  • private 意味着只在这个类内部(包含其所有成员)可见;
  • protected—— 和 private一样 + 在子类中可见。
  • internal —— 能见到类声明的 本模块内 的任何客户端都可见其 internal 成员;
  • public —— 能见到类声明的任何客户端都可见其 public 成员。

请注意在 Kotlin 中,外部类不能访问内部类的 private 成员。

如果你覆盖一个 protected 成员并且没有显式指定其可见性,该成员还会是 protected 可见性。

例子:

open class Outer {
    private val a = 1
    protected open val b = 2
    internal val c = 3
    val d = 4  // 默认 public
    
    protected class Nested {
        public val e: Int = 5
    }
}

class Subclass : Outer() {
    // a 不可见
    // b、c、d 可见
    // Nested 和 e 可见

    override val b = 5   // “b”为 protected
}

class Unrelated(o: Outer) {
    // o.a、o.b 不可见
    // o.c 和 o.d 可见(相同模块)
    // Outer.Nested 不可见,Nested::e 也不可见
}

构造函数

在一个类中 可以有一个构造函数以及一个或多个次构造函数,主构造函数是类头的一部分,跟在类名(与可选的类型参数)后。

class Person constructor(firstName: String) { /*……*/ }

如果主构造函数没有任何注解或者可见性修饰符,可以省略这个 constructor 关键字。

**class** Person(firstName: String) { /*……*/ }

如果我们在构造器主动调用了了父类构造,那么在继承类的时候就不能在类的后面加上小括号

constructor(context: Context) : this(context, null)
// 主动调用用了父类的构造器

constructor(context: Context, attr: AttributeSet?) :
super(context, attr)

@JvmField

生成属性通过 @JvmField 注解可以让编译器只生成一个 public 的成员属性,不生成对应的 setter/getter 函数

Any 和 Unit

Any Kotlin 的顶层父类是Any,对应 Java 当中的 Object,但是比 Object 少了wait()/notify() 等函数

Unit Kotlin 中的 Unit 对应 Java 中的 void

数组

arrayOf() 不可变

val arrayOf = arrayOf(1,2,3,4,5) Kotlin 会进行装箱,消耗内存

会使用 专门的数据类型

val intArray = intArrayof(1,2,3,4,6778)

基本数据类型 和包装类型

/****
Java
int long double long
Float null? 包装类型是有可能为null的

Kotlin
Int (不为空的)Float Double  Long
(都是对象 都有对应的方法)

Float?

****/
1.toFloat()

kotlin的静态函数定义(三种方法)

函数和属性都可以定义在文件中)

1.Class 类 中直接定义 (顶层函数 or 包级函数)

Untitled 2.png

调用的时候不需要导包,这种函数叫做 顶层函数 or 包级函数 ,Java 也可以调用eg.

文件名Kt.dp2px(12f);

UtilsKt.dp2px(12f);

2.Object (声明单例对象)

Untitled 3.png

Java中调用:

CacheUtils.INSTANCE.save()

3.companion object 伴生对象

Untitled 4.png 转为Kotlin 👇

compaion obejct : 维护内部类的一个作用 ,是一个单列,不是静态函数

Untitled 5.png

Untitled 6.png

Java 调用: 加上 @JvmStatic 可变为真正的静态

BaseApplication.Companion.currentApplication();
BaseApplication.currentApplication(); (加注解的 @JvmStatic

Kotlin 为什么会把 static 删掉? 不是更简洁嘛, 但是使用static 声明函数后,这函数就不属于任何对象了 。使用Object 则是加强了这个万物皆对象的概念。

Java 1.8 仅支持 字符串,枚举。

Java 12 的 switch 支持表达式了

其中,「顶层函数」直接在文件中定义函数和属性,会直接生成静态的,在 Java 中通过「文件名Kt」来访问,同时可以通过 @file:JvmName 注解来修改这个「类名」。

需要注意,这种顶层函数不要声明在 module 内最顶层的包中,至少要在一个包中例如 com 。不然不能方便使用。object 和 companion object 都是生成单例例对象,然后通过单例对象访问函数和属性的。

枚举 特殊的class

注解 接口

List 不可修改

ArrayList, MutableList 可修改集合

标签

在 Java 中通过「类名.this例如Outer.this」获取目标类引用在 Kotlin 中通过「this@类名例如this@Outer」获取目标类引用

内部类

Kotlin 中,内部类默认是静态内部类 。没有关键字,

嵌套内部类 有关键字 inner, 可以被外部访问 ,不能有伴生对象

如果想让一个类 在 其他模块中被访问到,但同时又不想让app 模块 访问

Java 不支持,Java 中是 使用 @hide 注解 实现类似功能

Kotlin 模块内访问的修饰符 internal (当前模块可见)

注释

注释中可以在任意地方使用[]来引用目标,代替 Java 中的@param@link等。

非空断言

可空类型强制类型转换成不可空类型可以通过在变量后面加上!!,来达到类型转换。

open / final

Kotlin 中的类和函数,默认是被final修饰的 ( abstract 和 override 例外)

编译期常量

什么是编译期常量:编译过后的字节码,所有引用该常量的地方都会直接替换成值,这样有利于提高代码运行效率。

Java 中 同时被 final 修饰:

private static final String LESSON_PATH = “lesson”

Kotlin 中:

在静态变量上加上const关键字变成编译期常量

	const val = "lesson"

进阶

主构造器

Untitled 7.png 成员变了初始化可以直接访问到主构造参数

次级构造

Untitled 8.png

init代码块

Untitled 9.png

主构造不能包含任何的代码,初始化代码可以放到 init 代码块中,自上而下的顺序执行。

Untitled 10.png 在初始化的时候,初始化代码会按照

构造属性

在主构造函数前面加上 val /var 时构造参数同事成为成员变量

Untitled 11.png

data class

数据类同时会生成

toStirng()

hashCode()

equals()

copy() 浅拷贝

componentN() 解构

相等性

== 表示 equal

=== 表示地址比较 有可能是不同的两个对象

解构

可以把一个对象「解构」成很多变量

Untitled 12.png 对应的 Java 代码

Untitled 13.png

Elvis 操作符

可以通过?:的操作来简化if null的操作

?. ?:

eg.

//lesson.date 为空时使用默认值
val date = lesson.date?: "日期待定"

//lesson.state 为空时提前返回
val state = lesson?.state?: return 

when 操作符

when 表达式可以接受返回值,多个分支相同的处理方式可以放在一起,用逗号分隔

Untitled 14.png

when 表达式可以用来取代 if-else-if 链。如果不提供参数,所有的分支条件都是布尔表达式

Untitled 15.png

operator

通过 operator 修饰「特定函数名」的函数,例如plus、get,可以达到重载运算符的效果

表达式含义
a + ba.plus(b)
a - ba.minus(b)
a * ba.times(b)
a / ba.div(b)

lambda

如果函数的最后一个参数是 lambda,那么 lambda 表达式可以放在圆括号之外:

lessons.forEach(){ lesson : Lesson->// ...}

如果你的函数传入参数只有一个 lambda 的话,那么小括号可以省略的:

lessons.forEach { lesson : Lesson->// ...}

如果 lambda 表达式只有一个参数,那么可以省略,通过隐式的it来访问循环 lessons.

lessons.forEach { // it// ...}

循环

1.标准函数repeat():

repeat(100) {
		//...
}

2.区间

for (i in 0..99) {
}

infix 函数

必须是成员函数或者拓展函数

必须只能接受一个参数,并且不能有默认值

// until() 函数的源码
public infix fun Int.until(to: Int): IntRange {
if (to<=Int.MIN_VALUE) 
	return IntRange.EMPTYreturnthis .. (to-1).toInt()
}

嵌套函数

Kotlin 可以在函数中继续声明函数

fun func() {
	fun innerFunc(){
	
	}
}
  • 内部函数可以访问外部函数的参数
  • 每次调用时,会产生一个函数对象

注解使用处目标

函数简化

通过 = 简化 return 的函数

fun get(key :String) = SP.getString(key, null)

函数参数默认值

可以通过函数参数默认值来代替

// 使用 @JvmOverloads 对 Java 暴露重载函数
@JvmOverloads
fun toast(text: CharSequence, duration: Int=Toast.LENGTH_SHORT) {
	Toast.makeText(this, text, duration).show()
}

扩展函数

  • 可以为任何类添加上一个函数,用于代替工具类
  • 和成员函数相同时,成员函数优先被调用
  • 是静态解析的,在编译时就确定了调用函数(没有多态)

函数类型

函数类型由「传入参数类型」和「返回值类型」组成,用「->」连接,传入参数需要用「()」。

如果返回值为 Unit 不能省略,函数类型实际是一个接口,我们传递函数的时候可以通过「::函数名」,或者「匿名函数」或者使用「lambda」

内联函数

  • 内联函数配合函数类型 ,可以减少函数类型生成的对象 (减少调用栈,行数多的话编译时间会增加。)

  • 使用 inline 关键字声明 内联函数,在编译时会将内敛函数中的函数体直接插入到调用处。

    在写内联函数的时候需要注意,尽量将内联函数中的代码行数减少!

部分禁用内联

noinline 可以禁止部分参数参与内联编译

inline fun foo(inlined: () ->Unit, noinline notInlined:() ->Unit) {
	//......
}

具体化的类型参数

因为内联函数的存在,可以通过配合 inline + reified 达到 「 真泛型 」的效果

val RETROFIT=Retrofit.Builder()
	.baseUrl("https://api.hencoder.com/")
	.build()

inline fun<reifiedT>create(): T {
	return RETROFIT.create(T::class.java)
}

val api=create<API>()

抽象属性

在 Kotlin 中,可以声明抽象属性, 子类对抽象属性重写的时候需要重写对应的 setter / getter

委托

属性委托

有些常⻅的属性操作,我们可以通过委托的方式,让它只实现一次,例如:

  • lazy 延迟属性:值只在第一次访问的时候计算
  • observable 可观察属性: 属性发生改变时可通知
  • map 集合:将属性存入一个 map中

对于一个只读属性(即 val 声明的),委托对象必须提供一个名为 getValue() 的函数

对于一个可变属性(即 var 声明的),委托对象同时提供 setValue()、getValue() 函数

类委托

可以通过类委托的模式来减少继承

类委托的,编译器会优先使用自身重写的函数,而不是委托对象的函数

interface Base() {
		fun print()
}

class BaseImpl(val x: Int) : Base {
	override fun print() {
		print(x)
	}
}

// Derived 的 print 实现会通过构造参数中的 b 对象来完成。

class Derived(b: Base) : Base by b

Kotlin 标准函数

使用时可以通过简单的规则作出一些判断:

  • 返回自身 → apply\color{red}applyalso\color{red}also

    作用域中使用 this 作为参数 → apply\color{red}apply

    作用域中使用 it 作为参数 → also\color{red}also

  • 不需要返回自身 → 从 run\color{red}runlet\color{red}let 选择

    作用域中使用 this 作为参数 → run\color{red}run

    作用域中使用 it 作为参数 → let\color{red}let

apply 适合对一个对象做附加操作的时候,

let 适合配合空判断的时候 (最好是成员变量,而不是局部变量,局部变量更适合用 if )

with 适合对同一个对象进行多次操作的时候

过滤(Filtering)集合

过滤(Filtering)集合