Nhiều biến cho phép trong Kotlin


127

Có cách nào để xâu chuỗi nhiều phép cho nhiều biến nullable trong kotlin không?

fun example(first: String?, second: String?) {
    first?.let {
        second?.let {
            // Do something just if both are != null
        }
    }
}

Ý tôi là, một cái gì đó như thế này:

fun example(first: String?, second: String?) {
    first?.let && second?.let { 
        // Do something just if both are != null
    }
}

1
Bạn có muốn N mục, không chỉ 2? Tất cả các mặt hàng cần cùng một loại, hay khác loại? Nên chuyển tất cả các giá trị vào hàm, dưới dạng danh sách hay dưới dạng các tham số riêng lẻ? Giá trị trả về nên là một mục duy nhất hay một nhóm có cùng số mục như đầu vào?
Jayson Minard

Tôi cần tất cả các đối số, có thể là hai cho trường hợp này nhưng cũng muốn biết một cách để làm điều này cho nhiều hơn, nhanh chóng là rất dễ dàng.
Daniel Gomez Rico

Bạn có đang tìm kiếm điều gì khác biệt so với những câu trả lời bên dưới không, nếu có hãy comment điểm khác biệt mà bạn đang tìm kiếm là gì.
Jayson Minard

Làm thế nào nó sẽ được tham chiếu đến "nó" đầu tiên trong khối let thứ hai?
Javier Mendonça

Câu trả lời:


48

Nếu quan tâm, đây là hai chức năng của tôi để giải quyết vấn đề này.

inline fun <T: Any> guardLet(vararg elements: T?, closure: () -> Nothing): List<T> {
    return if (elements.all { it != null }) {
        elements.filterNotNull()
    } else {
        closure()
    }
}

inline fun <T: Any> ifLet(vararg elements: T?, closure: (List<T>) -> Unit) {
    if (elements.all { it != null }) {
        closure(elements.filterNotNull())
    }
}

Sử dụng:


// Will print
val (first, second, third) = guardLet("Hello", 3, Thing("Hello")) { return }
println(first)
println(second)
println(third)

// Will return
val (first, second, third) = guardLet("Hello", null, Thing("Hello")) { return }
println(first)
println(second)
println(third)

// Will print
ifLet("Hello", "A", 9) {
 (first, second, third) ->
 println(first)
 println(second)
 println(third)
}

// Won't print
ifLet("Hello", 9, null) {
 (first, second, third) ->
 println(first)
 println(second)
 println(third)
}

Điều này rất hay, nhưng tôi vẫn thiếu một trường hợp mà tôi có thể sử dụng đầu vào đầu tiên trong thứ hai. Ví dụ: ifLet ("A", toLower (first)) {// first = "A", second = "a"}
Otziii

Nguyên nhân trong câu lệnh ifLet, đối số đầu tiên chưa được bỏ gói, một hàm như của bạn không khả thi. Tôi có thể đề nghị sử dụng GuardLet không? Nó khá thẳng về phía trước. val (đầu tiên) = GuardLet (100) {return} val (thứ hai) = GuardLet (101) {return} val average = Average (đầu tiên, thứ hai) Tôi biết đó không phải là những gì bạn đã hỏi, nhưng hy vọng nó sẽ hữu ích.
Dario Pellegrini

Cảm ơn. Tôi có nhiều cách để giải quyết vấn đề này, lý do để nói là trong Swift có thể có nhiều ifLets sau mỗi if được phân tách bằng dấu phẩy và chúng có thể sử dụng các biến của lần kiểm tra trước. Tôi ước điều này cũng có thể thực hiện được ở Kotlin. :)
Otziii

1
Nó có thể được chấp nhận câu trả lời, nhưng có phí cho mỗi cuộc gọi. Vì vm tạo đối tượng Hàm trước hết. Cũng xem xét giới hạn dex, điều này sẽ thêm khai báo lớp Hàm với 2 tham chiếu phương thức cho mỗi lần kiểm tra duy nhất.
Oleksandr Albul

147

