kotlin学习

155 阅读13分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第2天,点击查看活动详情

这里整理android开发必备的kotlin学习(资料整理于郭神的《第一行代码》第三版)。

为什么用kotlin

Google在2017年的I/O大会上宣布,Kotin正式称谓Android的一级开发语言,AndroidStudio也对Kotlin进行了全面的支持。两年后在2019年的I/O大会上宣布,Kotlin已经成为Android第一开发语言。Google推荐开发者使用Kotlin来编写Android程序,并且未来提供的官方API也会优先考虑Kotlin。

Java作为一种解释性语言:java虚拟机不直接和编写的Java代码打交道,而是和编译之后生成的class文件打交道。如果开发了一门新语言,再做一个编译器,让这门语言的代码编译成同样规格的class文件,那么Java虚拟机也能识别,这就是Kotlin的工作原理。

为什么Kotlin备受欢迎呢?

  • Kotlin语法简洁;
  • Kotlin语法高级,开发效率大大提升;
  • Kotlin语法安全,几乎杜绝了空指针全球崩溃率最高的异常;
  • Kotlin可以直接调用Java编写的代码。

1、变量

  • val来声明不可变的变量(对应java中的final);
  • var来声明可变的变量(对应java中的非final变量);
  • lateinit:延迟赋值,需要显式的声明变量的类型。
private var adapter :Adapter?=null

adapter=Adapter()
adapter?.setNewData(data)

//日常使用的时候将adapter设置为全局变量,但如果代码里有了越来越多的全局变量实例时,就需要写大量的判空处理。
//那么使用延迟初始化关键字:lateinit,这样kotlin可以不在开始的时候对变量赋值为null了。
//注:如果使用lateinit关键字,那么要确保它在被任何地方调用之前已经完成了初始化工作。

//如何判断变量是否已经完成了初始化?
if(!::adapter.isInitialized)(){

}

tips:

  • kotlin里每一行代码是不用加分号的;
  • 永远优先使用val来声明一个变量,当val无法满足需求时再使用var;
  • Kotlin中判断字符串或对象是否相等,可以直接使用==关键字。

2、函数

  • 不使用new创建对象;
  • 如果类需要被集成,需要加上open关键字;
  • 可见修饰符:默认为public;
  • fun是定义函数的关键字。参数的格式是 参数名:参数类型。
fun largeNumber(num1:Int,num2:Int):Int{
 return max(num1,num2)
}

// public 方法则必须明确写出返回类型
public fun sum(a: Int, b: Int)Int = a + b   

//无返回值的函数(类似Java中的void):
fun printSum(a: Int, b: Int)Unit { 
    print(a + b)
} 

// 如果是返回 Unit类型,则可以省略(对于public方法也是这样):
public fun printSum(a: Int, b: Int) { 
    print(a + b)
}

语法糖

//如果一个函数中只有一行代码是,kotlin允许我们不必编写函数体
fun largeNumber(num1:Int,num2:Int):Int = max(num1,num2)

//kotlin出色的类型推导机制:
fun largeNumber(num1:Int,num2:Int) = max(num1,num2)

//内嵌表达式
val brand = "apple"
val price = 139
println(brand = $band , price = $price)

//函数默认值
//我们可以在定义函数的时候给任意参数设定一个默认值,这样调用此函数时就不会强制要求调用方为此参数传值。

标准函数

Kotlin系列之let、with、run、apply、also函数的使用

kotlin标准函数指的是Standard.kt文件中定义的函数,任何kotlin代码都可以自由的调用所有标准函数。

//let函数可以(配合?.)处理全局判空的问题。
fun doStudy(study:Study?){
    study?.let{stu->
        stu.readBooks()
        stu.doHomeWork()
    }
}
fun doStudy(study:Study?){
	study?.readBooks()
    study?.doHomeWork()
}

//lambda语法特性:lambda表达式的参数列表只有一个参数时,可以不用声明参数名,直接使用it关键字来代替
fun doStudy(study:Study?){
    study?.let{
        it.readBooks()
        it.doHomeWork()
    }
}

//主要用途
object.let{
   it.todo()//在函数体内使用it替代object对象去访问其公有的属性和方法
   ...
}

//另一种用途 判断object为null的操作
object?.let{//表示object不为null的条件下,才会去执行let函数体
   it.todo()
}


