Kotlin学习记录(一)简介、变量和函数、逻辑控制、面向对象、Lambda、空指针检查
一、简介
编程语言大致可以分为编译型语言和解释性语言。
- 编译型语言:编译器会将我们编写的源代码一次性编译成计算机可识别的二进制文件,然后计算机直接执行,例如C和C++。
- 解释型语言:程序运行时,解释器会一行行读取编写的源代码,实时将源代码解释成计算机可识别的二进制数据后执行,例如
Python、JavaScript
和Java
。
Java是先编译为只有Java虚拟机(Android中叫ART,移动优化版的虚拟机)才能识别种特殊的class
文件,Java虚拟机担当的就是解释器。将.class
文件解释成计算机可识别的二进制数据。
Kotlin工作原理:开发了一门新的编程语言,然后自己做了编译器,让它将这门新语言的代码编译成同样规格的class文件,只要是符合规格的class文件,java虚拟机都能识别
Kotlin优点:相较于Java,Kotlin增加了很多现代高级语言的语法特性,安全性方面也下了很多功夫,几乎杜绝了空指针。Kotlin与Java100%兼容,可以无缝使用Java第三方开源库
二、编程之本:变量和函数
2.1 变量
val(value)
声明一个不可变变量,对应Java中final。不可重新赋值!
var(variable)
声明一个可变变量。- Kotlin
完全抛弃基本数据类型
,全部使用对象数据类型
。
对象数据类型
Java中int是关键字,Kotlin中Int是一个类,拥有自己的方法和继承结构
好的编程习惯是,除非一个变量明确允许被修改,否则都应该给它加上final关键字。
永远优先使用val声明变量。防止项目变大后本不该修改的变量不知在何处被修改的问题。
2.2 函数
fun
是定义函数的关键字- 参数声明格式为"
参数名:参数类型
" - Kotlin函数语法糖:函数**
只有一行代码时
可以不写函数体!
直接用等号连接在函数定义的尾部**
fun largerNumber(num1 : Int,num2 : Int)max = (num1,num2)
max()是函数。Kotlin的类型推导机制:
fun largeNumber(num1:Int,num2:Int) = max(num1,num2)
三、Kotlin中的程序逻辑控制
3.1 if语句
if可以有**返回值
,返回值为if语句中每个条件最后一行代码**的返回值。
fun largerNumber(num1 : Int,num2 : Int) : Int{
val value = if(num1 > num2){ //将返回值赋给value变量
num1
}else {
num2
}
return value
}
还可以简化成:
fun largerNumber(num1:Int,num2:Int):Int{
return if(num1>num2){
num1
}else{
num2
}
}
//语法糖,去掉**多余**的value变量,并且**省略函数体**,等效于
fun largerNumber(num1 : Int,num2 : Int) = if(num1>num2) num1 else num2
3.2when语句
when语句有返回值。当判断条件非常多时应考虑使用when语句。
fun getScore(name:String) = when(name){
"Tom"->86
"Jim"->77
"Jack"->95
"Lily"->100
else ->0
}
when
与switch
的区别:
- switch只能传入整型或短于整型的变量,JDK1.7后增加了对字符串变量的支持,switch中每个case条件都要主动加上break,否则会依次执行下面的case。
- when语句允许传入一个任意类型的参数,格式为:
匹配值->{执行逻辑}
,执行逻辑只有一行时{}
可以省略。 - when语句支持
类型匹配
,is
关键字是类型匹配的核心,相当于Java中的instanceof
关键字,N
umber是Kotlin中内置的一个抽象类,Int、Long、Float、Double等与数字相关的类都是checkNumber()
抽象类的子类。
fun checkNumber(num:Number){
when(num){ //类型匹配
is Int -> println("number is Int")
is Double -> println("number is Double")
else -> println("number not support")
}
Kotlin中判断字符串或对象是否相等可以直接使用==关键字
,而不必像Java调用equals()。
- 有些场景必须使用
when不带参数
的用法,如所有名字以Tom开头的人分数都是86,这时无法用带参数的when
语句实现。
//when语句不带参数的用法
fun getScore(name : String) = when{
**name.startsWith("Tom" -> 86) //不管传入Tom还是Tommy都是86.**
name == "Jim" -> 77
name == "Jack" -> 95
name == "Lily" -> 100
else -> 0
}
3.3 循环语句
for-i
循环在Kotlin中被舍弃,for-each
循环被大幅度增强为for-in
循环。while循环没有区别。
Kotlin区间
val range = 0..10
表示0到10的区间,两端闭。
val range = 0 until 10
表示0到10左闭右开区间。
fun main(){
for(i in 0..10){
println(i)
}
}
..
关键字创建两端闭区间。until
关键字创建左闭右开区间。step a
关键字在for-in
循环中,相当于i = i+a
的效果,每次递增a
fun main(){
for(i in 0 until 10 step 2){
println(i)
}
}
输出:0,2,4,6,8
downTo
关键字用来创建一个降序区间。
fun main(){
for(i in 10 downTo 1){ //创建一个[10,1]降序区间
println(i)
}
}
- 如果有一些特殊场景for-in循环无法实现我们可以改用
while循环
。
四、面向对象编程
4.1 类与对象
面向对象的语言可以创建类,类是对事物的一种封装,如人,汽车,房子等都可以将它封装成一个类,类通常是名词,类中可以拥有自己的字段和函数,字段:该类拥有的属性,字段名通常是名词,函数:该类可以拥有的行为,函数名通常是动词。通过这种类的封装,我们可以在适当的时候创建该类的对象,调用对象中的字段和函数满足实际编程的要求。
Kotlin中实例化一个类的方式与Java类似,只是去掉了new关键字。val p = Person()
Kotlin本着最简化的设计原则,将诸如new、行尾分号等不必要的语法结构取消。
4.2 继承与构造函数
写一个Student类,继承Person类,这样Student类就自动拥有了Person中的字段和函数,另外还可以定义自己独有的字段和函数
如果一个类不是专门为继承设计,应该主动加上final声明。Kotlin中任何一个非抽象类都是不可以被继承的,相当于被声明了final关键字。因为一个类允许被继承就无法预知子类会如何实现,就会产生未知的风险。抽象类本身是无法创建实例的,一定要有子类去继承它才能创建实例,因此抽象类必须可以被继承。
- class前加
open关键字
表示该类可被继承。 - 继承关键字为
:
。
Kotlin将构造函数分为主构造函数和次构造函数
主构造函数
- 主构造函数是最常用的构造函数,没有函数体,直接定义在类名后面
class Student(val sno : String, val grade :Int):Person(){
}
//进行实例化:实例化时必须传入构造函数要求的所有参数,
val student = Student("s1",5)
-
Kotlin提供了
init结构体
,所有主构造函数中的逻辑写在里面。与Java相同,Kotlin中子类构造函数必须调用父类构造函数,子类的主构造函数调用父类中的哪个构造函数,在继承的时候通过括号来指定。
class Student(val sno:String,val grade:Int):Person(){ init{ println("sno is " + sno) println("grade is " + grade.) } }
代表
Student
类主构造函数初始化时会调用Person类无参构造函数
,即使无参数,括号也不能省略。 -
在主构造函数中声明val或var,参数将自动成为该类的字段。
次构造函数
- 一个类只能有一个主构造函数,多个次构造函数
- 次构造函数通过
constructor关键字
定义。 - 当类既有主构造函数,又有次构造函数,所有次构造函数必须调用主构造函数(包括间接调用)。
class Student(val sno:String, val grade:Int, name:String, age:Int):
Person(name,age){
constructor(name: String,age: Int):this("",0,name,age){ //通过this关键字调用了主构造函数
}
constructor():this("",0){ //调用了第一个次构造函数,间接调用了主构造函数
}
}
- 当一个类没有显示定义主构造函数且定义次构造函数,此类没有主构造函数,
class Student:Person{
constructor(name:String,age:Int):super(name,age){
}
}
4.3 接口
接口:用于实现多态编程的重要组成部分
interface Study {
fun readBooks()
fun doHomework()
}
//让Student类去实现Study接口
class Student(name: String,age: Int):Person(name,age),Study{
override fun readBooks() {
println(name+" is reading")
}
override fun doHomework() {
println(name + " is doing homework")
}
}
//调用
fun main(){
val student = Student("Jack",19)
student.readBooks()
student.doHomework()
}
override关键字重写父类或者实现接口中的函数
这种叫做面向接口编程,也叫**多态,**也可以对接口中的函数进行默认实现
函数的可见修饰符
public与private在两种语言中一样,分别是对所有类和当前类可见。
- Kotlin中public修饰符是默认项,Java中default是默认项。
- protected关键字Java中表示对
当前类、子类和同一包路径下的类
可见。Kotlin中表示对当前类和子类可见,删除了同一包路径下的类
。 Kotlin抛弃了default的同一包路径下的类可见
,internal
修饰符表示同一模块下的类可见。
4.4数据类与单例类
hashCode()与equals()方法配套重写,否则会导致HashMap、HashSet等hash相关系统无法正常工作,toString()如果不重写默认打印一行内存地址。
数据类
将服务器端或数据库中的数据映射到内存中,为编程逻辑提供数据模型的支持。数据类通常需要重写equals(),hashCode(),toString()这几个方法
data关键字
用于修饰数据类,Kotlin会根据主构造函数中的参数重写equals()、hashCode()、toString()等固定方法。
data class Cellphone(val brand:String,val price:Double)
fun main(){
val cellphone1 = Cellphone("Samsung",1299.99)
val cellphone2 = Cellphone("Samsung",1299.99)
println(cellphone1)
println(cellphone1 == cellphone2)
}
- 类中无代码可将尾部大括号省略。
单例类
避免创建重复对象,Singleton.singletonTest(),
Kotlin帮我们创建了一个单例类实例,并保证全局只存在一个Singleton实例
Java:
public class Singleton {
private static Singleton instance;
private Singleton(){}
//禁止外部创建Singleton实例,private关键字将**构造函数**私有化
//给外部提供了一个**getInstance()静态方法用于获取Singleton的实例**
public synchronized static Singleton getInstance(){
if(instance!=null){
instance = new Singleton();
}
return instance;
}
public void singletonTest(){
System.out.println("SingleTonTest is called. ");
}
}
//要使用单例中的方法时
Singleton singleton = Singleton.getInstance();
singleton.singletonTest();
Kotlin:
object关键字
object Singleton {
fun singletonTest(){
println("SingleTonTest is called. ")
}
}
//调用很简单
Singleton.singletonTest()
- class关键字改成object关键字即可创建单例类。
- 无需私有化构造函数,也不需要
getInstance()
方法。
五、Lambda编程
Lambda 表达式是 JDK8 的一个新特性,可以取代大部分的匿名内部类,写出更优雅的 Java 代码,尤其在集合的遍历和其他集合操作中,可以极大地优化代码结构。JDK 也提供了大量的内置函数式接口供我们使用,使得 Lambda 表达式的运用更加方便、高效。
对接口的要求
- Lambda 规定接口中只能有一个需要被实现的方法,不是规定接口中只能有一个方法
- jdk 8 中有另一个新特性:default, 被 default 修饰的方法会有默认实现,不是必须被实现的方法,所以不影响 Lambda 表达式的使用。
- @FunctionalInterface修饰函数式接口的,要求接口中的抽象方法只有一个。 这个注解往往会和 lambda 表达式一起出现。
5.1 集合的创建与遍历
- Kotlin中提供了内置的
listOf()
简化初始化集合的写法、还有setOf()
和mutableSetOf()
、mapOf()
和mutableMapOf()
listOf函数,创建的是一个不可变的集合,只能用于读取,无法添加、修改或删
val list = listOf("Apple","Banana","Orange","Pear")
for (fruit in list){
println(fruit)
}
mutableListOf()函数,创建的是可变集合
Set集合:setOf(),mutableSetOf(),注意Set集合底层是使用hash映射机制来存放数据的,集合中的元素无法保证有序,这是与List的不同
Map集合:
//写法1:
val map = HashMap<String,Int>()
map["Apple"] = 1
map["Banana"] = 2
map["Orange"] = 3
map["Pear"] = 4
map["Grape"] = 5
//写法2:
val map = mapOf("Apple" to 1,"Banana" to 2)
for((fruit,number) in map){
println("fruit is "+fruit+" , number is "+number)
}
- Kotlin中建议用
类似于数组下标的语法
结构对Map进行添加和读取数据。 - for-in遍历map需要将map的键值对变量声明在一对括号中。
- 这里的to不是关键字,而是
infix
函数。
5.2 集合的函数式API
例子:找出长度最长的水果单词:
//普通写法
val list = listOf("Apple","Orange","Banana","Pear","Grape","Watermelon")
var maxLengthFruit = ""
for (fruit in list){
if(fruit.length > maxLengthFruit.length){
maxLengthFruit = fruit
}
}
println("max length fruit is "+maxLengthFruit)
//集合的函数式API
val list = listOf("Apple","Orange","Banana","Pear","Grape","Watermelon")
var maxLengthFruit = list.maxBy { it.length }
println("max length fruit is "+maxLengthFruit)
maxBy是一个普通的函数,只不过接收一个Lambda类型的参数
//maxBy,函数,接收的是一个Lambda类型的参数,
//遍历集合时将每次遍历的值作为参数传递给Lambda表达式
//工作原理:根据我们传入的条件来遍历集合,从而找到该条件下的最大值
fruit是参数名,String是参数类型,fruit.length是函数体
//1.
val list = listOf("Apple","Orange","Banana","Pear","Grape","Watermelon")
val lambda = { fruit:String ->fruit.length }
maxLengthFruit = list.maxByOrNull (lambda).toString()
println("max length fruit is "+maxLengthFruit)
//2.简化
val maxLengthFruit = list.maxByOrNull ({fruit:String -> fruit.length})
//3.简化——当Lambda参数是函数的最后一个参数时,将Lambda表达式移到函数括号的外面
val maxLengthFruit = list.maxByOrNull (){fruit:String -> fruit.length}
//4.简化——当Lambda参数是函数的唯一一个参数时,将函数的括号省略
val maxLengthFruit = list.maxByOrNull {fruit:String -> fruit.length}
//5.简化——类型推导机制
val maxLengthFruit = list.maxByOrNull {fruit-> fruit.length}
//6.简化——当Lambda表达式的参数列表中只有一个参数时,不必声明参数名,可以使用it关键字来代替
val maxLengthFruit = list.maxByOrNull { it.length }
-
Lambda
就是一小段可以作为参数传递的代码。
语法结构:{参数名1 : 参数类型,参数名2 : 参数类型 → 函数体}
-
当Lambda是函数的最后一个参数时,可以将Lambda表达式移到函数的括号外面。
-
Lambda是函数唯一一个参数时,可以将函数的括号省略。
-
Kotlin拥有出色的类型推导机制,参数列表大多数情况下不必声明参数类型。
-
只有一个参数时不必声明参数名,使用
it关键字
代替。 -
map函数用于将集合中的每个元素映射成另外的值,映射的规则在Lambda表达式中指定按照需求对集合中元素进行任意映射转换。
val list = listOf("Apple","Orange","Banana","Pear","Grape","Watermelon")
val newList = list.map { it.toUpperCase() }
for (fruit in newList){
println(fruit)
}
- filter函数用来过滤集合中的数据。
//保留5个字母以内的水果
val list = listOf("Apple","Orange","Banana","Pear","Grape","Watermelon")
val newList = list.filter { it.length <5 }
for (fruit in newList){
println(fruit)
}
- 先进行过滤再进行映射转换效率会高的多。
- any函数会判断集合中是否至少存在一个元素满足指定条件,all函数判断集合中是否所有元素满足指定条件。
val list = listOf("Apple","Orange","Banana","Pear","Grape","Watermelon")
val anyResult = list.any { it.length<=5 }
val allResult = list.all { it.length <=5 }
5.3 Java函数式API的使用
如果我们再Kotlin代码中调用一个Java方法,并且该方法接收一个Java单抽象方法接口参数,就可以使用函数式API。Java单抽象方法接口——接口中只有一个待实现方法。最常见的单抽象方法接口——Runnable接口
public interfac Runnable{
void run();
}
java:
new Thread(new Runnable(){
@Override
public void Run(){
System.out.println("Thread is running");
}
}).start();
kotlin:
Thread(object : Runnable{
override fun run(){
println("Thread is running")
}
}).start()
//化简
Thread(Runnable {
println("Thread is running")
}).start()
//化简参数列表中有且仅有一个Java单抽象方法接口参数,将接口名省略
Thread( {
println("Thread is running")
}).start()
//最终化简
Thread{
println("Thread is running")
}.start()
注意:本节中的Java函数式API使用都限定从Kotlin中调用Java方法,并且单抽象方法接口也必须是用Java语言定义的
小结:Kotlin完全舍弃new
关键字,创建匿名类实例改用object关键字,**object可以省略,仅有一个Java单抽象方法接口参数时接口名也可以省略**。
六、空指针检查
如果每处检查代码都用if判断,会让代码变得比较啰嗦。
6.1可空类型系统
Kotlin默认所有的参数和变量不可为空。
可为空的类型系统:类名后加上一个问号?
表示可为空的类型。
var adapter : MsgAdapter? = null
表示adapter继承自可为空的MsgAdapter且初始赋值为null。
fun main(){
doStudy(null)
}
fun doStudy(study: Study?){
if(study!=null){
study.doHomework()
study.readBooks()
}
}
6.2 判空辅助工具
- ?.操作符可以省略if判断语句,对象不为空正常调用,对象为空什么都不做。
if(a!=null){
a.doSomething()
}
即
a?.doSomething()
val c = if(a!=null){
a
}else{
b
}
即
val c = a?:b
- ?:操作符左右两边都接收一个表达式,如果左边表达式不为空就返回左边表达式结果,否则返回右边表达式结果。
fun getTextLength(text : String?) = text?.length?:0
如果text为空,text?.length返回null,借助?:
让他返回0(text?.length?为null
的的话就返回:右边的0)
辅助工具——let
let函数
会将调用的对象本身作为参数传递到Lambda表达式中,let函数可提供了函数式API的编程接口而且可以处理全局变量的判空问题,if是无法解决的
fun doStudy(study:Study?){
study?.let{ stu -> //对象为空时什么都不做,不为空时调用let函数
//let函数将study对象本身作为参数传递到Lambda表达式中
//study的对象肯定不为空
stu.readBooks()
stu.doHomework()
}
}
- !!.为非空断言工具,意在告诉Kotlin,非常确信这里的对象不会为空,出现问题抛出空指针异常,后果自己承担。但往往一个潜在的空指针异常发生在最自信不会为空的对象上。
字符串内嵌表达式——${}
当表达式中仅有一个变量的时候,将两边的大括号省略
fun main(){
doStudy(null)
val name = "Tome"
val age = 19
println("name ${name} ,age $age")
}
不需要加号连接符,很好用。
函数的参数默认值
可以给函数设定参数默认值
printParams(str = "world",name = 123)
//不管哪个参数在前还是在后,都可以准确将参数匹配上
- 给函数设置参数默认值可以很大程度上替代次构造函数作用**。**
- 在没有给参数传值的情况下会自动使用参数的默认值。
- Kotlin可以通过键值对传参,而不必像传统写法按照参数定义顺序传参。