Поскольку мне нравится понимать, как вещи работают и почему они сделаны такими, какие они есть, я задался вопросом о методе with в Котлине. Но прежде чем идти дальше, для тех, кто не знает, with часто используется, чтобы избежать повторения одного и того же объекта, когда мы хотим использовать его снова и снова. Например, когда вы привязываете свой ViewHolder к RecyclerView, вы часто получаете что-то вроде этого:

override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
  Person person = persons[position]
  holder.tvTirstName.text = person.firstName
  holder.tvLastName.text = person.lastName
  holder.tvAge.text = person.age.toString()
  Glide.with(context).load(person.avatarURL).into(holder.ivAvatar)
}

Тот же пример с использованием with будет выглядеть следующим образом:

override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
  with(persons[position]) {
    holder.tvTirstName.text = firstName
    holder.tvLastName.text = lastName
    holder.tvAge.text = age.toString()
    Glide.with(context).load(avatarURL).into(holder.ivAvatar)
  }
}

Как видите, это позволило нам не повторять Person снова и снова. Разумеется, перенос метода привязки в ViewHolder устранит необходимость в holder. каждый раз.

Итак, вам может быть интересно, что было для меня таким странным в with, что я написал об этом статью. Для меня это шло вразрез с принципами Котлина. Действительно, with может получить значение nullable, и какой смысл было окружать мой вызов if (object != null) { with(object) {...} }, когда они могли бы сделать object?.with{...} . Затем, когда вы посмотрите на доступные методы, вы увидите, что уже есть функция расширения, которая делает именно это: run. Так в чем смысл with ?

Чтобы понять, я начал с просмотра исходного кода и, что наиболее важно, части, выделенной жирным шрифтом:

@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T.() -> R): R = receiver.block()

Как разработчик Android, первое, что мне понравилось в Kotlin, — это понятие nullable. Но в этом коде их, похоже, не волнует, что параметр receiver потенциально равен нулю. Поэтому у меня возник еще один вопрос, как это на самом деле компилируется и работает? Итак, я попытался выяснить, что произойдет, если я скомпилирую и запущу этот код:

val test : String? = null
with(test) {
  println(test.toString())
}

На самом деле он компилирует и печатает null. Я был удивлен, так как мне не нужно было говорить test?.toString(), и он печатал «значение» моей переменной. Означало ли это, что внутри моего with он не считается нулевым, даже если значение равно null ? Я был в замешательстве, поэтому я попробовал что-то еще:

val test : String? = null
with(test) {
  println(this.length)
  println(this.toString())
}

Но этот не компилируется, так как test по-прежнему имеет значение null, и мы должны использовать оператор безопасного вызова ?, чтобы код скомпилировался:

val test : String? = null
with(test) {
  println(this?.length)
  println(this.toString())
}

Итак, что происходит, как возможно, что block вызывается в receiver в методе with? Почему нам не нужно использовать оператор безопасного вызова для toString, но мы делаем это для length? Ответ на самом деле находится в документации, что-то, что я знал бы, если бы прочитал все это, что вы, конечно, сделали, и поэтому вы уже знаете, что происходит. Он называется получатели, допускающие значение NULL. При создании функции расширения для объекта, допускающего значение NULL, метод должен обрабатывать случай, когда объект имеет значение NULL. Вот почему with позволяет выполнять код, даже если значение равно null.

Это относится к block, полученному with, а также к toString(), и именно поэтому я мог вызвать this.toString() без оператора безопасного вызова!
Документация показывает нам код toString():

fun Any?.toString(): String {
    if (this == null) return "null"
    // after the null check, 'this' is autocast to a non-null type,
    // so the toString() below
    // resolves to the member function of the Any class
    return toString()
}

Вот и все! with позволяет выполнить код, даже если значение равно null, потому что в некоторых случаях это именно то, что вам нужно. Это предотвратит повторение имени переменной и сделает код более простым для чтения.
С другой стороны, если вы не хотите выполнять код при нулевом значении и иметь те же преимущества, что и with, вам следует использовать ?.run .

Можно возразить, что они могли не включать with в Kotlin, но это правда, что для большинства with(object) {...} читается лучше. Это остается на ваше усмотрение :)