by被引入的需求背景
咱们先来看一些在Java中常遇到的场景。
1、类委托。
在Java中咱们常会有这样的代码
interface KotlinBy{
void test1();
void test2();
void test3(int a);
}
class KotlinByBase implements KotlinBy{
@Override
public void test1() {
System.out.println("test1 of Base");
}
@Override
public void test2() {
System.out.println("test2 of Base");
}
@Override
public void test3(int a) {
System.out.println("test3 of Base,"+a);
}
}
//KotlinByStub1只想改写一下test3(),其他方法交给kotlinBy去实现,
//但是test1、test2两个方法还是要明确去实现才行,在实现里再去调用kotlinBy的对应方法
class KotlinByStub1 implements KotlinBy {
private KotlinBy kotlinBy;
public KotlinByStub1(KotlinBy kotlinBy){
this.kotlinBy = kotlinBy;
}
@Override
public void test1() {
kotlinBy.test1();
}
@Override
public void test2() {
kotlinBy.test2();
}
@Override
public void test3(int a) {
System.out.println("KotlinByStub1的test3()");
kotlinBy.test3(a);
}
}
public class JavaShow {
static void main() {
KotlinByBase kotlinByBase = new KotlinByBase();
KotlinByStub1 kotlinByStub1 = new KotlinByStub1(kotlinByBase);
kotlinByStub1.test1();
kotlinByStub1.test3(5);
}
}
即把自己要实现的方法交给(或叫委托给)其他对象去处理。或者这样理解,已经有一个类KotlinByBase 实现了接口 KotlinBy ,但是我想再添加一些自己的逻辑进去,那么你通常会有两种方法,一是像上面代码所示那样,自己实现接口,添加自己的逻辑,同时把一部分逻辑委托给已经实现的类KotlinByBase,二是完全自己去实现接口,这样实现的方法里会有很多和KotlinByBase是重复的,而且一旦KotlinBy有方法变化,自己也要跟着修改。
2、属性委托。属性的懒加载、设置等,当属性很多时就需要写很多这类的模板代码。
public class UserJava {
private String name;
public UserJava(String name) {
setName(name);
}
public String getName() {
return name;
}
public void setName(String name) {
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("name 不能为 null 或空白");
}
this.name = name;
}
}
-
每个需要校验的 String 属性,都要重复写一模一样的
if判断。 -
如果有 10 个这样的属性,代码会膨胀 10 倍,极易复制粘贴出错
by的用法
为了省去这些繁琐的模板代码,Kotlin在语言层面提供了by机制。
interface KotlinBy{
fun test1()
fun test2()
fun test3(a:Int)
}
class KotlinByBase: KotlinBy{
override fun test1() {
println("test1 of Base")
}
override fun test2() {
println("test2 of Base")
}
override fun test3(a: Int) {
println("test3 of Base,$a")
}
}
class KotlinByStub1(var t:KotlinBy):KotlinBy by t{
override fun test3(a: Int) {
println("这是Stub的test3")
t.test3(a)
}
}
//完全交给KotlinByBase处理,也可以这样写
class KotlinByStub2():KotlinBy by KotlinByBase(){
//自己的方法
fun stub2Test(){
}
}
//调用
fun main() {
val kotlinByBase = KotlinByBase()
val kotlinByStub1 = KotlinByStub1(kotlinByBase);
kotlinByStub1.test1()
kotlinByStub1.test3(5)
}
by t 告诉编译器:这个类实现了 KotlinBy接口,但除了主动重写的方法外,其余全部转发给 t对象。写起来跟"继承"一样简单,但实际是"组合"。类委托是代理模式的应用,而代理模式可作为继承的一个不错的替代。本质是将本类需要实现的方法委托给其他对象。
查看编译后的代码情况可知,其实by只是为程序员简化了模板代码的繁琐编写工作,其底层源码跟之前的并无二样
//Kotlin的by 代码编译后的Java代码如下
public final class KotlinByStub1 implements KotlinBy {
private final /* synthetic */ KotlinBy $$delegate_0;
@NotNull
private KotlinBy t;
public void test1() {
this.$$delegate_0.test1();
}
public void test2() {
this.$$delegate_0.test2();
}
public KotlinByStub1(@NotNull KotlinBy t) {
Intrinsics.checkNotNullParameter(t, "t");
this.$$delegate_0 = t;
this.t = t;
}
@NotNull
public final KotlinBy getT() {
return this.t;
}
public final void setT(@NotNull KotlinBy kotlinBy) {
Intrinsics.checkNotNullParameter(kotlinBy, "<set-?>");
this.t = kotlinBy;
}
public void test3(int a) {
System.out.println((Object) "这是Stub的test3");
this.t.test3(a);
}
}
而上面的属性委托,则可以写成这样
import kotlin.reflect.KProperty
class NonBlankStringDelegate(private var value: String = "") {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return value
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: String) {
if (newValue.isBlank()) {
throw IllegalArgumentException("${property.name} 不能为 null 或空白")
}
value = newValue
}
}
class UserKotlin {
var name: String by NonBlankStringDelegate()
// 如果有其他同类型属性,直接一行搞定:
// var title: String by NonBlankStringDelegate()
}
编译后Java代码如下
public final class UserKotlin {
static final /* synthetic */ KProperty<Object>[] $$delegatedProperties = {Reflection.mutableProperty1(new MutablePropertyReference1Impl(UserKotlin.class, "name", "getName()Ljava/lang/String;", 0)), Reflection.mutableProperty1(new MutablePropertyReference1Impl(UserKotlin.class, "title", "getTitle()Ljava/lang/String;", 0))};
@NotNull
private final NonBlankStringDelegate name$delegate = new NonBlankStringDelegate((String) null, 1, (DefaultConstructorMarker) null);
@NotNull
private final NonBlankStringDelegate title$delegate = new NonBlankStringDelegate((String) null, 1, (DefaultConstructorMarker) null);
@NotNull
public final String getName() {
return this.name$delegate.getValue(this, $$delegatedProperties[0]);
}
public final void setName(@NotNull String str) {
Intrinsics.checkNotNullParameter(str, "<set-?>");
this.name$delegate.setValue(this, $$delegatedProperties[0], str);
}
@NotNull
public final String getTitle() {
return this.title$delegate.getValue(this, $$delegatedProperties[1]);
}
public final void setTitle(@NotNull String str) {
Intrinsics.checkNotNullParameter(str, "<set-?>");
this.title$delegate.setValue(this, $$delegatedProperties[1], str);
}
}
不过这看起来有点令人疑惑KProerty又是什么?
对于指定了委托对象的属性而言,由于它的实现已经完全交由委托对象,所以它不能再有getter和setter方法,而委托对象则一定要提供getValue和setValue(val不需要)方法。对于get和set两种方法,Kotlin也都做了规定:
get方法 三个参数,thisRef------属性所属的对象;property------代表被委托的属性本身, 通过它,你可以获得属性的名称 、类型 、可见性 、注解等元信息。该参数类型必须是KProperty<*>或其超类;返回值------必须返回与目标属性相同的类型。具体到上面的属性name示例,先看thisRef,理论上应该是一个UserKotlin对象,因为name所属的对象是一个UserKotlin实例,但是为了更加通用所以示例中用了Any?,如果写成别的,比如UserKotlin2,则可发现在UserKotlin的代码中by将被红色波浪线标记;property,代表属性name本身,比如可以通过property可以得知name是open的还是const的,等等,可以认为就是property里封装了属性的一些信息;返回值,很好理解。
set方法,也是三个参数,thisRef、property都一样,newValue,新值,也很好理解。
延迟属性
示例
val lazyProp:String by lazy{
"广东深圳"
}
Lazy<T>的getValue(),第一次调用该方法时会计算得出值并返回,此后再调用,直接使用上次的值。
属性监听
Kotlin提供了监听属性的功能,通过标准库中的 Delegates.observable() 和 Delegates.vetoable(),可监听属性值变化以及控制属性值变化。
class Person {
var name: String by Delegates.observable("初始名") { prop, old, new ->
println("${prop.name} 从 $old 变为 $new")
}
}
fun main() {
val p = Person()
p.name = "小明" // 输出: name 从 初始名 变为 小明
p.name = "小红" // 输出: name 从 小明 变为 小红
}
var age: Int by Delegates.vetoable(0) { _, old, new ->
new >= 0 // 只有年龄>=0才允许赋值
}
age = 18 // 成功
age = -5 // 被否决,age 仍为 18
fun <T> observable(initialValue:T,onChange:(property:KProperty<*>,oldValue:T,newValue:T)->Unit): ReadWriteProperty<Any?,T>
fun <T> vetoable(initialValue:T,onChange:(property:KProperty<*>,oldValue:T,newValue:T)->Boolean): ReadWriteProperty<Any?,T>。 vetoable意即可否决的。
by的限制
只能用于接口委托。只能是这样的 class C : Interface1 by delegate1, Interface2 by delegate2。这里的 delegate1、delegate2 必须分别实现了 Interface1 和 Interface2。
open class Base{
fun t(){
}
}
//不允许
class sBase(b:Base):Base by b{
}
上面的代码为什么不行呢?如果允许这样,那么当调用sBase的t()时该执行谁的呢?此时sBase至少会有两个t(),一个是继承自Base的,一个来自委托对象b,如果sBase又覆盖了t(),那就是三个t()方法。通过定义一些访问规则,比如如果sBase覆盖了t(),就访问sBase的t(),否则就用b的t(),如果要调用Base的,就用super.t(),理论上确实可以这样,但是会把问题复杂化,还容易出错,本来by是想让模板代码变得简单易用,现在却使使用越来越麻烦了,所以Kotlin干脆禁止这样。