kotlin基础知识点

前言:

想象一下,你走进一家餐馆,菜单上有你熟悉的老派Java汉堡,还有新推出的Kotlin牛排。Kotlin不仅看起来更美味,还附带了免费的甜点——更简洁的语法和更少的错误。学习Kotlin的必要性,就像是选择更健康、更美味的餐点一样,让你在编程的世界里更加游刃有余。而且,你不需要担心适应问题,因为Kotlin和Java一样,都是熟悉的味道。为什么不来一份Kotlin大餐,让你的编程生活更滋味呢?

学习Kotlin,是每一位追求技术进步的开发者不可忽视的一步。Kotlin以其简洁优雅的语法和强大的功能,正在迅速成为现代编程语言的翘楚。作为一种与Java完全兼容的语言,Kotlin不仅能够提高开发效率,还能大幅减少代码中的错误,提升代码的可读性和可维护性。这些特性使得Kotlin不仅适用于新项目的开发,更是优化和升级现有Java项目的理想选择。

在当今竞争激烈的技术领域,掌握Kotlin无疑会让你在职业发展中占据一席之地。Kotlin已经成为Android开发的首选语言,同时也在后端开发、数据分析和多平台开发中展现出强大的生命力。通过学习Kotlin,你不仅能紧跟技术发展的潮流,还能为自己打开更多职业发展的机会之门。

现在,正是你抓住机遇、迎接挑战的时刻。每一次新的学习,都是一次自我提升的机会。让我们一起投入到Kotlin的世界中,探索更多的可能性,拓宽我们的技术视野。相信在不久的将来,你会因为今天的选择而感到自豪。加油,未来因你而更加精彩!

话不多说,直接进入学习。                                        
                              

                                  数组的创建及使用数组的建立:

val  a = intArrayof(1,2,3,4).或 val a = IntArray(5){it+1}.(第二种相当于是new了一个对象创建数组)

建立的数组是不可以修改的。

想要访问数组的内容可以通过遍历的方式、直接索引、或者把整体访问。

  1. 遍历

val a = intArratyof(1,2,3,4,5)

        a.forEach{element->

        println(element)

}

for(element in a){

        println(element)

}

  1. 直接索引

val a = intArratyof(1,2,3,4,5)

a[0]   取出来的结果是1

3.整体访问

val a = intArratyof(1,2,3,4,5)

println(a.contentToString)

Kotlin数据类型和字符比较

在申明变量的时候,可以用val和var

val是申明一个不能被改变的变量,像java中给加上final

var是申明一个变量。

例如: var a=2

也可以补变量类型。var:int = 2     var:String = “hello world ”

申明Long类型的变量的时候,必须用大写的L,避免歧义。

var  a =123456L

对于无符号类型,只用在类型的前面加上U,val a:Uint = 10u

字符串比较:

 字符串的比较可以用 = = 和= = =。

a = = b相当于java中的equals,比较是 a和b两个的内容。

a= = = b 比较是否为同一个对象。

raw String:用于书写很长的字符。

val n里面写的就是 raw String。

  trimIndent()方法可以把前面共同的空格去掉。

Kotlin函数和方法的定义

定义一个函数:

fun main(a:Array<String>):Unit{

        println(a.contentToString())

}

fun 表示这是一个函数   main是指函数名,a是参数的形参

Arrat<String>是参数的类型,最后Unit是函数的返回值。Unit是指返回值为空。

定义一个方法:

方法可以看成一个特殊的函数,有receiver(也就是在类里面)的函数称为方法

class Text(){

fun main(a:int):Int{

        return a

}

}

函数的引用:

fun a(){}

 ::a

也可以去接收这些函数的引用

val c:() ->Unit = ::a

方法的引用:
class Text{

         fun a(e:String):Unit{}

}

 Text:a

如果要接收这个方法

val c:(Text,String) -> Unit=Text:a

val c:Text.(String) -> Unit=Text:a

val c:Function2(Text,String,Unit) =Text:a

在函数内调用类中的方法:

class Text{

         fun a(e:String):Unit{}

}

fun t1(u:(Text,String)->Unit){

        u(Text(),”1”)

u.invoke(Text(),”1”)

}

定义一个变长参数

 fun a(vararg args:String){

        println(args.contentToString())

}

