前言
Kotlin 问世也有好几年了,尤其作为Android 官方指定的开发语言,许多项目已经替换为Kotlin编写,广泛流行的第三方库也开始支持Kotlin。在使用Kotlin的过程中,你将会逐渐了解其优势并学会物尽其用。
通过本篇文章,你将了解到:
1、为什么需要属性和函数
2、Kotlin 属性详解
3、Kotlin 函数详解
1、为什么需要属性和函数
简单来说:
属性就是入参,函数就是处理变量的过程体
因此,在高级语言范畴,属性和函数是基础。
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 类型。
属性类型
可以看出,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);
}
与全局属性相比,类成员属性依赖于类,因此想要访问它们需要先新建对象,通过对象来访问。其余访问方式两者一致。
属性权限与初始化
访问控制权限
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")
}
函数分类
依据函数作用域的不同,可分为以下几种:
顶层函数
定义在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请点击
网友评论