Kotlin 属性与函数入门(与Java 比对)

前言

Kotlin 问世也有好几年了,尤其作为Android 官方指定的开发语言,许多项目已经替换为Kotlin编写,广泛流行的第三方库也开始支持Kotlin。在使用Kotlin的过程中,你将会逐渐了解其优势并学会物尽其用。
通过本篇文章,你将了解到:

1、为什么需要属性和函数
2、Kotlin 属性详解
3、Kotlin 函数详解

1、为什么需要属性和函数

image.png

简单来说:

属性就是入参,函数就是处理变量的过程体

因此,在高级语言范畴,属性和函数是基础。

2、Kotlin 属性详解

属性声明

Kotlin 属性声明的关键词是var 与 val,var 是 variable的缩写,val 是 value的缩写。var 指向的值是可变的,后续可以给它赋其它值,而val 是常量,初始化后就不能再变化。
属性分为两种:

常量&变量

//kotlin
//变量
var num: Int = 3
//常量
val age: Int = 4

//java
//变量
int num = 3;
//常量
final int age = 4;

与Java 属性声明不同的是,Kotlin 需要使用var/val 声明属性,并且属性类型写在":"后边,你可能会说了:说好的Kotlin简洁呢,咋还多写了几个单词?
实际上Kotlin 可以进行类型推断,比如上面声明的属性可以改写为如下:

//kotlin
//变量
var num = 3
//常量
val age = 4

省略了":"与类型,编译器自动推断num与age 的类型为Int。
当然,若是:

//kotlin
//变量
var num = 3.4

那么此时num 为Double 类型。

属性类型

image.png

可以看出,Kotlin里不存在所谓的基本数据类型,而它里边的"基本数据类型引用"与Java 基本数据类型对应,类似Java 的包装类。
来看看数据类型转换:

var numInt : Int = 4
var numLong : Long = 5

//kotlin 转换
fun test() {
    //不被允许
    numInt = numLong
    numLong = numInt

    //允许
    //大范围转小范围可能会发生溢出
    numInt = numLong.toInt()
    numLong = numInt.toLong()
}

//Java 转换
void test() {
    int a = 5;
    //小转大  允许
    long b = a;
    //大转小 允许
    a = (int) b;
}

可见:Kotlin 大转小、小转大需要依赖toXX()函数才行。

属性访问

全局属性

以上声明属性/函数定义在:VarTest.kt里,现在分别从另一个Kotlin文件和Java 文件里访问它们。

Kotlin 里访问

#Start.kt
fun main(args:Array<String>) {
    //赋值
    num = 3.4
    //取值
    var numLong2 = num
    //调用函数
    test()
}

Java 里访问

    void test2() {
        //调用方法
        VarTestKt.test2();
        //获取变量
        VarTestKt.getNum();
        //设置变量
        VarTestKt.setNum(33);
    }

在VarTest.kt里并没有声明类,因此直接在文件里声明的属性/函数 是具备全局特性的,最终编译后的字节码里:

属性是静态属性,函数是静态方法。VarTest.kt会编译为VarTestKt Java类,因此在Java 代码里可以通过静态方法访问它们。
而在Kotlin 里直接访问它们。

VarTest.kt编译后字节码反编译如下:

#TestJava.java
public final class VarTestKt {
    private static double num = 3.4D;
    
    public static final double getNum() {
        return num;
    }

    public static final void setNum(double var0) {
        num = var0;
    }
    
    public static final void test2() {
        ...
    }
}

如果想在Java 文件里直接通过.xx的方式访问属性,需要在VarTest.kt 文件里添加@JvmField 修饰

#VarTest.kt
@JvmField
var num = 3.4

在Java 里调用如下:

#TestJava.java
    void test2() {
        //调用方法
        VarTestKt.test2();
//        //获取变量
//        VarTestKt.getNum();
//        //设置变量
//        VarTestKt.setNum(33);

        //直接访问属性
        VarTestKt.num = 2.4;
        double num = VarTestKt.num;
    }