定义多返回值:

fun a():Triple<Int,Long,Double>{

        ReturnTriple(1,3L,4.0)

}

Triple是三个返回值

Pair是两个返回值

 具名参数:

fun a(x:Int=1,y:String=””)Pair<Int,String>{

        return Pair(x,y)

}

再调用的时候可以只传入一个,就会有默认值

        a(y=”1”)

此时的x就会是默认值的1

kotlin中的区间

区间的创建:

val a = 1..10 从1取到10

val a = ’a’..’z’从a取到z

val a = 1L..10L 从1L取到10L

取值1-10的整型,属于离散型。1L-10L 属于连续型。

val a = 1 until 10// [1,10)  从1取到9

如果想要倒序的区间

val a = 10 downTo 1//[10…,1]

想要有间隔的区间

val a = 1..10 step 2  取值是1、3、5、7、9

想要把整型区间打印出来,可以用joinToString()方法

val a = 1..10

println(a.joinToString())

想要打出离散型,只能打出一段描述

val a=1f..2f

println(a.toString())// 结果是1.0 .. 2.0

遍历区间:

1.val a = intArrayOf(1,3,5,7)

for(i in o until a.size){

        println(a[i])

}

for(i in a.indices){

        println(i)

}

Kotlin集合

分为可变List(MutableList)、不可变List。可变Map(MutableMap)、不可变Map

可变Set、不可变Set(MutableSet)

创建一个集合

val a:List<Int> = listof(1,2,3)

Val b:MutableList<Int> = mutableListOf(1,2,3)

遍历集合:

a.forEach{element->

  println(element)

}

for(element in a){

        println(element)

}

Map键值对的创建

val a:Map<String,Any> = mapof(“age” to “18”)

val a:Map<String,Any> = mutableMapOf(“age” to “18”)

遍历map:

for(element in a.entries){

        println(“${element.key}, ${element.value}”)

}

  1. forEach{

   Println(it)

}

修改集合  可以用:

val a:MutableList<String> = mutableListOf(“s”)

  1. add(“t”)  也同等于 a +=  “t”
  1. remove(“s”)

Pair类

两种创建方式:

val a= ”age” to “18”

val a=Pair(“age”,”18”)

获取对应的元素:

val first = pair.first

val second= pair.second

val (x,y) = a

Triple类

两种创建方式:

val a=Triple(“age”,”18”,”20”)

获取对应的元素:

val first = triple.first

val second= triple.second

val third= triple.third

val (x,y,z) = a

类和接口

在kotlin中,类默认是Public,定义成员的时候,必须给赋值。

类的构造方法有三种写法:常用的是最后一种,主构造器,是定义在类定义那一行,在类实例化的时候,一定会被执行的主构造器。

class  Text{

        val a:Int = 0

        constructor(x:Int){

                this.x = x

        }

}

class Text constructor(x:Int){

        val a:Int = x

}

class Text(val a:Int){

}

类的实例化:

val a = Text(5)

如果类里面有个属性x,可以用a.x拿个这个值。一定要和a::x区别开,a::x只是拿个引用。

类中方法的调用和引用也是同理。假设Text类中有一个方法 fun fuction(){}

val a=Text(5) 这里也是先取到Text的实例化对象。

  1. fuction()  这个是调用     a::fuction

接口的定义:

interface IText{

  fun a(){

}

}

接口的实现:

class a():Text{

        override fun a(){

        }

}

抽象类的定义:  在其中  抽象方法没有方法体,普通方法如果不加open 继承的时候就没有办法复写,普通方法可以有方法体。

abstract class AText{
abstract fun a  ()

         Open fun b (){}

}

抽象类的继承:继承的类在冒号:后面跟()

class T:AText(){

        override fun a(){

        }

        override fun b(){

        }

}

如果继承的方法在前面再加上一个final,那么就不能继承再次继承这个方法。

例如:

abstract class A(){

        open fun a(){}

}

Open class b():A(){          //这个类如果不加open是不能被下面的继承。

        final override fun a(){}

}

class c():b(){

   //  在C这个类中就不能继承B中的方法。因为B中的方法是final。

}

在kotlin中定义一个property 是自动包含set和get:

class a(){

        val a1:Int=0

