内容简介:可见性修饰符我们先从泛型逆变(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") }
毕竟 String
是 CharSequence
的子类型,如果一个东西是字符串,那么这个东西也可以是一个字符序列。然后一个消费 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
调用 somethingToConsume
( this
省略了);在 consumeOthers
函数里调用 somethingToConsume
用的不是 this
,所以失败了。仅对 this
可见,字面意思。
为什么会有这样的限制呢?原因很简单, consumeOthers
的代码其实是违反了逆变泛型参数的安全限制, other.somethingToConsume
这里实际上是 other
在对外输出 T
,眼尖的同学可能早就发现了, somethingToConsume
的 T
是处在输出的位置上的。
但是“输出”是相对的,一个 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
的原因。
本文完。
本作品采用 知识共享 署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可,转载请注明出处。
本文链接: https://aisia.moe/2020/07/17/kotlin-private-to-this/以上所述就是小编给大家介绍的《Kotlin:比private更加自私的private to this》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。