kt 语法笔记

基本数据类型

本数值类型包括 Byte、Short、Int、Long、Float、Double 等。不同于 Java 的是,字符不属于数值类型,是一个独立的数据类型。
类型 位宽度
Double 64
Float 32
Long 64
Int 32
Short 16
Byte 8

三个等号 === 表示比较对象地址,两个 == 表示比较两个值大小
类型加上to前缀表示转换

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

字符串
字符串模板

使用 in 运算符来检测某个数字是否在指定区间内,区间格式为 x…y :

默认final 不可继承

变量

定义变量时,可在类型后面加一个问号?,表示该变量是Nullable,不加表示该变量不可为null
对于可以为null的变量,在使用该变量的时候,必须用变量名+?(如上面的s?)的形式进行调用,表示如果该变量为null,则不执行该变量调用的方法

所有成员变量(包括定义在类中,以及直接定义在kt文件中的)在定义时必须进行初始化,局部变量(定义在方法内)可以不进行初始化

在getter与setter中,使用field代表当前的变量,不能直接使用变量名

var test="haha"
	get()= field //getter只返回字段值,因此可以简写
	set(value){
    
    //setter是没办法简写的,因为它set(value) = field = value编译报错
		field = value
	}

pecs 原则

Producer Extends Consumer Super 如果参数化类型表示一个生产者,就使用

<? extends>T>;如果它表示一个消费者,就使用<? super T> out/ extends上限 in/super 下限

条件控制

when (x) {
    
    
    0, 1 -> print("x == 0 or x == 1")
    else -> print("otherwise")
}

循环控制

 for (i in  0..10){
    
    
      println("i=$i")
 }
 for (j in  0 until 11){
    
    
    println("j=$j")
 }

 for (k in  10 downTo 0){
    
    
       println("k=$k")
  }
 repeat(10){
    
    
       println("it=$it")
 }
  

点点是一个范围表达式rangTo()

类的修饰符包括 classModifier 和 accessModifier

classModifier: 类属性修饰符,标示类本身特性。
abstract // 抽象类
final // 类不可继承,默认属性
enum // 枚举类
open // 类可继承,类默认是final的
annotation // 注解类
accessModifier: 访问权限修饰符

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

var 声明为可变的,否则使用只读关键字 val 声明为不可变。

Kotlin 中类不能有字段。提供了 Backing Fields(后端变量) 机制,备用字段使用field关键字声明,field 关键词只能用于属性的访问器

非空属性必须在定义的时候初始化,kotlin提供了一种可以延迟初始化的方案,使用 lateinit
lateinit 对应使用var来声明属性
lateinit 修饰不可以修饰原始数据类型(byte,char,short ,int,long,float,double)

var aaa: Int = 0
get() {
    
    
    field // 这里必须出现一下field关键字,否则 var aaa: Int = 0 会报错,除非你去掉 = 0这部分,不要给它赋初始化值
    return 0
}
set(value) {
    
    }

类也可以有二级构造函数,需要加前缀 constructor: this关键字调用其他构造函数

如果一个非抽象类没有声明构造函数(主构造函数或次构造函数),它会产生一个没有参数的构造函数。构造函数是 public 。如果你不想你的类有公共的构造函数,你就得声明一个空的主构造函数
init 块中初始化

匿名内部类

继承

Kotlin 中所有类都继承该 Any 类,它是所有类的超类,对于没有超类型声明的类是默认超类:

1.如果子类有主构造函数, 则基类必须在主构造函数中立即初始化。
Any 默认提供了三个函数:
equals()
hashCode()
toString()

open class Person(var name : String, var age : Int){
    
    // 基类

}

class Student(name : String, age : Int, var no : String, var score : Int) : Person(name, age) {
    
    

}

2子类没有主构造函数
如果子类没有主构造函数,则必须在每一个二级构造函数中用 super 关键字初始化基类,或者在代理另一个构造函数。初始化基类时,可以调用基类的不同构造方法。

如果有多个相同的方法(继承或者实现自其他类,如A、B类),则必须要重写该方法,使用super范型去选择性地调用父类的实现

class C() : A() , B{
    
    
    override fun f() {
    
    
        super<A>.f()//调用 A.f()
        super<B>.f()//调用 B.f()
    }
}

属性重写使用 override 关键字,属性必须具有兼容类型,每一个声明的属性都可以通过初始化程序或者getter方法被重写:

扩展

扩展函数

fun receiverType.functionName(params){
    
    
    body
}