        set(value){

                field = value   //这是将set的值value给到之前的值field,实现覆盖。

        }

        get(){

                return field     //这里的field指的是值

        }

}

属性引用

Class A(){

        val a1:Int=0

}

        val c = A::a1

val a = A()    //  这一行相当于new了一个A类出来。

c.set(a,5)    //这里c并没有绑定receiver ,所以在传递参数的时候需要绑定。

a.set(5)     //a已经绑定好了receiver,就不需要再重复进行绑定。

扩展方法和扩展属性

  在方法的前面加上类,就是扩展方法:

class Utils(){

        var c:Boolean = true

}

fun Utils.isPass(passWord:String=””):Boolean{

        val isRight = passWord ==””

        return isRight

}

在属性的定义前面加上类就是扩展:属性的扩展没有field只有get和set。

field是一种状态。

var Utils.check:Boolean

        get(){

                return this.c

        }

        set(value){

                this.c = value

        }

在接口里面定义属性,只有属性的get 和 set,field是一种状态,但是可以定义行为。

在接口中定义方法的时候,也只能是行为而不能是状态,传递数据。

Interface  Text{

        var simple:String

        fun t(){

                println(“”)

        }

}

扩展方法的引用:

①没有receive的

class Utils(){

        var c:Boolean = true

}

fun Utils.isPass(passWord:String=””):Boolean{

val isRight = passWord ==””

  return isRight

}

Utils::ispass      (Utils,Boolean ) ->Boolean

②绑定了receive

true::ispass       (Boolean)   -> Boolean

空类型安全

在kotlin中,定义变量默认是不可以为空的。

var i: String =”  ”

i = null   //会报错,不能为空。

如果想要定义了有可能为空,可以用强制类型转换。

var i: String? =”  ”

i = null   

println(i!!.length)  //用了强制转换,让i.length不为空,这只是保证不为空,但还是有可能

                 空,不安全。

强制转换还是很不安全,不建议使用,可以换一种温柔的方法使用。

var i: String? =”  ”

var length:Int = i?.length ?: 0 //前面的i?  表示有可能为空。?: 叫做elvis表达式,会在为空的时候返回 0

println(length)

跨平台编译的时候,平台类型不一样,不可以转换,一定要防止为空。

平台客观存在,不能主观定义。

使用Retrofit

①先定义一个接口,根据使用类型命名,比如获取GitHub的内容。

interface GitHuber {

    @GET("/repos/{owner}/{repo}")

    fun getRepository(

        @Path("owner") owner:String,

        @Path("repo") repo: String

    ):retrofit2.Call<Repository>

}

其中@GET是一个注解,repos指定仓库的固定部分。owner和repo 是路径占位符,可以在传入的时候灵活改变。

retrofit2.Call<Repository> 是异步请求的结果,Repository指向的是一个json转Kotlin的类型(可以使用快捷键alt+k的方式,快速打开json转换)

②配置Retrofit的实例

Retrofit.Builder() 进行实例构造。返回的类型结果是Retrofit.Builder

用连点的形式,.baseUrl()  里面传入基本的URL,所有设置的API都会基于这个URL

.addConverterFactory,添加一个转换工厂,这里使用Gson将JSON转换成Kotlin对象。

.build() 构建Retrofit实例。

生成的retrofit属性,类型属于Retrofit.builder

val gitHuberApi = retrofit.create(GitHuber::class.java)  利用反射的形式生成上面接口的服务。

val respose = gitHuberApi.getRepository("JetBrains","Kotlin").execute()

getRepository传入的两个参数,第一个指向owner,第二个指向repo。

.execute()是一个同步方法,只有等一个执行完后,才能执行下一个。

respose.body()是拿到所有的信息。

然后先判断是否为空,为空就打印报错代码和信息。

否则里面有智能空类型转换,就能直接输出想要的信息了。

变量和常量

在kotlin中定义只读变量:

 val a:Int = 0   //  因为可以用get去设置return的返回值,所以是只读变量。

定义常量前面加上关键字const就可以了

const val a:Int = 0    //加上const后,只能定义全局范围,只能修饰基本类型,必须初始化。

常量引用:

val person = Person(18,”xiaoming”)  //person 是一个常量引用

person.age = 19   //对象改变但引用没变

编译器常量:

