前言
其实在刚接触 Kotlin 的时候,我内心是拒绝的,由于看习惯了 Java 的代码,以至于看 Kotlin 代码时有一股极大的别扭感,让我不想去学习(其实在我学 C 语言的时候也是这个感觉)。但是没办法呀,谁叫这货是 Android 官方开发语言呢,再加上现在的招聘要求多多少少也要会 Kotlin 了,于是只能硬着头皮上咯。
1. 基本数据类型
相比较 Java ,Kotlin 没有了基本数据类型,可以看作是 Java 的基本类型包装类拿了过来部分改了名字,如下:
Java | Kotlin |
---|---|
byte | Byte |
short | Short |
int | Int |
long | Long |
float | Float |
double | Double |
boolean | Boolean |
char | Char |
另外字符串是支持模板的,看下面:
fun main() {
val hello = "Hello"
println("$hello, World!")
println("length = ${hello.length}")
}
// 输出:Hello, World!
// length = 5
2. 数组
Kotlin 中没有了 [] 进行数组声明或创建,8 种基本数据类型有自己的数组类型,如下:
Java | Kotlin |
---|---|
byte[] | ByteArray |
short[] | ShortArray |
int[] | IntArray |
long[] | LongArray |
float[] | FloatArray |
double[] | DoubleArray |
boolean[] | BooleanArray |
char[] | CharArray |
那引用类型呢?例如我们要使用一个 String 数组,我们需要用 Array<T>
这么一个类,泛型代表数组所属类型。同理,基本数据类型也可以用 Array<T>
来声明,如 Array<Int>
,Array<Boolean>
等等。
初始化既可以用它们的构造函数,也可以用 Kotlin 提供的全局函数,arrayOf()
,emptyArray()
,前者接收一个可变参数,后者则是一个空数组。
3. 访问权限
public 、protected 、private 和 internal 作为 4 大访问权限,去除了 default 。
public 作为默认访问权限,因此很多时候我们可以不用写上 public 。
protected 相较于 Java 不变。
private 除了可以修饰类级别,其余不变。为什么可以修饰类呢?这是因为 Kotlin 中一个文件可以声明 1 个以上的类,对这些类加上 private 则表示仅可以在当前文件中使用。
internal 表示模块级别的访问权限,被 internal 标识则表示仅在当前模块可见,这在模块封装上是很有帮助的。
4. 继承性
Kotlin 中,类、字段和函数默认是 final 的,抽象类和接口除外。因此如果我们想要它们变得可继承,需要在前面加上 open 关键字。
对于普通类,如果想要字段或者函数可继承,仅在字段或函数上加 open 是不够的,因为这时候类还是不可继承的,因此类也需要加上 open ,这时字段和函数才真正可继承。
如果不想一个已 open 的字段或函数可继承,可以用 final 关键字进行修饰。
5. 变量与函数
Kotlin 中将类型进行了后置,什么意思呢?举个栗子:
// Java 中我们这样声明变量和函数
public final class Person {
public final String name;
public int age;
public final void talk(String str) {
int temp = 0
}
}
// Kotlin 中等价于
class Person {
val name: String? = null
var age: Int = 0
fun talk(str: String?): Unit {
var temp = 0
}
}
首先 Kotlin 中是可以省略分号的,当然前提是你一个语句一行,如果一行有两个语句的话就不能省略了,正常情况下咱们都是一行一语句的,接着来分析上面的代码。
变量:
由于类型后置,因此 Kotlin 使用关键字 val 和 var 来表示变量,val (value) 表示只读变量,在第一次赋值后就不可修改了;var (variable) 就是我们在 Java 中常用的变量了。
在 Kotlin 中,成员变量是没有默认值的,需要我们手动赋值。
另外,Kotlin 是支持类型推断的,在声明变量时,可以不用写类型,前提是有提供初始值。尽管如此,Kotlin 仍然是强类型语言,类型不匹配照样会报错,例如 var age = 0
,在这个声明中,尽管我们没有显式提供类型,但初始值表明了这个变量就是一个 Int 变量(整型默认为 Int),因此后续我们只能赋 Int 值。
函数:
由于类型后置,因此 Kotlin 使用关键字 fun 来表示函数,并且参数列表也遵循类型后置规则,Kotlin 中函数的参数是可以有默认值的,如:
fun setName(name: String = "aaronzzx"): Unit {
// ...
}
这个 : Unit 是啥?Unit 相当于 Java 的 void ,: Unit 表示返回 Unit 类型,相当于没有返回值,当然更多时候我们不需要写上这个多余的东西,上面的代码等价于:
fun setName(name: String = "aaronzzx") {
// ...
}
函数也可以进行类型推断,这里指返回值推断,仅在简写函数时可用,举个栗子:
fun getAge(): Int {
return age;
}
// 等价于
fun getAge() = age
// 当我们的函数只有 1 个语句时,即使没有返回值,也可以简写。
// 前提这个语句不是赋值语句,不然就 "=" 冲突了
fun function() {
operate()
}
// 等价于
fun function() = operate()
// 拥有可变参数的函数
fun function(vararg strs: String) {
for (string in strs) {
println(string)
}
}
// 参数为接口或抽象类
fun initView() {
HttpUtils.request(object: Callback {
override fun onSuccess() {
// ...
}
override fun onFailure() {
// ...
}
})
}
Ps:你可能有发现类型后面跟了个 ?
,这个表示此变量可被赋值 null ,如果不加问号是没办法赋值 null 的,这和 Kotlin 的“空安全”有关,后面会说。
6. 构造函数
Kotlin 的构造函数在第一次看到可能会优点懵逼,先来看下与 Java 的对比:
// Java
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name) {
this.name = name;
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
// Kotlin
class Person(
private var name: String? = null,
private var age: Int = 0
)
// 等价于
class Person(
name: String? = null,
age: Int = 0
) {
private var name: String? = name
private var age: Int = age
init {
// 可选
}
}
// 等价于
class Person() {
private var name: String? = null
private var age: Int = 0
constructor(name: String?): this() {
this.name = name
}
constructor(name: String?, age: Int): this() {
this.name = name
this.age = age
}
}
在这里可以看到,Kotlin 具有碾压性的优势,对于需要重载构造函数,Kotlin 最少可用 4 行代码搞定,接下来分析。
首先 Kotlin 可以省略类体,因为主构造函数被设定在了类名上,主函数可以有参数,也可以没有。如果有参数,可以在参数名前面加关键字 val 或 var ,表示将这个参数作为成员变量,在关键字前面可以用访问权限或注解进行修饰。
init 表示主构造函数的函数体,如果没有特殊用途可以被省略。
constructor 表示次构造函数,在拥有主构造函数的情况下需要显示调用主构造函数。
7. 空安全
在使用 Java 时,对于 NullPointerException 真的是无可奈何,一不小心就报空指针异常,然后程序就 Crash 了。因此我们只能经常写如下的判空代码:
public String getMobile(Response response) {
String mobile = null;
if (response != null) {
Data data = response.getData();
if (data != null) {
mobile = data.getMobile();
}
}
return mobile;
}
或许是知道 Java 程序员处于水深火热之中,Kotlin 添加了空安全这一语言特性,使用 ?
对可空变量进行标识,而没被 ?
标识的变量呢?理所当然的是不会为空了,于是我们对于不为空的变量就不用写繁琐的判空代码了,因为它不会出现空指针异常了。
对于可为空的变量,我们还是需要进行判空的,但是形式不与 Java 一样,于是上面的代码可以这么写:
fun getMobile(response: Response?): String? {
return response?.data?.mobile
}
?.
表示如果当前对象不为空就调用,与 Java 相比,简洁了好多。
8. 伴生对象
即 companion object ,这是个啥呢?看下面这段代码,你应该就明白了。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val url = intent.getStringExtra(EXTRA_URL)
// ...
}
companion object {
private const val EXTRA_URL = "EXTRA_URL"
fun start(context: Context, url: String) {
val intent = Intent(context, MainActivity::class.java)
intent.putExtra(EXTRA_URL, url)
context.startActivity(intent)
}
}
}
说白了,这东西就相当于 Java 的 static ,只是 Kotlin 中没有 static 这个概念,所以如果我们要声明静态变量或者静态函数,就需要用到 companion object 。
另外上面的代码,可能有些地方你看起来不太明白,这里说一下。
-
继承与实现,通通使用
:
操作符,不同的类或接口用,
隔开,并且需要显式调用父类构造函数。 -
首先 Kotlin 中创建对象是省略 new 关键字的,直接用构造函数即可。
-
Java 中的
@Override
注解在 Kotlin 中变成了关键字 override ,重写必不可少。 -
对于无参的 getter 函数,可以以变量名的形式书写,例如
getIntent()
就是intent
。 -
const val 关键字用来声明常量,仅在 object 、companion object 和 顶层变量 中可用。
-
object 匿名内部类和单例关键字,如果要创建一个单例类,只需这么写。
object Singleton { fun exec() { // ... } } class Test { fun function() { // 尽管看起来有点像静态调用,但这就是单例 Singleton.exec() } }
-
顶层变量,用代码比较直观,如下:
const val TAG = "当前文件" class MainActivity : AppCompatActivity() { // ... }
顶层变量 属于文件,不属于类或接口等等。
9. 分支、循环语句
9.1 分支语句
在 Kotlin 中分支语句属于表达式,因此我们可以这么写代码:
fun function(type: Int) {
// if-else
var str = if (type == 1) {
"Hello, World!"
} else if (type == 2) {
"Goodbye!"
} else ""
// switch ,在 Kotlin 中关键字变为 when
str = when (type) {
1 -> "Hello, World!"
2 -> "Goodbye!"
else -> ""
}
// 或者 when 不带参数
str = when {
type == 1 -> "Hello, World!"
type == 2 -> "Goodbye!"
else -> ""
}
}
当分支语句作为表达式时,除非你的条件已经包括所有情况了,否则一定要有 else 。
9.2 循环语句
while 和 do-while 相较于 Java 不变,for 有改动,举个栗子:
fun function() {
// for (int i = 0; i < 10; i++)
// 打印的 i 值 是一样的
for (i in 0..9) {
println(i)
}
for (i in 0 until 10) {
println(i)
}
// 下面这段将依次打印 10-0
for (i in 10 downTo 0) {
println (i)
}
// 还可以控制步长
// 依次打印 0, 2, 4, 6, 8, 10
for (i in 0..10 step 2) {
println(i)
}
}
10. 扩展函数
对于任何类型,我们都可以擅自给他添加新的函数,然后就可以用这个类对象调用我们自定义的扩展函数了,直接看代码:
fun String.suffix(suffix: String): String {
return this + suffix
}
fun function() {
val str = "Hello"
val newStr = str.suffix(", World!")
println(str)
println(newStr)
}
// 将依次输出:Hello
// Hello, World!
11. 高阶函数
高阶函数是将函数用作参数或返回值的函数。
在这里我们会接触到一个东西:闭包, 闭包就是能够读取其他函数内部变量的函数。
class Operator {
fun add(a: Int, b: Int) = a + b
fun minus(a: Int, b: Int) = a - b
fun change(
a: Int,
b: Int,
closure: (Int, Int) -> Int
): String {
return "${closure(a, b)}"
}
}
fun main() {
val operator = Operator()
val add: (Int, Int) -> Int = operator::add
val str1 = operator.change(2, 2, add)
val str2 = operator.change(5, 2) { a: Int, b: Int ->
a - b
}
println("str1 = $str1")
println("str2 = $str2")
}
// 输出:str1 = 4
// str2 = 3
从 main 函数中,可以看到变量 add 拥有函数类型 (Int, Int) -> Int ,这意味着这个变量只能被函数赋值,对象名::add
表示拿到这个函数的函数类型。
接着看下面的 change 函数,这个大括号里面的代码其实就是闭包,虽然看起来好像是重新定义了一个函数,当一个函数的参数列表最后一个参数是一个闭包时,可以单独将闭包抽出来放在小括号后面,如果参数列表只有一个闭包,那么小括号都可以省略。闭包中,->
左边表示闭包的参数,类型可以省略。
闭包其实看起来有点像回调,我们先写好代码,然后在 change 函数中进行调用。
函数类型可以没有参数,但是小括号是不能省略的,包括返回类型也不可省略,例如一个没有参数没有返回值的函数类型,可以这样表示:() -> Unit
。
另外函数类型也是可空的,举个栗子:
fun main() {
// 当加上?时,函数类型需要用括号括起来
val function: (() -> Unit)? = {
// ...
}
// 调用时需要用 invoke 进行调用
function?.invoke()
}