//with函数接收两个参数,第一个参数是任意类型的对象,第二个参数时lambda表达式,
(适用于调用同一个类的多个方法时,可以省去类名重复,直接调用类的方法)
val list = listOf("Apple","Banana","Orange","Pear")
val result = with(StringBuilder()){
	append("start eating fruite./")
    for(fruit in list){
    	apend(fruit).append("\n")
    }
    append("ate all fruits")
    toString()
}
println(result)



//run(run函数不能直接调用,一定要调用某个对象的run函数才行)
//(适用于let,with函数任何场景,因为run函数是let,with两个函数结合体,准确来说它弥补了let函数在函数体内必须使用it参数替代对象,在run函数中可以像with函数一样可以省略,直接访问实例的公有属性和方法,另一方面它弥补了with函数传入对象判空问题,在run函数中可以像let函数一样做判空处理)
val list = listOf("Apple","Banana","Orange","Pear")
val result = StringBuilder().run{
	append("start eating fruite./")
    for(fruit in list){
    	apend(fruit).append("\n")
    }
    append("ate all fruits")
    toString()
}
println(result)



//apply(无法返回制定值,会自动返回调用对象本身)
//(apply函数和run函数很像,唯一不同点就是它们各自返回的值不一样,run函数是以闭包形式返回最后一行代码的值,而apply函数的返回的是传入对象的本身。)
val intent = Intent(context,SecondAct::class.java)
intent.putExtra("param1","data1")
intent.putExtra("param2","data2")
context.startActivity(intent)

val intent = Intent(context,SecondAct::class.java).applay{
	putExtra("param1","data1")
    putExtra("param2","data2")
}
context.startActivity(intent)

静态函数

public class Util{
	pulic static void doAction(){
    	System.out.println("do something");
    }
}

//kotlin中推荐使用单例类来实现
object Util{
	fun doAction(){
    	println("do something")
    }
}

//使用单例类会将整个类的所有方法变为静态方法,如果只希望让类中的某一个方法变为静态,则使用companion object
class Util{
	fun doAction1(){
    	println("do something")
    }
    
    companion object{//companion object关键字会在Util类内部创建一个伴生类。
    	fun doAction2(){
    	println("do something2")
    	}
    }
}


class Util{
	fun doAction1(){
    	println("do something")
    }
    
    companion object{//companion object关键字会在Util类内部创建一个伴生类。
    	@Jvmstatic //加上这个注解,java才能按照静态方法来调用。这个注解只能加在单例类或者companion object方法上
        fun doAction2(){
    		println("do something2")
    	}
    }
}

扩展函数

//定义:在不修改某个类的源码的情况下,仍可以打开这个类,向该类添加新的函数。
//比如:
object StringUtil{
	fun letterCount(str:String):Int{
    	var count = 0
        for(char in str){
        	if(char.isLetter){
            	count++
            }
        }
        return count
    }
}

//java里一般都这样写,但有了扩展函数,我们就可以使用面向对象的思维来实现这个功能。比如:
//扩展函数的语法结构:只需要在函数名前加上一个ClassName,就表示将函数添加到指定类中了。
fun ClasName.methodName(param1:Int,param2:Int):Int{
	return 0
}

//比如我们希望在String类中添加一个扩展函数,所以需要显创建一个String.kt文件。
//(文件名没有要求,但建议向那个类中添加扩展函数,就定一个同名的kotlin文件,方便以后查找。)
fun String.letterCount():Int{
	var count = 0
    for(char in this){
    	if(char.isLetter()){
        	count++
        }
    }
    return count
}

//定义好扩展函数后,我们只需要这样写
val count = "1324sdafdsADSFAsdfdsA".lettersCount()

infix函数

使用它,相对于调用一个函数,可以更接近于使用英语的语法来编写程序。

比如:A.to(B)可以写成 A to B。

//简单的例子:String类中的startsWith()函数:
if("hello world").startWith("hello"){
    //
}

//借助infix函数,我们可以使用更具有可读性的语法来表达这段代码:新建一个infix文件,编写代码:
infix fun String.beginsWith(prefix:String) = startWith(prefix)

//解释:除去infix关键字,这是一个String类的扩展函数,我们给String类添加了一个beginsWith()
函数,它的内部实现调用的String类的startsWith()函数。
//加上了infix关键字后,一个beginsWith()函数就变成了一个infix函数,我们可以使用特殊的语法糖格式调用beginsWith()函数
if("hello world" beginsWith "hello"){
    //todo
}

