kotlin中的变量约束设计

可变状态的泛滥往往被认为是软件维护灾难的元凶之一,尤其是当过程封装遇上多线程,普通的面向对象编程技巧完全不堪大用,因为继承/封装/多态等手法针对的都是程序组织上的处理措施,具体到底层实现上,传统的C/C++/JAVA依然依赖过程式实现跟操作系统打交道

函数式编程里的副作用

在函数式编程的世界里,事情会变得很传统的过程式处理不一样,因为这里非常讲究函数本身是否是有副作用的,如果同样的输入不能保证相同的输出,那么则是有副作用的。这里的输出不仅仅表示返回值,还隐含其它形形色色的对环境的影响,包括

  • 申请但是没有释放的内存
  • 向操作系统请求占用共享资源如网络套接字
  • 屏幕输出,磁盘占用等

为什么要区分副作用

显然,副作用引入了额外需要程序员维护的状态,而传统的线程库或基本的OS机制将其完全交给了程序员负责。从而导致在多线程编程环境下,复杂的问题随着状态的增加成指数上升。状态意味着有共享资源需要维护,当有并发执行的进程或是线程的时候,为了保证正确的程序语意,则不得不引入锁(昂贵的操作)和竞争,从而制约性能。无锁算法通过CAS+重试机制,可以部分缓解锁的开销,却不能从本质上解决问题。

无副作用的函数则是天然适合并发的,因为没有共享自然可以并行不悖地执行,问题不是完美解决了吗?然而现实世界总是不允许绝对完美二字存在的,纯粹无副作用的函数几乎一无是处,因为它本质上没什么用,什么也做不了。

退而求其次的想法是,能否尽量隔离两者的实现,然后又可以优雅地将二者集成起来完成实际功能?HASKELL用其优雅的monad抽象回答了这个问题。然而对于抽象思维能力不是那么强(或者没有那么好数学基础)的程序员而言,Monad实在是太阳春白雪了而难以接近;想更加接地气一点的程序语言无一不选择和Monad保持距离,即使某些构造和设计的思想就来源于Monad, 譬如随处可见的Optional,基本的map/reduce链式操作等。

对于这些没有显示引入monad的非纯函数式语言来说,严格的隔离就显得有些太激进了。取而代之的相对折中一点的平庸策略是语言机制本身提供某些基础机制,剩下的怎么用这些基本机制,一切由程序员自己来定夺。

kotlin的语言层面基本机制

kotlin通过关键字 val 来声明只读的变量,用 var 来声明可变量。任何函数只要引入对可变量的使用,则其本身就是有明显的副作用的。然而一个变量声明为只读,仅仅表示在其对应的作用域中,不允许修改此变量的值,并不意味着实际指向的数据对象本身是不可变的, 因为在可能有其他的地方使用 var 的方式来操作此变量,或者有显示的方式将一个 val 的变量转换回可变的 var

考虑下边的例子:

// field1 是只读的,在本class中不允许修改它
class SomeClass(val field1 : SomeType, var field2 : Int) {
   fun doSth() {
       // can only modify field2, but not field1
   }
}

//calling site
var someTypeInst = SomeType()
val obj = SomeClass(someTypeInst, 112)
// someTypeInst can still be changed by others! Not recommended!
obj.doSth() 

虽然someTypeInst是以只读方式传入obj 的,然而并不能保证没有其它的线程并发地修改实际的对象,如果发生这种情况,程序员仍然需要保证数据的一致性和安全

只读变量的初始化

显然不可变变量则仅仅能够初始化一次,后续使用中不能再修改了。这样也带来一些限制,譬如在 init block 里想一次性初始化某些资源然后将其设置为在class内部是只读,则无能为力。一种变通的方式是将其设置为 var 类,然而这样做我们就损失了只读约束;另外一种做法则需要使用property构造来封装。

核心集合类

kotlin对来自JAVA的集合类库进行了二次封装,清晰地划分了只读集合类和可变集合。

接口定义

常用的集合类接口在kotlin,collections 包中被重新定义 ( 源码中位于 Collections.kt )

