zl程序教程

您现在的位置是:首页 >  移动开发

当前栏目

Android kotlin在实战过程问题总结与开发技巧详解

AndroidKotlin开发 详解 总结 实战 技巧 过程
2023-09-27 14:27:35 时间

1、介绍

        目前Android开发中,分为两派,Java派和Kotlin派,Java是宇宙开发第一语言,地位一直处于领先位置,但是Java会出现一个空指针的情况,所以kotlin的出现,填补了java的一些缺陷,但是也导致一些人习惯了Java语言,在转kotlin的时候,会经常写成java字段。所以我接下来把kotlin在开发过程常见的关键字和一些用法进行介绍,以及一些错误如何处理,看完基本就能正常上手写kotlin项目。

二、常见使用介绍

似曾相识,就是不会。看完恍然大悟。接下来对高频用法和高频关键字进行介绍

1、var 和val,变量声明

1.1 var 是声明为常量,有读与写权限

        var a:Int=10,

        a=20

1.2val 是声明为常量,相当于final,只能读,没有写权限

val b:Int=10

b=20//这行报错

2、对象的创建

var  chidl:Child

如果直接使用报错

var child=Child()直接new一个新的

3、!!和?区别

!!:如果有空指针会被抛出

?:如果当前对象是空指针,将不会执行后面的操作

如下:

 4、"?"的使用

1.var child:Child?=null

这种表示child为null,


如果你直接写成

var child:Child=null

直接报错

 变成的初始化可以这样

var child:Child= Child()
声明成Child的类型,然后初始化

var child1=Child()

直接初始化成child类型

5、可变数组与不可变数组

在kotlin中,数组分为可变和不可变,可变是可以添加值,不可变就是声明后,就不能再add进去。

 可变素组正常都是mutable开头,常见的有如下:

mutableListOf<>()
mutableMapOf<>()
mutableSetOf<>()

在这边,var和val对对象的修饰不是那么明显,即使你申请为val,可变数组还是可以操作的

6、静态类,关键字:object

我们在Java中会使用静态变量通过static来修饰,kotlin在修饰静态变量直接用object修饰类

object StaticMian {

    fun log() {
        MyLog.log("object is static")
    }

}

使用:StaticMian.log()

如果设置类为静态,里面的方法都不需要再进行修饰,和其他类一样使用

静态类的内部类也可以

object DemoOut {

    object DemoIn{

        fun log(){
            MyLog.log("DemoIn")
        }
    }

    fun log(){
        MyLog.log("DemoOut")
    }


}

7、单利模式:

1.Synchronized


单利在Java作为工具类用的好多地方,kotlin的单利模式是通过在get方法中

object StaticMian {
    private var mian: StaticMian? = null
    @get:Synchronized
    val instance: StaticMian?
        get() {
            synchronized(StaticMian::class.java) {
                if (mian == null) {
                    mian = StaticMian
                }
            }
            return mian
        }


    fun log() {
        MyLog.log("object is static")
    }

}

关于在对象下方的get(),可以参考我的另一篇:Kotlin语法详解与实践教程,区分JAVA以及如何闭坑

修饰该对象是同步,只需要修饰该方法的get,指向Synchronized,和申明变量差不多

@+get:+关键字

var +变量名:+类型


第二种单利写法:

class TestSing {

    companion object {
        private var test: TestSing? = null;


        fun getInstance(): TestSing {
            synchronized(TestSing::class.java) {
                if (test == null)
                    test = TestSing()
            }

            return test!!
        }

    }
}

8、常量const关键字用法

const是修饰val的,val是只有读权限,如果用const来修饰,那就变成了一个常量,const 只允许在top-level级别和object,也就是是说当前类必须是通过object修饰的,否则不能使用

而且const修饰的变量必须要被初始化。

1.正确写法

const val a:Int = 0;

2.错误写法
const val a:Int;

3.const的出现:只能出现在静态类或者静态模块中,否则报错

9、接口使用使用

1.接口创建

interface MyInterface {

    public fun change(msg: String): String;

    public fun add(a: Int, b: Int): Int {
        return a + b
    }

}

也是通过关键字interface 来修饰

写法没有啥区别,唯独有争议的地方就是方法可以有方法体,有方法体只能当方法使用

