Android Kotlin 高阶详解

news2024/11/25 3:32:55

前言

本文主要讲述kotlin高阶相关的内容,如果对kotlin基础还不了解的,

可以参考文章Android Kotlin 基础详解_袁震的博客-CSDN博客

1,与Java的相互调用

1.1在kotlin中调用java代码

大多数的java代码都可以直接在kotlin中调用,没有问题,但是有一些代码是不能直接调用的,这里需要注意一下

1.1.1 含有关键字的方法

某些kotlin关键字在java中是合法的标识符,in, object,is等等,如果java类中使用kotlin关键字作为方法名,我们仍然可以调用这个方法,只要使用反引号`对方法名转义即可:

在java中:

public class JavaTest {

    public void in(){
        System.out.println("输出in");
    }
}

在kotlin中调用:

var javaTest =JavaTest()
javaTest.`in`()

1.1.2null值安全性与平台数据类型

Java 中的所有引用都可以为 null 值, 因此对于来自 Java 的对象,
Kotlin 的严格的 null 值安全性要求就变得毫无意义了.
Java 中定义的类型在 Kotlin 中会被特别处理,
被称为 平台数据类型(platform type). 对于这些类型,
Null 值检查会被放松, 因此对它们来说, 只提供与 Java 中
相同的 null 值安全保证

 比如:

val list =ArrayList<String>()//非null值 因为是构造器方法的返回结果
list.add("yuanzhen")
val size =list.size //非null值 因为是基本数据类型int
//报异常 Index 1 out of bounds for length 1
val item =list.get(1)//类型自动推断结果为平台类型,通常的java对象 
item.substring(2)//

1.1.3@Nullable 和@NotNull

带有@Nullable 和@NotNull的注解的Java类型在kotlin中不会被当作平台数据类型,而会被识别为可为null的,或非null的kotlin类型

也可以对泛型的类型参数添加注解, 标记它是否可以为 null. 比如, 我们先来看看在 Java 中如何添加这些注解:
@NotNull
Set<@NotNull String> toSet(@NotNull Collection<@NotNull String> elements) {
    ...
}

在kotlin中如下:

fun toSet(elements: (Mutable)Collection<String>) : (Mutable)Set<String> { ... }
请注意在类型参数 String 上的 @NotNull 注解. 如果没有这些注解, 我们得到的类型参数就只能是平台数据类型:
fun toSet(elements: (Mutable)Collection<String!>) : (Mutable)Set<String!> { ... }
(Mutable)Collection<T>! 代表 “元素类型为 T 的Java 集合, 
内容可能可变, 也可能不可变,
值可能允许为 null, 也可能不允许为null”,
Array<(out) T>! 代表 “元素类型为 T (或 T 的子类型)的 Java 数组,
值可能允许为 null, 也可能不允许为 null”

 1.1.4 数据类型映射

kotlin会对某些java类型进行特殊处理,这些类型会被从java中原封不动的装载进来,但被映射为对应的kotlin类型。映射过程只会在编译时发生,运行时的数据表达不会发生变化。映射关系如下:

有些内建类虽然不是基本类型, 也会被映射为对应的 Kotlin 类型:

Java 中的装箱的基本类型(boxed primitive type), 会被映射为 Kotlin 的可为 null 类型:

注意, 装箱的基本类型用作类型参数时, 会被映射为平台类型: 比如, List<java.lang.Integer> 在 Kotlin 中会变为 List<Int!> .
集合类型在 Kotlin 中可能是只读的, 也可能是内容可变的, 因此 Java 的集合会被映射为以下类型(下表中所有的 Kotlin 类型都属于
kotlin.collections 包):
Java 数据的映射如下:

1.1.5泛型

Kotlin 的泛型 与 Java 的泛型略有差异 . 将 Java 类型导入 Kotlin 时, 我们进行以下变换:
Java 的通配符会被变换为 Kotlin 的类型投射,
Foo<? extends Bar> 变换为 Foo<out Bar!>! ,
Foo<? super Bar> 变换为 Foo<in Bar!>! ;
Java 的原生类型(raw type) 转换为 Kotlin 的星号投射(star projection),
List 变换为 List<*>! , 也就是 List<out Any?>! .
与 Java 一样, Kotlin 的泛型信息在运行时不会保留, 也就是说, 创建对象时传递给构造器的类型参数信息, 在对象中不会保留下来, 所以,
ArrayList<Integer>() 与 ArrayList<Character>() 在运行时刻是无法区分的. 这就导致无法进行带有泛型信息的 is 判断. Kotlin 只允许对
星号投射(star projection)的泛型类型进行 is 判断:
if (a is List<Int>) // 错误: 无法判断它是不是 Int 构成的 List
// 但是
if (a is List<*>) // OK: 这里的判断不保证 List 内容的数据类型

1.1.6 获得java类

要在kotlin中,获取java类,有两种写法:

 val clazz = javaTest::class.java
 val clazz1 = javaTest.javaClass

1.2 在java中调用kotlin代码

调用 Kotlin 中的方法时, 有时你可能会需要使用 KClass 类型的参数. Java 的 Class 不会自动转换为 Kotlin 的 KClass , 因此你必须手动
进行转换, 方法是使用 Class<T>.kotlin 扩展属性, 这个扩展属性对应的方法是:
kotlin.jvm.JvmClassMappingKt.getKotlinClass(MainView.class)

java中访问Kotlin文件中的函数,需要使用:文件名Kt.函数名进行访问

MyClassKt.test();

在Kotlin中,可以把很多具有相似功能的函数放到同一个kotlin文件中,如果想要以Java中类似静态方法的形式访问Kotlin文件中的函数(不使用文件名Kt进行访问),可以通过使用@file:JvmName("任意名称")注解来达成目标。需要注意的是,@file:JvmName("名称") 一定要放在文件的最开始,要在包声明之前

@file:JvmName("Test")
package com.yuanzhen.kotlinstudy

fun test1(){
    println("测试JvmName")
}

在java中调用:

Test.test1();

2,Lambda表达式与高阶函数

2.1Lambda表达式基本使用

最基本的lambda表达式闭包格式:

val lambda闭包名称 = { 参数名称:类型 -> 函数实现(返回值) }

调用:

闭包名称.invoke(参数列表)
闭包名称(参数列表)

例如:

val myLambda ={a:Int,b:Int -> println("${a+b}") }
myLambda.invoke(3,4)
myLambda(3,4)

2.2Lambda闭包三原则

①,如果lamdba闭包没有参数,可以省略箭头符号(仅写实现部分);如果lamdba仅有一个参数,可以省略声明,并在使用时使用`it`来代替

②,如果lambda闭包是函数的最后一个参数,则可以将大括号放到小括号(函数名称的小括号)外

③,如果函数只有一个参数并且这个参数是lambda,则可以省略小括号

下面来看一个例子:

val txt = findViewById<TextView>(R.id.txt)
txt.setOnClickListener(object :View.OnClickListener {
    override fun onClick(v: View?) {
       println("${v?.id}")
    }
})

如果我们用lambda表达式来写的话:

txt.setOnClickListener({v->println("${v?.id}")})

如果使用原则①的话:

txt.setOnClickListener({println("${it?.id}")})

如果使用原则②的话:

txt.setOnClickListener(){println("${it?.id}")}

如果使用原则③的话:

txt.setOnClickListener{println("${it?.id}")}

2.3高阶函数

函数的参数或者函数返回值是Lamdba闭包的函数,我们称之为高阶函数

比如:

findViewById(R.id.btn_click).setOnClickListener(v -> {

});

2.4 动态代理

Kotlin原生支持了动态代理语法,通过by关键字实现,省略了Java中要手写代理类实现InvocationHandler 接口, 通过Proxy获取代理类调用方法等一系列步骤。

interface Animal{
    fun bark()
}

class Dog : Animal{
    override fun bark() {
        println("Wang")
    }


class Duck : Animal{
    override fun bark() {
        println("Ga ga")
    }
}

class Zoo(animal : Animal) : Animal by animal

fun main(){
    Zoo(Dog()).bark()
    Zoo(Duck()).bark()
}

注意:如果在Zoo中重写了代理接口中的方法,则会执行重写方法中的代码,不会再执行代理类中的方法了

Kotlin的动态代理会在编译以后将动态代理转换为**静态代理**以后来使用,所以Kotlin中的动态代理要比java中的效率高。java动态代理请参考Java 代理模式之静态代理与动态代理_袁震的博客-CSDN博客

2.5密闭类

kotlin很少使用枚举类,通常使用更强大的密闭类。密闭类使用关键字sealed来定义

一个密闭类可以有多个子类,但是密闭类必须和其子类都在同一个kt文件中

密闭类可以有成员变量,但必须是abstract的

sealed class Fruit{
    abstract var name:String
}

class Apple:Fruit(){
    override var name: String = "Apple not good"
}

class Pear:Fruit(){
    override var name: String = "Pear good"
}

fun echoFruitName(fruit:Fruit){
    when(fruit){
        is Apple -> println(fruit.name)
        is Pear -> println(fruit.name)
    }
}

fun main() {
    echoFruitName(Pear())
    echoFruitName(Apple())

}

2.6 泛型

请参考文章Android泛型详解_袁震的博客-CSDN博客

2.7init代码块

init代码块会在类每次实例化时都执行一次,执行顺序为 主构造>init代码块>次级构造,因此可以在init代码块中执行一部分主构造中无法执行的逻辑代码。

class People(var name:String){

    constructor(name:String, age:Int):this(name){
        println("in constructor")
    }

    init {
        name = name + "00"
        println("in init block name = $name")
    }
}

fun main(){
    People("zhangsan", 123)
}

结果:
in init block name = zhangsan00
in constructor 

2.8 lateinit 修饰符

Kotlin中类的属性必须初始化,否则编译器会报错。为了让大家更适应类似Java中的写法,Kotlin提供了一个关键字lateinit,可以告诉编译器稍后我会初始化这个字段。但是需要注意:如果这个属性没有初始化就使用的话,会报空指针异常

2.9扩展函数,扩展属性

Kotlin中可以给一个类扩展它的成员方法或成员变量,常用于扩展系统内置库、三方SDK等不受控的类

无论是Kotlin中的类,还是Java中的类,kotlin都可以对他们的成员方法进行扩展

fun 被扩展方法的类名.扩展方法名(参数列表):返回值 = 实现{ 函数体 }
class TestKtClass{

}

fun TestKtClass.echoMsg(str:String) = println(str)

fun TestKtClass.getStrValue(str1:String, str2:String):Int{
    return (str1+str2).length
}

fun main(){
    TestKtClass().echoMsg("12345")
    println(TestKtClass().getStrValue("12345", "000"))
}

注意点:

1.扩展函数时静态的给一个类添加成员方法,不具备运行时多态效应

2.扩展函数和类内部定义的成员函数重名时,执行的是类的成员函数

扩展属性和扩展函数类似,

var 类名.属性名称:类型
    get(){
        
    }
    set(value){
        
    }
var StringBuilder.firstLetter:Char
    get(){
        return get(0)
    }
    set(value){
        this.setCharAt(0, value)
    }

fun main(){
    println(StringBuilder("123456").firstLetter)
}

2.10静态对象

使用关键字object定义的对象是静态对象,此对象类似于静态对象仅能被实例化一次,相当于单例并且线程安全

object类不能有构造函数 

object TestObj{
    fun echoName(){

    }
}

fun main(){
    TestObj.echoName()
}

在kotlin中调用object对象的方法,使用类名.方法名调用

在Java中调用object对象的方法,使用类名.INSTANCE.方法名调用

TestObj.INSTANCE.echoName();

2.11伴生对象

伴生对象使用companion object关键字定义,并且仅能在一个类的内部使用,因此才叫伴生对象,伴生对象中的方法和属性都是相当于静态(并不是真正的静态,而是包含在一个静态实例中)的。

class TestCompanionObj{
    companion object{
        fun echoMsg(){
            println("111111111111111111")
        }
    }
}

object类似,在kotlin中使用类名.方法名调用, 在Java中使用类名.Companion.方法名调用

//kotlin
TestCompanionObj.echoMsg()


//java
TestCompanionObj.Companion.echoMsg();

2.12幕后字段

幕后字段用关键字field表示,仅能用在get和set方法中使用。在Kotlin中,field这个字段代表这个属性本身,与class中的this相似。

需要注意的是:在get和set方法中,一定不要使用属性本身。因为一旦使用属性本身会被再次展开为get和set方法,造成无限递归,最终引发stack over flow。如果要使用属性(无论读或者写),一定使用幕后字段。

class Test{
    var name = ""
    set(value) {
        //错误的示范,在set使用了name属性本身,应该使用field
        name = value
    }
}
class Test{
    var name = ""
    private set(value) {
        field = value
    }
}

2.13常用的集合操作符

**元素操作类**

- contains :判断是否有指定元素
- elementAt:返回对应元素,越界会抛出IndexOutOfBoundsException
- firstOrNull:返回符合条件的第一个元素,没有返回null
- lastOrNull:返回符合提交的最后一个元素,没有返回null
- indexOf:返回指定元素的下标,没有返回-1
- singleOrNull:返回符合条件的单个元素,如果没有符合或超过1个,返回Null

**判断类**

- any:判断集合中是否有满足条件的元素
- all:判断集合中的元素是否都满足条件
- none:判断集合中是否都不满足条件
- count:查询集合中满足条件的个数
- reduce:从第一项累加到最后一项

**过滤类**

- filter:过滤所有满足条件的元素
- filterOrNot:过滤所有不满足条件的元素
- filterNotNull:过滤NULL元素
- take:返回前N个元素

**转换类**

- map:转换成另外一个集合
- mapIndexed:转换成另外一个集合,还可以拿下标
- mapNotNull:执行转换前先过滤掉为NULL的元素
- flatMap:自定义逻辑合并两个集合
- groupBy:按照某个条件分组,返回map

**排序类**

- reversed:反序

- sorted:升序

- sortedBy:自定义排序
- sortedDescending:降序

2.14运算符重载与中缀表达式

运算符重载要使用关键字operator,重载运算符有一个要求:只能重载Kotlin中定义好的运算符。

operator 将一个函数标记为重载一个操作符或者实现一个约定。运算符重载就是对已有的运算符赋予他们新的含义。

Kotlin中的 && 、 || 、 ?: 、 === 、 !== 是不能被重载的

data class Person(var name: String, var age: Int)
 
operator fun Int.plus(b: Person): Int {
    return this - b.age
}
 
fun main() {
 
    val person1 = Person("A", 3)
 
    val testInt = 5
 
    println("testInt+person1=${testInt +person1}")
}

输出结果
testInt+person1=2

运算符在Kotlin中是有数量上限的,当kotlin中已有的运算符不能满足我们使用的时候,我们需要对运算符进行扩展,这里就用到了中缀表达式(如step),定义中缀表达式使用关键字:infix

infix 类型.函数() 是中缀函数的声明形式,就是扩展函数前面使用了indfix关键字,函数签名的类型,称为函数的接收者类型,换言之只有该类型才能调用这个函数。

中缀也可以被通过正常的函数调用来使用:类对象.中缀函数()

sealed class CompareResult{
    object More : CompareResult(){
        override fun toString(): String {
            return "大于"
        }
    }

    object Less : CompareResult(){
        override fun toString(): String {
            return "小于"
        }
    }

    object Equal : CompareResult(){
        override fun toString(): String {
            return "等于"
        }
    }
}

infix fun Int.vs(value : Int) : CompareResult =
        if(this - value > 0){
            CompareResult.More
        }else if(this - value < 0){
            CompareResult.Less
        }else{
            CompareResult.Equal
        }

fun main(){
    println(5 vs 6)
    println(8.vs(7))
}

需要注意一点是,如果在一个类中重载了别的类的运算符,那么这个重载运算符仅能在这个类及其子类中使用,在这个类的外部、派生类、object修饰的类均不能使用

总结

kotlin的高阶用法差不多就是上面这些,后面会讲协程。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1020538.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Spring Cloud Alibaba Nacos注册中心(单机)

文章目录 Spring Cloud Alibaba Nacos注册中心&#xff08;单机&#xff09;1. docker 安装 nacos&#xff08;先别着急&#xff09;2. 配置nacos持久化到mysql、2.1 properties 文件 3. java注册3.1 POM文件3.2 properties文件3.3 测试配置中心 4.注册中心4.1 配置文件4.2测试…

【八大经典排序算法】选择排序

【八大经典排序算法】选择排序 一、概述二、思路解读三、代码实现&#xff08;升序&#xff09;四、优化&#xff08;升序&#xff09; 一、概述 选择排序作为一种简单直观的排序算法&#xff0c;最早由美国计算机科学家 Donald Knuth 在1968年提出。 选择排序的思想是将数组…

小程序从无到有教学教程-- 01.重置华为云服务器Huawei Cloud EulerOS 2.0版本并且设置安全组

概述 专门拿了专栏来讲解&#xff0c;所以目录结构就比较简单了 文章目录 概述修改华为云操作系统选择Huawei Cloud EulerOS 2.0 镜像顺便配置华为安全组 修改华为云操作系统 这里选择华为最新的系统&#xff0c;不过也就2.0~ 选择Huawei Cloud EulerOS 2.0 镜像 这里记住密…

企业架构LNMP学习笔记61

Nginx作为tomcat的前段反向代理&#xff1a; 在实际业务环境中&#xff0c;用户是直接通过域名访问&#xff0c;基于协议一般是http、https等。默认tomcat运行在8080端口。一般会通过前端服务器反向代理到后端的tomcat的方式&#xff0c;来实现用户可以通过域名访问tomcat的we…

bat写的git命令大全(适合初学者)掌握命令行下的Git操作!

欢迎来到Git&#xff01;无论你是一位Git初学者&#xff0c;这个在命令大全将帮助你在命令行下熟练运用Git&#xff0c;提高版本控制和团队协作的效率。从基本的仓库管理到分支操作&#xff0c;从提交修改到远程仓库同步&#xff0c;这个命令大全涵盖了Git的各种常用功能和技巧…

LeetCode-热题100-笔记-day31

105. 从前序与中序遍历序列构造二叉树https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/ 给定两个整数数组 preorder 和 inorder &#xff0c;其中 preorder 是二叉树的先序遍历&#xff0c; inorder 是同一棵树的中序遍历&#xff0c…

QT-day2

完善登录框 点击登录按钮后&#xff0c;判断账号&#xff08;admin&#xff09;和密码&#xff08;123456&#xff09;是否一致&#xff0c;如果匹配失败&#xff0c;则弹出错误对话框&#xff0c;文本内容“账号密码不匹配&#xff0c;是否重新登录”&#xff0c;给定两个按钮…

【QT】day2

1.完善登录框 点击登录按钮后&#xff0c;判断账号&#xff08;admin&#xff09;和密码&#xff08;123456&#xff09;是否一致&#xff0c;如果匹配失败&#xff0c;则弹出错误对话框&#xff0c;文本内容“账号密码不匹配&#xff0c;是否重新登录”&#xff0c;给定两个按…

孙哥Spring源码第23集

第23集 finishBeanFactoryInitialization处理过程 1、处理国际化&#xff0c;事件操作&#xff0c;onRresh()的操作&#xff0c;注册监听器。 2、finishBeanFactoryInitialization(beanFactory)核心目的 通过Spring的BeanFactory创建单例&#xff08;非延迟&#xff09;这些对…

mongodb 无法远程连接

在本机可以通过 mongo 命令进行连接&#xff0c;但是外部无法连接&#xff0c;排查以下两个方面&#xff1a; 一&#xff0c;检查防火墙是否未开放端口 如果不确定&#xff0c;可以先关闭防火墙再试 二&#xff0c;查看mongodb启动时绑定的ip 执行如下命令&#xff1a; nets…

织梦CMS_V5.7任意用户密码重置漏洞复现

一、漏洞说明 织梦内容管理系统&#xff08;DeDeCMS&#xff09;以其简单、实用、开源的特点而著名。作为国内最知名的PHP开源网站管理系统&#xff0c;它在多年的发展中取得了显著进步&#xff0c;无论在功能还是易用性方面都有长足的发展。该系统广泛应用于中小型企业门户网站…

Linux集群时间同步方法

参考&#xff1a;https://www.cnblogs.com/felixzh/p/10638399.html

Foxmail 备份旧数据导入新电脑

文章目录 前言方法参考 前言 如何转移Foxmail 邮箱中的邮件&#xff0c;以便转移电脑后也同样可以使用呢&#xff1f; 方法 找到Foxmail 安装目录找到存放账号的文件夹【Storage】 该文件夹下存放了邮箱账户的所有相关信息&#xff08;账户配置&#xff0c;收件箱&#xff0…

[TI] [Textual Inversion] An image is worth an word

自己的理解&#xff1a; 根据几个图像&#xff0c;找出来一个关键字可以代表它们&#xff0c;然后我们可以再用这个关键字去生成新的东西。 提出关键字 1 Introduction word->token->embedding Textual Inversion过程 需要&#xff1a; ① a fixed, pre-trained text…

C语言的文件操作(炒详解)

⭐回顾回顾文件操作的相关细节⭐ 欢迎大家指正错误 &#x1f4dd;在之前的学习中&#xff0c;不管增加数据&#xff0c;减少数据&#xff0c;当程序退出时&#xff0c;所有的数据都会销毁&#xff0c;等下次运行程序时&#xff0c;又要重新输入相关数据&#xff0c;如果一直像这…

升级OpenSSL并进行编译安装

Packaging (OpenSSL)组件存在安全漏洞的原因是由于当前爆出的Openssl漏洞。 这个漏洞可能会导致泄露隐私信息&#xff0c;并且涉及的机器和环境也有所不同&#xff0c;因此修复方案也会有所不同。 目前&#xff0c;一些服务器使用的Nginx是静态编译OpenSSL&#xff0c;直接将Op…

【数据结构】C++实现红黑树

【数据结构】C实现红黑树 红黑树的概念 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制&#xff0c;红黑树确保没有一条路径会比其他路…

多数网工碌碌无为,都是败在这件事上

大家好&#xff0c;我是老杨。 这周是网络安全周&#xff0c;博览会的现场很热闹&#xff0c;我也被邀请去参观了一圈。 主要是学习观摩各大厂出的最新的安全产品、解决方案等。 这几天&#xff0c;也遇上了不少大佬。 有从路由交换转到安全的&#xff0c;也有从渗透转到防御…

Unity中Shader的模板测试

文章目录 前言什么是模板测试1、模板缓冲区2、模板缓冲区中存储的值3、模板测试是什么&#xff08;看完以下流程就能知道模板测试是什么&#xff09;模板测试就是在渲染&#xff0c;后渲染的物体前&#xff0c;与渲染前的模板缓冲区的值进行比较&#xff0c;选出符合条件的部分…