Kotlin的by

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干脆禁止这样。

相关推荐
就叫飞六吧2 小时前
QT写一个桌面程序exe并动态打包基本流程(c++)
开发语言·c++
threelab2 小时前
Three.js 代码云效果 | 三维可视化 / AI 提示词
开发语言·javascript·人工智能
jinanwuhuaguo2 小时前
(第二十八篇)OpenClaw成本与感知的奇点——从“Token封建制”到“全民养虾”的本体论地基
android·人工智能·kotlin·拓扑学·openclaw
V搜xhliang02462 小时前
OpenClaw科研全场景用法:从文献到实验室的完整自动化方案
运维·开发语言·人工智能·python·算法·microsoft·自动化
kaikaile19952 小时前
风、浪、流环境模型的船舶三自由度(纵荡、横荡、艏摇)运动仿真MATLAB
开发语言·人工智能·matlab
fish_xk2 小时前
map和set
java·开发语言
李崧正2 小时前
Java技术分享:Lambda表达式与函数式编程
java·开发语言·python
老了,不知天命2 小时前
鳶尾花項目JAVA
java·开发语言·机器学习
BIGmustang2 小时前
python练手之用tkinter写一个计算器
开发语言·python