kotlin
1.变量
val: 不可重新赋值(等效于final修饰)
var: 可重新赋值
如果不是在声明变量的同时就进行赋值需要声明变量类型
val a : Int
a = 10
kotlin去除了基本数据类型,只有对象数据类型
Int Long Short Float Double Boolean Char Byte
2.函数
一条语句的函数:
fun 函数名(变量名 : 变量类型 , 变量名 :变量类型):返回值类型 = 函数内容
fun largerNumber(num1: Int, num2: Int): Int = max(num1, num2)
由于这个函数右边通过等号连接,右边max函数必将返回Int值,故kotlin可以推导出左边largerNumber函数的返回值类型为Int,所有函数可以简化为:
fun largerNumber(num1: Int, num2: Int) = max(num1, num2)
多条语句的函数:
fun 函数名(变量名 : 变量类型 , 变量名 :变量类型):返回值类型{
函数内容
}
fun largerNumber(num1: Int, num2: Int): Int {
max(num1, num2)
}
3.程序的逻辑控制
if语句
if语句具有返回值,其他与java一致
fun largerNumber(num1: Int, num2: Int): Int {
var value = 0
if (num1 > num2) {
value = num1
} else {
value = num2
}
return value
}
简化1
fun largerNumber(num1: Int, num2: Int): Int {
var value = if (num1 > num2) {
num1
} else {
num2
}
return value
}
简化2
fun largerNumber(num1: Int, num2: Int): Int {
return if (num1 > num2) {
num1
} else {
num2
}
}
简化3
fun largerNumber(num1: Int, num2: Int): Int = if (num1 > num2) {
num1
} else {
num2
}
简化4
fun largerNumber(num1: Int, num2: Int)= if (num1 > num2) {
num1
} else {
num2
}
简化5
fun largerNumber(num1: Int, num2: Int)=if (num1 > num2) num1 else num2
when语句
①有返回值
②when的判断条件可为任意数据类型
精确匹配:
fun getScore(name: String) = when (name) {
"Tom" -> 86
"Jim" -> 77
"Jack" -> 95
"Lily" -> 100
else -> 0
}
类型匹配:
fun checkNumber(num: Number) {
when (num) {
is Int -> println("number is Int")
is Double -> println("number is Double")
else -> println("number not support")
}
}
when语句不传参:
fun getScore(name: String) = when {
name == "Tom" -> 86
name == "Jim" -> 77
name == "Jack" -> 95
name == "Lily" -> 100
else -> 0
}
无参数when语句使用场景举例:
所有名字以Tom开头的人,他的分数都是86分
name.startsWith(“Tom”) -> 86
fun getScore(name: String) = when {
name.startsWith("Tom") -> 86
name == "Jim" -> 77
name == "Jack" -> 95
name == "Lily" -> 100
else -> 0
}
注:kotlin判断字符串或对象是否相等可以直接使用==关键字
while语句
与java一致
for-in语句
补充知识:
①闭区间
val range = 0..10
②左闭右开区间
var range = 0 until 10
fun main() {
for (i in 0..10) {
println(i)
}
}
fun main() {
for (i in 0 until 10) {
println(i)
}
}
注:step关键字:在for-i循环中跳过其中的一些元素
step 2:相当于i+=2,递增2
fun main() {
for (i in 0 until 10 step 2) {
println(i)
}
}
注:downTo关键字:创建降序的区间
fun main() {
for (i in 10 downTo 1) {
println(i)
}
}
4.面向对象
4.1类与对象的概念
不同于面向过程的语言(比如C 语言),面向对象的语言是可以创建类的。类就是对事物的一种封装,比如说人、汽车、房 屋、书等任何事物,我们都可以将它封装一个类,类名通常是名词。而类中又可以拥有自己的 字段和函数,字段表示该类所拥有的属性,比如说人可以有姓名和年龄,汽车可以有品牌和价 格,这些就属于类中的字段,字段名通常也是名词。而函数则表示该类可以有哪些行为,比如 说人可以吃饭和睡觉,汽车可以驾驶和保养等,函数名通常是动词。 通过这种类的封装,我们就可以在适当的时候创建该类的对象,然后调用对象中的字段和函数 来满足实际编程的需求,这就是面向对象编程最基本的思想。
class Person {
var name = ""
var age = 0
fun eat() {
println(name + " is eating. He is " + age + " years old.")
}
}
val p = Person()
4.2继承与构造函数
4.2.1继承
在Kotlin中任何一个非抽象类默认都是不可以被继承的,相当于Java中给类声明了final 关键字。之所以这么设计,其实和val关键字的原因是差不多的,因为类和变量一样,最好都是 不可变的,而一个类允许被继承的话,它无法预知子类会如何实现,因此可能就会存在一些未 知的风险。Effective Java这本书中明确提到,如果一个类不是专门为继承而设计的,那么就应 该主动将它加上final声明,禁止它可以被继承。。之 所以这里一直在说非抽象类,是因为抽象类本身是无法创建实例的,一定要由子类去继承它才 能创建实例,因此抽象类必须可以被继承才行,要不然就没有意义了。
//open关键字:让类可被继承
open class Person {
...
}
//Student 继承 Person ,将java中的extends关键字替换成了一个冒号
class Student : Person() {
var sno = ""
var grade = 0
}
4.2.2主构造函数与次构造函数
主构造函数将会是你最常用的构造函数,每个类默认都会有一个不带参数的主构造函数,当然 你也可以显式地给它指明参数。主构造函数的特点是没有函数体,直接定义在类名的后面即 可。
class Student(val sno: String, val grade: Int) : Person() {
}
主构造函数没有函数体,如果我想在主构造函数中编写一些逻辑,该怎么办呢? Kotlin给我们提供了一个init结构体,所有主构造函数中的逻辑都可以写在里面:
class Student(val sno: String, val grade: Int) : Person() {
init {
println("sno is " + sno)
println("grade is " + grade)
}
}
子类中的构造函数必须调用父类中的构造函数!!!
主构造函数并没有函数体,我们怎样去调用父类的构造函数呢?你可能会说,在init结构体中去调用不就好了。这或许是一种办法,但绝对不是一 种好办法,因为在绝大多数的场景下,我们是不需要编写init结构体的。
子类的主构造函数调用父类中的哪个构造函数,在继承的时候通过括号来指定。
open class Person() {
...
}
/**
Person类后面的一对空括号表示Student类的主构造函数在初始化的时候会调用
Person类的无参数构造函数
*/
class Student(val sno: String, val grade: Int) : Person() {
}
//Person类现在已经没有无参的构造函数了
open class Person(val name: String, val age: Int) {
...
}
//给Person类的构造函数传入name和age字段
/**
在Student类的主构造函数中增加name和age这两个字段时,不能再将它们声明成
val,因为在主构造函数中声明成val或者var的参数将自动成为该类的字段,这就会导致和父
类中同名的name和age字段造成冲突。因此,这里的name和age参数前面我们不用加任何关键字,让它的作用域仅限定在主构造函数当中即可
*/
class Student(val sno: String, val grade: Int, name: String, age: Int) :
Person(name, age) {
...
}
任何一个类只能有一个主构造函数,但是可以有多个次构造函数。次构造函数也可 以用于实例化一个类,这一点和主构造函数没有什么不同,只不过它是有函数体的。其实你几乎是用不到次构造函数的,Kotlin提供了一个给函数设定参数默认值的功能,基本上可 以替代次构造函数的作用。
Kotlin规定,当一个类既有主构造函数又有次构造函数时,所有的次构造函数都必须调用主构造 函数(包括间接调用)。
class Student(val sno: String, val grade: Int, name: String, age: Int) :
Person(name, age) {
//次构造函数是通过constructor关键字来定义的
/**
第一个次构造函数接收name和age参数,然后它又通过this关键字调用了主构造函数,并将sno和grade这两个参数赋值成初始值
*/
constructor(name: String, age: Int) : this("", 0, name, age) {
}
/**
第二个次构造函数不接收任何参数,它通过this关键字调用了我们刚才定义的第一个次构造函数,并将name和age参数也赋值成初始值,第二个次构造函数间接调用了主构造函数
*/
constructor() : this("", 0) {
}
}
/**
首先Student类的后面没有显式地定义主构造函数,同时又因为定义了次构造函数,所以现在Student类是没有主构造函数的。那么既然没有主构造函数,继承Person类的时候也就不需要再加上括号了。
*/
class Student : Person {
/**
由于没有主构造函数,次构造函数只能直接调用父类的构造函数,上述代码也是将this
关键字换成了super关键字
*/
constructor(name: String, age: Int) : super(name, age) {
}
}
4.3接口
interface Study {
fun readBooks()
fun doHomework()
}
/**
Java中继承使用的关键字是extends,实现接口使用的关键字是implements,而Kotlin中统一使用冒号,中间用逗号进行分隔。
*/
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.")
}
}
Kotlin还增加了一个额外的功能:允许对接口中定义的函数进行默认实现。
如果接口 中的一个函数拥有了函数体,这个函数体中的内容就是它的默认实现。
/**
现在当一个类去实现Study接口时,只会强制要求实现readBooks()函数,而doHomework()函数则可以自由选择实现或者不实现,不实现时就会自动使用默认的实现逻辑。
*/
interface Study {
fun readBooks()
fun doHomework() {
println("do homework default implementation.")
}
}
修饰符
修饰符 | Java | Kotlin |
---|---|---|
public | 所有类可见 | 所有类可见(无修饰符时默认) |
private | 当前类可见 | 当前类可见 |
protected | 当前类、子类、同一包路径下的类可见 | 当前类,子类可见 |
default | 同一包路径下的类可见(无修饰符时默认) | 无 |
internal | 无 | 同一模块中的类可见 |
4.4数据类
数据类通常需要重写equals()、hashCode()、toString()这几个方法。其中,equals() 方法用于判断两个数据类是否相等。hashCode()方法作为equals()的配套方法,也需要一起 重写,否则会导致HashMap、HashSet等hash相关的系统类无法正常工作。toString()方法 用于提供更清晰的输入日志,否则一个数据类默认打印出来的就是一行内存地址。
/**
当在一个类前面声明了data关键字时,就表明你希望这个类是一个数据类,Kotlin会根据主构造函数中的参数帮你将equals()、hashCode()、toString()等固定且无实际逻辑意义的方法自动生成
*/
data class Cellphone(val brand: String, val price: Double)
4.5单例类
单例模式,这是最常用、最基础的设计模式之一,它可以用于避免创建重 复的对象。比如我们希望某个类在全局最多只能拥有一个实例,这时就可以使用单例模式。
首先为了禁止外部创建Singleton的实例,我们需要用private关键 字将Singleton的构造函数私有化,然后给外部提供了一个getInstance()静态方法用于获 取Singleton的实例。在getInstance()方法中,我们判断如果当前缓存的Singleton实例 为null,就创建一个新的实例,否则直接返回缓存的实例即可,这就是单例模式的工作机制。
//java 创建单例类
public class Singleton {
private static Singleton instance;
private Singleton() {}
public synchronized static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
public void singletonTest() {
System.out.println("singletonTest is called.");
}
}
//kotlin 单例类创建完毕
object Singleton {
//我们可以直接在这个类中编写需要的函数,比如加入一个singletonTest()函数
fun singletonTest() {
println("singletonTest is called.")
}
}
//调用单例类中的函数
Singleton.singletonTest()
5.Lambda编程
5.1集合的创建与遍历
传统意义上的集合主要就是List和Set,再广泛一点的话,像Map这样的键值对数据结构也可 以包含进来。List、Set和Map在Java中都是接口,List的主要实现类是ArrayList和 LinkedList,Set的主要实现类是HashSet,Map的主要实现类是HashMap。
//java
val list = ArrayList<String>()
list.add("Apple")
list.add("Banana")
list.add("Orange")
list.add("Pear")
list.add("Grape")
//kotlin ,Kotlin专门提供了一个内置的listOf()函数来简化初始化集合的写法
//listOf()函数创建的是一个不可变的集合。
//不可变的集合指的就是该集合只能用于读取,我们无法对集合进行添加、修改或删除操作。
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
//遍历集合
fun main() {
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
for (fruit in list) {
println(fruit)
}
}
//mutableListOf()函数可以要创建一个可变的集合.
fun main() {
val list = mutableListOf("Apple", "Banana", "Orange", "Pear", "Grape")
list.add("Watermelon")
for (fruit in list) {
println(fruit)
}
}
前面我们介绍的都是List集合的用法,实际上Set集合的用法几乎与此一模一样,只是将创建 集合的方式换成了setOf()和mutableSetOf()函数而已。
Set集合中是不可以存放重复元素的,如果存放了多个相同的元素,只会保留其中一 份,这是和List集合最大的不同之处。
Map是一种键值对形式的数据结构,因此在用法上和List、 Set集合有较大的不同。传统的Map用法是先创建一个HashMap的实例,然后将一个个键值对数 据添加到Map中。但其实 在Kotlin中并不建议使用put()和get()方法来对Map进行添加和读取数据操作,而是更加推荐 使用一种类似于数组下标的语法结构。
//JAVA
val map = HashMap<String, Int>()
map.put("Apple", 1)
map.put("Banana", 2)
map.put("Orange", 3)
map.put("Pear", 4)
map.put("Grape", 5)
//kotlin
val map = HashMap<String, Int>()
map["Apple"] = 1
map["Banana"] = 2
map["Orange"] = 3
map["Pear"] = 4
map["Grape"] = 5
Kotlin毫无疑问地提供了一对mapOf()和 mutableMapOf()函数来继续简化Map的用法。在mapOf()函数中,我们可以直接传入初始化 的键值对组合来完成对Map集合的创建
//实to并不是关键字,而是一个infix函数
//infix函数(中缀表达式):Kotlin允许在不使用括号和点号的情况下调用函数,那么这种函数被称为 infix函数。
val map = mapOf("Apple" to 1, "Banana" to 2, "Orange" to 3, "Pear" to 4, "Grape" to 5)
//遍历
fun main() {
val map = mapOf("Apple" to 1, "Banana" to 2, "Orange" to 3, "Pear" to 4, "Grape" to 5)
for ((fruit, number) in map) {
println("fruit is " + fruit + ", number is " + number)
}
}
5.2集合的函数式API
Lambda就是一小段可以作 为参数传递的代码。
为正常情况下,我们向某个函数传 参时只能传入变量,而借助Lambda却允许传入一小段代码。这里两次使用了“一小段代码”这 种描述,那么到底多少代码才算一小段代码呢?Kotlin对此并没有进行限制,但是通常不建议在 Lambda表达式中编写太长的代码,否则可能会影响代码的可读性。
Lambda表达式的语法结构:
{参数名1: 参数类型, 参数名2: 参数类型 -> 函数体}
maxBy 函数:maxBy函数的工作原理是根据我们传入的条件来遍历集合,从而找到该条件下的最大值。
//在一个水果集合里面找到单词最长的那个水果
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
val maxLengthFruit = list.maxBy { it.length }
println("max length fruit is " + maxLengthFruit)
/**
前面使用的函数式API的语法结构看上去好像很特殊,但其实maxBy就是一个普通的函数而已,只不过它接收的是一个Lambda类型的参数,并且会在遍历集合时将每次遍历的值作为参数传递给Lambda表达式。
*/
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
val lambda = { fruit: String -> fruit.length }
val maxLengthFruit = list.maxBy(lambda)
//简化:不需要专门定义一个lambda变量,而是可以直接将lambda表达式传入maxBy函数当中
val maxLengthFruit = list.maxBy({ fruit: String -> fruit.length })
//简化:后Kotlin规定,当Lambda参数是函数的最后一个参数时,可以将Lambda表达式移到函数括号的外面
val maxLengthFruit = list.maxBy() { fruit: String -> fruit.length }
//简化:如果Lambda参数是函数的唯一一个参数的话,还可以将函数的括号省略
val maxLengthFruit = list.maxBy { fruit: String -> fruit.length }
//简化:由于Kotlin拥有出色的类型推导机制,Lambda表达式中的参数列表其实在大多数情况下不必声明参数类型
val maxLengthFruit = list.maxBy { fruit -> fruit.length }
//简化:当Lambda表达式的参数列表中只有一个参数时,也不必声明参数名,而是可以使用it关键字来代替
val maxLengthFruit = list.maxBy { it.length }
map函数:它用于将集合中的每个元素都映射成一个另外的 值,映射的规则在Lambda表达式中指定,最终生成一个新的集合
//比如,这里我们希望让所有的水果名都变成大写模式
fun main() {
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
val newList = list.map { it.toUpperCase() }
for (fruit in newList) {
println(fruit)
}
}
filter函数:filter函数 是用来过滤集合中的数据的,它可以单独使用,也可以配合刚才的map函数一起使用。
fun main() {
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
val newList = list.filter { it.length <= 5 }
.map { it.toUpperCase() }
for (fruit in newList) {
println(fruit)
}
}
any和all函数:
any函数用于判断集 合中是否至少存在一个元素满足指定条件,all函数用于判断集合中是否所有元素都满足指定条 件。
fun main() {
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
val anyResult = list.any { it.length <= 5 }
val allResult = list.all { it.length <= 5 }
println("anyResult is " + anyResult + ", allResult is " + allResult)
}
5.3Java函数式API的使用
在Kotlin代码中调用了一个 Java方法,并且该方法接收一个Java单抽象方法接口参数,就可以使用函数式API。Java单抽象方法接口指的是接口中只有一个待实现方法,如果接口中有多个待实现方法,则无法使用函数式API。
Java函数式API的使用都限定于从Kotlin中调用Java方法
//最常见的单抽象方法接口--Runnable接口
public interface Runnable {
void run();
}
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Thread is running");
}
}).start();
Thread({
println("Thread is running")
}).start()//由于Kotlin完全舍弃了new关键字,因此创建匿名类实例的时候就不能再使用new了,而是改用了object关键字
Thread(object : Runnable {
override fun run() {
println("Thread is running")
}
}).start()
//简化:因为Runnable类中只有一个待实现方法,即使这里没有显式地重写run()方法,Kotlin也能自动明白Runnable后面的Lambda表达式就是要在run()方法中实现的内容。
Thread(Runnable {
println("Thread is running")
}).start()
//简化:,如果一个Java方法的参数列表中有且仅有一个Java单抽象方法接口参数数,我们还可以将接口名进行省略。
Thread({
println("Thread is running")
}).start()
//简化:当Lambda表达式是方法的最后一个参数时,可以将Lambda表达式移到方法括号的外面。同时,如果Lambda表达式还是方法的唯一一个参数,还可以将方法的括号省略
Thread {
println("Thread is running")
}.start()
6.空指针检查
Kotlin利用编译时判空检查的机制几乎杜绝了空指针异常。虽然编译时判空检查的机制有时候会导致代码变得比较难写,但是Kotlin提供了 一系列的辅助工具。
Kotlin默认所有的参数和变量都不可为空
Kotlin提供 了另外一套可为空的类型系统,就是在类名的后面加上一个问号
比如,Int表 示不可为空的整型,而Int?就表示可为空的整型;
//本来所有变量和参数都不可为空,也就没有空指针问题;
//当我们使用可为空的类型系统时,参数就可以为空了,那么参数调用方法就存在空指针问题
fun doStudy(study: Study?) {
if (study != null) {
study.readBooks()
study.doHomework()
}
}
判空辅助工具
?.操作符:当对象不为空时正常调用相 应的方法,当对象为空时则什么都不做。
?:操作符:如果左边表达式的结果不为空就返回左边表达式的结果,否则就返回右边表达式的结果。
//?.操作符
fun doStudy(study: Study?) {
study?.readBooks()
study?.doHomework()
}
//?:操作符
fun getTextLength(text: String?) = text?.length ?: 0
有的时候我们可能从逻辑上已经将空指针异 常处理了,但是Kotlin的编译器并不知道
//例子如下:
var content: String? = "hello"
fun main() {
if (content != null) {
printUpperCase()
}
}
//在main方法中先进行了判空操作再调用printUpperCase方法,逻辑上不存在空指针
fun printUpperCase() {
val upperCase = content.toUpperCase()
println(upperCase)
}
非空断言工具:写法是在对象的后面加上!!
这是一种有风险的写法,意在告诉Kotlin,我非常确信这里的对象不会为空,所以不用你来帮我 做空指针检查了,如果出现问题,你可以直接抛出空指针异常,后果由我自己承担。
fun printUpperCase() {
val upperCase = content!!.toUpperCase()
println(upperCase)
}
辅助工具——let:
let既不是操作符,也不是什么关键 字,而是一个函数。这个函 数提供了函数式API的编程接口,并将原始调用对象作为参数传递到 Lambda表 达式中。
obj.let { obj2 ->
// 编写具体的业务逻辑
}
//?.操作符表示对象为空时什么都不做,对象不为空时就调用let函数,而let函数会将study对象本身作为参数传递到Lambda表达式中,此时的study对象肯定不为空了,我们就能放心地调用它的任意方法了。
fun doStudy(study: Study?) {
study?.let { stu ->
stu.readBooks()
stu.doHomework()
}
}
//简化:?当Lambda表达式的参数列表中只有一个参数时,可以不用声明参数名,直接使用it关键字来代替即可
fun doStudy(study: Study?) {
study?.let {
it.readBooks()
it.doHomework()
}
}
7.字符串内嵌表达式
语法规则
//Kotlin允许我们在字符串里嵌入${}这种语法结构的表达式,并在运行时使用表达式执行的结果替代这一部分内容。
"hello, ${obj.name}. nice to meet you!"
//当表达式中仅有一个变量的时候,还可以将两边的大括号省略
"hello, $name. nice to meet you!"
8.函数参数默认值
之前在学习次构造函数用法的时候我就提到过,次构造函数在Kotlin中很少用,因为Kotlin 提供了给函数设定参数默认值的功能,它在很大程度上能够替代次构造函数的作用。
//给参数设定默认值的方式也很简单,观察如下代码:
//如果对第一个变量设置参数默认值,当我们调用该方法时,传递的参数会因为类型不匹配而报错。因为kotlin认为我们传入的参数为函数第一位参数。
fun printParams(num: Int, str: String = "hello") {
println("num is $num , str is $str")
}
//Kotlin提供了另外一种神奇的机制,就是可以通过键值对的方式来传参,从而不必像传统写法那样按照参数定义的顺序来传参。
fun printParams(num: Int = 100, str: String) {
println("num is $num , str is $str")
}
fun main() {
printParams(str = "world")
}
//主构造函数
class Student(val sno: String = "", val grade: Int = 0, name: String = "", age: Int = 0) :
Person(name, age) {
}