Dưới đây là một vài biến thể, tùy thuộc vào phong cách bạn muốn sử dụng, nếu bạn có mọi thứ giống nhau hoặc khác loại và nếu danh sách không xác định số lượng mục ...

Các loại hỗn hợp, tất cả không được rỗng để tính giá trị mới

Đối với các kiểu hỗn hợp, bạn có thể xây dựng một loạt các hàm cho mỗi số tham số trông có vẻ ngớ ngẩn, nhưng hoạt động tốt cho các kiểu hỗn hợp:

inline fun <T1: Any, T2: Any, R: Any> safeLet(p1: T1?, p2: T2?, block: (T1, T2)->R?): R? {
    return if (p1 != null && p2 != null) block(p1, p2) else null
}
inline fun <T1: Any, T2: Any, T3: Any, R: Any> safeLet(p1: T1?, p2: T2?, p3: T3?, block: (T1, T2, T3)->R?): R? {
    return if (p1 != null && p2 != null && p3 != null) block(p1, p2, p3) else null
}
inline fun <T1: Any, T2: Any, T3: Any, T4: Any, R: Any> safeLet(p1: T1?, p2: T2?, p3: T3?, p4: T4?, block: (T1, T2, T3, T4)->R?): R? {
    return if (p1 != null && p2 != null && p3 != null && p4 != null) block(p1, p2, p3, p4) else null
}
inline fun <T1: Any, T2: Any, T3: Any, T4: Any, T5: Any, R: Any> safeLet(p1: T1?, p2: T2?, p3: T3?, p4: T4?, p5: T5?, block: (T1, T2, T3, T4, T5)->R?): R? {
    return if (p1 != null && p2 != null && p3 != null && p4 != null && p5 != null) block(p1, p2, p3, p4, p5) else null
}
// ...keep going up to the parameter count you care about

Ví dụ sử dụng:

val risk = safeLet(person.name, person.age) { name, age ->
  // do something
}   

Thực thi khối mã khi danh sách không có mục rỗng

Hai cách ở đây, đầu tiên để thực thi khối mã khi danh sách có tất cả các mục không phải là null và thứ hai để thực hiện tương tự khi danh sách có ít nhất một mục không phải là rỗng. Cả hai trường hợp đều chuyển một danh sách các mục không rỗng vào khối mã:

Chức năng:

fun <T: Any, R: Any> Collection<T?>.whenAllNotNull(block: (List<T>)->R) {
    if (this.all { it != null }) {
        block(this.filterNotNull()) // or do unsafe cast to non null collectino
    }
}

fun <T: Any, R: Any> Collection<T?>.whenAnyNotNull(block: (List<T>)->R) {
    if (this.any { it != null }) {
        block(this.filterNotNull())
    }
}

Ví dụ sử dụng:

listOf("something", "else", "matters").whenAllNotNull {
    println(it.joinToString(" "))
} // output "something else matters"

listOf("something", null, "matters").whenAllNotNull {
    println(it.joinToString(" "))
} // no output

listOf("something", null, "matters").whenAnyNotNull {
    println(it.joinToString(" "))
} // output "something matters"

Một chút thay đổi để chức năng nhận danh sách các mục và thực hiện các thao tác tương tự:

fun <T: Any, R: Any> whenAllNotNull(vararg options: T?, block: (List<T>)->R) {
    if (options.all { it != null }) {
        block(options.filterNotNull()) // or do unsafe cast to non null collection
    }
}

fun <T: Any, R: Any> whenAnyNotNull(vararg options: T?, block: (List<T>)->R) {
    if (options.any { it != null }) {
        block(options.filterNotNull())
    }
}

Ví dụ sử dụng:

whenAllNotNull("something", "else", "matters") {
    println(it.joinToString(" "))
} // output "something else matters"

Các biến thể này có thể được thay đổi để có các giá trị trả về như let().

Sử dụng mục không rỗng đầu tiên (Coalesce)

Tương tự như một hàm SQL Coalesce, trả về mục không rỗng đầu tiên. Hai hương vị của hàm:

fun <T: Any> coalesce(vararg options: T?): T? = options.firstOrNull { it != null }
fun <T: Any> Collection<T?>.coalesce(): T? = this.firstOrNull { it != null }

