【Kotlin】by lazy关键字的实现原理

本文深入探讨了Kotlin中`by lazy`关键字的实现机制,通过字节码和Java代码的角度揭示其延时初始化的原理。在ViewModel和ViewBinding的例子中,`by lazy`确保了变量在首次使用时才进行初始化,避免了因依赖未准备好而导致的错误。分析了字节码后,发现`by lazy`将变量转换为`Lazy`类型,通过`Lazy`的`getValue`方法判断并执行初始化。此外,还展示了`by lazy`的Java实现,证实了字节码反编译后的等价性。

前言

kotlin的by lazy关键字是很常用的,它表示延时初始化变量,只在第一次使用时才给它初始化。那么它是如何实现这种功能的呢?这篇文章从字节码和Java语言的角度揭密它的实现原理。

ViewModel和ViewBinding变量初始化过程

先举两个项目中最常见的例子:ViewModel和ViewBinding,了解一下为什么需要延时初始化。

看一段代码:

class MainActivity : AppCompatActivity() {
   
   

    private val viewModel: MainViewModel by lazy {
   
   
        ViewModelProviders.of(this).get(MainViewModel::class.java)
    }

    private val binding: ActivityMainBinding by lazy {
   
   
        ActivityMainBinding.inflate(layoutInflater)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
   
   
        super.onCreate(savedInstanceState)
        Log.i("MainActivity", "onCreate")
     }
}

Jetpack库中的ViewModel和ViewBinding的使用是非常常见的,ViewModel和ViewBinding类型的变量都是需要延时初始化,不能在声明时初始化。ViewModel是因为内部需要依赖Activity的成员变量mApplication,而mApplication是在attach时给赋值的。ViewBinding的初始化需要依赖Window的layoutInflater变量,而Window变量也是在attach时赋值的。

先看ViewModel是如何初始化的,在以下ViewModelProviders.of方法里会调用checkApplication判断application是否为空,为空则抛出异常:

public class ViewModelProviders {
   
   

    /**
     * @deprecated This class should not be directly instantiated
     */
    @Deprecated
    public ViewModelProviders() {
   
   
    }

    private static Application checkApplication(Activity activity) {
   
   
        Application application = activity.getApplication();
        if (application == null) {
   
   
            throw new IllegalStateException("Your activity/fragment is not yet attached to "
                    + "Application. You can't request ViewModel before onCreate call.");
        }
        return application;
    }
    @NonNull
    @MainThread
    public static ViewModelProvider of(@NonNull Fragment fragment, @Nullable Factory factory) {
   
   
        Application application = checkApplication(checkActivity(fragment));
        if (factory == null) {
   
   
            factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
        }
        return new ViewModelProvider(fragment.getViewModelStore(), factory);
    }

mApplication是Activity的成员变量,它是在attach时赋值的:

 final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
         
<think>好的,用户想了解Kotlin中的by关键字的作用。首先,我得回忆一下自己学过的Kotlin知识,特别是关于委托的部分。记得by关键字主要和委托模式相关,但可能还有其他用途吗? 首先,我应该确认by关键字的几个主要使用场景。比如类委托、属性委托,还有接口委托之类的。用户可能想知道这些不同的用法,所以需要分点说明,结构清晰。 然后,我需要具体举例说明每个场景。比如类委托,可以举一个Base类被Derived类通过by关键字委托给一个实例的例子。这样用户能直观地看到代码结构,理解如何通过by实现委托,避免重复代码。 接下来是属性委托,这部分可能更复杂。需要提到Kotlin标准库中的lazy、observable等委托,以及如何自定义委托类。比如lazy用于延迟初始化,observable用于属性变化的监听。这里可能需要写个例子,展示如何用by lazy初始化一个属性,或者用observable来监听变化。 另外,记得属性委托需要遵循一定的约定,比如提供getValue和setValue方法。这部分可能需要解释一下,但不用太深入,保持简洁。 还有,用户可能对委托模式的好处感兴趣,比如代码复用、减少样板代码,还有实现关注点分离。这些优点需要明确点出来,让用户明白为什么要使用by关键字。 有没有可能用户混淆了by的其他用途?比如在Kotlin协程或其他库中的用法?不过应该主要是在委托模式中使用。需要确认这一点,避免给出错误信息。 另外,需要检查是否有最新的Kotlin特性引入了by的新用法,但根据目前的知识,by主要还是用于类和属性委托。所以应该没问题。 最后,组织回答结构,先总述by关键字的作用,然后分点详细说明类委托和属性委托,给出代码示例,再总结优点。这样用户能够逐步理解,结构清晰。 可能需要验证一些代码示例是否正确,比如类委托的例子是否正确实现了接口的委托,属性委托的例子是否语法正确。确保LaTeX格式符合要求,行内公式用$...$,独立公式用$$...$$,但这里可能用不上数学公式,所以主要注意代码块的格式是否正确。 总结下来,回答应该分为几个部分:介绍by的作用,类委托的示例和解释,属性委托的示例和标准库委托的例子,自定义委托的简要说明,以及优点总结。这样用户就能全面了解by关键字的用途了。</think>在Kotlin中,`by`关键字主要用于实现**委托模式(Delegation)**,其核心作用是减少重复代码并增强可维护性。以下是分步骤解析: --- ### 一、类委托(Class Delegation) 通过`by`将接口的实现委托给另一个对象,避免手动转发所有方法。 #### 示例: ```kotlin interface Base { fun print() } class BaseImpl(val x: Int) : Base { override fun print() { println(x) } } // 将Base接口的实现委托给b对象 class Derived(b: Base) : Base by b fun main() { val b = BaseImpl(10) Derived(b).print() // 输出:10 } ``` - ✅ **优势**:当接口有多个方法时,无需在`Derived`类中逐个重写方法 --- ### 二、属性委托(Property Delegation) 通过`by`将属性的读写操作委托给特定对象,需满足约定:提供`getValue()`和`setValue()`方法。 #### 1. 标准库委托示例 - **延迟初始化(`lazy`)**: ```kotlin val heavyResource: Resource by lazy { Resource.load() // 首次访问时执行初始化 } ``` - **可观察属性(`observable`)**: ```kotlin var name: String by Delegates.observable("<no name>") { prop, old, new -> println("$old → $new") } ``` #### 2. 自定义委托 需实现`ReadWriteProperty`或`ReadOnlyProperty`接口: ```kotlin class StringDelegate(private var s: String = "") : ReadWriteProperty<Any?, String> { override fun getValue(thisRef: Any?, property: KProperty<*>): String = s override fun setValue(thisRef: Any?, property: KProperty<*>, value: String) { s = value.uppercase() // 自动转大写 } } var text by StringDelegate() text = "hello" // 实际存储为"HELLO" ``` --- ### 三、接口委托(Kotlin 1.4+) 允许直接委托给其他表达式: ```kotlin interface ApiService { fun fetchData(): String } class RealApi : ApiService { override fun fetchData() = "Data from server" } class MockApi : ApiService { override fun fetchData() = "Mock data" } // 动态选择委托对象 val service: ApiService by if (isTesting) { ::MockApi } else { ::RealApi } ``` --- ### 四、优点总结 | 场景 | 核心价值 | |--------------|------------------------------------| | 类委托 | 避免继承导致的脆弱基类问题 | | 属性委托 | 逻辑复用(如缓存、校验、日志) | | 动态委托 | 运行时灵活切换实现 | 通过合理使用`by`关键字,可以显著提升Kotlin代码的简洁性和扩展性。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值