Kotlin学习笔记
文章目录
1.Kotlin特点:
-
有优秀的类型推导机制
-
完全抛弃了Java中的 基本数据类型,全部使用了对象数据类型
-
也是面向对象编程
-
Kotlin第一个版本就使用Lambda表达式(Java是从JDK1.8后才引入),并且拥有一些高阶函数和语法糖
-
提供函数式编程
-
编译时判空,因此Kotlin是空指针安全的
2.变量
val(value的简写)用来声明一个不可变的变量,这种变量在初始赋值之后就再也不能重新赋值
var(variable的简写)用来声明一个可变的变量,这种变量在初始赋值之后仍然可以再被重新赋值
错误写法:
fun main() {
val a: Int = 10
a = a * 10 // 错误写法
println("a = " + a)
}
正确写法:
fun main() {
var a: Int = 10
a = a * 10 // 错误写法
println("a = " + a)
}
3.函数
基本函数形式:
fun method(a: Int, b:Int) :Int {
return -1;
}
fun:声明这是一个方法
method:方法名
a、b:变量
:Int 第一和第二个指前面那个变量的类型 括号外面的指这个函数的返回值类型(如果返回void类型可以直接不写)
函数简略形式:
fun largeNumber(a:Int, b:Int) = max(a,b)
由于Kotlin出色的类型推导机制,这里的max(a,b)返回的是Int类型,因此可以省略返回值类型声明,同时用了=号来删去{}和return
4.if条件语句
看一个简化的例子:
fun largeNumber2(a:Int, b: Int) : Int {
var value = 0
if(a > b){
value = a
} else{
value = b
}
return value
}
初步简化:(if直接作为变量赋值)(默认ifelse最后一行为返回值)
fun largeNumber2(a:Int, b: Int) : Int {
var value = if(a > b){
a
} else{
b
}
return value
}
删除多余变量
fun largeNumber2(a:Int, b: Int) : Int {
return if(a > b){
a
} else{
b
}
}
删除返回值和括号:
fun largeNumber2(a:Int, b: Int) = if(a > b) a else b
得到最简形式
5.when语句
一个变化看懂when的用法
fun getScore(name: String) = if (name == "Tom") {
86
} else if (name == "Jim") {
77
} else if (name == "Jack") {
95
} else if (name == "Lily") {
100
} else {
0
}
使用when:
fun getScore(name: String) = when (name) {
"Tom" -> 86
"Jim" -> 77
"Jack" -> 95
"Lily" -> 100
else -> 0
}
两端代码运行效果是一样的,但是when却比多个ifelse要清爽很多
匹配类型:
fun checkNum(num: Number) {
when(num) {
is Int -> println("number is Int")
is Double -> println("number is Double")
else -> println("number not support")
}
}
更多细节:https://kotlinlang.org/docs/control-flow.html#when-expression
6.循环语句
while(和Java基本一致)
for - i 循环被kotlin舍弃
使用for-in循环(类似python)
val range = 1..10 // 表示闭区间[0,10]
for-in循环使用:
fun forRang() {
for (i in 1..10) {
println(i)
}
}
使用左闭右开区间关键词until,步长用step
fun forStep() {
for(i in 0 until 10 step 2) { // until左闭右开
println(i)
}
}
downTo降序关键词
fun forDownTo() {
for (i in 10 downTo 0 step 2) { // downTo是闭区间
println(i)
}
}
7.编写类
class Person {
var name = "" // 成员变量使用var,因为后面需要对其赋值
var age = 0
fun eat(){
println("$name is eating and he is $age years old")
}
}
调用此类:
val person = Person()
person.name = "Mary"
person.age = 18
person.eat()
结果:
继承:
继承的条件:
1.父类可继承(在Kotlin中任何一个非抽象类默认都是不可以被继承的,相当于Java中给类声明了final 关键字,为了就是让类成为和val一样不可变),使用open关键词
open class Person {
...
}
2.用:继承此类(继承的是父类的构造函数)
class Student : Person() {
var sno = ""
var grade = 0
}
Kotlin的类里面有1个主构造函数和一个或多个构造函数
- 主构造函数:
在类定义主构造函数:
class Student(val sno : String, val grade : Int) : Person() {
}
这样每次初始化Student类就必须要传入sno和grade这两个成员变量
主构造函数不能有函数体,如果我们需要在类初始化的时候执行一些操作,我们需要用到init{}代码块
class Student(val sno : String, val grade : Int) : Person() {
init {
println("sno is $sno")
println("grade is $grade")
}
}
继承父类的构造函数——把构造方法的参数传给父类的构造方法
class PrimaryStudent(val cla: String, sno: String, grade : Int) : Student(sno, grade) {
}
- 次构造函数
次构造函数也可以用于实例化一个类,有函数体,但是Kotlin规定,当一个类既有主构造函数又有次构造函数时,所有的次构造函数都必须调用主构造 函数(包括间接调用)
class Student(val sno : String, val grade : Int, name:String, age:Int) : Person(name, age) {
constructor(name : String, age : Int) : this("", 0, name, age){ // 调用主构造函数
}
constructor() :this ("", 0){ // 调用上面的次构造函数
}
}
8.编写接口
interface Study {
fun doHomework()
fun readBooks()
}
继承接口和继承类差不多,不同的是接口没有构造函数,所以不需要()
val student = Student("Jack", 99)
student.doHomework()
student.readBooks()
运行结果:
改成使用多态的方法:
fun doStudy(study :Study) {
study.doHomework()
study.readBooks()
}
fun main(){
val student = Student("Jack", 99)
doStudy(student) // student实现了study接口,所以Student类的实例是可以传递给
}
结果是一样的
9.修饰符
10.数据类
data class Cellphone(val brand: String, val price: Double)
public class Cellphone {
String brand;
double price;
public Cellphone(String brand, double price) {
this.brand = brand;
this.price = price;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Cellphone) {
Cellphone other = (Cellphone) obj;
return other.brand.equals(brand) && other.price == price;
}
return false;
}
@Override
public int hashCode() {
return brand.hashCode() + (int) price;
}
@Override
public String toString() {
return "Cellphone(brand=" + brand + ", price=" + price + ")";
}
}
上面kotlin的代码和java的代码时等效的
11.单例类
object Singleton {
fun singletonTest(){
println("singletonTest is called")
}
}
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中的单例类方法(和调用Java中的静态方法一样)
Singleton.singletonTest()
12.Kotlin集合操作
12.1 List集合
val list = ArrayList<String>()
list.add("Apple")
list.add("Banana")
list.add("Orange")
list.add("Pear")
list.add("Pear")
list.add("Grape")
列表初始化:
val list2 = listOf<String>("Apple", "Banana", "Orange", "Pear", "Grape")
遍历操作:
for (fruit in list2) {
println(fruit)
}
内置函数**listOf() & mutableListOf() **
listOf() 函数: 创建的是一个不可变集合(即,只能用于读取,无法进行添加、修改、删除操作)
mutableListOf() : 创建的是一个可变集合(可添加删除和修改)
val list3 = mutableListOf("Apple", "Banana", "Orange", "Pear", "Grape")
list3.add("watermelon")
list3.remove("Apple")
for (fruit in list3) {
println(fruit)
}
12.2 Set集合
val set = setOf("Apple", "Banana", "Orange", "Pear", "Grape")
for (fruit in set){
println(fruit)
}
和List集合基本一致,不同的是:Set是不可以放重复元素(会去重)
12.3 Map集合
val map = HashMap<String, Int>()
map.put("Apple", 1)
map.put("Banana", 2)
map.put("Orange", 3)
map.put("Orange", 4)
map.put("Orange", 5)
println(map["Orange"])
map出现重复的键时,以最新的为准
遍历:
for((fruit, map) in map) {
println("fruit: $fruit $map")
}
并且只允许存在最新的那个键值对
推荐使用以下方法来初始化Map元素:
val map = HashMap<String, Int>()
map["Apple"] = 1
map["Banana"] = 2
读取:
val number = map["Apple"]
mapOf()和 mutableMapOf()
val map2 = mapOf("Apple" to 1, "Banana" to 2, "Orange" to 3, "Pear" to 4, "Grape" to 5)
for((fruit, num) in map2) {
println("fruit: $fruit $num")
}
二者区别同listOf() & mutableListOf() ,mapOf()是不可变的,mutableMapOf()是可变的
而mapOf() 的to并不是关键字,而是一个infix函数
12.4 集合的函数式API语法结构
需求:找到list集合中单词长度最长的水果
val list4 = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
var maxLengthFruit = ""
for(fruit in list4) {
if(fruit.length > maxLengthFruit.length) {
maxLengthFruit = fruit
}
}
println("max length fruit is $maxLengthFruit")
使用函数式API
val list4 = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
// 使用函数式API
var maxLengthFruit = list4.maxBy { it.length }
println("max length fruit is $maxLengthFruit")
{参数名1: 参数类型, 参数名2:参数类型 -> 函数体}
val list4 = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
var lambda = {fruit : String -> fruit.length}
var maxLengthFruit = list4.maxBy(lambda)
以上是未简化的lambda表达式
简化1:Kotlin规定,Lambda参数是函数最后一个参数时, 可以把Lambda表达式移到函数括号外面:
var maxLengthFruit = list4.maxBy(){fruit : String -> fruit.length}
简化2:Lambda参数时函数的唯一一个参数的话,可以省略括号:
var maxLengthFruit = list4.maxBy{fruit : String -> fruit.length}
简化3:Kotlin的类型推导机制,可以不用声明参数类型
var maxLengthFruit = list4.maxBy{fruit -> fruit.length}
简化4:当Lambda表达式的参数列表只有一个参数时,不必声明参数名,可以用it关键字代替
var maxLengthFruit = list4.maxBy{it.length}
完毕
同样Java的函数式API(单抽象方法接口为参数)也可以有以上的简化过程
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()
简化1:省略run方法(因为Runnable接口只有一个方法)和参数类型
Thread(Runnable {
println("Thread is running")
}).start()
简化2:省略接口名
Thread({
println("Thread is running")
}).start()
简化3:Lambda方法是最后一个参数,可以提到括号外面,同时如果该参数数唯一参数,省略括号
Thread{
println("Thread is running")
}.start()
同理,控件的监听事件也可以如此法简化,最终结果:
button.setOnClickListener {
TODO("sfsfs")
}
12.5 常用函数
12.5.1 map()
对集合中的元素进行任意的映射转换,常用场景:把集合内的元素大小写转换、取首字母、统一长度等
var newList = list.map{it.uppercase()}
println(newList)
12.5.2 filter()
用于过滤数据,可以单独使用,也可以配合刚才的map函数一起使用
var newList = list4.filter { it.length <= 5 }
.map { it.uppercase() }
for(fruit in newList){
println(fruit)
}
先调用filter再调用map,效率高于二者顺序调换
12.5.3 any和all函数
判断是否所有元素满足lambda表达式条件,any是存在,all是全部
val list4 = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
var anyResult = list4.any{ it.length <= 5 }
var allResult = list4.all{ it.length <= 5 }
println("anyResult is $anyResult allResult is $allResult")
13. 空指针检查
Kotlin默认所有的参数和变量都不可为空 ,但我们可以通过在参数后面加符号控制:
? 在类型后面加表示可以为空,在参数后面加表示如果为空不做处理,否则正常调用
val age : String? = "23"
val age1 =age?.toInt()
!! 为空的话抛出空指针异常
var ages = age!!.toInt()
?: 为空返回默认值
var ages2 = age?.toInt() ?: -1
!! 断言该参数不为空
fun printUpperCase() {
val upperCase = content!!.toUpperCase()
println(upperCase)
}
以此例子为例:
fun doStudy(study: Study?) {
if (study != null){
study.readBooks()
study.doHomework()
}
}
interface Study {
fun doHomework()
fun readBooks()
}
Study 是一个接口,并且类型后面加了?, 表示这个参数可以为空,我们看看如果不加的话,调用doStudy方法如果传入的是空,编译器就会报错:
那我们如果加了?,这个报错就会消失,然后加上一个判空处理,我们就能防止study为空,如果为空,我们什么都不做,如果不为空,正常调用方法🤔等等,这个好像是我们刚刚看到的在参数后面加?的作用是一样的,于是我们改一下:
fun doStudy(study: Study?) {
study?.readBooks()
study?.doHomework()
}
这样就和if判空是等效的了,这就是kotlin空指针检查的好用之处
使用let函数简化:
fun doStudy(study: Study?) {
study?.let {
it.readBooks()
it.doHomework()
}
}
it指study本身
同样可以省略if的另一个常用操作
简化前:
val a = if (c != null) {
c
} else{
b
}
简化后:
val a = c ?: b
14. 语法糖
14.1 字符串内嵌表达式
使用${}符号
println("hello, ${obj.name}. nice to meet you!")
当表达式中仅有一个变量的时候,可以将两边的大括号省略
println("hello, $name. nice to meet you!")
14.2 函数的参数默认值
fun printParams(num: Int, str: String = "hello") {
println("num is $num , str is $str")
}
第二个参数可以不传,因为有默认值
如果是第一个参数有默认值呢?
fun printParams(num: Int = 100, str: String) {
println("num is $num , str is $str")
}
只传第二个参数会报错,怎么解决呢?kotlin有通过键值对传参的机制:
printParams(str="world")
15.标准函数
15.1 with
格式:
with(object){
// ...
}
作用:
调用同一个对象的多个方法/属性时,可以省去对象名重复,直接调用方法名/属性即可
例子:
fun testWith() {
/**
* 简化前
val list = listOf("Apple", "Banana", "Orange")
val builder = StringBuilder()
builder.append("Start eating fruits.\n")
for(fruit in list) {
builder.append(fruit).append("\n")
}
builder.append("Ate all fruits.")
val result = builder.toString()
print(result)
*/
// with简化
val list = listOf("Apple", "Banana", "Orange")
val result = with(StringBuilder()){
append("Start eating fruits.\n")
for(fruit in list) {
append(fruit).append("\n")
}
append("Ate all fruits.")
toString()
// 最后一行/return表达式 为返回值
}
print(result)
}
15.2 let
格式:
object?.let{ // ? 判断object是否为空,不为空则执行let代码块
it.todo()
}
作用:
定义一个变量在一个特定的作用域范围内,避免写一些判断null的操作
例子:
fun doStudy(study: Study?) {
study?.readBooks()
study?.doHomework()
}
使用let函数简化:
fun doStudy(study: Study?) {
study?.let {
it.readBooks()
it.doHomework()
}
}
15.3 also
类似let函数,但区别在于返回值:
- let函数:返回值 = 最后一行 / return的表达式
- also函数:返回值 = 传入的对象的本身
例子:
// let函数
var result = mVar.let {
it.function1()
it.function2()
it.function3()
999
}
// 最终结果 = 返回999给变量result
// also函数
var result = mVar.also {
it.function1()
it.function2()
it.function3()
999
}
// 最终结果 = 返回一个mVar对象给变量result
15.4 run
格式:
object.run{
// ...
}
// 返回值 = 函数块的最后一行 / return表达式
作用:
和with函数类似,不同的是run函数通常是在一个对象的基础上调用,并且只接收一个Lambda参数
例子:
// run函数
val list = listOf("Apple", "Banana", "Orange")
val result = StringBuilder().run{
append("Start eating friuts\n")
for(fruit in list){
append(fruit).append("\n")
}
append("Ate all fruits.")
toString()
}
print(result)
15.5 apply
格式:
object.apply{
// ...
}
// 最终结果 = 返回一个object对象给变量
作用:
与run函数类似,但区别在于返回值:
- run函数返回最后一行的值 / 表达式
- apply函数返回传入的对象的本身
可用于对象实例初始化时需要对象中的属性进行赋值 & 返回该对象
例子:
// apply函数
val list = listOf("Apple", "Banana", "Orange")
val result = StringBuilder().apply{
append("Start eating friuts\n")
for(fruit in list){
append(fruit).append("\n")
}
append("Ate all fruits.")
}
print(result.toString())
15.6 标准函数总结
16.静态方法
16.1定义静态方法
在Java中:
public class Util {
public static void doAction() {
System.out.println("do action");
}
}
调用时(无需创建实例):
Util.doAction()
Kotlin提供了比静态方法更好用的语法特性——单例:
object Util {
fun doAction(){
println("do action")
}
}
调用时:
Util.doAction()
使用单例类的写法会将整个类中的所有方法全部变成类似于静态方法的调用方式,如果我们只是希望让类中的某一个方法变成静态方法的调用方式该怎么办呢?
改为普通类class,使用companion object
class Util {
// 是一定要先创建Util类的实例才能调用的
fun doAction1(){
println("do action1")
}
// 直接使用Util.doAction2()的方式调用
companion object{
fun doAction2(){
println("do action2")
}
}
}
以上仅仅只是类似静态方法的特性,要定义真正的静态方法,Kotlin提供了两种实现方式:注解和顶层方法
(1)注解
@JvmStatic
class Util {
fun doAction1(){
println("do action1")
}
companion object{
@JvmStatic
fun doAction2(){
println("do action2")
}
}
}
注意: @JvmStatic注解只能加在单例类或companion object中的方法上,如果你尝试加在一个普通方法上,会直接提示语法错误。
(2)顶层方法
顶层方法指的是那些没有定义在任何类中的方法,如fun main(){} ;Kotlin编译器会将所有的顶层方法全部编译成静态方法。
做法:
新建file文件,里面写的函数都会编译成静态方法,可以在全局被直接调用
注意:如果是在Java代码中调用,你会发现是找不到doSomething()这个方法的,因为Java中没有顶层方法这个概念,所有的方法必须定义在类中,如果一定要调用,需要在使用 文件名kt.method()
的方式 ( 因为Kotlin编译器会自动创建一个叫作KotlinHighClassFunKt的Java类 )