const val a:Int = 0    //编译的时候确定的值

运行时常量:

 val a:Int = 0       // 运行的时候才能确定的值

分支表达式

  • if else

if else可以作为三元表达式:

  c=if(a==3) 4 else 5    //先判断a是否等于3  等于3,把4赋值给c,否则把5赋值给c。

if else  基础写法:

If(a==3){

        c=4

}else{

        c=5

}

②when表达式,也就是java中的switch

when (a){

        0 -> c = 5   //当a等于0的时候,c赋值为5

        1 -> c = 7   //当a等于1的时候,c赋值为7

        else -> c = 20  //其他情况,c赋值为20

}

还可以把c提出来写:

c=when (a){

        0 -> 5   //当a等于0的时候,c赋值为5

        1 -> 7   //当a等于1的时候,c赋值为7

        else -> 20  //其他情况,c赋值为20

}

还可以把() 括号中的变量去掉。放在里面:

when {

        a = 0 -> c = 5   //当a等于0的时候,c赋值为5

        a = 1 -> c = 7   //当a等于1的时候,c赋值为7

        else -> c = 20  //其他情况,c赋值为20

}

when的()里面还可以做一些赋值:

var e = when (val a = readLine()) {

        null -> 0

        else -> a.length

}

try...catch 分支

fun pp(){

        val a:Int = 0

        val b:Int = 5

        var c:Int

        if(5==2){

        c = 9

        }else{

                c = 15

        }

try {

        c = a/b

        }catch (e:Exception){

                e.printStackTrace()

                c = 0

        }

}

还可以把里面的c提出来。

c=try {

        a/b

        }catch (e:Exception){

                e.printStackTrace()

                0

}

运算符的重载和中缀表达式

运算符的重载:

在使用类进行运算的时候,可以通过重载运算符完成运算。

fun main(){

    val c = Compare(3,5)

    println((c - 5))    //打印出来的结果是     real:-2    image:5

}

class Compare(var real:Int,var image:Int ){

    override fun toString() = "real:${real}    image:${image}"   // 如果不复写toString方法的话,返回的是对象类名+哈希码

}

operator fun Compare.minus(value: Int): Compare {

    return Compare(this.real-value,this.image)

}

中缀表达式:

先用关键字infix 申明

infix fun <A,B> A.to(x:B):Int(){

        Return ....

}

先用关键字infix申明,A和B代表两个类型,中间的“to”可以换成你想要的任何字符。

x表示变量名,:Int表示返回值的类型。

按照上面的写出来的中缀表达式就是:

A to B

A.to(B)

Kotlin中lambda表达式

匿名函数的写法:

val text :(Int) ->Unit = fun(p:Int){      //  把一个匿名函数赋值给了一个变量text,调用时需要传入的                                                           参数为    Int,返回值是Unit

        println(p.toString)            

}

调用匿名函数,可以用text(5)  或  text.invoke(5)    //都是一样的效果

Lambda表达式:

val text: (Int) - > Int= {p:Int - >

        println(“”)       //:(Int)表示传入参数的类型是Int。- >Int,表示返回值是Int。

        123            //返回值的类型跟代码的最后一行有关。

}

可以进行类型推导,省略参数类型代码:

val text= {p:Int - >

        println(“”)       

        123           

}

或者是

val text: Function1<Int,Unit>= {p - >

        println(“”)       

          

}

如果lambda只有一个参数,可以省略不写。

val text: Function1<Int,Unit>= {     //这里只有一个参数,不写的情况,默认就是it。

        println(it)       

           

}

HashSet中hashCode和equals方法

在HashSet中本质上用的HashMap,不过在HashMap中每一个元素是 key-value,HashSet是使用Key作为传入的元素,value是默认值都是一样的。

无论是HashSet还是HashMap中,都会先比较hashCode,hashCode比较的是哈希值,如果两个不一样,直接添加进去。如果一样的话,再调用equals方法,判断是否一样。

如果想要将相同值的对象添加进HashSet中,需要重写hashCode方法和equals方法。

fun main(){

        val teach = HashSet<Teach>()

        (0..5).forEach{

                teach.add(Teach("math",5))

        }

        println(teach.size)

}

class Teach (var t:String, var u:Int){

        override fun hashCode(): Int {

                var resultU = u

                return 31*resultU+t.hashCode()

        }

