หลายตัวแปรให้ใน Kotlin


127

มีวิธีใดบ้างในการเชื่อมโยงหลายตัวให้กับตัวแปรที่เป็นโมฆะหลายตัวใน kotlin?

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

ฉันหมายถึงสิ่งนี้:

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

1
คุณต้องการ N รายการไม่ใช่แค่ 2? รายการทั้งหมดต้องเป็นประเภทเดียวกันหรือต่างกัน? ควรส่งค่าทั้งหมดไปยังฟังก์ชันเป็นรายการหรือเป็นพารามิเตอร์แต่ละตัว? ค่าที่ส่งคืนควรเป็นรายการเดียวหรือกลุ่มของรายการจำนวนเดียวกันกับอินพุต?
Jayson Minard

ฉันต้องการอาร์กิวเมนต์ทั้งหมดอาจเป็นสองข้อสำหรับกรณีนี้ แต่ก็อยากรู้วิธีทำเช่นนี้ให้มากขึ้นด้วยความรวดเร็วนั้นง่ายมาก
Daniel Gomez Rico

คุณกำลังมองหาสิ่งที่แตกต่างจากคำตอบด้านล่างหรือไม่หากเป็นเช่นนั้นแสดงความคิดเห็นว่าอะไรคือความแตกต่างที่คุณกำลังมองหา
Jayson Minard

จะอ้างถึง "it" ตัวแรกภายใน let block ที่สองได้อย่างไร
Javier Mendonça

คำตอบ:


48

หากสนใจนี่คือสองฟังก์ชันของฉันสำหรับการแก้ปัญหานี้

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())
    }
}

การใช้งาน:


// 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)
}

นี่เป็นสิ่งที่ดีมาก แต่ฉันยังขาดกรณีที่ฉันสามารถใช้อินพุตแรกในวินาทีได้ ตัวอย่าง: ifLet ("A", toLower (first)) {// first = "A", second = "a"}
Otziii

สาเหตุในคำสั่ง ifLet อาร์กิวเมนต์แรกยังไม่ถูกคลายออกฟังก์ชันอย่างของคุณเป็นไปไม่ได้ ฉันสามารถแนะนำให้ใช้ guardLet ได้หรือไม่? ตรงไปตรงมาสวยมาก val (first) = guardLet (100) {return} val (second) = guardLet (101) {return} val average = average (ครั้งแรกวินาที) ฉันรู้ว่านั่นไม่ใช่สิ่งที่คุณถาม แต่หวังว่ามันจะช่วยได้
Dario Pellegrini

ขอบคุณ ฉันมีหลายวิธีในการแก้ปัญหานี้เหตุผลที่บอกก็คือใน Swift เป็นไปได้ที่จะมี ifLets หลายรายการหลังจากแต่ละคนคั่นด้วยเครื่องหมายจุลภาคและสามารถใช้ตัวแปรของการตรวจสอบก่อนหน้านี้ได้ ฉันหวังว่าสิ่งนี้จะเป็นไปได้ใน Kotlin เช่นกัน :)
Otziii

1
อาจเป็นคำตอบที่ยอมรับได้ แต่มีค่าใช้จ่ายในการโทรทุกครั้ง เนื่องจาก vm สร้างวัตถุ Function ประการแรก นอกจากนี้เมื่อพิจารณาถึงข้อ จำกัด dex สิ่งนี้จะเพิ่มการประกาศคลาสฟังก์ชันพร้อมการอ้างอิง 2 วิธีสำหรับการตรวจสอบที่ไม่ซ้ำกันทุกครั้ง
Oleksandr Albul

147

ต่อไปนี้เป็นรูปแบบบางส่วนขึ้นอยู่กับว่าคุณต้องการใช้สไตล์ใดหากคุณมีทุกอย่างเหมือนกันหรือต่างกันและหากไม่ทราบจำนวนรายการ ...

ประเภทผสมทั้งหมดต้องไม่เป็นค่าว่างเพื่อคำนวณค่าใหม่

สำหรับประเภทผสมคุณสามารถสร้างชุดฟังก์ชันสำหรับการนับพารามิเตอร์แต่ละตัวที่อาจดูไร้สาระ แต่ใช้ได้ดีกับประเภทผสม:

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

ตัวอย่างการใช้งาน:

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

ดำเนินการบล็อกรหัสเมื่อรายการไม่มีรายการว่าง

สองรสชาติที่นี่อันดับแรกจะดำเนินการบล็อกโค้ดเมื่อรายการมีรายการที่ไม่ใช่ค่าว่างทั้งหมดและอย่างที่สองจะทำเช่นเดียวกันเมื่อรายการมีอย่างน้อยหนึ่งรายการที่ไม่ใช่รายการว่าง ทั้งสองกรณีส่งผ่านรายการที่ไม่ใช่ค่าว่างไปยังบล็อกของรหัส:

ฟังก์ชั่น:

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())
    }
}

ตัวอย่างการใช้งาน:

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"