2.继承接口

类和的继承和接口都是通过":"实现的

如果接口中定义的方法有方法体,在接口继承将不会需要重写,已被定义为方法了,可以直接调用,也可以重写

class MyTestDemo :MyInterface {
    override fun change(msg: String): String {
        return msg+"===change";
    }

    override fun add(a: Int, b: Int): Int {

        return super.add(a, b)

    }

}

fun main() {

    var  demo=MyTestDemo()
    MyLog.log(demo.change("123"))
    MyLog.log(demo.add(2,3))

}

3.参数为接口,如果View的onclick实践

1.直接和java写法一致:

    demo.setInterface(object : MyInterface {
        override fun change(msg: String) {

        }
    })


2.lambda表达式:

无参:直接{}在空号内写回调

demo.setOnclick {

}

有参:直接将参数名按顺序列出来,指向方法体->

demo.addOnDestinationChangedListener {controller,destination,arguments->
    
    toast("${destination.displayName}")
    
}

10、多构造器以及继承类的构造器传参

非继承类构造器

class MyConstrDemo() {


    constructor(name: String) : this() {

    }

    constructor(name: String,age:Int) : this() {

    }

}

fun main() {
   var  demo1=MyConstrDemo()

    var  demo2=MyConstrDemo("")

    var  demo3=MyConstrDemo("",12)
}

默认构造器通过类名后面追加。如果多重载,通过关键字来完成constructor(),返回值就是默认构造器类型

类名后带参数的构造器又叫默认构造器,如果在类的内部通过constructor定义构造器,返回值类型一定是this(),this就是类名后面接的默认构造器。

继承类构造器:

open class Parent(name: String = "parent") {
}

class MyConstrDemo(name:String):Parent(name) {

    constructor() : this("") {

    }

    constructor(name: String,age:Int) : this() {

    }

}

和单类一样,只是要传参给父类。其他写法和非继承一样

11、lateinit 关键字

lateinit 关键是需要对变量进行初始化,切变量属性是private类型,切用来修饰var

在Android开发过程中,比较常见的就是lateinit var来定义view

lateinit var text=findViewById(id)as TextView

 这种修饰是申请变量可以为null,一般简单类型不适合

1.错误,这种直接报错

lateinit var age:Int 

同样,这样修饰过的变量不能去修改set()和get()方法

 可以为null和不可以的示例:

lateinit var mp1:HashMap<String,String>
 var mp2:HashMap<String,String>

12、强制转换 as

as在kotlin中作为类型的强制转换,Java中  int a=(int) 100.123f

kotlin: 

对象类型

a=b as Int

常见的view初始化如下:

var text=findViewById(id) asTextView

13、构造器对象使用

我们如果在kotlin中构造器中有对象变量,需要引用构造器变量,无须将构造器变量赋值到类的内部变量,可以直接通过var修饰完直接引用

class DataMapConst(var map:HashMap<String,String>?) 
class DataMapConst(var map:HashMap<String,String>?) {

    lateinit var mp1:HashMap<String,String>
   
    constructor( msg:String):this(null){

    }

    fun log(){
        MyLog.log(map!!?.toString())

    }

    inner class Child{

        fun log(){
            MyLog.log(map!!?.toString())
        }
    }
}


fun main() {
    var map:HashMap<String,String>?=null
    map= HashMap()
    DataMapConst("ssss")
    var demo=DataMapConst(map);
    map.put("one","1")
    map.put("two","1")
    map.put("three","1")
    demo.Child().log()


}

14、对象默认set()和get()使用

kotlin的变量都提供了set()和get() 方法,但是lateinit修饰过的变量,无法修改set()和get()方法

 

class People {
    var name: String=""
        set(value) {
            field=value+"=123"
        }
        get() {
            return field + "==modify"
        }
    var age: Int = 0

}

fun main() {

    var people=People()
    people.name="zhangshan"
    MyLog.log(people.name)
}

set(value):这个value是 val,只能读,不可修改

get():需要一个return

field:是变量自身的值,如果了解Java反射的,应该明白,field是class获取到的变量

想了解Java反射,可以参考我的另一篇文字  Android Java反射与Proxy动态代理详解与使用基础篇(一)