        override fun equals(other: Any?): Boolean {

                val compare = (other as? Teach)?:return false

                return this.t == other.t && this.u == other.u

                return true

        }

}

在kotlin中 == 是判断内容是否相等。  ===是判断对象是否相同。

重载String运算

在重载实现的过程中,写到了minus、times、div

在实现对String中的删除字符用replace方法:

operator fun String.minus(right:Any?):String{

    return this.replaceFirst(right.toString(),"")

}

Replace(oldValue: String, newValue: String, ignoreCase: Boolean = false)方法三个参数,第一个是要被替换的内容,第二个是替换成什么内容,第三个是是否忽视大小写,默认是不忽视。

在实现对String中times(也就是*)的功能时,用到joinToString方法:

operator fun String.times(right: Int):String{

    return (1..right).joinToString(separator = "", transform = {this})

}

joinToString()

separator: 分隔符,默认为 ", "。

prefix: 前缀,默认为空字符串。

postfix: 后缀,默认为空字符串。

limit: 限制要连接的元素数量,超过数量的元素会用 truncated 替换。

truncated: 当元素数量超过 limit 时,用于替换多余元素的字符串,默认为 "..."。

transform: 一个可选的 lambda 表达式,用于转换每个元素。

在实现对String中div 的widowed方法:

operator fun String.div(right: Any?):Int{

    val tRight = right.toString()

    return this.windowed(tRight.length,1){it == right

    }.count(){it}      //标黄部分返回的是一个List列表,通过数正确的数,返回this中包

}                    含多少个right。

Windowed()方法有以下参数:

size: 每个窗口的大小。

step: 每次滑动的步幅(默认为 1)。

partialWindows: 是否包含不完整的窗口(即不足 size 大小的窗口)(默认为 false)。

transform: 转换函数,可以将每个窗口转换成不同的类型(可选)。

高阶函数

高阶函数的定义:在函数的参数或者在返回类型为函数的时候,称为高阶函数。

①在函数的参数部分传入函数:

fun costTime(cost:()-> Unit){

        val sTime = System.currentTimeMillis()   //  记录当前的时间

        cost() //运行参数中的函数的代码

        println (System.currentTimeMillis()-sTime) //最后返回运行代码花费多少时间

}

在costTime函数的参数,传入了一个函数,里面函数的参数为空,返回值为Unit。

调用costTime高阶函数:

costTime(){

        //这里面写cost做的事情

}

在调用的时候,这里实际上是一个lambda表达式,把括号里面的函数写成大括号,如果里面只有一个参数,可以把小括号省略掉。

比如:

costTime{

        //这里面写cost做的事情

}

②在函数的返回类型传入函数:

fun fibonacci():()->Long{

        var first = 0L

        var second = 1L

        return {                 //这里的返回是一个匿名函数,也可以看出闭包。

                val next = first+second //闭包就可以获取函数变量外部的值

                val current = first

                first = second

                second = next

                current

        }

}

上面的fibonacci也是一个高阶函数,可以获取斐波那契函数。

调用fibonacci函数:

val  fib  = fibonacci()      //获取fibonacci函数的实例,fib后面隐藏了

 //如果把上面的fib写全,应该是 val fib:()->Long = fibonacci()

内联函数

内联函数的作用:

把函数的调用,转换成实际函数体的调用。可以减少函数调用开销,提高性能。

通常配合高阶函数一起使用,同时还能减少高阶函数中lambda表达式的对象创建和内存分配。

①定义内联函数:

给函数定义的最前面加上 inline:

inline fun cTime(cost:()->Unit):Long{

        val cS = System.currentTimeMillis()

        cost()

        return System.currentTimeMillis()-cS

}

②调用内联函数:

  cTime{

        println(“调用内联函数”)

}

调用的结果相当于:

val cS = System.currentTimeMillis()

println(“调用内联函数”)

return System.currentTimeMillis()-cS

③在使用forEach中如果想要对某次的值进行跳出:

val i = intArrayOf(1,2,3,4)

i.forEach{element->    //默认是it,可以通过这样的方式改变为element

if(element == 3 ) return @forEach    //在element为3的时候,跳过这次循环。element等于4还是会

                                 正常执行

}

