• kotlin-筑基(1)


    1. val、var、const

    var、val 声明的变量分为三种类型:顶层属性、类属性和局部变量;

    var 属性可以生成 getter 和 setter,是可变的(mutable),val 属性只有 getter,是只读的(read-only,注意不是 immutable);

    局部变量只是一个普通变量,不会有 getter 和 setter,它的 val 等价于 Java 的 final,在编译时做检查。

    const 只能修饰没有自定义 getter 的 val 属性,而且它的值必须在编译时确定。

    val 没有 setter,是只读但非不可变

    var 可变

    const 只读不可变,只存在顶层或 object 中

    Java 中可以使用 static final 来定义常量,这个常量会存放于全局常量区,这样编译器会针对这些变量做一些优化,例如,有三个字符串常量,他们的值是一样的,那么就可以让这个三个变量指向同一块空间。

    局部变量无法声明为 static final,因为局部变量会存放在栈区,它会随着调用的结束而销毁。

    kotlin引入关键字 const 来定义常量,但是这个常量跟 Java 的 static final 是有所区别的,如果它的值无法在编译时确定,则编译不过,因此 const 所定义的常量叫编译时常量


    2.什么是幕后字段

    2.1 kotlin的普通属性

    在kotlin中,声明一个属性涉及到2个关键字:var 和 val

    • var 声明一个可变属性
    • val 声明一个只读属性

    在kotlin中 gettter 和 setter 跟Java的getXX 和 setXX 方法一样,叫做访问器。读一个属性的本质是执行了读访问器getter,类似的,写一个属性的实质就是执行了属性的写访问器setter。

    2.2 幕后字段

    在 getter 和 setter 中用 field 来表示幕后字段,重写 setter 和 getter ,没有使用 field 不会有幕后字段,有幕后字段的属性转换成Java代码一定有一个对应的Java变量

    class Bean{
        val gender:Int?=null
        
        // field:幕后字段的实际使用场景
        var name = ""
           set(value) {
                field= if (value==gender.toString()) "男" else "女"
            }
            get() {
                field = if (gender==1) "男" else "女"
                return field
            }
         
        // 重写 setter 和 getter 并且未显示使用 field 是没有幕后字段的
        var nameCopy :Int
            set(value) {
            }
            get() {
               return 1
            }
            
        // 另外一种 val 的写法 ,没有幕后字段   
    	val name:String
            get() {
                return if (gender==1) "男" else "女"
            }
    }
    
    • 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

    3. for 循环

    // 1. until:左闭右开
    for (i in 0 until 100) {
        println(i) // 0 ~ 99
    }
    
    // 2. ..:全闭区间(补充:可以通过 val test = 0..100 这种语句声明一个区间
    for (i in 0..100) {
        println(i) // 0 ~ 100
    }
    
    // 3. downTo:降序的全闭区间
    for (i in 100 downTo 0){
        println(i) // 100 ~ 0
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    4. init{}

    class Init{
        companion object{
           	// 相当于 java 中的 static {}
            init{
                
            }
        }
        
        // 相当于 java 的构造函数,主构造函数,没有声明则是默认的无参构造函数
        init{
            
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    5. data class

    查看kotlin字节码反编译java文件后

    1. data class 的类已经重写了hashCode() 和 equals() 方法
    2. 普通class 是不会的

    5.1 gson解析

    下面例子用到 @SerializedName 标记后台字段,解析时候就能成功解析。

    data class Goods(
    	val name:String,
        @SerializedName("price")
        var _price:Int
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5

    5.2 重写setter/getter

    由于data class 是 final 的,并且其 setter/getter也是final的,所以无法重写,要想实现重写,则需要将属性定义到类中。

    data class Person(val name:String){
        var age:Int=0
        	set(value){
                field =value
            }
        	get()=field
    }
    
    //使用的时候
    fun call(){
        val p = Person("monk")
        p.age = 10
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    6. 内联函数

    6.1 inline

    让变量内联用的是const ,而让函数内联则用 inline

    可以在某些情况下提升效率:

    1. 在编译时期,把调用这个函数的地方用这个函数的方法体进行替换(复制和 铺平);
    2. inline 适合在包含 lambda 参数的函数上,是因为 lambda 参数多出来的类会增加内存的分配。
    // kotlin 
    inline fun setCallback(listener: () -> Unit) {
        listener.invoke()
    }
    
    fun testInline(){
        setCallback { 
            print("测试 inline ")
        }
    }
    
    /**===============================java=====================================*/
     public static final void setCallback(@NotNull Function0 listener) {
          listener.invoke();
     }
    
     // 如果加了inline,方法调用栈就少了一层
    public static final void testInline() {
        String var2 = "测试 inline ";
        System.out.print(var2);
    }
    
    // 如果不加inline,反编译后的java函数如下,会产生Function0实例
    public static final void testInline(){
        setCallback((Function0)null.INSTANCE);
    }
    
    • 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

    6.2 return

    inline 函数支持 return,没加 inline 的函数在被调用时,要使用return ,一般会 return@方法名 方式,并且一般方法的后续代码还是会执行的。inline函数则不会让后续代码执行(想想代码平铺就理解了)。

    Lambda表达式里不允许使用return,除非—— 这个Lambda是内联函数的参数。

    inline fun hello(block:()->Unit){
        println("inline start")
        block()
        println("inline end")
    }
    
    fun call(){
        hello{
            println("block start")
            return
        }
        println("block end ")
    }
    
    fun main() {
        // 结果:inline start  -> block start
        call()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    6.3 noinline

    内联函数的「函数参数」 不允许作为参数传递给非内联的函数(看起来跟java 的 static 有点像,静态方法不能调用非静态方法)。

    也就是说如果希望只内联一部分传给内联函数的 lambda 表达式参数,那么可以用 noinline 修饰符标记不希望内联的函数参数。

    // 编译器提示这里的 inline 对性能预期影响是微不足道的(无需inline,但这里为了说明noinline的用法)
    inline fun hello(noinline block:()->Unit){
        println("inline start")
        // 如果这里没有用 noinline 修饰 block,此处会报错,
        call(block) 
        println("inline end")
    }
    
    // 非 inline 函数,
    fun call(block:()->Unit){
        println("call start")
        block()
        println("call end")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在其他函数调用时候,noinline 也会其作用:

    inline fun hello(preAction:()->Unit, noinline postAction:()->Unit){
        preAction()//做点前置工作
        println("Hello!")
        postAction()//做点后置工作
        
        //如果没有oninline修饰,这里编译器会报错
        return postAction
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    6.4 crossinline

    什么时候需要crossinline?当你需要突破内联函数的「不能间接调用参数」的限制的时候。但其实和noinline一样,你并不需要亲自去判断,只要在看到Android Studio给你报错的时候把它加上就行了。

    inline fun hello(crossinline postAction:()->Unit){
        println("hello!")
        runOnUiThread{
            //不加crossinline编译器会报错
            postAction()
            //return 这里不再允许使用return
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    Kotlin增加了一条额外规定:内联函数里被crossinline修饰的函数类型的参数,将不再享有「Lambda表达式可以使用return」的福利。所以这个return并不会面临「要结束谁」的问题,而是直接就不允许这么写。


    7. sealed class

    sealed class,密封类。具备最重要的一个特点:

    其子类可以出现在定义 sealed class 的不同文件中,但不允许出现在与不同的 module 中,且需要保证 package 一致。这样既可以避免 sealed class 文件过于庞大,又可以确保第三方库无法扩展你定义的 sealed class,达到限制类的扩展目的。

    是一个有特定数量子类的类,看上去和枚举有点类似。

     // TestSealed.kt
     sealed class GameAction(times: Int) {
         // Inner of Sealed Class
         object Start : GameAction(1)
         data class AutoTick(val time: Int) : GameAction(2)
         class Exit : GameAction(3)
     }
    
    fun main(args:Array<String>){
        var sc:GameAction = GameAction.Start()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    8. object

    类和对象同时创建,相当于 懒汉式单例,里面的方法是 final 方法

    1. val 变量相当于 private static final
    2. var 变量相当于 private static

    kotlin中有四种方式表示静态:

    1. object单例类模式
    2. companion object
    3. JvmStatic注解模式
    4. 顶层函数模式

    object:反编译java代码是由一个静态类包含一系列非静态函数

    companion object:同上

    JvmStatic:只能注解在单例或companion object 中的方法上,反编译java代码是一个静态函数

    顶层函数:反编译java代码是一个静态函数


    9. open

    在java中允许创建任意的子类并重写任意的方法,除非显示的使用了final关键字进行阻止。

    而在kotlin的世界里面则不是这样,在kotlin中它所有的类默认都是 final的,那么就意味着不能被继承,而且在类中所有的方法也是默认是final的,那么就是kotlin的方法默认也不能被重写,所以open 关键字就是用来解决这个现象的。


    10. inner

    kotlin 默认内部类是 static

    1. inner 会让内部类成为非静态,这样可以使用外部类方法和函数

    2. 因为外部类方法或函数有非静态,而 java 是不允许静态使用非静态(kotlin也是一样)

    静态属于类,非静态属于对象,也就是静态调用非静态可能会出现对象未初始化的情况,因此编译不通过

  • 相关阅读:
    在 NVIDIA DGX Cloud 上使用 H100 GPU 轻松训练模型
    王学岗音视频开发(一)—————配置NDK开发环境
    数字孪生技术如何提高化工生产安全性?
    面向对象技术--概念及程序设计
    libusb开源库使用说明
    一文让前端搞懂shell编程
    Linux——多线程1
    交易总额高达600亿美元?亚马逊、微软和谷歌完成100多笔并购
    Go 结合Gin导出Mysql数据到Excel表格
    【Javascript】设计模式之单例模式
  • 原文地址:https://blog.csdn.net/qq_37776700/article/details/133992472