Ví dụ sử dụng:

coalesce(null, "something", null, "matters")?.let {
    it.length
} // result is 9, length of "something"

listOf(null, "something", null, "matters").coalesce()?.let {
    it.length
}  // result is 9, length of "something"

Các biến thể khác

... Có những biến thể khác, nhưng với nhiều đặc điểm kỹ thuật hơn, điều này có thể được thu hẹp lại.


1
Bạn cũng có thể kết hợp whenAllNotNullvới destructuring như vậy: listOf(a, b, c).whenAllNotNull { (d, e, f) -> println("$d $e $f").
dumptruckman

10

Bạn có thể viết hàm của riêng mình cho điều đó:

 fun <T, U, R> Pair<T?, U?>.biLet(body: (T, U) -> R): R? {
     val first = first
     val second = second
     if (first != null && second != null) {
         return body(first, second)
     }
     return null
 }

 (first to second).biLet { first, second -> 
      // body
 }

7

Bạn có thể tạo một arrayIfNoNullshàm:

fun <T : Any> arrayIfNoNulls(vararg elements: T?): Array<T>? {
    if (null in elements) {
        return null
    }
    @Suppress("UNCHECKED_CAST")
    return elements as Array<T>
}

Sau đó, bạn có thể sử dụng nó cho một số giá trị thay đổi với let:

fun example(first: String?, second: String?) {
    arrayIfNoNulls(first, second)?.let { (first, second) ->
        // Do something if each element is not null
    }
}

Nếu bạn đã có một mảng, bạn có thể tạo một takeIfNoNullshàm (lấy cảm hứng từ takeIfrequireNoNulls):

fun <T : Any> Array<T?>.takeIfNoNulls(): Array<T>? {
    if (null in this) {
        return null
    }
    @Suppress("UNCHECKED_CAST")
    return this as Array<T>
}

Thí dụ:

array?.takeIfNoNulls()?.let { (first, second) ->
    // Do something if each element is not null
}

3

Đối với trường hợp chỉ kiểm tra hai giá trị và cũng không phải làm việc với danh sách:

fun <T1, T2> ifNotNull(value1: T1?, value2: T2?, bothNotNull: (T1, T2) -> (Unit)) {
    if (value1 != null && value2 != null) {
        bothNotNull(value1, value2)
    }
}

Ví dụ sử dụng:

var firstString: String?
var secondString: String?
ifNotNull(firstString, secondString) { first, second -> Log.d(TAG, "$first, $second") }

2

Trên thực tế, bạn có thể đơn giản làm điều này, bạn biết không? ;)

if (first != null && second != null) {
    // your logic here...
}

Không có gì sai khi sử dụng kiểm tra null thông thường trong Kotlin.

Và nó dễ đọc hơn cho tất cả những người sẽ xem mã của bạn.


36
Sẽ không đủ khi đối phó với một thành viên trong lớp có thể thay đổi.
Michał K,

3
Không cần phải cung cấp cho loại câu trả lời, mục đích của câu hỏi là để tìm một "cách hiệu quả" hơn xử lý này, vì ngôn ngữ cung cấp các letphím tắt để làm những kiểm tra
Alejandro Moya

1
Về khả năng bảo trì, đây là sự lựa chọn của tôi, ngay cả khi nó không thanh lịch. Đây rõ ràng là một vấn đề mà mọi người luôn gặp phải và ngôn ngữ nên giải quyết.
Brill Pappin

2

Tôi thực sự thích giải quyết nó bằng cách sử dụng các hàm trợ giúp sau:

fun <A, B> T(tuple: Pair<A?, B?>): Pair<A, B>? =
    if(tuple.first == null || tuple.second == null) null
    else Pair(tuple.first!!, tuple.second!!)

fun <A, B, C> T(tuple: Triple<A?, B?, C?>): Triple<A, B, C>? =
    if(tuple.first == null || tuple.second == null || tuple.third == null) null
    else Triple(tuple.first!!, tuple.second!!, tuple.third!!)