④non-local return    非本地退出 :

inline fun cTime(cost:()->Unit):Long{

        val cS = System.currentTimeMillis()

        cost()

        return System.currentTimeMillis()-cS

}

fun main(){

        cTime{

        return                 //这里因为ruturn跳出了main函数的循环了

        }

        println("这一行不能被打印,因为前面return了")

}

⑤如果有可能存在不合法的non-local return,加关键字crossinline:

inline fun Runnable(crossinline block:()- Unit):Runnable{

}

⑥没有被赋值的属性,可以内联。

class Bi(){

        var pocket = 0

        var money: Int

        inline get() {

                return 0

        }

        inline set(value) {

                pocket = value

        }

}

⑦内联函数的限制:

内联函数只能访问public 成员:

内联函数参数不能被存储:

内联函数的内联参数只能传递给其他内联函数参数:

常见的高阶函数

五个常见的高阶函数,let、run、also、apply、use。

①let函数,默认it作为参数传入,返回lambda表达式的return:

class Bi(){

        var pocket = 0

}

fun main(){

        bi.let {

                println(it.pocket)

        }

}

②run函数,指定了receiver,返回lambda表达式的return:

class Bi(){

        var pocket = 0

}

fun main(){

        bi.run{

                println(run.pocket)

        }

}

③also函数,默认指定it,返回Unit:

class Bi(){

        var pocket = 0

}

fun main(){

        bi.also {

                it.pocket = 5

                println("also打印的结果是${it.pocket}")

        }

}

④apply函数,指定了receiver,返回Unit:

class Bi(){

        var pocket = 0

}

fun main(){

        bi.apply {

                this.pocket = 7

                println("apply打印的结果是${this.pocket}")

        }

}

⑤use函数,默认是it,返回的是lambda表达式的返回值

use函数可以自动关闭资源:

fun main() {

        val file = File("example.txt")

        // 使用 use 读取文件内容

        file.bufferedReader().use { reader ->    //默认是it,这里进行了更改为reader

        val content = reader.readText()

        println(content)

        }

}

集合的变化和序列

集合的映射操作:filter、map、flatMap

①filter的使用有两种,一种是饿汉式,另外一种懒汉列

饿汉式:

val lo = listOf<Int>(1,2,3)

lo.filter {

        println("饿汉式会立即执行${it}")     //立即执行,打印结果

        it%2 ==0

}

懒汉式:

lo.asSequence().filter {

        println("懒汉式不会立即执行${it}")   //不会立即执行

        it%2 ==0

}

懒汉式不会立即执行,需要显式调用获取结果。

②map,会把元素映射成元素。

在map的使用过程中,也会分为饿汉式和懒汉式。

饿汉式:

lo.map {

        println("饿汉式执行map的结果为${it}")  //饿汉式,输出的是元素

}

懒汉式:

lo.asSequence().map(){

        println("懒汉式执行map的结果为${it}")  //不会立即执行

}.toList()

不会立即执行打印,会在显示调用,也就是toList()后,再执行里面的打印。

③flatMap,会把元素映射成集合,并且返回调用的类型。

饿汉式:

val lo = listOf<Int>(1,2,3)

val fm = lo.flatMap {

        0 until it

}

println(fm)   //立即调用,执行的结果是[0,0,1,0,1,2 ]

懒汉式:

val fma = lo.asSequence().flatMap {

        println(it)

        (0 until it).asSequence()

}.joinToString()    //通过joinToString显式调用

println(fma.toString())

④fold函数:

val numbers = listOf(1, 2, 3, 4, 5)

    val sum = numbers.fold(0) { acc, number ->      //初始化值 0.   0第一次会给到acc

        acc + number                      //acc 和number可以变化,number会逐个取值。

0+1=1 算完一次后,会把结果给acc,再进行下次计算

}

println(sum) // 输出: 15

SAM转换和匿名内部类

  1. 匿名内部类:

interface  A{

    fun a(){}

}

val ascasv = object : A{

                    override fun a() {

                     println("")

                    }

           }

  1. kotlin中的SAM:

与java中的SAM不一样,kotlin只是支持SAM的调用,返回的是一个函数。

Java中的SAM返回的是一个确定的对象。

所以在类似于监听的时候,需要指的类型:

也可以用简写: