Kotlin笔记(五):泛型基础,委托

1. 泛型

 Java自1.5版本引入了泛型的概念, Kotlin自然也支持泛型,Kotlin的泛型跟Java的泛型有相同之处,也有一些特别之处.

1.1 泛型类和泛型方法

 在一般的编程模式下,我们需要给任何一个变量指定一个具体的类型,而泛型允许我们在不指定具体类型的情况下进行编程,这样编写出来的代码将会拥有更好的扩展性。

 举个例子,List是一个可以存放数据的列表,但是List并没有限制我们只能存放整型数据或字符串数据,因为它没有指定一个具体的类型,而是使用泛型来实现的。也正是如此,我们才可以使用List< Int>、List< String>之类的语法来构建具体类型的列表。

 泛型主要有两种定义方式:一种是定义泛型类,另一种是定义泛型方法,使用的语法结构都是< T>。当然括号内的T并不是固定要求的,事实上你使用任何英文字母或单词都可以,但是通常情况下,T是一种约定俗成的泛型写法。如果我们要定义一个泛型类,就可以这么写:

class MyClass<T> {fun method(param: T): T {return param}
}

 而如果我们不想定义一个泛型类,只是想定义一个泛型方法,应该要怎么写呢?也很简单,只需要将定义泛型的语法结构写在方法上面就可以了,如下所示:

class MyClass {fun <T> method(param: T): T {return param}
}

 Kotlin还允许我们对泛型的类型进行限制。目前你可以将method()方法的泛型指定成任意类型,但是如果这并不是你想要的话,还可以通过指定上界的方式来对泛型的类型进行约束,比如这里将method()方法的泛型上界设置为Number类型,如下所示:

class MyClass {fun <T : Number> method(param: T): T {return param}
}

 这种写法就表明,我们只能将method()方法的泛型指定成数字类型,比如Int、Float、Double等。但是如果你指定成字符串类型,就肯定会报错,因为它不是一个数字。

 在默认情况下,所有的泛型都是可以指定成可空类型的,这是因为在不手动指定上界的时候,泛型的上界默认是Any?。而如果想要让泛型的类型不可为空,只需要将泛型的上界手动指定成Any就可以了。

 我们可以定义一个build()方法并通过使用泛型来实现类似apply函数一样的功能.

fun StringBuilder.build(block: StringBuilder.() -> Unit): StringBuilder {block()return this
}

 只需要使用将build函数定义成泛型函数,再将原来所有强制指定StringBuilder的地方都替换成T就可以了。新建一个build.kt文件,并编写如下代码:

fun <T> T.build(block: T.() -> Unit): T {block()return this
}

1.2 类委托和委托属性

 委托是一种设计模式,它的基本理念是:操作对象自己不会去处理某段逻辑,而是会把工作委托给另外一个辅助对象去处理。这个概念对于Java程序员来讲可能相对比较陌生,因为Java对于委托并没有语言层级的实现,而像C#等语言就对委托进行了原生的支持。

 Kotlin中也是支持委托功能的,并且将委托功能分为了两种:类委托和委托属性。

类委托,它的核心思想在于将一个类的具体实现委托给另一个类去完成。类似于代理模式. 我们用HashSet来实现一个自定义的Set类.

class MySet<T>(val helperSet: HashSet<T>) : Set<T> {override val size: Intget() = helperSet.sizeoverride fun contains(element: T) = helperSet.contains(element)override fun containsAll(elements: Collection<T>) = helperSet.containsAll(elements)override fun isEmpty() = helperSet.isEmpty()override fun iterator() = helperSet.iterator()
}

 可以看到,MySet的构造函数中接收了一个HashSet参数,这就相当于一个辅助对象。然后在
Set接口所有的方法实现中,我们都没有进行自己的实现,而是调用了辅助对象中相应的方法实
现,这其实就是一种委托模式。

 那么,这种写法的好处是什么呢?既然都是调用辅助对象的方法实现,那还不如直接使用辅助
对象得了。这么说确实没错,但如果我们只是让大部分的方法实现调用辅助对象中的方法,少
部分的方法实现由自己来重写,甚至加入一些自己独有的方法,那么MySet就会成为一个全新
的数据结构类,这就是委托模式的意义所在。

 但是这种写法也有一定的弊端,如果接口中的待实现方法比较少还好,要是有几十甚至上百个
方法的话,每个都去这样调用辅助对象中的相应方法实现,那可真是要写哭了。那么这个问题
有没有什么解决方案呢?在Java中确实没有,但是在Kotlin中可以通过类委托的功能来解决。

Kotlin中委托使用的关键字是by,我们只需要在接口声明的后面使用by关键字,再接上受委托的辅助对象,就可以免去之前所写的一大堆模板式的代码了,如下所示:

class MySet<T>(val helperSet: HashSet<T>) : Set<T> by helperSet {
}

 另外,如果我们要对某个方法进行重新实现,只需要单独重写那一个方法就可以了,其他的方法仍然可以享受类委托所带来的便利,如下所示:

class MySet<T>(val helperSet: HashSet<T>) : Set<T> by helperSet {fun helloWorld() = println("Hello World")override fun isEmpty() = false
}

类委托的核心思想是将一个类的具体实现委托给另一个类去完成,而委托属性的核心思想是将一个属性(字段)的具体实现委托给另一个类去完成

 委托属性的语法结构,如下所示:

class MyClass {var p by Delegate()
}

 这里使用by关键字连接了左边的p属性和右边的Delegate实例,这种写法就代表着将p属性的具体实现委托给了Delegate类去完成。当调用p属性的时候会自动调用Delegate类的getValue()方法,当给p属性赋值的时候会自动调用Delegate类的setValue()方法。

 因此,我们还得对Delegate类进行具体的实现才行,代码如下所示:

class Delegate {var propValue: Any? = nulloperator fun getValue(myClass: MyClass, prop: KProperty<*>): Any? {return propValue}operator fun setValue(myClass: MyClass, prop: KProperty<*>, value: Any?) {propValue = value}
}

 这是一种标准的代码实现模板,在Delegate类中我们必须实现getValue()和setValue()这两个方法,并且都要使用operator关键字进行声明。

 getValue()方法要接收两个参数:第一个参数用于声明该Delegate类的委托功能可以在什么类中使用,这里写成MyClass表示仅可在MyClass类中使用;第二个参数KProperty<>是Kotlin中的一个属性操作类,可用于获取各种属性相关的值,在当前场景下用不着,但是必须在方法参数上进行声明。另外,<>这种泛型的写法表示你不知道或者不关心泛型的具体类型,只是为了通过语法编译而已,有点类似于Java中<?>的写法。至于返回值可以声明成任何类型,根据具体的实现逻辑去写就行了,上述代码只是一种示例写法。

 setValue()方法也是相似的,只不过它要接收3个参数。前两个参数和getValue()方法是相同的,最后一个参数表示具体要赋值给委托属性的值,这个参数的类型必须和getValue()方法返回值的类型保持一致。

 整个委托属性的工作流程就是这样实现的,现在当我们给MyClass的p属性赋值时,就会调用Delegate类的setValue()方法,当获取MyClass中p属性的值时,就会调用Delegate类的getValue()方法。是不是很好理解?

 不过,其实还存在一种情况可以不用在Delegate类中实现setValue()方法,那就是MyClass中的p属性是使用val关键字声明的。这一点也很好理解,如果p属性是使用val关键字声明的,那么就意味着p属性是无法在初始化之后被重新赋值的,因此也就没有必要实现setValue()方法,只需要实现getValue()方法就可以了。

 委托功能本身不难理解,真正的难点在于如何灵活地进行应用。

 学习了Kotlin的委托功能之后,我们就可以对by lazy的工作原理进行解密了,它的基本语法结构如下:

val p by lazy { ... }

 现在再来看这段代码,是不是觉得更有头绪了呢?实际上,by lazy并不是连在一起的关键字,只有by才是Kotlin中的关键字,lazy在这里只是一个高阶函数而已。在lazy函数中会创建并返回一个Delegate对象,当我们调用p属性的时候,其实调用的是Delegate对象的getValue()方法,然后getValue()方法中又会调用lazy函数传入的Lambda表达式,这样表达式中的代码就可以得到执行了,并且调用p属性后得到的值就是Lambda表达式中最后一行代码的返回值。

 这样看来,Kotlin的懒加载技术也并没有那么神秘,掌握了它的实现原理之后,我们也可以实现
一个自己的lazy函数。

 我们首先定义了一个Later类,并将它指定成泛型类。Later的构造函数中接收一个函数类型参数,这个函数类型参数不接收任何参数,并且返回值类型就是Later类指定的泛型。接着我们在Later类中实现getValue()方法,代码如下所示:

class Later<T>(val block: () -> T) {var value: Any? = nulloperator fun getValue(any: Any?, prop: KProperty<*>): T {if (value == null) {value = block()}return value as T}
}

 由于懒加载技术是不会对属性进行赋值的,因此这里我们就不用实现setValue()方法了。

 代码写到这里,委托属性的功能就已经完成了。虽然我们可以立刻使用它,不过为了让它的用法更加类似于lazy函数,最好再定义一个顶层函数。这个函数直接写在Later.kt文件中就可以了,但是要定义在Later类的外面,因为只有不定义在任何类当中的函数才是顶层函数。代码如下所示:

fun <T> later(block: () -> T) = Later(block)

 我们将这个顶层函数也定义成了泛型函数,并且它也接收一个函数类型参数。这个顶层函数的作用很简单:创建Later类的实例,并将接收的函数类型参数传给Later类的构造函数。现在,我们自己编写的later懒加载函数就已经完成了,你可以直接使用它来替代之前的lazy函数,如下所示:

val uriMatcher by later {val matcher = UriMatcher(UriMatcher.NO_MATCH)matcher.addURI(authority, "book", bookDir)matcher.addURI(authority, "book/#", bookItem)matcher.addURI(authority, "category", categoryDir)matcher.addURI(authority, "category/#", categoryItem)matcher
}

 虽然我们编写了一个自己的懒加载函数,但由于简单起见,这里只是大致还原了lazy函数的基本实现原理,在一些诸如同步、空值处理等方面并没有实现得很严谨。因此,在正式的项目中,使用Kotlin内置的lazy函数才是最佳的选择。

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

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

相关文章

记录阿里云服务器(Centos7.9)部署Thingsboard(3.4.2)遇到的一些问题

记录编译Thingsboard遇到的一些问题 部署了一个thingsboard项目到阿里云服务器上&#xff0c;历时十一天&#xff0c;遇到了很多困难&#xff0c;国内关于Thingsboard的资料确实很少&#xff0c;所以想着写一篇博客记录一下&#xff0c;或许能够给以后编译遇到类似问题的人一些…

Python+playwright 实现Web UI自动化

实现Web UI自动化 技术&#xff1a;Pythonplaywright 目标&#xff1a;自动打开百度浏览器&#xff0c;并搜索“亚运会 金牌榜” 需安装&#xff1a;Playwright &#xff08;不用安装浏览器驱动&#xff09; # 使用浏览器&#xff0c;并可视化打开 browser playwright.ch…

tomcat、nginx实现四层转发+七层代理+动静分离实验

实验环境&#xff1a; nginx1——20.0.0.11——客户端 静态页面&#xff1a; nginx2——20.0.0.21——代理服务器1 nginx3——20.0.0.31——代理服务器2 动态页面&#xff1a; tomcat1——20.0.0.12——后端服务器1 tomcat2——20.0.0.22——后端服务器2 实验步骤&…

【CANoe】文件处理_hex文件读取解析

hex文件里面只有00&#xff0c;01&#xff0c;04三种码。那么我们在解析的时候只需要对这三种不同状态的进行不同的解析即可。 hex文件格式的解析&#xff0c;可阅读&#xff1a;HEX文件格式详解 首先创建一个Block的结构体&#xff0c;根据经验我们知道&#xff0c;一个数据…

EthernetIP 转MODBUS RTU协议网关连接FANUC机器人作为EthernetIP通信从站

远创智控YC-EIPM-RTU网关产品是一款高效的数据采集工具&#xff0c;它可以通过各种数据接口与工业领域的仪表、PLC、计量设备等产品连接&#xff0c;实时采集这些设备中的运行数据、状态数据等信息。采集到的数据经过整合和运算等操作后&#xff0c;可以被传输到其他设备或者云…

[python]如何操作Outlook实现邮件自动化

【背景】 邮件自动化存在很多需求场景,有的场景希望会出现Outlook窗口在发送前进行一下人工检查等等的人为干预,有的则希望定时直接发送,有的需要加附件等等。本篇讨论用Python覆盖这些Outlook邮件自动化场景的方法。 【解决方法】 首先Outlook和SMTP的邮件自动化方法所使…

【C++】stackqueue

适配器是一种设计模式 &#xff0c; 该种模式是将一个类的接口转换成客户希望的另外一个接口 。 虽然 stack 和 queue 中也可以存放元素&#xff0c;但在 STL 中并没有将其划分在容器的行列&#xff0c;而是将其称为 容器适配 器 &#xff0c;这是因为 stack 和队列只是对其他容…

linux上在docker中使用anaconda创建虚拟环境

conda的一些命令以及创建环境的基本命令可参考&#xff1a;Conda环境搭建以及激活 以及 conda 本地环境常用操作 前言 这里是梳理linux上在docker中使用conda&#xff0c;以配置MLD-TResNet-L-AAM模型为例。论文笔记参考&#xff1a;多标签分类论文笔记 | Combining Metric Lea…

分类选择,最多五级

效果图&#xff0c;这种竖向的分类选择&#xff0c;每一列可以用不同的背景颜色 组件代码 <template><view class"toolTypeBox" :style"max-height:${maxHeight}"><block v-for"(item,index) in datalist"><block v-if&…

spark集成hive

集群使用ambarihdp方式进行部署,集群的相关版本号如下所示: ambari版本 Version 2.7.4.0 HDP版本 HDP-3.1.4.0 hive版本 3.1.0 spark版本 2.3.0 集群前提条件: 1.Hdp、Spark、Hive都已部署好 2.Hive数据层建好&#xff0c;在Hdfs生成相应各层目录&#xff0c;后面配…

大数据学习(16)-mapreduce详解

&&大数据学习&& &#x1f525;系列专栏&#xff1a; &#x1f451;哲学语录: 承认自己的无知&#xff0c;乃是开启智慧的大门 &#x1f496;如果觉得博主的文章还不错的话&#xff0c;请点赞&#x1f44d;收藏⭐️留言&#x1f4dd;支持一下博主哦&#x1f91…

企业c#语言源代码防泄密解决方案

在当今数字化时代&#xff0c;企业的核心业务往往依赖于软件应用程序。为了保护企业的知识产权和敏感信息&#xff0c;源代码的保密至关重要。对于制造类企业尤其是智能制造业来讲&#xff0c;最近几年是高速发展的时期&#xff0c;很多公司在做工厂流水线设备时&#xff0c;就…

Python学习笔记——类、魔术方法

食用说明&#xff1a;本笔记适用于有一定编程基础的伙伴们。希望有助于各位&#xff01; 类 类的运用很常见&#xff1a;在大部分情况下&#xff0c;对一些特有的对象&#xff0c;可以使用特定的类来指向它&#xff1a; class Person:name unknownage -1sex 0partner No…

微信小程序三种授权登录以及授权登录流程讲解

&#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&#x1f389; &#x1f3c5;我是Java方文山&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;推荐给大家我的专栏《微信小程序开发实战》。&#x1f3af;&#x1f3a…

小县城蔬菜配送小程序制作全攻略

随着互联网的普及和人们对生活品质要求的提高&#xff0c;越来越多的小县城开始开发蔬菜配送小程序&#xff0c;以满足当地居民对新鲜蔬菜的需求。制作一个小县城蔬菜配送小程序&#xff0c;需要经过以下步骤&#xff1a; 步骤一&#xff1a;登录乔拓云平台 首先&#xff0c;打…

揭开 Amazon Bedrock 的神秘面纱 | 基础篇

在 2023 年 4 月&#xff0c;亚马逊云科技曾宣布将 Amazon Bedrock 纳入使用生成式人工智能进行构建的新工具集。Amazon Bedrock 是一项完全托管的服务&#xff0c;提供各种来自领先 AI 公司&#xff08;包括 AI21 Labs、Anthropic、Cohere、Stability AI 和 Amazon 等&#xf…

【编解码格式】DivX系列、XviD

DivX DivX是DivX公司&#xff08;前身是DivX Networks公司&#xff09;的著名品牌&#xff0c;一种MPEG-4技术视频编解码器。该公司2007年秋以2200万美元收购德国Main Concept。 DivX将encore2的代码继续发展成DivX 4.0&#xff0c;于2001年7月推出。至于曾有份参与OpenDivX的…

Android音视频开发之基础知识

一、视频文件 1、视频格式 常见格式&#xff1a;mp4、mkv、flv 封装的数据&#xff1a;音频码流、视频码流 常用工具&#xff1a; [FFmpeg下载]:https://ffmpeg.org/download.html 下载、安装并配置环境变量 ffmpeg.exe 视频编解码 ffplay.exe 播放器库 ffprobe.exe 音视频分…

操作系统【OS】线程与进程的比较

进程 线程 是什么的单位? 是资源分配的基本单位 是调度的基本单位 不能共享什么? 不能共享虚拟地址空间 不能共享栈指针 可以共享什么? 拥有一个完整的资源平台 每个进程都有独立的地址空间和资源 除了共享全局变量&#xff0c;不允许其他进程访问 某进程中的线程…

git(部分)

1、git三个区域&#xff1a;工作区&#xff0c;暂存区&#xff0c;版本库 2、git文件状态&#xff1a;未跟踪&#xff0c;已跟踪&#xff08;新添加&#xff0c;未修改&#xff0c;已修改&#xff09; 如何查看暂存区和工作区文件状态&#xff1a;git status -s 3、查看版本记…