需要注意的是:对于Java 来说,此时num 属性的访问权限为public,并且其默认的get/set 方法都没有了,在Java里只能通过.num访问它,而不通过getNum/setNum访问。

类成员属性

先声明属性
#VarTestClass.kt
class VarTestClass {
    var num:Int = 3;
}

Kotlin 里访问

fun main1() {
    //新建对象
    var varTestClass = VarTestClass()
    //赋值
    varTestClass.num = 3
    //获取值
    var num = varTestClass.num
}

Java 里访问

#TestJava.java
    void test3() {
        //新建对象
        VarTestClass varTestClass = new VarTestClass();
        varTestClass.getNum();
        varTestClass.setNum(3);
    }

与全局属性相比,类成员属性依赖于类,因此想要访问它们需要先新建对象,通过对象来访问。其余访问方式两者一致。

属性权限与初始化

访问控制权限

image.png

Koltin 默认是public,因此大多数情况下都可以直接访问属性。
若是使用private 修饰属性,那么将不会生成get/set 方法。

初始化

初始化时机
在Java里 声明属性的同时给属性赋值即可完成属性初始化,而对于Kotlin来说可以选择不立刻初始化。
先看常量val 初始化:

//正常初始化
val myNum = 4
//延迟初始化 常量
val myNum2:Int by lazy { 3 }

看起来延迟初始化没啥用处呢?再来看引用类型延迟初始化

class MyBean {
    var age = 3
}
//正常初始化
val myBean = MyBean()
//延迟初始化
val myBean1:MyBean by lazy { 
    MyBean()
}

这么看作用就比较明显了,当用到myBean1时才会构造MyBean对象,节省了内存。
注:by 和 lazy 都是函数
当我们使用by lazy 延迟初始化变量时会报错。

var myBean1:MyBean by lazy {
    MyBean()
}

变量的延迟初始化需要另一个关键字:lateinit

//变量正常初始化
var name:String = "fish"
//变量延迟初始化
lateinit var name1:String

fun useName() {
    name1 = "fish1"
}

需要注意的是,以下方式将不被允许:

//错误,不能修饰基本类型
lateinit var num3:Int
//错误,不能修饰空类型
lateinit var name2?:String

总结来说,属性可以选择延迟初始化:

var 使用 lateinit,该关键字不能修饰基本类型(Int等)
val 使用 by lazy

get/set 方式
在Java 里对于属性我们一般使用get/set 对它进行操作,Kotlin 里虽然自动生成了get/set 方法,但有时我们需要对get/set 进行额外操作,因此可以重写它们。

var myName: String = ""
    get() {
        //获取值
        var age = 2
        if (age > 18)
            return field
        return ""
    }
    set(value) {
        //设置值
        if (value == "fish")
            field = value
    }

field 称为后端变量,是变量的实际值,此处为myName的实际值。

注意:get/set 里不能使用myName,因为访问myName本质上是通过get/set 方法进行的,会造成无限递归访问,因此需要使用field。

3、Kotlin 函数详解

名称不同

在Java 里:

    void test4() {
        
    }

称为方法
而在Kotlin里:

fun test4() {
    
}

称为函数

形参与返回值

fun test4(name : String):String {
    return "hello"
}

声明函数需要自定关键字:fun
与属性的定义类似,: 后表示返回类型。
name:String 表示形参。

看起来和Java 相差不大,接着来看细节之处。
Kotlin 函数若是没有返回值,则用Unit表示:

fun test5(name: String):Unit {
}

类似Java里的Void。
通常来说,此处的Unit可以省略,变为如下:

fun test5(name: String) {
}

命名参数与默认参数

先说命名参数:

//函数声明
fun testV2(name: String, age: Int, score: Double) {
}

调用该函数:

fun main2() {
    testV2("fish", 5, 4.5)
}

当我们看testV2调用时,可能无法一下子看出实参和形参如何对应上的,此时就需要命名参数(具名参数):

fun main2() {
    testV2("fish", 5, 4.5)
    testV2(score = 4.5, name = "fish", age = 5)
}

"命名"顾名思义可以指定形参的名字与实参的值,当使用命名参数时,参数的顺序可以调换。
需要注意的是:命名参数的后面跟的其它参数也需要写成命名参数的形式。
比如以下就是编译错误:

    testV2(score = 4.5, name = "fish", 5)

再说默认参数:

fun testV4(name: String = "fish", age: Int, score: Double){
}

name 默认值是"fish",调用testV4()如下:

fun  main3() {
    testV4(score = 4.5, age = 5)
}

此时若是name值没变化,就可以不用传递,使用默认值即可。
需要注意的是:当不传递默认参数时,其它参数需要使用命名参数的方式传递。
当然,如果默认参数是最后一个值,那么其它参数可以直接传递值而无需使用命名参数形式。

fun testV4(name: String = "fish", age: Int, score: Double = 4.5){
}
fun  main3() {
    testV4("fish", 4)
}

Java 中没有命名参数和默认参数的说法。

可变参数

Java 中的可变参数:

    void test(String... names) {
        for (String name : names) {
            Log.d("fish", name);
        }
    }

Koltin中可变参数为:

fun testV5(vararg name:String) {
    name.forEach {
        println(it.length)
    }
}

通过vararg 声明。
在Kotlin 里调用如下:

fun  main4() {
    testV5("fish", "fish2", "fish3")
}

函数分类

依据函数作用域的不同,可分为以下几种:
image.png

顶层函数

定义在Koltin 文件里的函数,与类无关。
在Start.kt 文件里定义如下:

fun  main4() {
    testV5("fish", "fish2", "fish3")
}

class MyStart {
    
}

此时main4 与MyStart 没有任何关系,它是存在于Start.kt里的,其它文件访问该方法与访问全局(顶层)属性类似。

类成员函数

class MyStart {
    fun start() {
       
    }
}

start() 为成员函数,外部访问它与访问类成员属性类似。

类构造函数

涉及知识点较多,下篇分析。

嵌套函数

顾名思义:放在函数里的函数。

class MyStart {
    fun start() {
        fun end() {
        }
        //调用
        end()
    }
}

此时end 为嵌套函数,主要用途:

1、递归函数。
2、函数内部统一逻辑抽取,又不想暴露给外部。

与Java 比对,Kotlin 属性变动也不是特别大,真正变化大的是Kotlin 的函数。
以上只是简单介绍了函数的一些基本知识,它还有一些更高级的功能:
扩展函数、函数参数/返回值 为函数、函数表达式、函数泛型、Lambda等,理解了这些知识是看懂协程的前提。
我们下篇将补齐这些知识点。

本文基于Kotlin 1.5.3,文中Demo请点击

您若喜欢,请点赞、关注,您的鼓励是我前进的动力

持续更新中,和我一起步步为营系统、深入学习Android/Kotlin

1、Android各种Context的前世今生
2、Android DecorView 必知必会
3、Window/WindowManager 不可不知之事
4、View Measure/Layout/Draw 真明白了
5、Android事件分发全套服务
6、Android invalidate/postInvalidate/requestLayout 彻底厘清
7、Android Window 如何确定大小/onMeasure()多次执行原因
8、Android事件驱动Handler-Message-Looper解析
9、Android 键盘一招搞定
10、Android 各种坐标彻底明了
11、Android Activity/Window/View 的background
12、Android Activity创建到View的显示过
13、Android IPC 系列
14、Android 存储系列
15、Java 并发系列不再疑惑
16、Java 线程池系列
17、Android Jetpack 前置基础系列
18、Android Jetpack 易学易懂系列

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值