Kotlin:比private更加自私的private to this

栏目: IT技术 · 发布时间: 4年前

内容简介:可见性修饰符我们先从泛型逆变(contravariant)说起。比如说我们有一个消费者:它的类型参数

可见性修饰符 private ,对于顶层声明来说是该文件内可见,对于类内部的成员来说是该类内部可见,这是大家都知道的事。不过 Kotlin 里还存在着可见范围更小的,那就是 private to this ,仅对 this 可见 。

我们先从泛型逆变(contravariant)说起。比如说我们有一个消费者:

interface Consumer<in T> {
   fun consume(t: T): Unit
}

它的类型参数 T 是逆变的。这意味着 T 只能出现在成员的输入位置,如例子中 consume 函数的参数位置,而不能出现在成员的输出位置(比如说返回值位置)。这个限制是为了确保类型安全。

然后因为是逆变的,这使得 Consumer<CharSequence>Consumer<String> 的子类型:

fun test(c: Consumer<CharSequence>) {
   val consumer : Consumer<String> = c
   consumer.consume("test")
}

毕竟 StringCharSequence 的子类型,如果一个东西是字符串,那么这个东西也可以是一个字符序列。然后一个消费 CharSequence 的消费者当然可以拿一个 String 当做 CharSequence 来消费,所以说 “消费 String 的消费者” 可以用 “消费 CharSequence 的消费者” 来代替。

这个替代关系很好地阐释了 Consumer<CharSequence>Consumer<String> 的子类型。

现在我们改一下消费者的逻辑,把接口删了换成类,并且让她在出生的时候就能拿到消费品,这样并不会改变逆变的性质:

class Consumer<in T>(t: T) {
   private val somethingToConsume: T = t

   fun consumeMyThing(): Unit = println(somethingToConsume)

   fun consume(t: T): Unit = println(t)
}

fun test() {
   val consumer : Consumer<String> = Consumer<Any>(Any())
   consumer.consume("test")
}

是时候回归主题了,我们的消费者小姐,她的 somethingToConsume ,可见性就是 private to this ,仅对 this 可见。

比如说消费者小姐看中了别人的消费品,想要抢过来玩:

class Consumer<in T>(t: T) {
   private val somethingToConsume: T = t

   fun consumeMyThing(): Unit = println(somethingToConsume)

   fun consumeOthers(other: Consumer<String>): Unit {
      val string = other.somethingToConsume
      println(string)
      // Error: Cannot access 'somethingToConsume'
      // It is private/*private to this*/ in 'Consumer'
   }
}

编译器看到了这样的违法行为,马上阻止了她:你只能玩你自己的东西。

之所以 consumeMyThing 可以通过编译,是因为通过 this 调用 somethingToConsumethis 省略了);在 consumeOthers 函数里调用 somethingToConsume 用的不是 this ,所以失败了。仅对 this 可见,字面意思。

为什么会有这样的限制呢?原因很简单, consumeOthers 的代码其实是违反了逆变泛型参数的安全限制, other.somethingToConsume 这里实际上是 other 在对外输出 T ,眼尖的同学可能早就发现了, somethingToConsumeT 是处在输出的位置上的。

但是“输出”是相对的,一个 private 的东西,自产自销自己用,那不算输出,是安全的。但是像上面那样从别人家里那东西,那就相当于是别人在输出了。

可以演示一下如果不存在 private to this 的限制会发生什么问题。

class Consumer<in T>(t: T) {
   private val somethingToConsume: T = t

   fun consumeMyThing(): Unit = println(somethingToConsume)

   fun consumeOthers(other: Consumer<String>): Unit {
      @Suppress("INVISIBLE_MEMBER")
      val string = other.somethingToConsume // dangerous!!
      println(string)
   }
}

fun test() {
   val intConsumer = Consumer(42)
   val anyConsumer = Consumer(Any())
   // 因为是逆变的,所以 Consumer<Any> 是 Consumer<String> 的子类型
   intConsumer.consumeOthers(anyConsumer)

   // intConsumer 想要从别人手里拿到一个 String,但是实际上拿到的是 Any
}

这里使用了我的那篇文章介绍的技巧,使用 @Suppress("INVISIBLE_MEMBER") 强行无视可见性的限制。

运行代码然后就得到了一个类型转换异常:

Exception in thread "main" java.lang.ClassCastException: java.lang.Object cannot be cast to java.lang.String
    at Consumer.consumeOthers

所以现在来总结一下。当一个类、接口的逆变的泛型参数出现在 private 成员的输出位置时(比较常见的是返回值位置),那么那个 private 成员,实际上可见性是 private to this 。举例:

class Test<in T> {
   private val foo: T = TODO()
   private var bar: T = TODO()

   private fun bas(): T = TODO()
}

为了允许上面这些代码合法存在,但是又要禁止不安全的调用,这就是为什么要有 private to this 的原因。

本文完。


以上所述就是小编给大家介绍的《Kotlin:比private更加自私的private to this》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

腾讯方法

腾讯方法

潘东燕、王晓明 / 机械工业出版社 / 2014-12-11 / 39.00

这是国内第一本深度讲述腾讯产品研发与团队转型的书。本书介绍了腾讯三个不同生命周期的产品的开发过程,包括如何踏足新领域开发新产品;如何救活一个即将半路夭折的产品;如何让一个老产品持续盈利。本书呈现了互联网产品开发时会遇到普遍问题和解决方法,涉及大企业如何内部创业,并迅速组建新的项目团队;如何实现跨部门的合作;在面临新团队和紧急开发任务时如何提高团队沟通效率;在产品研发方面,如何定位产品、如何敏捷开发......一起来看看 《腾讯方法》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具