package kotlin.collections 
//...
// by default not mutable
public interface Iterable<out T> {//... }

// mutable iterable supports removing elements during iterating
public interface MutableIterable<out T> : Iterable<T> {//...}

//Only read access to collection
public interface Collection<out E> : Iterable<E> {//...}

// Supports read/write operations
public interface MutableCollection<E> : Collection<E>, MutableIterable<E> {//...}

具体的集合类接口则选择从以上接口中选择对应的来扩展实现,因而对同一个类型有两种实现,分别是只读的 (没有前缀) 的和可变类型 (用 Mutable 做前缀区分) 。譬如 List 类就定义为

// Read only list interface
public interface List<out E> : Collection<E> {//...}
// Mutable list
public interface MutableList<E> : List<E>, MutableCollection<E> {//...}

需要注意的是,实际的具体实现类是复用Java中的定义,可参考collection包中的 TypeAliases.kt 文件

package kotlin.collections
//...
@SinceKotlin("1.1") public typealias ArrayList<E> = java.util.ArrayList<E>

默认的集合操作以及Streams API返回的大部分是不可变接口对象。

集合类扩展/工具函数

除了使用默认的JDK实现来生成具体集合类对象,Kotlin标准库中同时提供了大量的封装函数方便程序员使用,某些来源于对JDK的直接封装,有一些则是直接inline实现。

譬如返回空list的包装和初始化形形色色的list

/** Returns an empty read-only list. */
public fun <T> emptyList(): List<T> = EmptyList

/** Returns a new read-only list of given elements.  */
public fun <T> listOf(vararg elements: T): List<T> = if (elements.size > 0) elements.asList() else emptyList()

/** Returns an empty read-only list. */
@kotlin.internal.InlineOnly
public inline fun <T> listOf(): List<T> = emptyList()

/**
 * Returns an immutable list containing only the specified object [element].
 * The returned list is serializable.
 */
@JvmVersion
public fun <T> listOf(element: T): List<T> = java.util.Collections.singletonList(element)

生成可变List的函数封装大多也是清晰明了 , 并且有很多种类的封装,使得就地生成 List 的工作大大简化;大部分情况仅仅需要使用已有的函数即可,不需要发明新的轮子

/** Returns an empty new [MutableList]. */
@SinceKotlin("1.1")
@kotlin.internal.InlineOnly
public inline fun <T> mutableListOf(): MutableList<T> = ArrayList()

/** Returns an empty new [ArrayList]. */
@SinceKotlin("1.1")
@kotlin.internal.InlineOnly
public inline fun <T> arrayListOf(): ArrayList<T> = ArrayList()

/** Returns a new [MutableList] with the given elements. */
public fun <T> mutableListOf(vararg elements: T): MutableList<T>
        = if (elements.size == 0) ArrayList() else ArrayList(ArrayAsCollection(elements, isVarargs = true))

其它集合类 (set/map等) 的实现原理大概类似,可以通过查看对应源码。

不可变集合转换为可变集合

很多场景下,API返回的都是不可变集合,将其变成一个可变对象再行编辑修改是常见不过的变成任务;kotlin 通过其自身的扩展机制将这些工具函数自动添加到了对应的集合类上

如果想要将一个只读的 Array 对象变为一个可变的 MutableList,那么其实现是通过重新初始化一个新对象实现的:

// Below code is copied from generated standlib as _Arrays.kt
//  see https://github.com/JetBrains/kotlin/tree/master/libraries/stdlib

/**
 * Returns a [MutableList] filled with all elements of this array.
 */
public fun <T> Array<out T>.toMutableList(): MutableList<T> {
    return ArrayList(this.asCollection())
}

对于具体的Array类,有不同的实现,如 ByteArray 的初始化方法则有所不同,直接调用其构造函数,然后注意添加现有的各个元素

/**
 * Returns a [MutableList] filled with all elements of this array.
 */
public fun ByteArray.toMutableList(): MutableList<Byte> {
    val list = ArrayList<Byte>(size)
    for (item in this) list.add(item)
    return list
}

之所以如此,是因为具体这些子类是被映射到具体的 JVM 对象上的。如ByteArray的文档如是说

public final class ByteArray defined in kotlin
An array of bytes.
When targeting the JVM, instances of this class are represented as byte[].

而对于CharArray,则其映射到char []类型上去。

IDEA支持

作为官方的IDE环境,IDEA对可变量的引用做了显示的下划线提醒,程序员可以一目了然地看到代码中对可变量的使用。

然而想要更深入的查看整个实现调用链中,哪些引入副作用哪些没有,工具的支持就比较有限了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,757评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,478评论 1 289
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,540评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,593评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,903评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,329评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,659评论 2 309
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,383评论 0 195
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,055评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,337评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,864评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,227评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,820评论 3 231
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,999评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,750评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,365评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,260评论 2 258

推荐阅读更多精彩内容