初识 Kotlin
历史
Kotlin由JetBrains公司开发设计,2011年公布第一版,2012年开源。
2016年发布1.0正式版,并且JetBrains在IDEA加入对Kotlin的支持,安卓自此又有新的选择。
2019年谷歌宣布Kotlin成为安卓第一开发语言,安卓程序员由java转Kotlin已经迫在眉睫。
工作原理
Java 虚拟机只认识 class 文件,并不关心是由什么文件编译来的, 因此当我们创造一个自己的语法规则时,再做一个对应的编译器,就可以让我们的语言跑在 Java 的虚拟机上。Kotlin 就是这个原理,运行前会先编译成 class,在给 Java 虚拟机运行。
第一个Hello World!
创建项目只需要注意以下几点即可(这里使用 Gradle 构建项目)
修改配置文件中的 JDK 版本(这里我使用的是 JDK8)
如下代码(Kotlin 以 main 方法作为程序的入口)
fun main() {
println("hello world!")
}
运行结果如下
接下来,进入语法的学习
Kotlin 语法
变量
var 表示可变变量
val 表示不可变变量
a)由于 Kotlin 存在类型推导机制,因此不用声明具体的数据类型。当然也可以手动指定数据类型,如下代码
var a = 1 //可变
val b = 2 //不可变
var c: Int = 1 //指定数据类型(Kotlin有类型推导机制,这里可以不用指明)
他们的本质区别就类似 Java 中如下代码
private static int a = 1;
private static final int b = 2;
private static int c = 3;
Kotlin 这样设计是为了防止非 final 类型的滥用,也就是说,如果一个变量永远不会被修改,就有必要给他加上 final,让其他人看到代码更好理解。
Ps:建议写代码时,可以先使用 val,如果真的需要修改,再改为 var
b)如何查看 Kotlin 对应的 Java 代码?如下
基本数据类型
Kotlin 不存在基本类型,全部都是对象类型
Java基本类型 | Kotlin对象类型 | 对象类型说明 |
---|---|---|
int | Int | 整型 |
long | Long | 长整型 |
short | Short | 短整型 |
float | Float | 单精度浮点型 |
double | Double | 双精度浮点型 |
boolean | Boolean | 布尔型 |
char | Char | 字符型 |
byte | Byte | 字节型 |
函数
无参无返回值
fun add1() {
}
有参有返回值,语法如下:
fun 方法名(参数名: 类型): 返回值类型 { 函数体 }
fun add2(a: Int, b: Int): Int {
return a + b
}
当函数体只有一行代码时可以直接使用以下方式声明
fun add3(a: Int, b: Int): Int = a + b
Kotlin 存在类型推导,返回值类型也可省略
fun add4(a: Int, b: Int) = a + b
调用的的时候,直接写函数名即可
fun add4(a: Int, b: Int) = a + b
fun main() {
println(add4(3, 5))
}
== 和 ===
Kotlin 中的 == 等价于 Java 的 equals,比较的是对象里的内容(Kotlin 中只有对象类型,不存在值比较),而 === 等价于 Java 中 == ,比较的是对象的引用.
选择控制(if、when)
if
Kotlin 中的 if 和 Java 基本上没有区别
例如,实现一个返回最大值的函数,有以下多种写法
fun max1(a: Int, b: Int): Int {
if(a > b) {
return a
}
return b
}
fun max2(a: Int, b: Int): Int {
return if(a > b) a else b //if 也可以直接返回值
}
fun max3(a: Int, b: Int) = if(a > b) a else b
when
a)类似 Java 中的 switch 语句,可以进行选择控制
如下代码
fun whenTest(num: Int) {
when(num) {
1 -> println("1")
2 -> println("2")
3 -> println("3")
4 -> println("4")
else -> println("非法!") //else 可有可无
}
}
解释:例如当 num 为 2 时,when 就会匹配 num 为 2,执行 println("2"),程序结束. 与 switch 不同的是,这里执行完 num 为 2 逻辑后,不需要 break,自动跳出 when 语句;如果 num 非 1、2、3、4,就会执行 else 逻辑.
c)when 也支持执行代码块
fun whenTest2(name: String) {
when(name) {
"1" -> {
println("你好1")
println("你好2")
println("你好3")
}
"2" -> println("我好像作用不大")
else -> println("非法")
}
}
b)when 支持参数检查
fun checkNumber(num: Number) {
when (num) {
is Int -> println("Int")
is Double -> println("Double")
else -> println("others")
}
}
c)when 也可以不传递参数
fun whenTest3(name: String) {
when {
name == "cyk" -> println("男")
name == "lyj" -> println("女")
else -> println("emm...")
}
}
循环语句
Kotlin 有两种循环方式:while 和 for-in
while 和 Java 中的 while 没有区别(因此这里就不再赘述 while 了),而 for-in 则是对 for-each 的加强,舍弃了 for-i 的写法.
对于 for-in 的写法,首先要明确一个 区间 的概念
val range = 0..10 //代表区间 [0, 10],前闭后闭
a)for-in 需要使用区间
fun test1() {
val range = 0..10 //前闭后闭
for(i in range) { //也可以使用 for(i in 0..10)
print("${i} ")
}
}
b)0..10 表示双闭区间,如果想使用左闭右开,需要借助 until 关键字
fun test2() {
for(i in 0 until 10) { //前闭后开 [0, 10)
print("${i} ")
}
}
c)上面的代码类似于 i++,Kotlin 也支持跳步
fun test3() {
for(i in 0 until 10 step 2) {
print("${i} ")
}
}
d)以上实现都是升序,Kotlin也可以实现降序循环.
fun test4() {
for(i in 10 downTo 0) { //前闭后闭
print("${i} ")
}
}
for-in 还可以进行集合的遍历,后续再演示.
通过 main 函数依次执行以上函数,如下:
fun main() {
test1()
println("----------------------")
test2()
println("----------------------")
test3()
println("----------------------")
test4()
println("----------------------")
}
执行结果如下:
类和对象
创建和使用
创建一个 Person 类,具有 name 和 age 属性,showInfo 方法(打印信息),代码如下
class Person {
var name = ""
var age = 0
fun showInfo() {
println("name: ${name}, age: ${age}")
}
}
对象的创建和使用如下
val person = Person()
= "cyk"
person.age = 20
person.showInfo()
继承
声明 Person 类,声明 Student 类并继承 Person 类
open class Person {
var name = ""
var age = 0
fun showInfo() {
println("name: ${name}, age: ${age}")
}
}
class Student : Person() { //被继承的类需要使用 open 修饰(需要在 Person 类前添加 open)
var num = 1
var grade = 0
fun study() {
println("student: ${name} do homework")
}
}
注意:Person 类默认情况下不能被继承的,因为默认情况下类似于 Java 中的 final 修饰的,因此。这里需要使用 open 关键字才可以解除 final。(否则 IDEA 自动语法检测报错)
构造
主构造
在 Kotlin 中,分为主构造和次构造。
a)主构造直接写在类后面即可,如下代码
//主构造(直接写在类后面)
class Student(val num: Int, val grade: String) {
}
创建方式如下
var student = Student(1, "三年二班")
b)当 Person 和 Student 都有主构造,并且 Student 继承了 Person 类时,就需要修改 Student 的主构造,附带上父类的参数(此时不需要指定 var 或 val),如下代码
open class Person(val name: String, val age: Int) {
}
class Student(val num: Int, val grade: String, name: String, age: Int):
Person(name, age) {
}
fun main() {
//创建 Student 对象
var student = Student(1, "三年二班", "cyk", 20)
}
c)如果在构造时需要进行一些特殊处理怎么办?Kotlin 中提供了 init 结构体,主构造的逻辑可以在 init 中进行处理,如下代码
class Student(val num: Int, val grade: String, name: String, age: Int):
Person(name, age) {
init {
println("对主构造中的参数进行了一些特殊处理")
println("...")
}
}
如果一个类想要有多种构造方法,该怎么做?这就需要用到次构造
次构造
a)constructor 中只需要指定创建该对象时需要的参数(也就是说,无参构造无需指定任何参数),this 中就表明了需要什么样的参数,代码如下
class Student(val num: Int, val grade: String, name: String, age: Int):
Person(name, age) {
//两个参数的构造
constructor(name: String, age: Int) : this(0, "", name, age){
}
//无参构造
constructor(): this(0, "", "", 0) {
}
}
通过以上构造,就有三种创建 Student 对象的方式,如下
//创建 Student 对象
var student1 = Student(1, "三年二班", "cyk", 20)
var student2 = Student("cyk", 20)
var student3 = Student()
b)如果类不要主构造,那么继承类也无需通过 () 的方式初始化参数,子类次构造可以通过 super 来初始化父类的构造器
class Student: Person {
constructor(name: String, age: Int, grade: String) : super(name, age){
}
}
创建对象如下
//创建 Student 对象
Student("cyk", 20, "三年二班")
接口
定义
和 Java 基本没什么区别
interface Study {
fun study()
fun sleep()
fun doHomework()
}
Kotlin 支持接口的方法有默认实现(JDK1.8以后支持此功能),如果有默认实现,则继承类可以重写该方法,也可以不重写,若重写,以重写为主,否则执行默认实现.
interface Study {
fun study() {
println("学习ing ~")
}
fun doHomework()
}
class Student(val name: String, val age: Int): Study, Other { //多个接口只需要使用逗号隔开
// override fun study() { //可以不重写~ 若重写,则执行此方法
// println("学习!")
// }
override fun doHomework() {
TODO("Not yet implemented")
}
override fun sleep() {
TODO("Not yet implemented")
}
}
实现
Kotlin 和 Java 一样,支持一个类实现多个接口,需要实现所有方法(若接口的方法有默认实现,则可以不重写该方法)
class Student(val name: String, val age: Int): Study, Other { //多个接口只需要使用逗号隔开
override fun study() {
TODO("Not yet implemented") //TODO 表明该方法未被实现,因此实现该方法时,需要将这一行删除
}
override fun doHomework() {
TODO("Not yet implemented")
}
override fun sleep() {
TODO("Not yet implemented")
}
}
权限修饰符
Java 和 Kotlin 的异同点.
需要注意的是,Kotlin 中移除了 default,引入了 internal 修饰.
修饰符 | Java | Kotlin |
---|---|---|
public | 所有类可见 | 所有类可见(默认) |
private | 当前类可见 | 当前类可见 |
protected | 当前类,子类,同包下类可见 | 当前类,子类可见 |
default | 同包下类可见(默认) | 无 |
internal | 无 | 同模块下的类可见 |
使用方式和 Java 没什么区别
//类上
public open class Solution(val name: String, val number: Int) {
//变量上
private val key = 1
//方法上
private fun test() {
}
}
数据类(实体类)和单例类
数据类
数据类则只处理数据相关(实体类),与Java Bean
类似,通常需要实现其get
,set
,hashCode
,equals
,toString
等方法
例如一个用户实体类,Java 代码如下:
public class UserJava {
private Integer id;
private String username;
private String password;
public UserJava() {
}
public UserJava(Integer id, String username, String password) {
this.id = id;
this.username = username;
this.password = password;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
UserJava userJava = (UserJava) o;
return Objects.equals(id, userJava.id) && Objects.equals(username, userJava.username) && Objects.equals(password, userJava.password);
}
@Override
public int hashCode() {
return Objects.hash(id, username, password);
}
@Override
public String toString() {
return "UserJava{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
而 Kotlin 实现以上实体类,只需要一个新建一个 kt 文件,选择创建 Data Class 类型,一行代码即可搞定(虽然一行可以搞定,但是建议还是分行写,可读性高),如下
data class UserKotlin(
val id: Int,
val username: String,
val password: String
)
若无data
关键字,上述方法(hashCode
,equals
,toString
)无法正常运行.
单例类
Java 最广泛使用的单例如下
public class Singleton {
private static final Singleton singleton = new Singleton();
public static Singleton getSingleton() {
return singleton;
}
private Singleton() {
}
/**
* 测试方法
*/
public void test() {
}
}
而 Kotlin 中实现单例模式只需要 object 修饰即可,如下代码
object Singleton {
fun test() {
println("hello")
}
}
使用如下
fun main() {
Singleton.test() //等同于 Java 中 Singleton.getSingleton.test
}
集合
List
fun listTest() {
//常规创建
//list1 类型:ArrayList<Int>
var list1 = ArrayList<Int>()
list1.add(1)
list1.add(2)
list1.add(3)
//创建不可变类型,创建后不可进行增加和删除操作,只能进行查询
//list2 类型: List<Int>
val list2 = listOf<Int>(1, 2, 3)
//创建可变类型,创建后可以继续增删改查
//list3 类型: MutableList<Int>
var mutableListOf = mutableListOf<Int>()
mutableListOf.add(1)
mutableListOf.add(2)
for(value in list1) {
print("$value ")
}
}
Set
fun setTest() {
//常规创建
//set1 类型: HashSet<Int>
var set1 = HashSet<Int>()
set1.add(1)
set1.add(2)
set1.add(3)
//创建不可变类型,创建后不可进行增加和删除操作,只能进行查询
//set2 类型: Set<Int>
var set2 = setOf<Int>(1,2,3)
//创建可变类型,创建后可以继续增删改查
//set3 类型: MutableList<Int>
var set3 = mutableSetOf<Int>()
set3.add(1)
for(value in set1) {
print("$value ")
}
}
Map
fun mapTest() {
//常规创建
//map1 类型: HashMap<String, String>
var map1 = HashMap<String, String>()
map1.put("name", "cyk")
map1.put("age", "20")
//Kotlin 中 map 支持下表的赋值和访问
map1["id"] = "1"
map1["gender"] = "男"
//创建不可变类型,创建后不可进行增加和删除操作,只能进行查询
//map2 类型: mapOf<String, String>
var map2 = mapOf<String, String>("name" to "cyk", "age" to "20")
//创建可变类型,创建后可以继续增删改查
//set3 类型: MutableList<Int>
var map3 = mutableMapOf<String, String>()
map3.put("name", "cyk")
map3.put("age", "20")
map3["id"] = "1"
map3["gender"] = "男"
for((key, value) in map3) {
println("$key $value");
}
}
Lambda 的使用
基本认识
方法在传递参数时都是普通变量,而Lambda
可以传递一段代码
Lambda
表达式的语法结构
{参数名1: 参数类型, 参数名2:参数类型 -> 函数体}
list.maxByOrNull
Kotlin
的 list
提供了 maxByOrNull
函数,参数就是一个 lambda 表达式,用来返回当前 list
中 xx 最大的元素,xx 是我们定义的条件,可能为长度,可能是别的.
这里拿长度来举例,例如我有一个 list,现在要找出 list 中长度最大的元素,如下代码
fun test1() {
//找出 list 中长度最大的元素
val list = listOf<String>("a", "abc", "abcd", "ab")
//写法1
val lambda = {str: String -> str.length}
var maxStr = list.maxByOrNull(lambda)
//写法2
maxStr = list.maxByOrNull {str: String -> str.length}
//写法3
maxStr = list.maxByOrNull() {str: String -> str.length}
//写法4: 基于 Kotlin 的类型推导机制,Lambda 可以省略参数类型
maxStr = list.maxByOrNull {str -> str.length}
//写法5: 若 Lambda 只有一个参数,可以使用 it 代替参数名
maxStr = list.maxByOrNull {it.length}
println(maxStr)
}
list.map、list.filter、list.any、list.all
//创建一个 list,后续操作都以此展开
var list = listOf("a", "ab", "abc", "abcd")
//map 用于返回一个新的集合,例如将集合中的元素都换成大写
val mapList = list.map { it.toUpperCase() }
//filter 过滤,返回一个新集合,例如对集合中的元素进行筛选(筛选出长度大于 3 的元素)
val filterList = list.filter { it.length > 3 }
//any 返回 Boolean,用来判断集合中是否有元素满足 Lambda 条件,只要有任意一个满足返回 true,否则返回 false
val anyList = list.any {it.length > 3} //存在一个字符串长度大于 3,因此返回 true
//all 返回 Boolean,集合中元素是否全部都满足 Lambda 的条件,全都满足才返回true,有任意一个不满足就返回 false
val allList = list.all { it.length <= 4 } //所有元素长度都满足字符串长度小于等于 4, 因此返回 true
空指针检查机制
国外统计程序出现最多的异常为空指针异常,Kotlin
存在编译时检查系统帮助我们发现空指针异常。
Kotlin
把空指针异常的检查提前到了编译期,若空指针则编译期就会崩溃,避免在运行期出现问题,因此在 Kotlin 中,任何变量和参数都不允许为空.
a)参数为空,报错
fun study(study: Study) {
study.doHomework() //正确
study.readBook() //正确
}
fun main() {
study(null) //报错
study(Study()) //正确
}
b)如果有需求就是要传入 null,那么可以通过 "?" 来对传入可能为 null 的参数在类型后进行声明
fun study(study: Study?) {
study.doHomework() //报错
study.readBook() //报错
}
fun main() {
study(null) //正确
study(Study()) //正确
}
c)?
的意思则是当前参数可为空,如果可为空的话,则此对象调用的方法必须要保证对象不为空,上面代码没有保证,则报错,修改如下
fun study(study: Study?) {
if(study != null) {
study.doHomework() //正确
study.readBook() //正确
}
}
fun main() {
study(null) //正确
study(Study()) //正确
}
辅助判空工具
?.
表示 ?
前面对象不为空才执行.
后面的方法
fun study(study: Study?) {
study?.doHomework()
study?.readBook()
}
?:
表示 ?
前不为空则返回问号前的值,为空则返回:
后的值
fun test1(a: Int, b: Int) {
val c = a ?: b //a 不为空返回 a,为空返回 b
}
!!
如果想要强行通过编译,就需要依靠!!,这时就是程序员来保证安全
fun study(study: Study?) {
study!!.doHomework()
study!!.readBook()
}
let 函数
let 是一个函数,提供了函数式 API 接口,会将调用者作为参数传递到 Lambda 表达式,调用之后会立马执行 Lambda 表达式的逻辑.
aaa.let { it -> // it 就是 aaa(调用者)
//执行业务逻辑
}
例如,原本函数的逻辑是这样的
fun study(study: Study?) {
if(study != null) {
study.doHomework()
study.readBook()
}
}
通过 let 则可以改为
fun testLet(study: Study?) {
//此时通过 ?.就保证了 study 不为空的时候才会执行 let 函数
study?.let { it ->
it.doHomework()
it.readBook()
}
}
a)好处1:最常用的就是使用 let 函数处理一个可 null 的对象做统一判空处理(例如上述代码).
b)好处2:在有限的范围内引入局部变量提高代码的可读性.
内嵌表达式
以前我们拼接字符串可能是这样的,如下
fun printTest() {
val name = "cyk"
val age = 20
println("name: " + name + ",age: " + age)
}
a)通过 Kotlin 提供的内嵌表达式就不需要拼接了,只需要如下代码
fun printTest() {
val name = "cyk"
val age = 20
println("name: $name, age: $age")
}
b)内敛表达式还支持复杂的操作,语法为 ${表达式}
fun printTest() {
val name = "cyk"
val age = 20
println("name: ${if (2 > 1) "cyk" else "lyj"}, age: $age")
}
函数参数默认值
a)Kotlin 支持函数默认值,如下
fun study(name: String, age: String = "男") {
println("name: $name, age: $age")
}
fun main() {
study("cyk")
study("lyj", "女")
}
运行结果
b)如果方法的第一个参数设置成默认值,那么传入的第一个实参就会匹配第一个形参,因此就可能出现以下问题.
fun study(age: String = "男", name: String) {
println("name: $name, age: $age")
}
fun main() {
study("cyk") //报错
}
你可能会这样想:因为第一个设置了默认值,而我又想让我传入的实参就不匹配设置了默认值的形参了...
如果一定要这么做,Kotlin 也提供了键值对的形式来匹配形参,解决上述问题,如下
fun study(age: String = "男", name: String) {
println("name: $name, age: $age")
}
fun main() {
study(name = "cyk") //报错
}