Kotlin 通常要求我们在定义属性后立即对其进行初始化。当我们不知道理想的初始值时,这样做似乎很奇怪,尤其是在生命周期驱动的 Android 属性的情况下。

幸运的是,有一种方法可以解决这个问题。如果您声明一个类属性而不初始化它,IntelliJ IDEA 编辑器会警告您,并建议添加一个lateinit关键字。

如果初始化的属性或对象实际上并没有在程序中使用怎么办?好吧,这些未使用的初始化将成为程序的责任,因为对象创建是一个繁重的过程。这是另一个lateinit可以帮助我们的例子。

本文将解释lateinit修饰符和惰性委托如何处理未使用或不必要的早期初始化。这将使您的 Kotlin 开发工作流程更加高效。

  • lateinit在科特林

    • 主要特征

    • lateinit使用中的修饰符示例

    • 生命周期驱动的属性和lateinit

    • 何时使用lateinit

    • 使用时要记住的事情lateinit

  • Kotlin 中的懒惰代表团

    • 主要特征

    • 使用惰性委托的示例

    • 中间动作

    • Android 应用程序中的延迟委托

    • 和之间的区别by lazy``= lazy

    • 何时使用惰性

    • 使用时要记住的事情lazy

lateinit在科特林

lateinit关键字代表“后期初始化” 。当与类属性一起使用时,lateinit修饰符会阻止该属性在其类的对象构造时被初始化。

lateinit仅当变量稍后在程序中初始化时才分配内存,而不是在声明它们时。这在初始化的灵活性方面非常方便。

让我们看看必须提供的一些重要功能lateinit!

主要特征

lateinit首先,在声明时没有为属性分配内存。当您认为合适时,稍后会进行初始化。

一个lateinit属性可能在整个程序中多次更改,并且应该是可变的。这就是为什么您应该始终将其声明为 avar而不是 a valor const。

初始化可以使您免于在将属性初始化为可空类型时可能需要的lateinit重复空检查。属性的这个特性lateinit不支持可空类型。


超过 20 万开发人员使用 LogRocket 来创造更好的数字体验了解更多 →


扩展我的最后一点,lateinit可以很好地用于非原始数据类型。它不适用于像longor之类的原始类型int。这是因为每当lateinit访问一个属性时,Kotlin 都会在后台为其提供一个空值,以指示该属性尚未初始化。

原始类型不能是null,因此无法指示未初始化的属性。lateinit因此,原始类型在与关键字一起使用时会引发异常。

最后,一个lateinit属性必须在被访问之前的某个时间点被初始化,否则它会抛出一个UninitializedPropertyAccessException错误,如下所示:

在初始化之前访问的lateinit属性会导致此异常。

Kotlin 允许您检查lateinit属性是否已初始化。这可以很方便地处理我们刚刚讨论的未初始化异常。

lateinit var myLateInitVar: String
...
​
if(::myLateInitVar.isInitialized) {// Do something
}

lateinit使用中的修饰符示例

lateinit让我们通过一个简单的例子来看看修饰符的作用。下面的代码定义了一个类,并用 dummy 和 null 值初始化了它的一些属性。

class TwoRandomFruits {var fruit1: String = "tomato" var fruit2: String? = null
​
​fun randomizeMyFruits() {fruit1 = randomFruits()fruit2 = possiblyNullRandomFruits()}
​
​fun randomFruits(): String { ... }fun possiblyNullRandomFruits(): String? { ... }
}
​
fun main() {val rf= RandomFruits()rf.randomizeMyFruits()
​
​println(rf.fruit1.capitalize())println(rf.fruit2?.capitalize()) // Null-check
}

这不是初始化变量的最佳方法,但在这种情况下,它仍然可以完成工作。

正如您在上面看到的,如果您选择使属性可以为空,则无论何时修改或使用它都必须对其进行空检查。这可能相当乏味和烦人。


来自 LogRocket 的更多精彩文章:

  • 不要错过来自 LogRocket 的精选时事通讯The Replay

  • 了解LogRocket 的 Galileo 如何消除噪音以主动解决应用程序中的问题

  • 使用 React 的 useEffect优化应用程序的性能

  • 在多个 Node 版本之间切换

  • 了解如何使用 AnimXYZ 为您的 React 应用程序制作动画

  • 探索 Tauri,一个用于构建二进制文件的新框架

  • 比较NestJS 与 Express.js


lateinit让我们用修饰符解决这个问题:

class TwoRandomFruits {lateinit var fruit1: String // No initial dummy value neededlateinit var fruit2: String // Nullable type isn't supported here
​
​fun randomizeMyFruits() {fruit1 = randomFruits()fruit2 = when {possiblyNullRandomFruits() == null -> "Tomato" // Handling null valueselse -> possiblyNullRandomFruits()!!}}
​
​fun randomFruits(): String { ... }fun possiblyNullRandomFruits(): String? { ... }
}
​
fun main() {val rf= RandomFruits()rf.randomizeMyFruits()
​
​println(rf.fruit1.capitalize())println(rf.fruit2.capitalize())
}

您可以在此处查看此代码的运行情况。

该lateinit实现不言自明,并展示了一种处理变量的简洁方法!除了 的默认行为之外lateinit,这里的主要内容是我们可以多么容易地避免使用可空类型。

生命周期驱动的属性和lateinit

数据绑定是lateinit稍后用于初始化活动的另一个示例。开发人员通常希望更早地初始化绑定变量,以便在其他方法中将其用作访问不同视图的引用。

在下面的MainActivity类中,我们声明了与修饰符的绑定lateinit以实现相同的目的。

package com.test.lateinit
​
import androidx.appcompat.app.AppCompatActivity
import ...
​
class MainActivity : AppCompatActivity() {lateinit var binding: ActivityMainBinding
​override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
​...}...
}

只有在活动生命周期函数被触发后,MainActivity才能初始化绑定。因此,在这里用修饰符声明绑定是完全有意义的。onCreate()``lateinit

何时使用lateinit

使用常规变量初始化,您必须添加一个虚拟值,并且很可能是一个空值。这将在访问它们时添加大量空检查。

// Traditional initialization
var name: String? = null
...
name = getDataFromSomeAPI()
...
// A null-check will be required whenever `name` is accessed.
name?.let { it-> println(it.uppercase())
}
​
// Lateinit initialization
lateinit var name: String
...
name = getDatafromSomeAPI()
...
println(name.uppercase())

我们可以使用lateinit修饰符来避免这些重复的空检查,特别是当属性可能经常波动时。

使用时要记住的事情lateinit

最好记住lateinit在访问属性之前始终对其进行初始化,否则,您会在编译时看到一个很大的异常。

确保还通过使用var声明来保持属性可变。使用valandconst没有任何意义,因为它们表示不可变的属性lateinit将不起作用。

最后,避免lateinit在给定属性的数据类型是原始数据类型或空值的可能性很高时使用。它不适用于这些情况,并且不支持原始类型或可为空的类型。

Kotlin 中的懒惰代表团

顾名思义,lazy在 Kotlin 中以惰性方式初始化属性。本质上,它创建一个引用,但仅在第一次使用或调用该属性时进行初始化。

现在,您可能会问这与常规初始化有何不同。好吧,在类对象构造时,它的所有公共和私有属性都在其构造函数中初始化。初始化类中的变量会产生一些开销;变量越多,开销就越大。

让我们通过一个例子来理解它:

class X {fun doThis() {}
}
​
class Y {val shouldIdoThis: Boolean = SomeAPI.guide()val x = X()
​if(shouldIdoThis) {x.doThis()}...
}

尽管没有使用它,Y上面代码中的 class 仍然有一个由 class 创建的对象X。如果它是一个重度构建的类,它也会X减慢速度。Y

不必要的对象创建效率低下,可能会减慢当前类的速度。根据程序流程,在某些条件下可能不需要某些属性或对象。

也可能是属性或对象依赖于其他属性或对象来创建。分享三款浏览器扩展插件,广告拦截、垃圾内容、标签美化统统搞定!惰性委托有效地处理了这两种可能性。

主要特征

延迟初始化的变量在被调用或使用之前不会被初始化。这样,变量只被初始化一次,然后它的值被缓存以供程序进一步使用。

由于使用惰性委托初始化的属性应该始终使用相同的值,因此它本质上是不可变的,通常用于只读属性。您必须用val声明对其进行标记。

它是线程安全的,即只计算一次并默认由所有线程共享。一旦初始化,它就会在整个程序中记住或缓存初始化值。

与 相比lateinit,惰性委托支持自定义 setter 和 getter,允许它在读取和写入值时执行中间操作。

使用惰性委托的示例

下面的代码实现了简单的数学运算来计算某些形状的面积。在圆的情况下,计算将需要 的常数值pi。

class Area {val pi: Float = 3.14f
​
​fun circle(radius: Int): Float = pi * radius * radiusfun rectangle(length: Int, breadth: Int = length): Int = length * breadthfun triangle(base: Int, height: Int): Float = base * height * .5f
}
​
fun main() {val area = Area()val squareSideLength = 51
​
​println("Area of our rectangle is ${area.rectangle(squareSideLength)}")
}

正如你在上面看到的,没有完成任何圆的面积计算,使得我们的定义pi毫无用处。该属性pi仍会被初始化并分配内存。

让我们用惰性委托来纠正这个问题:

class Area {val pi: Float by lazy {3.14f}
​
​fun circle(...) = ...fun rectangle(...) = ...fun triangle(...) = ...
}
​
fun main() {val area = Area()val squareSideLength = 51val circleRadius = 37
​println("Area of our rectangle is ${area.rectangle(squareSideLength)}")println("Area of our circle is ${area.circle(circleRadius)}")
}

您可以在此处查看上述示例的演示。

上述惰性委托的实现pi仅在访问时使用。一旦被访问,它的值就会被缓存并保留在整个程序中使用。我们将在下一个示例中看到它与对象的作用。

中间动作

以下是在通过惰性委托写入值时如何添加一些中间操作的方法。以下代码在 Android 活动中lazy初始化 a 。TextView

每当TextView在 中第一次调用它时MainActivity,将记录带有LazyInit标签的调试消息,如下面委托的 lambda 函数所示:

...
class MainActivity : AppCompatActivity() {override fun onCreate(...) {...  val sampleTextView: TextView by lazy {Log.d("LazyInit", "sampleTextView")findViewById(R.id.sampleTextView)}}...
}

Android 应用程序中的延迟委托

现在让我们继续讨论延迟委托在 Android 应用程序中的应用。最简单的用例可以是我们之前有条件地使用和操作视图的 Android 活动示例。

package com.test.lazyimport androidx.appcompat.app.AppCompatActivity
import ...class MainActivity : AppCompatActivity() {lateinit var binding: ActivityMainBindingoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)binding = DataBindingUtil.setContentView(this, R.layout.activity_main)val sharedPrefs by lazy {activity?.getPreferences(Context.MODE_PRIVATE)} val startButton by lazy {binding.startButton}if(sharedPrefs.getBoolean("firstUse", true)) {startButton.isVisible = truestartButton.setOnClickListener {// Finish onboarding, move to main screen; something like thatsharedPrefs.setBoolean("firstUse", false)}}}
}

上面,我们用惰性委托初始化了SharedPreferencesand a 。Button该逻辑需要基于从共享偏好中获取的布尔值来实现入职屏幕。

和之间的区别by lazy``= lazy

该语句将惰性委托直接添加到给定属性的增强。它的初始化只会在第一次访问时发生一次。by lazy

val prop by lazy {...
}

另一方面,该语句包含对委托对象的引用,您可以通过它使用委托方法或使用属性访问它。= lazyisInitialized()value

val prop = lazy {...
}
...if(prop.isInitialized()) {println(prop.value)
}

您可以在此处查看上述代码的快速演示。

何时使用lazy

考虑使用惰性委托来减轻涉及多个和/或有条件地创建其他类对象的类。如果对象的创建依赖于类的内部属性,那么惰性委托是可行的方法。

class Employee {...fun showDetails(id: Int): List<Any> {val employeeRecords by lazy {EmployeeRecords(id) // Object's dependency on an internal property}}...
}

使用时要记住的事情lazy

延迟初始化是一个委托,它只初始化一次并且只在它被调用时初始化。这是为了避免不必要的对象创建。

委托对象缓存第一次访问时返回的值。需要时,此缓存值将在程序中进一步使用。

在读取和写入值时,您可以利用其自定义 getter 和 setter 进行中间操作。我也更喜欢将它与不可变类型一起使用,因为我觉得它最适合在整个程序中保持不变的值。

结论

在本文中,我们讨论了 Kotlin 的lateinit修饰符和惰性委托。我们展示了一些基本示例来展示它们的用途,并讨论了 Android 开发中的一些实际用例。

感谢您抽出宝贵时间阅读本入门指南!我希望您能够使用本指南在您的应用程序开发过程中实现这两个功能。

Kotlin 中的初始化lazy和变量lateinit相关推荐

  1. Kotlin专题「二」:变量(var与val)、常量、注释

    前言: 莫问良人长与短,从此山水不相逢. 一.概述   大家都知道 Kotlin 现在被 Gooogle 定为 Android 的官方开发语言.Kotlin 在项目中的使用将会越来越广泛,这也掀起了一 ...

  2. kotlin中的var和val与编译时常量

    我们都知道,在kotlin中,var定义的变量是可读可变的,而val定义的变量是只读不可变的,这是为什么呢?这里我们来看下面的代码: class Player{val name = "jac ...

  3. Scala中的延迟初始化(Lazy vals)

    延迟初始化(Lazy vals) 除了前面介绍的预先初始化成员值外,你还是让系统自行决定何时初始化成员的初始值,这是通过在 val 定义前面添加 lazy(懒惰),也是说直到你第一次需要引用该成员是, ...

  4. python实例变量初始化_Python – 应该在__init__中初始化所有成员变量

    也许这更像是一个样式问题,而不是技术问题,但我有一个带有几个成员变量的python类,我想让它工作,以便在用户首次创建类的实例时初始化一些成员变量(即在__init__函数中)我希望从稍后将调用的成员 ...

  5. C++中类成员变量在初始化列表中的初始化顺序

    引子:我们知道,C++中类成员变量的初始化顺序与其在类中的声明顺序是有关的. 先看代码: 1 class TestClass1 2 { 3 public: 4 TestClass1() { 5 cou ...

  6. Kotlin中变量不同于Java: var 对val(KAD 02)

    原文标题:Variables in Kotlin, differences with Java. var vs val (KAD 02) 作者:Antonio Leiva 时间:Nov 28, 201 ...

  7. [译]带你揭开Kotlin中属性代理和懒加载语法糖衣

    翻译说明: 原标题: How Kotlin's delegated properties and lazy-initialization work 原文地址: https://medium.com/t ...

  8. Kotlin中使用简洁明了的代码替换findViewByid

    第一种(推荐使用) Kotlin Android 扩展插件(Android Studio 内置) 首先添加 apply plugin: 'kotlin-android-extensions' 官方示例 ...

  9. java中使用kotlin_在Kotlin中使用libGDX

    java中使用kotlin 最近,我一直在阅读有关不同语言的信息,以及它们可以为已经拥挤的软件开发人员带来什么,并且一种语言对我来说很突出:Kotlin. ( https://kotlinlang.o ...

最新文章

  1. 利用MyEclipse开发一个调用webservice接口的程序
  2. ORB算法原理解读【不错】
  3. 神州易桥财税 java项目经理_【高级项目经理(体系运营)职责】2021年燕园财税高级项目经理(体系运营)岗位职责-看准网...
  4. ZooKeeper私人学习笔记
  5. plpythonu_postgresql plpythonu例子
  6. Echarts文字大小自适应,案例详解
  7. html重复div绘制,[DIV+CSS]绘制2重交叉表_html/css_WEB-ITnose
  8. 测试对于list的sort与sorted的效率
  9. iOS开发之$ pod setup时,CocoaPods报CocoaPods was not able to update the `master` repo.
  10. mybatis注解开发-动态SQL
  11. Android 启动模式及singleTask与Home键存在的问题
  12. 清华大学超级计算机中心,中国科学技术大学超级计算中心
  13. 标准误计算机excel公式,如何用excel或wps计算标准差、方差、标准误差?
  14. SQL案例分析之部分查询和全部查询
  15. 微信小程序开发——评论功能
  16. python中的方法是什么_Python方法
  17. 【水】【SCOI】 精简题解
  18. 鸭绒和鹅绒的区别RDS人道羽绒标准
  19. Golang源码探索----GC的实现原理(6)
  20. 如何理解逻辑回归中的似然函数

热门文章

  1. 客服系统对接微信公众号-访客在聊天界面扫码-临时访客绑定公众号OpenID可接收客服回复消息通知...
  2. IOCP编程之重叠IO
  3. Oracle ODP.NET ConnectionString接池及连接参数
  4. 玉雕工作室php,时晓印传承海派玉雕工艺印象记 - 大师风采 - 千秋宝玉雕工作室...
  5. 利用深度学习进行医疗图像分析【全】
  6. 木马、定制U盘和邮政快递
  7. DOG算子--------的特征提取(二)
  8. XSS漏洞原理和利用
  9. 视频管理服务器维护内容,视频管理服务器
  10. brackets编写java,Brackets - 一款免费的前端开发工具