Kotlin简介
Kotlin来源于一个岛屿的名字,全称是Kotlin Island,是英语【科特林岛】之意,这个小岛位于俄罗斯的圣彼得堡附近。之所以要命名为Kotlin,是因为Kotlin的主要开发工作就是由位于圣彼得堡分公司团队完成的。Kotlin是由JetBrains公司开发与设计的。
早在2011年,JetBrains就公布了Kotlin的第一个版本,并在2012年将其开源。
2016年,Kotlin发布了1.0的正式版本,并在自家IDE开发工具中加入了对Kotlin的支持。
2017年,Google宣布Kotlin正式成为Android一级开发语言,Android Studio也加入了对Kotlin的支持。
两年之后,Google又在2019年的I/O大会上宣布,Kotlin已经成为Android的第一开发语言。
在开发Kotlin之前,JetBrains团队一直使用Java来创建他们的IDE以及使用Java进行业务逻辑开发。之所以开发Kotlin,是因为JetBrains 的工程师们在使用Java开发应用程序的过程中,发现了大量的问题。为了提升开发效率以及解决使用Java开发带来的问题,在借鉴了其他语言后,他们决定开发一款致力于解决Java问题的编程语言Kotlin。JetBrains深知开发者的需求和痛处,在孜孜不倦地为开发者提供最实用、最高效的IDE的同时,也为开发者提供全新的编程语言以解决目前的技术问题。
或许你会产生一些疑惑:Android操作系统明明是由Google开发的,为什么JetBrains作为一个第三方公司,却能够自己设计出一门编程语言来开发Android应用程序呢?
先大概熟悉一下Java语言的运行机制。编程语言大致可以分为两类:编译型语言和解释型语言。编译型语言的特点是编译器会将我们编写的源代码一次性地编译成计算机可识别的二进制文件,然后计算机直接执行,像C和C++都属于编译型语言。
解释型语言则完全不一样,它有一个解释器,在程序运行时,解释器会一行行地读取我们编写的源代码,然后实时地将这些源代码解释成计算机可识别的二进制数据后再执行,因此解释型语言通常效率会差一些,像Python和JavaScript都属于解释型语言。
那Java是属于编译型语言还是解释型语言呢?
虽然Java代码确实是要先编译再运行的,但是Java代码编译之后生成的并不是计算机可识别的二进制文件,而是一种特殊的class文件,这种class文件只有Java虚拟机才能识别,而这个Java虚拟机担当的其实就是解释器的角色,它会在程序运行时将编译后的class文件解释成计算机可识别的二进制数据
后再执行,因此,准确来讲,Java属于解释型语言。了解了Java语言的运行机制之后,可以看出,其实Java虚拟机并不直接和你编写的Java代码打交道,而是和编译之后生成的class文件打交道。那么如果开发了一门新的编程语言,然后自己做了个编译器,让它将这门新语言的代码编译成同样规格的
class文件,Java虚拟机能不能识别呢?没错,这其实就是Kotlin的工作原理了。Java虚拟机不关心class文件是从Java编译来的,还是从Kotlin编译来的,只要是符合规格的class文件,它都能识别。也正是这个原因,JetBrains才能以一个第三方公司的身份设计出一门用来开发Android应用程序的编程语言。
下面数据是来对比kotlin和C++在不同的长度运算过程的耗时情况:
算式 | kotlin耗时 | C++耗时 |
---|---|---|
2组 【9个9】相乘 | 6ms | 1ms |
10组【9个9】相乘 | 17ms | 10ms |
100组 【9个9】相乘 | 288ms | 25ms |
1000组【9个9】相乘 | 95171ms | 237ms |
在此应用场景下,C++算法明显优于Kotlin,效率提升明显。因为C++是编译型的,直接将源码编译成机器代码,可以直接运行;而Kotlin是解释型,源码被编译成二进制字节码,但不是机器可以识别的语言,由虚拟机解释才能执行,效率会慢一些。
Kotlin的优势
Kotlin的语法简洁,同样的功能,Kotlin开发的代码量可能会比Java少50%,可能更多。
Kotlin增加了很多高级语言的很特性,大大的提高了开发效率。
Kotlin和Java100%兼容,Kotlin可直接调用Java编写的代码,也可以无缝使用Java第三方的开源库,使得Kotlin在加入新特性的同时,继承了Java的全部财富。
Kotlin基础语法
变量
val(value):不可变变量,它的值在初始化以后就无法再次修改,相当于java中final变量
var(variable):可变变量,对应java中的普通变量
基础类型
Type | Bit width | 备注 |
---|---|---|
Double | 64 | Kotlin没有double |
Float | 32 | Kotlin没有float |
Long | 64 | Kotlin没有long |
Int | 32 | kotlin没有int/Integer |
Short | 16 | kotlin没有short |
Char | 16 | Kotlin没有char |
Byte | 8 | Kotlin没有byte |
Boolean | 8 | Kotlin没有boolean |
基础类型,包含我们常见的数字类型,布尔类型,字符类型,及这些类型组成的数组,这些常遇到的概念,统一归纳为基础类型
从上图也可以看出,在Kotlin语言体系中,是没有原始类型这个概念的,这就意味着,在Kotlin中,一切都是对象。
对比后,从某种程度上讲,Java的类型系统并不是完全面向对象的,因为它存在原始类型,而原始类型并不属于对象,而Kotlin则不一样,它从语言设计的层面就规避了这个问题,类型系统是完全面向对象的。
空安全
Kotlin强制要求开发者在定义变量的时候,就指定这个变量是否可能为null,对于可能为null的变量,我们需要在声明的时候,在变量类型后面加一个问号“?”
val i: Double = null // 编译器报错
val j: Double? = null // 编译通过
数字类型
val i = 1
val l = 1234567L
val d = 13.14
val f = 13.14F
val hex = 0xAF
val b = 0b01010101
整数默认会被推导为“Int”类型;
Long 类型,我们则需要使用“L”后缀;小
数默认会被推导为“Double”,我们不需要使用“D”后缀;
Float 类型,我们需要使用“F”后缀;
使用“0x”,来代表十六进制字面量;
使用“0b”,来代表二进制字面量。
对于数字类型的转换,Kotlin与Java的转换行为是不一样的,Java可以隐式转换数字类型,而Kotlin更推崇显示转换,在一些严谨的逻辑判断中,碰到一些边界条件问题,隐式转换会因存在精度问题而导致的一些Bug不容易被排除出来。而在Kotlin中,提供了toLong(),toByte()、toShort()、toInt()、toLong()、toFloat()、toDouble()、toChar()等,使得代码可读性更强,也易维护。
布尔类型
两种值,true和false,布尔类型是支持一些逻辑操作的,如下:
“&”代表“与运算”;
“|”代表“或运算”;
“!”代表“非运算”;
“&&”和“||”分别代表它们对应的“短路逻辑运算”
val i = 1
val j = 2
val k = 3
val isTrue: Boolean = i < j && j < k
字符:Char
Char用于代表单个字符,‘A’、’B’、’C’、写法和Java类似
字符串:String
val s = "Hello Kotlin!"
写法和Java类似,但是Kotlin提供了非常简洁的字符串模板:
val name = "Kotlin"
print("Hello $name!")
/* ↑
直接在字符串中访问变量
*/
// 输出结果:
Hello Kotlin!
当然,在Java中也能实现,但需要使用“+”进行拼接,在字符串格式相对复杂的情况下啊,代码就会显得很臃肿。
如果需要在字符串中引用更加复杂的变量,则需要使用花括号括起来:
val array = arrayOf("Java", "Kotlin")
print("Hello ${array.get(1)}!")
/* ↑
复杂的变量,使用${}
*/
// 输出结果:
Hello Kotlin!
此外,Kotlin还新增了原始字符串,是用三个引号来表示,可用来存放复杂发多行文本,打印格式和定义格式保持同步:
val s = """
原始字符串
所见即所得。 """
println(s)
数组
在Kotlin中,一般会使用arrayOf()来创建数组,括号当中可以用于传递数组元素进行初始化,同时,Kotlin编译器也会根据传入的参数进行类型推导:
val arrayInt = arrayOf(1, 2, 3)
val arrayString = arrayOf("apple", "pear")
如上,arrayInt的类型会被推导为整形数组,arrayString会被推导为字符串数组。
获取数组长度方式和一些操作和集合类似:
val array = arrayOf("apple", "pear")
println("Size is ${array.size}")
println("First element is ${array[0]}")
// 输出结果:
Size is 2
First element is apple
函数声明
在Kotlin中,函数的声明和Java不太一样:
/*
关键字 函数名 参数类型 返回值类型
↓ ↓ ↓ ↓ */
fun helloFunction(name: String): String {
return "Hello $name !"
}
/* ↑
花括号内为:函数体
*/
1)使用了fun关键字来定义函数
2)函数名称,驼峰命名规则
3)是以 (name: String) 这样的形式传递的,这代表了参数类型为 String 类型
4)返回值类型,跟在参数后面
5)函数体(业务逻辑)
如果函数体只有一行代码,这种情况下,其实可以省略函数体的花括号,直接使用“=”来连接,将其变成一种类似变量赋值函数的形式:
fun helloFunction(name: String): String = "Hello $name !"
这种写法,称之为 单一表达式函数,其中“retuen”是需要去掉的
继续简化,由于Kotlin是支持类型推导的,返回值的类型可省略:
fun helloFunction(name: String) = "Hello $name !"
代码极其简洁,Kotlin的优势不仅仅体现在函数声明上,在函数调用的地方,它也有很多独到之处。
函数调用
代码风格基本和Java一致
helloFunction("Kotlin")
但Kotlin提供了一些新的特性,命名参数。可以理解为:就是它允许我们在调用函数的时候传入“形参的名字”
helloFunction(name = "Kotlin")
fun createUser(
name: String,
age: Int,
gender: Int,
friendCount: Int,
feedCount: Int,
likeCount: Long,
commentCount: Int
) {
//..
}
// Java调用方式:
createUser("Tom", 30, 1, 78, 2093, 10937, 3285)
// Kotlin调用方式:
createUser(
name = "Tom",
age = 30,
gender = 1,
friendCount = 78,
feedCount = 2093,
likeCount = 10937,
commentCount = 3285
)
可以看到,把函数的形参加了进来,形参和实参通过“=”连接,建立了两者的对应关系,其实这样代码的可读性会更强。
此外,Kotlin还支持参数默认值:
fun createUser(
name: String,
age: Int,
gender: Int = 1,
friendCount: Int = 0,
feedCount: Int = 0,
likeCount: Long = 0L,
commentCount: Int = 0
) {
//..
}
createUser(
name = "Tom",
age = 30,
commentCount = 3285
)
以上方法调用时只传了三个参数,剩余的参数没有传,但是Kotlin编译器会自动帮忙填充默认值
但在Java中,需要定义三个参数的重载方法,才能实现。其实,在一些场景下还是提高了开发效率的。
流程控制
在Kotlin中,流程控制主要有if,when,for,white,这些语句可以控制代码的执行流程,具体如下:
if
if 语句,在程序中主要用于逻辑判断,和Java基本一致,此外,还可以作为表达式来使用:
val i = 1
val message = if (i > 0) "Big" else "Small"
print(message)
//输出结果:
Big
另外,在Kotlin中明确规定了类型分为可空类型和不可空类型,会遇到一些可空的变量,判断是否为空:
fun getLength(text: String?): Int {
return if (text != null) text.length else 0
}
类似这种逻辑判断出现的很频繁,为此,Kotlin提供了一种简写,称之为Elvis表达式
fun getLength(text: String?): Int {
return text?.length ?: 0
}
when
when 语句,在程序当中也是主要用于逻辑判断的,当代码逻辑只有两个分支的时候,一般用if/else,而大于两个逻辑分支的时候,一般使用when:
val i: Int = 1
when(i) {
1 -> print("一")
2 -> print("二")
else -> print("i 不是一也不是二")
}
// 输出结果:
一
when语句和Java中的switch case语句很像,不过Kotlin的when更加强大,同时可以作为表达式,为变量赋值:
val i: Int = 1
val message = when(i) {
1 -> "一"
2 -> "二"
else -> "i 不是一也不是二" // 如果去掉这行,会报错
}
print(message)
循环遍历 while,for
while语法和Java类似,不做过多讲解
Kotlin在for循环做了大幅度调修改,Java中最常用的for-i循环在kotlin中直接被舍弃了,而Java中另外一种for-each循环则被Kotlin进行了加强,变成了for-in循环。
val array = arrayOf(1, 2, 3)
for (i in array) {
println(i)
}
此外,Kotlin还支持迭代一个区间。
在Kotlin中用关键字 .. 来创建一个两端闭区间,两边指定区间的左右端点:
val range = 0..10 // 代表 [0, 10]
可通过for-in来遍历这个区间:
for (i in range){
printIn(i)
}
在很多时候,需要创建单闭区间,可通过until关键字来创建一个左闭右开的区间:
val range = 0 until 10 // 代表 [0, 10)
默认情况下,for-in循环每次执行循环时会在区间范围内递增1,即i++的效果,如果想跳过某一些元素,可以使用step关键字:
for(i in range step 2){
printIn(i)
}
// 打印:0 2 4 6 8
相当于在遍历range区间时,每次递增2,即可i=i+2的效果。
不管是..,还是until关键字都要求区间的左端小于等于区间的右端,即这两个关键字创建的都是一个升序区间,如果想创建一个降序区间,可使用downTo关键字:
for (i in 6 downTo 0 step 2) {
println(i)
}
// 输出结果:
6 4 2 0
类与对象
在Kotlin中也是使用class关键字来声明一个类的,这一点和Java是保持一致的。
class Person{
var name = “”
var age = 0
fun eat(){
printIn(name + “is eating. He is ”+age +” years old.”)
}
}
对这个类进行实例化:
val p = Person()
Kotlin实例化一个类的方式和Java基本类似,只是去掉了new关键字。
继承
我们先定义一个Student类:
class Student{
var sno =””
var grade = 0
}
怎么让Student继承Person呢?
在Kotlin中任何一个非抽象类默认是不可以被继承的。相当于Java中给类声明了final。之所以这么设计,其实和val关键字的原因是差不多的,类和变量一样,最好是不可变的。
如果需要被继承,在Person类前面加上open关键字即可:
open class Person{
}
加上open关键字之后,就是在主动告知Kotlin编译器,Person这个类时专门为继承而设计的,它就会被允许继承。
在Java中继承的关键字是extends,而在Kotlin中变成了一个冒号:
class Student : Person(){
var sno = “”
var grade = 0
}
接口
一般会在接口中定义一系列的抽象行为,然后由具体的类去实现。
Java中实现接口使用的是关键字implements,而Kotlin中统一使用冒号,中间用逗号进行分隔。
class Student(name:String,age int):Person(name,age),Study{
override fun doHomework(){
}
}
修饰符
Kotlin中也有四种,public,private,protected 和internal
区别在于:在Java中默认的是default,而Kotlin中public是默认修饰符。
权限跟Java稍微有出入:
protected: Java中表示对当前类,子类和同一个包路径下的类可见。在Kotlin表示只对当前类和子类可见。
internal:只对同一个模块的类可见,。如:开发了一个模块给别人使用,但一些函数只允许在模块内部调用,不想暴露在外部,可以用internal来声明。
修饰 | Java | Kotlin |
---|---|---|
public | 所有类可见 | 所有类可见(默认) |
private | 当前类可见 | 当前类可见 |
protected | 当前类,子类和同一个包路径下的类可见 | 只对当前类和子类可见 |
default | 同一个包路径下面的类可见(默认) | 无) |
internal | 无 | 同一个模块中的类可见 |
数据类和单列类
数据类通常需要重写equals(),hashCode().toStirng()这几个方法,其中equals()用于判断两个数据类是否相等,这些方法会让代码显得比较臃肿,有些时候却又不得不写。但在Kotlin中,这种情况就变得极其简单,可通过data关键字来声明
data class CellPhone(val brand:String,val price:Double)
当在一个类的前面声明了data关键字时,就表明它是一个数据类,Kotlin会根据主构造函数中的参数自动生成equals(),hashCode(),toString()等固定方法,一定程度上减少了工作量。
最后再讲一个Kotlin中的单例类
在java中写单例的常规写法,首先为了进制外部创建Singleton的实例,需要用private关键字将Singleton的构造函数私有化,然后给外部提供一个getInstance()静态方法用于获取Singleton的实例。在getInstance()方法中判断当前缓存的Singleton实例为null,就创建一个新的实例,否则直接返回缓存的实例。
然而,在Kotlin中,会将一些固定的,重复的逻辑实现了隐藏,只暴露一个最简单的用法。在Kotlin中创建单例,只需要将class关键字改为object关键字即可。Kotlin会自动一个Singleton实例,且会保证全局只会存在一个Singleton实例。
小结
Kotlin 和 Java 的语法很像,但在一些细节之处,Kotlin 总会有一些新的东西。熟悉完基础语法之后,我们可以来看看 Kotlin 在这方面都做了以下改进:
支持类型推导;
代码末尾不需要分号;
字符串模板;
原始字符串,支持复杂文本格式;
单一表达式函数,简洁且符合直觉;
函数参数支持默认值,替代 Builder 模式的同时,可读性还很强;
if 和 when 可以作为表达式。
同时,JetBrains 也非常清楚开发者在什么情况下容易出错,所以,它在语言层面也做了很多改进:
强制区分“可为空变量类型”和“不可为空变量类型”,规避空指针异常;
推崇不可变性(val),对于没有修改需求的变量,IDE 会智能提示开发者将“var”改为“val”;
基础类型不支持隐式类型转换,这能避免很多隐藏的问题;
数组访问行为与集合统一,不会出现 array.length、list.size 不统一的情况;
函数调用支持命名参数,提高可读性,在后续维护代码的时候不易出错;
when 表达式,强制要求逻辑分支完整,尽可能的减少了逻辑上的漏洞