//infix函数允许我们将函数调用时的小数点,括号等计算机相关的语法去掉,而使用一种更接近英语的语法来编写程序,使代码看起来更加具有可读性

//更多例子
infix fun <T> Collection<T>.has(element:T)=contains(element)

val list = listOf("apple","Banana","Orange")
if(list has "Banana"){
    //todo
}

内联函数(泛型相关)

使用内联函数将泛型实例化。使用inline关键字来修饰函数,在声明泛型的地方加上reified关键字来表示泛型要实例化。

inline fun <reified T> getGEnericTyoe{}

//常见的情况
val intent = Intent(context,MainActivity::class.java)
context.startActivity(intent)

//这样写代码 MainActivity::class.java  很难受,可以使用内联函数优化一下
inline fun <reified T> startActivity(context:Context){
    val intent = Intent(context,T::class.java)
    context.startActivity(intent)
}

//然后这样写代码
startActivity<MainActivity>(context)

//如果需要传参,那么可以这样写

```kotlin
inline fun <reified T> startActivity(context:Contextblock:Intent.()->Unit){
    val intent = Intent(context,T::class.java)
    intent.block()
    context.startActivity(intent)
}
startActivity<MainActivity>(context){
    putExtra("param1","data")
}

3、逻辑控制

//if语句
private fun getMax1(a: Int, b: Int): Int {
        return if (a > b) {
            a
        } else {
            b
        }
    }

private fun getMax2(a: Int, b: Int) = if (a > b) a else b
//when语句
private fun getScore(name: String) = if (name == "imfondof") {
    100
} else if (name == "fondof") {
    90
} else {
    0
}

private fun getScore(name: String) = when (name) {
    "imfondof" -> {
        100
    }
    "fondof" -> {
        90
    }
    else -> {
        0
    }
}

private fun getScore(name: String) = when (name) {
    "imfondof" -> 100
    "fondof" -> 90
    else -> 0
}

//is 类型匹配
private fun checkNumber(num: Number){
    is Int -> printLn("number is Int")
    is Double -> printLn("number is Double")
    else -> printLn("number not support")
}

//可以不在when传入参数(不常用,但是很强)
private fun getScore2(name: String) = when {
    name.startsWith("imfondof") -> 100
    name == "fondof" -> 90
    else -> 0
}
//循环
fun test() {
    for (i in 1..10) {
        print(i)
    }
}

fun test() {
    for (i in 0 until 10 step 2) {
        print(i)
    }
}

fun test() {
    for (i in 10 downTo 1) {
        print(i)
    }
}
// 10 9 8 7 6 5 4 3 2 1 

可变长参数函数:函数的变长参数可以用 vararg 关键字进行标识:

fun vars(vararg v:Int){
    for(vt in v){
        print(vt)
    }
}

// 测试
fun main(args: Array<String>) {
    vars(1,2,3,4,5)  // 输出12345
}

4、面向对象

类与构造函数

Kotlin中任何一个非抽象类默认都是不可以被继承的,相当于Java中给类声明了final关键字。(如果需要设置为可以继承,在类前添加关键字open就可以了。不是使用extrends,只使用冒号就可以了。)

open class Person{}

class Student : Person(){}

//Person类后边为什么加上括号?这就涉及到Kotlin的主构造函数和次构造函数。
//子类的构造函数必须调用父类中的构造函数。
open class Personval name : String,val age :Int){}

class Student(val sno : String,val grade :Int,val name : String,val age :Int):
	Person(name,age){}

//当一个类既有主构造函数又有次构造函数,所有的次构造函数都必须调用主构造函数。

class Student(val sno : String,val grade :Int,val name : String,val age :Int):Person(name,age){
    constructor(name:String,age:Int):this("",0,name,age){    }
    constructor():this("",0){    }
}

数据类

传统的java数据类,需要重写构造方法、get、set方法。但Kotlin只需要data一个关键字就够了。

//Kotlin会根据主构造函数中的参数自动生成equals、hashCode、toString方法。
data class CellPhone(val brand :String,val price :Double)

单例类

使用Kotlin创建一个单例类很简单,只需要将class关键字改为object关键字即可。

object Singleton{
	fun singletonTest(){
    	println("singletonTest is called")
    }
}

//调用方法:
Singleton.singletonTest()

密封类

Kotlin中引入了新可见性概念,只对同一模块中的类可见,使用的是internal修饰符。

如果我们开发了一个模块给别人用,但是有一些函数只允许在模块内部使用,不想暴露给外部,就可以讲这些函数声明城internal。

//密封类通常结合Recyclerview适配器中的ViewHolder一起使用。
//案例:
interface Result
class Success(val msg:String):Result
class Failure(val error:Exception):Result

fun getResultMsg(result:Result) =when(result){
	is Success -> result.msg
    is Failure -> result.error.message
    else -> throw IllegalArgumentException()
}

//这个方法里不得不写一个else条件,否则kotlin会认为这里缺少条件分支。
//但如果使用密封类就不不同了:(sealed class,使用也非常简单)

sealed class Result
class Success(val msg:String):Result()
class Failure(val error:Exception):Result()

fun getResultMsg(result:Result) =when(result){
	is Success -> result.msg
    is Failure -> result.error.message
}

//将interface关键字改为sealed class,且因为它是一个类,每次继承的时候在result后边加上括号。

接口

允许对接口进行默认实现。

interface Study{
    fun readBooks()
    
    fun doHomeWork(){
    	println("do homework default implementation")
    }
}

5、空指针检查

Kotlin的空安全设计对于声明可为空的参数,在使用时要进行空判断处理,有两种处理方式,字段后加!!像Java一样抛出空异常,另一种字段后加?可不做处理返回值为 null或配合?:做空判断处理

//类型后面加?表示可为空
var age: String? = "23" 
//抛出空指针异常
val ages = age!!.toInt()
//不做处理返回 null
val ages1 = age?.toInt()
//age为空返回-1
val ages2 = age?.toInt() ?: -1
//当一个引用可能为 null 值时, 对应的类型声明必须明确地标记为可为 null。
当 str 中的字符串内容不是一个整数时, 返回 null:
fun parseInt(str: String): Int? {
  // ...
}
//以下实例演示如何使用一个返回值可为 null 的函数:
fun main(args: Array<String>) {
  if (args.size < 2) {
    print("Two integers expected")
    return
  }
  val x = parseInt(args[0])
  val y = parseInt(args[1])
  // 直接使用 `x * y` 会导致错误, 因为它们可能为 null.
  if (x != null && y != null) {
    // 在进行过 null 值检查之后, x 和 y 的类型会被自动转换为非 null 变量
    print(x * y)
  }
}


//let是一个函数,提供了函数式API的编程接口。,并将原始调用对象作为参数传递到Lambda表达式中。
obj.let{obj2 ->
	//todo
}

fun doStudy(study:Study?){
	study?.readBooks()
    study?.doHomework()
}
//--->
fun doStudy(study:Study?){
    study?.let{stu ->
        stu.readBooks()
    	stu.doHomework()
    }
}
//--->(当Lambda表达式的参数列表中只有一个参数时,可以不用声明参数名,使用it关键字来代替)
fun doStudy(study:Study?){
    study?.let{
        it.readBooks()
    	it.doHomework()
    }
}

6、Lambda编程(函数式API)

Lambda定义:一小段可以作为参数传递的代码

//最外层是一对大括号,如果有参数传入Lambda表达式的话,需要声明参数列表,参数列表结尾使用一个 -> 符号,表示参数列表的结束以及函数体的开始,函数体中可以编写任意行代码,最后一行代码会自动作为Lambda表达式的返回值。
{参数1:参数类型,参数2:参数类型 -> 函数体}
    
    
//常用的函数式API:map函数、filter函数
list.map(it.toUppercaCase)
list.filter(it.length<=5)


//常用的函数式API:any和all函数
list.any(it.length<5) //list里是否存在5个字母以内的单词
list.all(it.length<5) //list里是否所有的单词都在5个字母以内


java函数式API的使用
如果在kotlin代码中调用了一个java代码,且该方法接受一个java但抽象接口参数,就可以使用函数式API
(比如常见的Runnable接口)
new Thread(new Runnable{
	@Override
    Public void run(){
    	...
    }
}).start();

Thread{
	...
}.start()

Button.setOnClickListener(new View.OnClickListener(){
	@Override
    Public void Onclick(){
    	...
    }
})

button.setOnClickListener{

}

其他

顶层方法

顶层方法指的是没有定义在任何类中的方法,kotlin编译器会将所有的顶层方法全部编译成静态方法。

//创建kotlin文件:New--Kotlin File Class,创建类型选择File。
//如果我们的kotlin文件为:Helper.kt,那么Kotlin会为我们创建一个HelperKt的java类,在java里使用HelperKt.doSomething()。

fun doSomething(){
    println("do something")
}