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了一个对象创建数组)
建立的数组是不可以修改的。
想要访问数组的内容可以通过遍历的方式、直接索引、或者把整体访问。
- 遍历
val a = intArratyof(1,2,3,4,5)
a.forEach{element->
println(element)
}
或
for(element in a){
println(element)
}
- 直接索引
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}”)
}
- forEach{
Println(it)
}
修改集合 可以用:
val a:MutableList<String> = mutableListOf(“s”)
- add(“t”) 也同等于 a += “t”
- 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的实例化对象。
- 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转换和匿名内部类
- 匿名内部类:
interface A{
fun a(){}
}
val ascasv = object : A{
override fun a() {
println("")
}
}
- kotlin中的SAM:
与java中的SAM不一样,kotlin只是支持SAM的调用,返回的是一个函数。
Java中的SAM返回的是一个确定的对象。
所以在类似于监听的时候,需要指的类型:

也可以用简写:
