Kotlin Reference (十二) Extensions
most from reference
Kotlin与C#和Gosu类似,提供了扩展一个新功能的类,而不必继承类或使用任何类型的设计模式,如Decorator(装饰者模式)。这是通过称为扩展的特殊声明完成的。Kotlin支持扩展功能和扩展属性。
扩展功能
要声明一个扩展函数,我们需要一个接收器类型(即被扩展的类型)作为其名称的前缀。以下是为MutableList扩展的swap功能:
fun MutableList<Int>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // 'this' corresponds to the list
this[index1] = this[index2]
this[index2] = tmp
}
扩展功能中的this关键字对应于接收器对象(在.之前传递的对象)。现在我们可以在任何时候调用MutableList的这个函数:
val l = mutableListOf(1, 2, 3)
l.swap(0, 2) // 'this' inside 'swap()' will hold the value of 'l'
当然,这个功能对任何MutableList都是有用的,我们可以 让其更加通用:
fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // 'this' corresponds to the list
this[index1] = this[index2]
this[index2] = tmp
}
我们在函数名称之前声明通用类型参数,使其在接收器类型表达式中可用。请参考通用功能
扩展函数静态解析
扩展实际上不会修改他们扩展的类。通过定义扩展名,不会将新成员插入到类型,而只是使用这种类型的变量上点符号来调用新的函数。
需要强调的是,扩展功能是静态调用的,它们不是虚拟的接收器类型。这意味着被调用的扩展函数由调用该函数的表达式的类型决定,而不是在运行时评估该表达式的结果的类型。例如:
open class C
class D: C()
fun C.foo() = "c"
fun D.foo() = "d"
fun printFoo(c: C) {
println(c.foo())
}
printFoo(D())
此例将先打印”c”,因为被调用的扩展函数仅取决于声明的参数类型c,即C类。
如果一个类有一个成员函数,并且定义了一扩展函数,它具有相同的接收器类型,相同的名称,并且适用于给定的参数,则成员函数的优先级更高。例如:
class C {
fun foo() { println("member") }
}
fun C.foo() { println("extension") }
如果我们调用c.foo(),它将打印”member”而不是”extension”。
但是,扩展函数可以重载具有相同名称不同参数的成员函数,这是完全可行的:
class C {
fun foo() { println("member") }
}
fun C.foo(i: Int) { println("extension") }
调用C().foo(1)将打印”extension”。
空接收器
我们可以使用空接收器类型定义扩展。这样的扩展可以在对象变量上调用,即使他的值为null,并且可以在方法体内检查this==null。这样就可以在Kotlin中调用toString()而无需检查null:检查发生在扩展函数内。
fun Any?.toString(): String {
if (this == null) return "null"
// after the null check, 'this' is autocast to a non-null type, so the toString() below
// resolves to the member function of the Any class
return toString()
}
扩展属性
与扩展函数类似,Kotlin支持扩展属性:
val <T> List<T>.lastIndex: Int
get() = size - 1
请注意,由于扩展名实际上并没有将成员插入到类中,因此扩展属性没有有效的备份字段。这就是为什么不允许扩展属性的初始化。它们的行为只能通过提供明确getter/setter来定义。
val Foo.bar = 1 // error: initializers are not allowed for extension properties
伴生对象扩展
如果一个类定义了一个伴生对象,那么还可以定义该对象的扩展函数和属性:
class MyClass {
companion object { } // will be called "Companion"
}
fun MyClass.Companion.foo() {
// ...
}
就像伴生对象的常规成员一样,只能使用该类名作为限定词:
MyClass.foo()
扩展范围
大多数时候我们在顶层定义扩展,即直接在包下:
package foo.bar
fun Baz.goo() { ... }
要在其声明包之外使用这样的扩展,我们需要在调用的地方导入它:
package com.example.usage
import foo.bar.goo // importing all extensions by name "goo"
// or
import foo.bar.* // importing everything from "foo.bar"
fun usage(baz: Baz) {
baz.goo()
}
有关详细信息,请参考导入。
作为成员函数定义扩展函数
在类中,您可以为另一个类声明扩展名。在这样的扩展中,有多个隐式接收器,可以在没有限定符的情况下访问对象成员。声明扩展名的类的实例为调用接收方,扩展方法的接收方类型称为扩展接收方。
class D {
fun bar() { ... }
}
class C {
fun baz() { ... }
fun D.foo() {
bar() // calls D.bar
baz() // calls C.baz
}
fun caller(d: D) {
d.foo() // call the extension function
}
}
在发送接收机的成员分机接受者之间发生姓名冲突的情况下,分机接受者优先。要引用法发送接收器的成员,您可以使用this语法。
class C {
fun D.foo() {
toString() // calls D.toString()
this@C.toString() // calls C.toString()
}
作为成员的扩展可以用open声明在类中覆盖。这意味着这种功能的调用对于调用接收器类型是虚拟的,但是关于扩展接收器类型时静态的。
open class D {
}
class D1 : D() {
}
open class C {
open fun D.foo() {
println("D.foo in C")
}
open fun D1.foo() {
println("D1.foo in C")
}
fun caller(d: D) {
d.foo() // call the extension function
}
}
class C1 : C() {
override fun D.foo() {
println("D.foo in C1")
}
override fun D1.foo() {
println("D1.foo in C1")
}
}
C().caller(D()) // prints "D.foo in C"
C1().caller(D()) // prints "D.foo in C1" - dispatch receiver is resolved virtually
C().caller(D1()) // prints "D.foo in C" - extension receiver is resolved statically
目的
在Java中,我们习惯命名*.utils的类:FileUtils,StringUtils等等。跟ava.util.Collections属于统一类型。
关于这些Utils类令人不快的是使用它们如下例所示:
// Java
Collections.swap(list, Collections.binarySearch(list, Collections.max(otherList)), Collections.max(list))
这些类型总是阻碍了我们理解。我们可以使用静态导入:
// Java
swap(list, binarySearch(list, max(otherList)), max(list))
这样使用稍微好点,从强大的代码补全IDE,我们不需要或需要很少的帮助。如果我们这样写,会不会好点:
// Java
list.swap(list.binarySearch(otherList.max()), list.max())
但是我们不想在类中实现所有可能的方法List,对吧?这是扩展帮助我们的地方。
Kotlin Reference (十二) Extensions的更多相关文章
- Kotlin Reference (十) Interfaces
most from reference 接口 Kotlin中的接口非常类似于Java8,它们可以包含抽象方法的声明以及方法实现.与抽象类不同的是接口不能存储状态.它们可以具有属性,但这些需要是抽象的或 ...
- 20135316王剑桥 linux第十二周课实验笔记
第十二章并发编程 1.如果逻辑控制流在时间上重叠,那么它们就是并发的.这种现象,称为并发(concurrency). 2.为了允许服务器同时为大量客户端服务,比较好的方法是:创建并发服务器,为每个客户 ...
- CG基础教程-陈惟老师十二讲笔记
转自 麽洋TinyOcean:http://www.douban.com/people/Tinyocean/notes?start=50&type=note 因为看了陈惟十二讲视频没有课件,边 ...
- NeHe OpenGL教程 第二十二课:凹凸映射
转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...
- perl5 第十二章 Perl5中的引用/指针
第十二章 Perl5中的引用/指针 by flamephoenix 一.引用简介二.使用引用三.使用反斜线(\)操作符四.引用和数组五.多维数组六.子程序的引用 子程序模板七.数组与子程序八.文件句 ...
- Linux时间子系统之(十二):periodic tick
专题文档汇总目录 Notes:TickDevice模式,以及clocckevent设备.TickDevice设备的初始化,TickDevice是如何加入到系统中的.周期性Tick的产生. 原文地址:L ...
- MongoDB十二种最有效的模式设计【转】
持续关注MongoDB博客(https://www.mongodb.com/blog)的同学一定会留意到,技术大牛Daniel Coupal 和 Ken W. Alger ,从 今年 2月17 号开始 ...
- .NET Core实战项目之CMS 第十二章 开发篇-Dapper封装CURD及仓储代码生成器实现
本篇我将带着大家一起来对Dapper进行下封装并实现基本的增删改查.分页操作的同步异步方法的实现(已实现MSSQL,MySql,PgSQL).同时我们再实现一下仓储层的代码生成器,这样的话,我们只需要 ...
- (C/C++学习笔记) 十二. 指针
十二. 指针 ● 基本概念 位系统下为4字节(8位十六进制数),在64位系统下为8字节(16位十六进制数) 进制表示的, 内存地址不占用内存空间 指针本身是一种数据类型, 它可以指向int, char ...
随机推荐
- ubuntu 14.04 安装 glog
1.下载 git clone https://github.com/google/glog 2.配置 sudo apt-get install autoconf automake libtool 3. ...
- Qt_OpenGL_教程
1. 中文版: Qt OpenGL教程 http://blog.csdn.net/myths_0/article/details/24431597 http://qiliang.net/old/neh ...
- SpringBoot开发案例之整合Kafka实现消息队列
前言 最近在做一款秒杀的案例,涉及到了同步锁.数据库锁.分布式锁.进程内队列以及分布式消息队列,这里对SpringBoot集成Kafka实现消息队列做一个简单的记录. Kafka简介 Kafka是由A ...
- 寻找重复的子树 Find Duplicate Subtrees
2018-07-29 17:42:29 问题描述: 问题求解: 本题是要求寻找一棵树中的重复子树,问题的难点在于如何在遍历的时候对之前遍历过的子树进行描述和保存. 这里就需要使用之前使用过的二叉树序列 ...
- windows7 asp.net发布IIS 拒绝访问 解决方法
在windows7中打开DNN网站有以下问题: CS0016: 未能写入输出文件“c:\Windows\Microsoft.NET\Framework\v4.0.30319\Temporary ASP ...
- eclipse 与 tomcat 的那些路径
我们用mvn创建了一个web工程,同时希望在eclipse里调试开发.mvn有mvn的路径要求,eclispe有eclipse的默认路径,怎么整合二者? 首先介绍一下eclipse的默认路径. 重点在 ...
- English trip -- VC(情景课)4 A Health
Word doctor doctor's office medicine [ˈmɛdɪsɪn] n. 药:医学:内科:巫术 vt. 用药物治疗:给…用药 pill n. 药丸 nurse ...
- Confluence 6 使用 LDAP 授权连接一个内部目录 - 用户组 Schema 设置
请注意:这部分仅在拷贝用户登录(Copy User on Login)和 同步组成员(Synchronize Group Memberships)被启用后可见. 其他用户组 DN(Additional ...
- python-day67--MTV之Template
一.什么是模板? html+模板语法 二.模版包括在使用时会被值替换掉的 变量,和控制模版逻辑的 标签. 三.嵌入变量的三种方式: def current_time(req): # ========= ...
- python-day34--并发编程之多线程
理论部分 一.什么是线程: 1.线程:一条流水线的工作过程 2.一个进程里至少有一个线程,这个线程叫主线程 进程里真正干活的就是线程 3.进程只是用来把资源集中到一起(进程只是一个资源单位,或者说资 ...