这样我们就能随意修改field值,以及返回值。这类似Java hook技术。同时,也破坏了数据原有的安全性,谨慎使用。

15、companion关键字

companion只能修饰object,且该object必须是内部类静态模块。

常见的用法如下

class TestSing {

    companion object {
        //类的静态模块
      
       
      
    }
}

所以object有两种用法

1.修饰类为静态内

2.和companion搭档,companion object修饰非静态类的静态模块

如果类为静态类,就不能这样使用,因为静态类,里面的方法和熟悉都可以直接使用,再修饰成静态模块就会报错。

所以 :只能出现在非静态类中

companion object{

}

这种用法最为非静态类中的静态模块

16、open关键字

 kotlin中的类和方法都是无法直接被继承或者重写,类似Java中被修饰了,但是可以通过open来修饰就变成了开放型类和方法。

open class Base {

   open fun log(){

    }
}

class TestBase :Base(){

    override fun log() {
        super.log()

    }

}

17、Any,Unit,Nothing,Void

1.Any:相当于java中的object使用

Unit:是一个object类,类似java中的void

fun aa(): Unit {
    
}

Nothing:是一个有私有构造器的类,常作为定义异常抛出类使用

fun  bb():Nothing=throw java.lang.Exception("error");

也经常在继承提示作为TD使用

@kotlin.internal.InlineOnly
public inline fun TODO(reason: String): Nothing = throw NotImplementedError("An operation is not implemented: $reason")

Void:作为空来使用,常作为返回一个空类型。

fun cc(): Void? {
    var map:Any;
    return null;
}

上面四种类型,使用最多的是Any和Nothing,关于unit和void可以作为了解,不做参考

18、inner:内部类修饰

如果类中嵌套类,我们常叫成为内部类,在Java中直接写,但是kotlin中,如果需要使用内部类,需要用一个inner来修饰

class People {


    inner class MySon {
        var name: String = ""

        fun log() {
            MyLog.log(name)
        }
    }
}

fun main() {


    var sun = People().MySon()
    sun.name="123"
    sun.log()


}

   注意: 在使用过程中,内部类的使用如下

 正确:

var sun = People().MySon()

错误:

var sun = People.MySon()

19、when

when的使用和Java中的Switch一个逻辑,但是格式右边

val a = 10
when (a) {
    0, 10 -> {
        MyLog.log("0-10")
    }
    20 -> {
        MyLog.log("==20")
    }
    30 -> {
        MyLog.log("==30")
    }
    else -> {
        MyLog.log("not value")
    }
}

when( 表达式)

0,10:a>=0&&a<=10

相当于

case 0:

case10:

break

else相当于default

注意:任何模块结束自带break。

20.、is的使用

在kotlin中,类型的判断是 is,和java instanceof一样

var b=10
if (b is Int)
{
    MyLog.log("Int")
}

21、in在for中的使用

 in在for循环中使用比较多,和迭代去关联。但是,关于for的使用如下

1.for (i in 1 until 100 )

  相当于 [1,100)的区间类,步长是1,相当于i ++

2.for (i in 1 until 100 step 2)

指定步长是多少,step跟上步长

 3.for (i in 0..100)

区间[0,100],步长为1

step:是步长,在for循环中是特有的

结果如下:

22、参数泛型

在kotlin中,对参数类型的泛型要求蛮高的,即使,实参和形参的泛型不一致都会提示错误

所以在使用过程中,定义参数泛型后,实参和形参一定要匹配,否则很容易报错,这种错误和java中不一样,java只要容器一样,参数是默认类型的。

 有时候在设置泛型一定要格外小心,避免新手排查问题无法定位到

解决:

如果你是在不想预先定义好,可以如下:

class FanxingDemo(var map:HashMap <*,*>) {

    fun log(){
        map.keys;
    }
}


fun main() {
    var map=HashMap<Any,Any>()

    var demo=FanxingDemo(map)

}

形参指定*

var map:HashMap <*,*>

 实参:

var map=HashMap<Any,Any>()

或者

var map: HashMap<*, *> = HashMap<Any?, Any?>()

或者先定义,后初始化

var map: HashMap<*, *> 

map= HashMap<Any?, Any?>()

参数put的时候需要注意:

key和value要转换成Nothing,否则报错

map.put("one" as Nothing, 1 as Nothing);
map.put("two" as Nothing, 1 as Nothing);

但是这种在编写没有问题,但是在执行的时候会报错

Nothing是Void类型,所以String不能转Void

所以在实战中,实参的泛型不要通过*来修饰,Demo如下

正确:

var map: HashMap< Any,  Any> = HashMap<Any, Any>()
var key = Any()
map.put("123",123)

23、Bean的封装

在kotlin中,类有两种写法

1.正常有类体的

class MyBean() {
    var name: String = ""
    var age: Int = 0
    var map: HashMap<*, *> = HashMap<Any?, Any?>()
}

2.无类体,只通过默认构造器完成

class MyBean(var name: String = "", var age: Int, var map: HashMap<*, *> = HashMap<Any?, Any?>())

3.空类

class  MyBean()

由于kotlin所有的变量都提供了set()和 get()方法,所以如果做bean来使用,无法处理结果,直接用第二种即可。

第二种写法也常和Gson、Json配合解析使用

正常使用如下:


    var bean = MyBean("zhangshan", 100, map)

    MyLog.log(bean.name + "," + bean.age + "," + bean.map.toString())

24、String的快捷内置API的使用

val value="123"
value.toFloat()
value.toInt()
value.toDouble()
value.toLong()
value.toCharArray()
value.toList()
value.toBigDecimal()
value.toByte()
value.toShort()
value.toBoolean()

kotlin的String类型,已提供了好多快捷的转换。我们可以直接使用,这样不用我们自己去换了

25、异常的抛出方法

kotlin中有两个异常抛出的方法

1.在方法体中抛出

try {

    Thread.sleep(122)
} catch (e: Exception) {

}

2.在方法外面抛:通过@Throws跟上异常类

@kotlin.jvm.Throws(Exception::class)
fun testException() {
    Thread.sleep(12)
}

26、Synchronized同步锁使用

kotlin的同步锁用两种用法

第一种:变量默认方法加锁

我们都知道,kotlin的变量默认有set和get方法。所以同步锁在锁变量的时候,可以单独对变量的set和get进行加锁。

Synchronized

class TestSyn {
    @set:Synchronized
    @get:Synchronized
    var instance = TestSyn()
}

加锁格式:@+(set、get)+ :+Synchronized

field域不能直接对变量加锁,因为变量不支持,锁只支持方法

错误写法:

@Synchronized
var instance = TestSyn()

第二种:自定义方法加锁

自定义方法加锁直接在方法名上方插入,如下:

@Synchronized
fun test(){

}

格式 :@Synchronized

日常我们了解这两种方式已够开始使用

27、Volatile:原子一致性

这些都是属于jvm的,直接@引入即可

@Volatile
var a: Int = 10;

28、占位符:${value}

在java中,如果需要在一个String文本中占位一个坑,然后通过数据替换,常见的做法就是MessageFormat来处理。在kotlin中占位可以直接在text文本中使用

var name="zhangshan"
val text="我的名字叫${name}名字长度${name.length}"

占位符的使用,可以简化我们正常的拼接和formate转换。

29、可变参数:vararg

在kotlin中可以参数需要通过vararg来修饰,

Java :

String... arry

kotlin:

vararg var arry:String

class Test( vararg var arry:String)

30、条件执行:let

let的是kotlin特有的条件,通过自身一些条件,执行let体

如:

if(entity!=null){

initView();

}

let:

entity?.let{

initView()

}

31、for的使用

遍历:forEach

java:

for(object item:arry)

kotlin:

arry.forEach {
    it
}

遍历:in

我们正常遍历Java如下:for(int a=0;a<100;a++),kotlin不支持这种写法,kotlin的for搭配in使用

所以我们正常使用

for(index in 0..100)

区间在[0,100],因为数据小标都是size-1,如果我们不小心导致数组越界

所以在kotlin集合有一个字段已处理了:indices

indices

public val Collection<*>.indices: IntRange
    get() = 0..size - 1
arry.indices=arr.size-1

我们在遍历采用indices属性,就可以避免数组越界的问题