扩展函数是静态解析的
扩展函数是静态解析的,并不是接收者类型的虚拟成员,在调用扩展函数时,具体被调用的的是哪一个函数,由调用函数的的对象表达式来决定的,而不是动态的类型决定的:

若扩展函数和成员函数一致,则使用该函数时,会优先使用成员函数。

扩展一个空对象
在扩展函数内, 可以通过 this 来判断接收者是否为 NULL,这样,即使接收者为 NULL,也可以调用扩展函数。

扩展的作用域
import

伴生对象的扩展
如果一个类定义有一个伴生对象 ,你也可以为伴生对象定义扩展函数和属性。
伴生对象通过"类名."形式调用伴生对象,伴生对象声明的扩展函数,通过用类名限定符来调用:

扩展声明为成员
其中扩展方法定义所在类的实例称为分发接受者,而扩展方法的目标类型的实例称为扩展接受者
在这类扩展函数的派 发过程中, 针对分发接受者是虚拟的(virtual), 但针对扩展接受者仍然是静态的。

假如在调用某一个函数,而该函数在分发接受者和扩展接受者均存在,则以扩展接收者优先,要引用分发接收者的成员你可以使用限定的 this 语法。

open class D {
    
    
}

class D1 : D() {
    
    
}

open class C {
    
    
    open fun D.foo() {
    
    
        println("D.foo in C")
    }

    open fun D1.foo() {
    
    
        println("D1.foo in C")
    }

    fun caller(d: D) {
    
    
        d.foo()   // 调用扩展函数
    }
}

class C1 : C() {
    
    
    override fun D.foo() {
    
    
        println("D.foo in C1")
    }

    override fun D1.foo() {
    
    
        println("D1.foo in C1")
    }
}


fun main(args: Array<String>) {
    
    
    C().caller(D())   // 输出 "D.foo in C"
    C1().caller(D())  // 输出 "D.foo in C1" —— 分发接收者虚拟解析
    C().caller(D1())  // 输出 "D.foo in C" —— 扩展接收者静态解析

}

数据类

data class User(val name: String, val age: Int)

主构造函数至少包含一个参数。
所有的主构造函数的参数必须标识为val 或者 var ;
数据类不可以声明为 abstract, open, sealed 或者 inner;
数据类不能继承其他类 (但是可以实现接口)。

copy方法 和 解构

equals() / hashCode()
toString() 格式如 “User(name=John, age=42)”
componentN() functions 对应于属性,按声明顺序排列
copy() 函数

密封类

sealed class Expr

声明一个密封类,使用 sealed 修饰类,密封类可以有子类,但是所有的子类都必须要内嵌在密封类中一个文件。
sealed 不能修饰 interface ,abstract class(会报 warning,但是不会出现编译错误)

伴生对象(类似静态调用)

  1. 类里面, java调用需要加上 类名.Companion.字段/方法
    kt调用不需要
  2. 如果类外面,类名Kt.字段/方法
    kt调用不需要

枚举类

自 Kotlin 1.1 起,使用 enumValues() 和 enumValueOf() 函数以泛型的方式访问枚举类中的常量

enum class Color{
    
    
    RED,BLACK,BLUE,GREEN,WHITE
}

fun main(args: Array<String>) {
    
    
    var color:Color=Color.BLUE

    println(Color.values())
    println(Color.valueOf("RED"))
    println(color.name)
    println(color.ordinal)

}

对象(单例)

请注意,匿名对象可以用作只在本地和私有作用域中声明的类型。如果你使用匿名对象作为公有函数的 返回类型或者用作公有属性的类型,那么该函数或属性的实际类型 会是匿名对象声明的超类型,如果你没有声明任何超类型,就会是 Any。在匿名对象 中添加的成员将无法访问。

class C {
    
    
    // 私有函数,所以其返回类型是匿名对象类型
    private fun foo() = object {
    
    
        val x: String = "x"
    }

    // 公有函数,所以其返回类型是 Any
    fun publicFoo() = object {
    
    
        val x: String = "x"
    }

    fun bar() {
    
    
        val x1 = foo().x        // 没问题
        val x2 = publicFoo().x  // 错误:未能解析的引用“x”
    }
}

obj/companion/class/inner class 区别

内部类类型 在A类内部调用 在A类外部调用
object C C.c(),C.c A.C.c,A.C.c()
companion object D 可以直接使用 d,d() A.d,A.d()
class B 实例化B对象val b = B(); b.b() ; b.b 实例化B对象val b = A.B(); b.b() ; b.b
inner class E 实例化E对象val e = E(); e.e() ; e.e 通过外部类的实例a来

重载

operator fun plus(other:Person)
一元运算符
表达式 函数名
+a unaryPlus
-a unaryMinus
!a not
++a, a++ inc
–a, a-- dec
二元运算符
a + b a.plus(b)
a - b a.minus(b)
a * b a.times(b)
a / b a.div(b)
a % b a.rem(b) 或 a.mod(b)
a … b a.rangTo(b)

标签处返回

如果我们需要从 lambda 表达式中返回,我们必须给它加标签并用以限制 return。

fun foo() {
    
    
    ints.forEach lit@ {
    
    
        if (it == 0) return@lit
        print(it)
    }
}

fun foo() {
    
    
    ints.forEach {
    
    
        if (it == 0) return@forEach
        print(it)
    }
}

该标签与接受该 lambda 的函数同名

协程

协程通过将复杂性放入库来简化异步编程。程序的逻辑可以在协程中顺序地表达,而底层库会为我们解决其异步性。该库可以将用户代码的相关部分包装为回调、订阅相关事件、在不同线程(甚至不同机器)上调度执行,而代码则保持如同顺序执行一样简单。

runBlocking启动的协程任务会阻断当前线程,直到该协程执行结束。当协程执行结束之后,页面才会被显示出来。

private fun test() = runBlocking {
    
    
    repeat(8) {
    
    
        Log.e(TAG, "协程执行$it 线程id:${Thread.currentThread().id}")
        delay(1000)
    }
}

从执行结果看出,launch不会阻断主线程

override fun onCreate(savedInstanceState: Bundle?) {
    
    
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    Log.e(TAG, "主线程id:${mainLooper.thread.id}")
    val job = GlobalScope.launch {
    
    
        delay(6000)
        Log.e(TAG, "协程执行结束 -- 线程id:${Thread.currentThread().id}")
    }
    Log.e(TAG, "主线程执行结束")
}

//Job中的方法
job.isActive
job.isCancelled
job.isCompleted
job.cancel()
jon.join()

suspend
同时协程挂起后不会阻塞其他线程的执行

async跟launch的用法基本一样,区别在于:async的返回值是Deferred,将最后一个封装成了该对象。async可以支持并发,此时一般都跟await一起使用

(1)当synchronized作用于普通方法是,锁对象是this;
(2)当synchronized作用于静态方法是,锁对象是当前类的Class对象;
(3)当synchronized作用于代码块时,锁对象是synchronized(obj)中的这个obj。

杂项

从字节码我们可以看到const val 和val修饰对象的主要区别是:

const val 可见性为public final static,可以直接访问。
val 可见性为private final static,并且val 会生成方法getNormalObject(),通过方法调用访问。
当定义常量时,出于效率考虑,我们应该使用const val方式,避免频繁函数调用。

关键字 描述
keep 保留类和类中的成员,防止被混淆或移除
keepnames 保留类和类中的成员,防止被混淆,成员没有被引用会被移除
keepclassmembers 只保留类中的成员,防止被混淆或移除
keepclassmembernames 只保留类中的成员,防止被混淆,成员没有引用会被移除
keepclasseswithmembers 保留类和类中的成员,防止被混淆或移除,保留指明的成员
keepclasseswithmembernames 保留类和类中的成员,防止被混淆,保留指明的成员,成员没有引用会被移除

Kotlin 将包级别的函数表示为静态方法。如果你将命名对象或伴生对象中定义的函数注解为 @JvmStatic,Kotlin 还可以为这些函数生成静态方法。如果使用此注解,编译器将在对象的封闭类中生成静态方法,并在对象本身中生成实例化方法。

kotlin部分方法在java和kt调用不同

class MyCompanion {
    
    
    companion object MyObject {
    
    
        var a:Int = 100
        @JvmStatic
        fun method() {
    
    
            println("method invoked")
        }
    }
}

fun main(args: Array<String>) {
    
    
    println(MyCompanion.a)
    MyCompanion.method() // 类似于静态方法,kotlin中没有静态方法
    println("--------------")

    println(MyCompanion.javaClass)
}

二:内联函数之let
let扩展函数的实际上是一个作用域函数,当你需要去定义一个变量在一个特定的作用域范围内,let函数的是一个不错的选择;let函数另一个作用就是可以避免写一些判断null的操作。

猜你喜欢

转载自blog.csdn.net/u012787710/article/details/118189774