背景
目前来说Android开发越来越不好干了,虽然我们只是一个客户端的工程师,但是一方面我们要忍受住学习java,深入JVM的研究的痛苦,另一方面目前大前端的趋势很多应用的界面都使用H5去实现,这又抢了Android的一份羹,同时我们还要学习各种新型的Kotlin,以及跨平台的技术如Flutter,React Native等,不过辉煌的人生必定是伴随着痛苦,最近准备学习Kotlin,特起此系列博客记录总结学习过程中的知识点还有遇到的问题。
官方文档:https://kotlinlang.org/docs/reference/
Kotlin简介
Kotlin 是一种在 Java 虚拟机上运行的静态类型编程语言,被称之为 Android 世界的Swift,由 JetBrains 设计开发并开源。
Kotlin 可以编译成Java字节码,也可以编译成 JavaScript,方便在没有 JVM 的设备上运行。
在Google I/O 2017中,Google 宣布 Kotlin 成为 Android 官方开发语言。Kotlin的文件是以**.kt**为后缀的。
选择Kotlin的原因
- 简洁: 大大减少样板代码的数量。
- 安全: 避免空指针异常等整个类的错误。
- 互操作性: 充分利用 JVM、Android 和浏览器的现有库。
- 工具友好: 可用任何 Java IDE 或者使用命令行构建。
基础语法
先看一个我使用默认的java文件转换成Kotlin的文件代码
package com.example.koltlintest
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
// 注意: 方法可以定义在这里,是的没有错。
fun test(): Unit {
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
整体结构与java类似,都是从上到下的顺序:包名->导包->类结构
但是!!!,你看这个test方法。他可以定义在类外边。很神奇有木有。
代码规范
- kotlin中基本与java类似,尽量每一行代码都是一个语句
- 最大的区别就是kotlin中的 ; 是可以省略的
- kotlin编码规范
包声明
- kotlin导包与java类似,但是有几点差别,这里强调一下
- 存在默认导包,每一个kotlin源文件中都会默认导入这些包
kotlin.*
kotlin.annotation.*
kotlin.collections.*
kotlin.comparisons.*(⾃ 1.1 起)
kotlin.io.*
kotlin.ranges.*
kotlin.sequences.*
kotlin.text.*
JVM平台还会导入 java.lang.* 和 kotlin.jvm.* 包- 如果出现冲突包名,即最后的名称相同,可以使用as关键字来进行重命名(与js相似)
import com.a.b.TestUtil // 可以直接通过TestUtil进行访问 import com.a.b.c.TestUtil as test // 可以通过test 进行访问此TestUtil
Kotlin的包名是可以不用与路径匹配的。比如,我上面的Class的包名为com.example.koltlintest,但是我的目录结构可以是com.example.testpackage。不会像java一样报错,但是会报异常
Package directive doesn’t match file location
所以个人建议还是保持对应吧。
- 上例中的test方法全名为com.example.koltlintest.test;
- 上例中的MainActivity类的全名为com.example.koltlintest.MainActivity。
还有一个有意思的是使用其他文件中定义在类外边的方法直接导入方法的全名即可,无需导入文件,如果是同包名下可以直接使用。
例子如下: MainActivity中调用TestUtil中的add方法
// MainActivity.kt
package com.example.koltlintest
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
fun test(): Unit {
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
add()
}
}
// TestUtils.kt
package com.example.koltlintest
fun add() {
print("add")
}
class TestUtils {
fun addInside() {
}
}
方法声明
常规方法定义格式:
权限修饰符 方法关键字 方法名(参数:参数类型, 参数:参数类型): 返回值类型{
方法体
}
例子:
// Unit表示无返回值, 相当于java中的void 可省略
private fun fun1(name:String, age: Int):Unit {
}
public fun fun2():Int {
return -1;
}
// 表达式作为函数体,返回类型自动推断
protected fun fun3(num1: Int, num2: Int) = num1 + num2
// Unit可以被省略, vararg表示可变参数
fun fun4(vararg v: Int) {
for (vt in v) {
print(vt)
}
}
常量与变量
kotlin中的常量与变量比较好区分
- 定义的形式都是
关键字(val / var) <标识符> : <类型> = <初始化值>
- 常量使用val 关键字(类似于java的final关键字);变量使用var 关键字
- 常量和变量都可以声明的时候不进行设置初始化值,但是在引用之前必须进行初始化
- 可以不指定常量或者变量的类型,编译器可以根据初始值进行判断确认类型,但是如果不指定类型必须设置初始值 (很好理解,如果都不指定的话,编译器就无法确定你到底是什么了,永远不要做让计算机连计算机都不确定的事情)
- 常量只能进行一次初始化
例子如下:
val a:Int
var b:Int = 3
// 在使用之前必须初始化常量a,并且只能初始化一次
a = 2
a + b
// 不指定常量或变量的类型
var c = "这个变量的type编译器会自动判断"
注释
注释还是分成单行,多行注释,格式与java亦相同
// 这个是单行注释
/*
这个是多行注释
*/
注意:/** …*/ 这个注释格式是javaDoc的格式,它的主要作用是把我们写在里面的内容解析成java的文档,当然在kotlin中就无法使用了,无法使用不是说使用就会报错,测试了一下也是可以注释的,但是估计是无法解析生成文档,所以它的意义实际上就是多行 注释,只不过每一行多了一个 * 号罢了。
字符串模板
这个也算是一个特色吧,感觉挺好用的,当然我们也可以像java一样通过String.format或者直接采用字符串拼接的形式。
- $ 表示一个变量名或者变量值
- $varName 表示变量值
- ${varName.fun()} 表示变量的方法返回值:
例子如下:
var ten = 10;
var value = "ten is $ten."
ten += 2
var value1 = "${value.replace("ten", "twelve")} + 2. "
print("value:$value")
println("value1:$value1")
输出结果为:
value:ten is 10.
value1:twelve is 10. + 2.
这里面应该是有一个坑,测试发现当使用变量的方法返回值得形式,即value1时,使用print()无法打印出结果,由于也是刚学习不太清楚具体原因是什么,测试结果显示一个方法里面最后一个打印(调用print或者println)调用print是无法打印的,调用println是可以打印的。
很神奇,如果有大佬知道是为什么,请指教。详细分析请点击这里
空指针保护
kotlin可以直接对可能为null的参数或者变量进行标记,然后进行处理。
- 使用 !! 抛空指针异常
- 使用 ? 直接忽略异常返回null
- 使用 ?: 设置默认值
// 在类型后边添加?表示name可能为null
var name: String? = ""
name = null
// 第一种处理方法, 在变量后边添加!!表示当前name为null时,直接抛空指针异常,此处运行结果: 应用崩溃, 抛异常java.lang.NullPointerException
var realName = name!!.toString()
print(realName)
// 第二种处理方法,在变量后边添加?表示当前name为null时, 选择忽略直接返回null,此处运行结果: 输出null
var realName = name?.toString()
println(realName)
// 第三种处理方式, 在变量后面添加?, 并且在后面添加 ?: 表示如果当前 name为null时, 选择?:后面的作为默认值处理。此处运行结果: 输出 default name
// 这里注意:如果将name后的?去除即name.toString() ?: "default name",即此处的运行结果:输出null。
var realName = name?.toString() ?: "default name"
println(realName)
类型检测
相对于java中使用instanceof来说,kotlin中要简单很多
- 使用is 或者 !is 来检测一个对象的类型
- 使用 is 或者 !is能确认类型的情况会直接转换成对应类型
var name:Any= "test"
// 如果name变量是一个String类型,进入条件体内
if (name is String) {
//...
}
// 如果name变量不是一个String类型,进入条件体内
if (name !is String) {
//...
}
// 例子1:.length 是属于String类的方法
var age:Any = "asdf"
// 此处直接调用会报错,无法编译通过
age.length
if (age !is String) {
println("is")
} else {
// 此处我们没有强转,但是也是可以调用的,看来kotlin是帮我们自动转换了
age.length
println("is not")
}
// 例子2:强转的规则同样适用于 && 和 ||, 怎么样刺不刺激,精不惊喜. 具体分析就不介绍了, 大家可以仔细考虑一下为什么,下边的两个表达式都是没问题的。可以编译通过。
var age:Any = "asdf"
if (age is Int && age.compareTo(1) == 0) {}
if (age !is String || age.length > 0 ) {}
数据类型
与java进行简单对比来了解常用数据结构的特性,后续还会不断补充.Kotlin与java的基本类型对比