การเปลี่ยนแปลงเล็กน้อยเพื่อให้ฟังก์ชันรับรายชื่อรายการและดำเนินการแบบเดียวกัน:

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())
    }
}

ตัวอย่างการใช้งาน:

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

let()การเปลี่ยนแปลงเหล่านี้อาจมีการเปลี่ยนแปลงที่จะมีค่าที่ส่งคืนเช่น

ใช้รายการแรกที่ไม่ใช่ค่าว่าง (Coalesce)

คล้ายกับฟังก์ชัน SQL Coalesce ส่งคืนรายการแรกที่ไม่ใช่ค่าว่าง ฟังก์ชั่นสองรสชาติ:

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

ตัวอย่างการใช้งาน:

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"

รูปแบบอื่น ๆ

... มีรูปแบบอื่น ๆ แต่หากมีข้อกำหนดมากกว่านี้อาจทำให้แคบลงได้


1
นอกจากนี้คุณยังสามารถรวมwhenAllNotNullกับ destructuring listOf(a, b, c).whenAllNotNull { (d, e, f) -> println("$d $e $f")ชอบโดย:
dumptruckman

10

คุณสามารถเขียนฟังก์ชันของคุณเองสำหรับสิ่งนั้น:

 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

คุณสามารถสร้างarrayIfNoNullsฟังก์ชัน:

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

จากนั้นคุณสามารถใช้สำหรับตัวแปรจำนวนค่าด้วยlet:

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

หากคุณมีอาร์เรย์อยู่แล้วคุณสามารถสร้างtakeIfNoNullsฟังก์ชัน (แรงบันดาลใจจากtakeIfและrequireNoNulls):

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

ตัวอย่าง:

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

3

สำหรับกรณีตรวจสอบค่าสองค่าและไม่ต้องทำงานกับรายการ:

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

ตัวอย่างการใช้งาน:

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

2

จริงๆแล้วคุณทำได้ง่ายๆนะรู้ยัง? ;)

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

ไม่มีอะไรผิดปกติในการใช้การตรวจสอบค่าว่างแบบปกติใน Kotlin

และทุกคนที่จะตรวจสอบโค้ดของคุณสามารถอ่านได้มากขึ้น


36
มันจะไม่เพียงพอเมื่อต้องจัดการกับสมาชิกชั้นเรียนที่ไม่แน่นอน
Michał K

3
ไม่จำเป็นต้องให้คำตอบแบบนี้ความตั้งใจของคำถามคือการค้นหา "วิธีการผลิต" ที่มีประสิทธิผลมากขึ้นเนื่องจากภาษาเป็นletทางลัดในการตรวจสอบเหล่านี้
Alejandro Moya

1
ในแง่ของการบำรุงรักษานี่เป็นทางเลือกของฉันแม้ว่ามอกจะไม่สวยหรูก็ตาม นี่เป็นปัญหาที่ทุกคนต้องเผชิญอยู่ตลอดเวลาและภาษาควรจัดการด้วย
Brill Pappin

2

ฉันชอบแก้ปัญหาโดยใช้ฟังก์ชั่นตัวช่วยต่อไปนี้:

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)

และนี่คือวิธีที่คุณควรใช้:

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

ฉันแก้ไขสิ่งนี้โดยการสร้างฟังก์ชั่นบางอย่างที่จำลองพฤติกรรมของด้วยมากหรือน้อย แต่รับพารามิเตอร์หลายตัวและเรียกใช้ฟังก์ชันของพารามิเตอร์ทั้งหมดเท่านั้นที่ไม่ใช่ค่าว่าง

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) } } } } }

จากนั้นฉันจะใช้มันดังนี้:

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")

ปัญหาที่ชัดเจนเกี่ยวกับเรื่องนี้คือฉันต้องกำหนดฟังก์ชันสำหรับแต่ละกรณี (จำนวนตัวแปร) ที่ฉันต้องการ แต่อย่างน้อยฉันก็คิดว่าโค้ดดูสะอาดเมื่อใช้


1

คุณสามารถทำได้เช่นกัน

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

คอมไพเลอร์จะยังคงบ่นว่าไม่สามารถรับประกันได้ว่า vars ไม่เป็นโมฆะ
Peter Graham

1

ฉันได้อัปเกรดคำตอบที่คาดไว้เล็กน้อย:

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
}

สิ่งนี้ทำให้เป็นไปได้:

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

เยี่ยมมาก แต่ไม่มีการตั้งชื่อพารามิเตอร์และควรแบ่งประเภท
Daniel Gomez Rico

0

สำหรับจำนวนค่าที่จะตรวจสอบคุณสามารถใช้สิ่งนี้:

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

และจะใช้ในลักษณะนี้:

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

    checkNulls(dada, dede) { strings ->

    }

องค์ประกอบที่ส่งไปยังบล็อกใช้สัญลักษณ์แทนคุณต้องตรวจสอบประเภทหากคุณต้องการเข้าถึงค่าหากคุณต้องการใช้เพียงประเภทเดียวคุณสามารถกลายพันธุ์สิ่งนี้เป็นยาสามัญได้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.