前言: 莫问良人长与短,从此山水不相逢。
一、概述
大家都知道 Kotlin 现在被 Gooogle 定为 Android 的官方开发语言。Kotlin 在项目中的使用将会越来越广泛,这也掀起了一波学习 Kotlin 的浪潮,作为一名 Android 开发者 Kotlin 是必须要掌握的。
Kotlin 是由 JetBrains 在2010 年推出的基于 JVM 的新编程语言,是一种新的静态类型编程语言。开发者称开发它的目的是避免 Java 语言中的一些难题。Kotlin 基于 JVM 的字节结构(与 Java 同属静态语言),可以编译为 Java 字节码,应用程序的运行速度与 Java 类似。但是随着 Kotlin 对内联函数的支持,使用 lambda 表达式写的代码通常比用 Java 写的代码运行更快。Kotlin 语言具有以下特点:
- 编译速度:与 Java 的编译、运行速度相似;
- 安全性:可以进行空安全判断,比 Java 更安全、简洁;
- 跨平台性:除了很好进行原生开发很好支持 Java 服务端程序开发、Android 应用开发、JavaScript 开发;
- 其他特性:懒加载,高阶函数,协程,inline 操作符,运算符重载,默认操作符等。
从这里开始,将会给大家带来 Kotlin 的系列文章,介绍 Kotlin 的特性以及与 Java 的差异性。
二、变量(var与val)
Kotlin 的变量分为 var (可变变量)和 val (只读变量,不可变变量)两种,与 Java 中的变量声明有很大区别:
- var: 用此关键词声明的变量为可变变量,即可读可写,可以多次赋值,相当于 Java 中的普通变量;
- val: 用此关键词声明的变量为不可变变量,也称为只读变量,运行常量,即可读但不可写,只能赋值一次,相当于 Java 中
final
修饰的变量。
var 与 val 是 Kotlin 中定义变量必须使用的关键字,每一行代码都可以省略分号;
,这一点与 Java 有点不同。
2.1 变量基础用法
Kotlin 中的 var 和 val 变量的格式如下:
var <表识符> : <类型> = 初始化值
val <表识符> : <类型> = 初始化值
下面我们在顶层声明的时候使用 var 和 val 的用法:
(1)var 可变变量
//Var可变变量
var varA: Int = 2//立即初始化
var varB = 5//推导类型 如果不声明类型,系统会根据你的赋值自动识别该数据类型,如5.0自动识别为Float类型
var varC: Float//没有初始化的时候必须声明类型
varC = 11.3f
varC += 1
Log.e(TAG, "var 可变变量:varA == $varA | varB == $varB | varB == $varC")
打印数据如下:
var 可变变量:varA == 2 | varB == 5 | varB == 12.3
(2)val 不可变变量
val valD: Int = 0//立即初始化
val valE = "字符串"//推导类型 如果不声明类型,系统会根据你的赋值自动识别该数据类型,如valE类型为字符串类型
val valF: Float//没有初始化的时候必须声明类型
valF = 0.5f
//valF += 0.1f//编译报错,因为valF是不可变变量
Log.e(TAG, "valD == $valD | valE == $valE | valF == $valF")
打印数据如下:
val 不可变变量:valD == 0 | valE == 字符串 | valF == 0.5
变量和不可变变量都可以没有初始化值,但是在使用前必须初始化,否则编译器会报错。并且编译器支持自动类型判断,即声明时可以不指定类型,由编译器判断。上述log中的$
是引用的意思,表示变量名和变量值,可以理解为字符串模板,后面的文章会详细讲到。
2.2 类中声明变量和可空变量
(1)类中声明变量
上面演示的变量时基本使用,而且只有在顶级声明的时候可以不用实例化,但是在实际开发中,一般都会在一个类中定义变量,这种情况被称为声明类的属性。
class BaseGrammarActivity : AppCompatActivity() {
//类中声明成员变量
var mVar_a: Int = 0
val mVal_b: Int = 0
init {//初始化
mVar_a = 100
//mVal_b = 101 为val的变量为不可变变量,即初始化后数值不能再变化
Log.e(TAG, "类中声明成员变量:mVar_a == $mVar_a | mVal_b == $mVal_b")
}
}
打印数据如下:
类中声明成员变量:mVar_a == 100 | mVal_b == 0
在 Java 中就是成员变量,在 Kotlin 中使用是必须要出初始化的,var 的变量是可以初始化后再修改,val 的变量初始化后不允许再修改了。如果不初始化,则需要使用关键字 lateinit
修饰,但是它不能声明于基本数据类型,如 Int
,Float
,Double
等数据类型,String
类型的除外,也不能声明val
,后面会分析到。
(2)可空变量
在 Java 声明一个变量我不需要关心这个变量是否为空,在使用这个变量的时候都会判断是否为空增加程序的安全性。这样的习惯是极好的,但是这无形中增加可代码量,还有可能是无用代码。但是在 Kotlin 中确定这属性或变量一定不为空时,就用上面的方法定义,否则就把它声明为可空变量。可空变量有下面几个特征:
- 在声明的时候一定要用标准的格式定义,不能用编译器推断的形式简写;
- 在变量类型后面加符号
?
,表示该变量可为null
,不能省略否则和普通变量没啥区别; - 初始化值可以为
null
或者确定的变量值。
Kotlin 中的声明可空变量的格式如下:
var <表识符> : <类型>? = null/指定值
val <表识符> : <类型>? = null/指定值
在类型后面加符号?
,例 String?
,即表示该变量声明可以为空,如果没有加符号?
变量没有声明可以为空,该变量是不可以赋值为 null
的,编译器会报错。
class BaseGrammarActivity : AppCompatActivity() {
//类中声明可空变量
//var mVar_c: Int = null 编译器报错,如果要初始化为null,则需要在类型中声明数据可以为null
var mVar_c: Int? = null
val mVal_d: String? = null
init {
mVar_c = 10
//mVal_d = "val可空变量" 编译器报错,val类型数据不能更改
Log.e(TAG, "可空变量:mVar_c == $mVar_c | mVal_d == $mVal_d")
}
}
打印数据如下:
可空变量:mVar_c == 10 | mVal_d == null
2.3 变量的延迟初始化和惰性初始化
前面我们提到在一个类中定义变量(属性)的时候是必须初始化一个值,这能满足我们平时开发的大部分需要,但是有时候在声明变量时还不需要立即初始化这个变量。这就需要 Kotlin 特有的延迟初始化来处理,这里还会带来惰性初始化的讲解,在开发中也很实用。
(1)延迟初始化
使用 lateinit
关键字修饰可以延迟初始化,Kotlin 编译器不会对 lateinit
修饰的变量做空检查,它就会默认你会初始化的,至于你什么时候怎么初始化它就不管了。使用 lateinit
关键字修饰的变量需要注意以下几点:
- 不能修饰
val
类型的变量; - 不能声明于可空变量,即类型后面加
?
,如String?; - 修饰后,该变量必须在使用前初始化,否则会抛
UninitializedPropertyAccessException
异常; - 不能修饰基本数据类型变量,例如:
Int
,Float
,Double
等数据类型,String
类型是可以的; - 不需额外进行空判断处理,访问时如果该属性还没初始化,则会抛出空指针异常;
- 只能修饰位于class body中的属性,不能修饰位于构造方法中的属性。
我们知道 Kotlin 中默认是空安全的,任何属性的声明都必须有初始化值,如果支持可空?
,才能把属性声明为 null
。然而经常这样不方便,例如:属性可以通过依赖注入来初始化,或者单元测试的setup方法中初始化,这种情况下你不能在构造函数内提供一个非空初始器,但是你想在类体引用改属性时规避空检查,可以使用 lateinit
处理这种情况。
//var nameB: String//编译器报错,必须初始化或者加abstract修饰
//lateinit var nameA: Int//编译器报错,lateinit不能用于Int,Float,Double等数据类型
//lateinit val nameC: String//编译器报错,lateinit不能修饰val
lateinit var nameD: String
延迟初始化主要是解决非空断言的出现,例如下面的代码:
class BaseGrammarActivity : AppCompatActivity() {
var tvName: TextView? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
tvName = TextView(this)
tvName!!.text = "延迟初始化"//需要加 !! 非空断言
}
}
我们知道 tvName
一定不为空,但是我们还是不得不加行非空断言!!
,这时候使用 lateinit
修饰来延迟初始化了:
class BaseGrammarActivity : AppCompatActivity() {
lateinit var tvName: TextView//lateinit修饰延迟初始化
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
tvName = TextView(this)
tvName.text = "延迟初始化"//不需要加 !! 非空断言
}
}
lateinit
修饰后 tvName
默认你会初始化的,至于你什么时候怎么初始化它就不管了,这样就不需要为 tvName
加上非空断言了。
自 Kotlin 1.2以来,可以检查lateinit
修饰的变量是否被初始化,在属性引用使用.isInitialized
:
lateinit var person: Person //lateinit 表示延迟初始化,必须是非空
fun method() {
person = Person()
if (this::person.isInitialized) {//如果已经赋值返回true,否则返回false
//TODO
}
Log.e(TAG, "延迟初始化: person.isInitialized == ${::person.isInitialized}")
}
注意 isInitialized
的使用格式:this::变量名.isInitialized
,this可省,打印数据如下:
延迟初始化: person.isInitialized == true
注意:这种检查只能对词法上可访问的属性可用,例如:在相同类型或外部类型声明的属性,或在同一个文件的顶层声明的属性。但是不能用于内联函数,为了避免二进制兼容性问题。
(2)惰性初始化
惰性初始化是指在程序第一次使用到这个变量(或者对象)时再初始化,当再次调用变量时只会得到结果不会再次初始化。当初始化过程需要消耗大量资源并且使用对象时并不总是需要数据的情况下这个非常有用。惰性初始化的使用需要以下几点:
- 不能用于类型推断,且该函数在变量的数据类型后面用
by
链接; - 必须只读变量,即用
val
变量。
Kotlin 中声明变量惰性初始化的格式如下:
val <表识符>: <类型> by lazy {
//TODO 当前变量初始化赋值
}
变量后需要使用 by
链接,当变量用到的时候才会初始化 lazy {}
里面的内容,而且在再次调用时,只会得到结果,而不会再执行 lazy {}
的运行过程。
class BaseGrammarActivity : AppCompatActivity() {
//声明一个惰性初始化的集合
val mLists: ArrayList<String> by lazy {
arrayListOf("aa", "bb", "cc")
}
//声明一个惰性初始化的字符串
val mStr: String by lazy {
Log.e(TAG, "-----mStr惰性初始化-----")
"山水有相逢"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.e(TAG, "惰性初始化:mLists地址是否相同 == ${mLists == mLists}, 内容 == $mLists")
Log.e(TAG, "惰性初始化:mStr == $mStr")
Log.e(TAG, "惰性初始化:mStr == $mStr")
}
}
两个数据 mLists
和 mStr
分别调用了两次,打印数据如下:
惰性初始化:mLists地址是否相同 == true, 内容 == [aa, bb, cc]
-----mStr惰性初始化-----
惰性初始化:mStr == 山水有相逢
惰性初始化:mStr == 山水有相逢
可以看到, mLists
地址相同则说明 mLists
只初始化了一次,每次调用的 mLists
都是第一次初始化的对象。mStr
中的 lazy {}
打印的log数据-----mStr惰性初始化-----
只有一次,说明 mStr
也是只初始化一次。后面输出的都是结果。
延迟初始化和惰性初始化都是用来初始化属性的,但是有点互补的意思,可以根据不同情况来选择使用。
三、常量(const val)
Kotlin 中声明常量的方式与 Java 中声明常量的方式有很大区别。Kotlin 中 val
声明的变量与 Java 的常量同等含义:
//Kotlin
val num = 101
//Java
public final int num = 101
上面 Kotlin 和 Java 的两种写法是等价的。
有人会提出疑问,Kotlin 中 val
声明的不就是常量吗?
答案是否定的,Kotlin 中 val
声明的变量不是常量,它只是一个不能修改的变量。
那么常量怎么定义呢?
其实很简单,在 val
前面加上关键字 const
,即表示该变量时常量。如:
//Kotlin
const val num = 101
注意,关键字 const
只能修饰val
,不能修饰var
。使用const修饰的字段可以通过类名+字段名来调用,类似于 Java 的public static final
变量,而val
修饰的变量只能通过get()
方法形式调用。
声明常量的三种正确的方式:
- 顶层声明,即在代码文件的顶层,我们导入包名的位置;
- 在
object
修饰的类中声明,称为对象声明,相当于 Java 的单列类; - 在伴生对象中声明。
(1)顶层声明
const val constA: String = "顶层声明"//顶层声明,写在代码文件的顶层
class BaseGrammarActivity : AppCompatActivity() {
}
kotlin 认为根本不需要创建这些无意义的类,可以直接将函数或者常量放在代码文件最顶层,不附属于任何一个类。
(2)对象声明
object Constants {
const val constB = "object修饰的类中声明"
}
在 object
修饰的类中声明,相当于 Java 的单列类。
(3)伴生对象中声明
class BaseGrammarActivity : AppCompatActivity() {
companion object {
const val constC = "伴生对象中声明"
}
}
上述几种常量的声明打印数据如下:
常量:constA == 顶层声明
常量:constB == object修饰的类中声明
常量:constC == 伴生对象中声明
四、注释
Kotlin 中的注释与 Java 中的注释相比几乎没有什么区别,但是有一点不同高的是,Kotlin 中的多行注释可以嵌套多行注释,而 Java 中是不行的。
(1)单行注释
两个 //
开头的表示单行注释
//1.单行注释
(2)多行注释
以斜杠加星号/*
开头, 同时以星号加斜杠*/
结尾是多行注释,中间是要注释的代码块。
/*
2.多行注释(块注释)
*/
(3)多行注释嵌套
/*
3.多行注释嵌套
第一层
/*
第二层
*/
*/
这种注释方式 Java 是不支持的,但是在 kotlin 支持。
(4)类,方法注释
以斜杠加星号/**
开头, 同时以星号加斜杠*/
结尾,中间还有*
,是类或者方法注释。
/**
* 4.类,方法注释
*/
五、总结
分类 | 名称 | 修饰符 | 含义 | 案例 | 注意事项 | |
---|---|---|---|---|---|---|
变量 | 基础用法 | var与val | var:可变变量, val:不可变变量 | var varA: Int = 2 val valD: Int = 0 | 1.变量和不可变变量都可以没有初始化值, 但是在使用前必须初始化,否则会报错。 2.支持自动类型判断。 | |
类中声明变量 | var与val | 同上 | var mVar_a: Int = 0 val mVal_b: Int = 0 | 1.必须要初始化的, 2.如果没有声明可空则不能初始化为null | ||
可空变量 | 类型后加`?` | 表示该变量可为空 | val mVal_d: String? = null | 在声明的时候一定要用标准的格式定义, 不能用推断的形式简写; 在变量类型后面加符号? | ||
延迟初始化 | lateinit 修饰 | Kotlin 不会对 lateinit 修饰的变量做空检查, 默认你会初始化, 至于什么时候 初始化它就不管了。 | lateinit var tvName: TextView | 1.不能声明于可空变量; 2.修饰的变量必须可读可写,即 var 变量; 3.修饰后,该变量必须在使用前初始化,否则会抛异常; 4.不能声明基本数据类型变量,String 类型除外。 | ||
惰性初始化 | by lazy {}修饰 | 程序第一次使用到这个 变量(或者对象)时再初始化, 当再次调用变量时只会得到 结果不会再次初始化 | val mStr: String by lazy { "山水有相逢" } | 1.必须只读变量,即用 val变量; 2.不能用于类型推断; 3.在变量的数据类型后用 by 链接 | ||
常量 | const修饰 | 在 `val` 前面加上关键字 `const`, 即表示该变量时常量 | const val constA: String = "常量" | 1.顶层声明,即在代码文件的顶层; 2.在 `object` 修饰的类中声明,称为对象声明; 3.在伴生对象中声明。 | ||
注释 | 与Java类似 | //1.单行注释 /* 2.多行注释(块注释) */ | 唯一不同的是java不能多行注释嵌套,而Kotlin可以 |
源码地址:https://github.com/FollowExcellence/KotlinDemo-master
点关注,不迷路
好了各位,以上就是这篇文章的全部内容了,能看到这里的人呀,都是人才。
我是suming,感谢各位的支持和认可,您的点赞、评论、收藏【一键三连】就是我创作的最大动力,我们下篇文章见!
如果本篇博客有任何错误,请批评指教,不胜感激 !
要想成为一个优秀的安卓开发者,这里有必须要掌握的知识架构,一步一步朝着自己的梦想前进!Keep Moving!