fun <A, B> T(first: A?, second: B?): Pair<A, B>? =
    if(first == null || second == null) null
    else Pair(first, second)

fun <A, B, C> T(first: A?, second: B?, third: C?): Triple<A, B, C>? =
        if(first == null || second == null || third == null) null
        else Triple(first, second, third)

Và đây là cách bạn nên sử dụng chúng:

val a: A? = someValue
val b: B? = someOtherValue
T(a, b)?.let { (a, b) ->
  // Shadowed a and b are of type a: A and b: B
  val c: C? = anotherValue
  T(a, b, c)
}?.let { (a, b, c) ->
  // Shadowed a, b and c are of type a: A, b: B and c: C
  .
  .
  .
}

1

Tôi đã giải quyết điều này bằng cách tạo một số hàm ít nhiều sao chép hành vi của với, nhưng nhận nhiều tham số và chỉ gọi hàm của tất cả các tham số là khác rỗng.

fun <R, A, B> withNoNulls(p1: A?, p2: B?, function: (p1: A, p2: B) -> R): R? = p1?.let { p2?.let { function.invoke(p1, p2) } }
fun <R, A, B, C> withNoNulls(p1: A?, p2: B?, p3: C?, function: (p1: A, p2: B, p3: C) -> R): R? = p1?.let { p2?.let { p3?.let { function.invoke(p1, p2, p3) } } }
fun <R, A, B, C, D> withNoNulls(p1: A?, p2: B?, p3: C?, p4: D?, function: (p1: A, p2: B, p3: C, p4: D) -> R): R? = p1?.let { p2?.let { p3?.let { p4?.let { function.invoke(p1, p2, p3, p4) } } } }
fun <R, A, B, C, D, E> withNoNulls(p1: A?, p2: B?, p3: C?, p4: D?, p5: E?, function: (p1: A, p2: B, p3: C, p4: D, p5: E) -> R): R? = p1?.let { p2?.let { p3?.let { p4?.let { p5?.let { function.invoke(p1, p2, p3, p4, p5) } } } } }

Sau đó, tôi sử dụng nó như thế này:

withNoNulls("hello", "world", Throwable("error")) { p1, p2, p3 ->
    p3.printStackTrace()
    p1.plus(" ").plus(p2)
}?.let {
    Log.d("TAG", it)
} ?: throw Exception("One or more parameters was null")

Vấn đề rõ ràng với điều này là tôi phải xác định một hàm cho từng trường hợp (số lượng biến) tôi cần, nhưng ít nhất tôi nghĩ rằng mã trông sạch sẽ khi sử dụng chúng.


1

Bạn cũng có thể làm điều này

if (listOfNotNull(var1, var2, var3).size == 3) {
        // All variables are non-null
}

Trình biên dịch vẫn sẽ phàn nàn rằng nó không thể đảm bảo rằng các vars không bị rỗng
Peter Graham

1

Tôi đã nâng cấp câu trả lời mong đợi một chút:

inline fun <T: Any, R: Any> ifLet(vararg elements: T?, closure: (List<T>) -> R): R? {
    return if (elements.all { it != null }) {
        closure(elements.filterNotNull())
    } else null
}

điều này làm cho điều này có thể:

iflet("first", "sconed") {
    // do somehing
} ?: run {
    // do this if one of the params are null
}

Thật tuyệt, nhưng các tham số không được đặt tên và nên chia sẻ loại.
Daniel Gomez Rico

0

Đối với bất kỳ lượng giá trị nào được kiểm tra, bạn có thể sử dụng:

    fun checkNulls(vararg elements: Any?, block: (Array<*>) -> Unit) {
        elements.forEach { if (it == null) return }
        block(elements.requireNoNulls())
    }

Và nó sẽ được sử dụng như thế này:

    val dada: String? = null
    val dede = "1"

    checkNulls(dada, dede) { strings ->

    }

các phần tử được gửi đến khối đang sử dụng ký tự đại diện, bạn cần kiểm tra các loại nếu bạn muốn truy cập các giá trị, nếu bạn chỉ cần sử dụng một loại, bạn có thể biến đổi loại này thành generic

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.