ก่อนอื่นคุณควรอ่านข้อมูลทั้งหมดเกี่ยวกับNull Safetyใน Kotlin ซึ่งครอบคลุมทุกกรณีอย่างละเอียด
ใน Kotlin คุณไม่สามารถเข้าถึงค่า nullable โดยไม่แน่ใจว่าไม่ใช่null
(การตรวจสอบ null ในเงื่อนไข ) หรือยืนยันว่ามันไม่ได้null
ใช้ตัว!!
ดำเนินการที่แน่นอนเข้าถึงโดยใช้?.
Safe Callหรือให้สิ่งที่อาจnull
เป็น ค่าเริ่มต้นโดยใช้?:
Elvis Operatorเอลวิสผู้ประกอบการ
สำหรับกรณีที่ 1 ของคุณในคำถามของคุณคุณมีตัวเลือกขึ้นอยู่กับความตั้งใจของรหัสที่คุณจะใช้อย่างใดอย่างหนึ่งเหล่านี้และทั้งหมดเป็นสำนวน แต่มีผลลัพธ์ที่แตกต่างกัน:
val something: Xyz? = createPossiblyNullXyz()
// access it as non-null asserting that with a sure call
val result1 = something!!.foo()
// access it only if it is not null using safe operator,
// returning null otherwise
val result2 = something?.foo()
// access it only if it is not null using safe operator,
// otherwise a default value using the elvis operator
val result3 = something?.foo() ?: differentValue
// null check it with `if` expression and then use the value,
// similar to result3 but for more complex cases harder to do in one expression
val result4 = if (something != null) {
something.foo()
} else {
...
differentValue
}
// null check it with `if` statement doing a different action
if (something != null) {
something.foo()
} else {
someOtherAction()
}
สำหรับ "ทำไมมันทำงานเมื่อตรวจสอบเป็นโมฆะ" อ่านข้อมูลพื้นหลังด้านล่างในสมาร์ทคาสท์
สำหรับกรณีที่ 2 ของคุณในคำถามของคุณในคำถามด้วยMap
หากคุณในฐานะนักพัฒนามีความมั่นใจในผลลัพธ์ที่ไม่เคยเกิดขึ้นnull
ให้ใช้!!
ตัวดำเนินการที่แน่นอนเป็นการยืนยัน:
val map = mapOf("a" to 65,"b" to 66,"c" to 67)
val something = map.get("a")!!
something.toLong() // now valid
หรือในกรณีอื่นเมื่อ map COULD ส่งคืนค่า null แต่คุณสามารถระบุค่าเริ่มต้นMap
ได้เองมีgetOrElse
วิธีการดังนี้
val map = mapOf("a" to 65,"b" to 66,"c" to 67)
val something = map.getOrElse("z") { 0 } // provide default value in lambda
something.toLong() // now valid
ข้อมูลพื้นฐาน:
บันทึก: ในตัวอย่างด้านล่างฉันใช้ประเภทที่ชัดเจนเพื่อทำให้พฤติกรรมชัดเจน ด้วยการอนุมานชนิดโดยปกติแล้วชนิดสามารถละเว้นสำหรับตัวแปรโลคัลและสมาชิกส่วนตัว
เพิ่มเติมเกี่ยวกับ !!
ผู้ประกอบการที่แน่นอน
!!
ผู้ประกอบการอ้างว่ามีค่าเป็นไม่ได้null
หรือพ่น NPE null
นี้ควรจะใช้ในกรณีที่นักพัฒนาจะรับประกันได้ว่าคุ้มค่าจะไม่ คิดว่ามันเป็นยืนยันที่ตามด้วยสหล่อสมาร์ท
val possibleXyz: Xyz? = ...
// assert it is not null, but if it is throw an exception:
val surelyXyz: Xyz = possibleXyz!!
// same thing but access members after the assertion is made:
possibleXyz!!.foo()
อ่านเพิ่มเติม: !! ผู้ประกอบการที่แน่นอน
เพิ่มเติมเกี่ยวกับการnull
ตรวจสอบและการส่งข้อมูลอย่างชาญฉลาด
หากคุณป้องกันการเข้าถึงประเภท nullable ด้วยการnull
ตรวจสอบคอมไพเลอร์จะส่งค่าภายในเนื้อความของคำสั่งให้เป็น non-nullable มีบางกระแสที่ซับซ้อนซึ่งสิ่งนี้ไม่สามารถเกิดขึ้นได้ แต่สำหรับกรณีทั่วไปก็ใช้งานได้ดี
val possibleXyz: Xyz? = ...
if (possibleXyz != null) {
// allowed to reference members:
possiblyXyz.foo()
// or also assign as non-nullable type:
val surelyXyz: Xyz = possibleXyz
}
หรือถ้าคุณทำการis
ตรวจสอบประเภทที่ไม่ใช่โมฆะ:
if (possibleXyz is Xyz) {
// allowed to reference members:
possiblyXyz.foo()
}
และเช่นเดียวกันสำหรับนิพจน์ 'เมื่อ' ที่ยังปลอดภัย:
when (possibleXyz) {
null -> doSomething()
else -> possibleXyz.foo()
}
// or
when (possibleXyz) {
is Xyz -> possibleXyz.foo()
is Alpha -> possibleXyz.dominate()
is Fish -> possibleXyz.swim()
}
บางสิ่งไม่อนุญาตให้ใช้การnull
ตรวจสอบเพื่อส่งสมาร์ทสำหรับการใช้ตัวแปรในภายหลัง ตัวอย่างข้างต้นใช้ตัวแปรท้องถิ่นว่าไม่มีทางที่จะได้กลายพันธุ์ในการไหลของแอพลิเคชันไม่ว่าจะเป็นval
หรือตัวแปรนี้มีโอกาสที่จะกลายพันธุ์ไม่เป็นvar
null
แต่ในกรณีอื่น ๆ ที่คอมไพเลอร์ไม่สามารถรับประกันการวิเคราะห์การไหลได้นี่จะเป็นข้อผิดพลาด:
var nullableInt: Int? = ...
public fun foo() {
if (nullableInt != null) {
// Error: "Smart cast to 'kotlin.Int' is impossible, because 'nullableInt' is a mutable property that could have been changed by this time"
val nonNullableInt: Int = nullableInt
}
}
วงจรชีวิตของตัวแปรnullableInt
ไม่สามารถมองเห็นได้อย่างสมบูรณ์และอาจได้รับมอบหมายจากกระทู้อื่น ๆ การnull
ตรวจสอบไม่สามารถส่งค่าสมาร์ทลงในค่าที่ไม่เป็นโมฆะ ดูหัวข้อ "การโทรอย่างปลอดภัย" ด้านล่างเพื่อดูวิธีแก้ปัญหา
อีกกรณีหนึ่งที่ไม่สามารถเชื่อถือได้โดยสมาร์ทคาสต์เพื่อไม่กลายพันธุ์คือval
คุณสมบัติบนวัตถุที่มีทะเยอทะยานที่กำหนดเอง ในกรณีนี้คอมไพเลอร์ไม่สามารถมองเห็นสิ่งที่เปลี่ยนแปลงค่าและดังนั้นคุณจะได้รับข้อความแสดงข้อผิดพลาด:
class MyThing {
val possibleXyz: Xyz?
get() { ... }
}
// now when referencing this class...
val thing = MyThing()
if (thing.possibleXyz != null) {
// error: "Kotlin: Smart cast to 'kotlin.Int' is impossible, because 'p.x' is a property that has open or custom getter"
thing.possiblyXyz.foo()
}
อ่านเพิ่มเติม: การตรวจสอบค่าว่างในเงื่อนไข
เพิ่มเติมเกี่ยวกับ ?.
ผู้ประกอบการโทรปลอดภัย
ผู้ประกอบการโทรปลอดภัยส่งกลับ null ถ้าค่าทางด้านซ้ายเป็นโมฆะมิฉะนั้นยังคงประเมินนิพจน์ทางด้านขวา
val possibleXyz: Xyz? = makeMeSomethingButMaybeNullable()
// "answer" will be null if any step of the chain is null
val answer = possibleXyz?.foo()?.goo()?.boo()
อีกตัวอย่างหนึ่งที่คุณต้องการย้ำรายการ แต่ถ้าnull
ไม่ว่างเปล่าอีกครั้งผู้ประกอบการโทรปลอดภัยก็มีประโยชน์:
val things: List? = makeMeAListOrDont()
things?.forEach {
// this loops only if not null (due to safe call) nor empty (0 items loop 0 times):
}
หนึ่งในตัวอย่างข้างต้นเรามีกรณีที่เราได้อีกด้วยif
การตรวจสอบ แต่มีโอกาสที่หัวข้ออื่นกลายพันธุ์คุณค่าและจึงไม่หล่อสมาร์ท เราสามารถเปลี่ยนตัวอย่างนี้เพื่อใช้ตัวดำเนินการโทรที่ปลอดภัยพร้อมกับlet
ฟังก์ชั่นเพื่อแก้ปัญหานี้:
var possibleXyz: Xyz? = 1
public fun foo() {
possibleXyz?.let { value ->
// only called if not null, and the value is captured by the lambda
val surelyXyz: Xyz = value
}
}
อ่านเพิ่มเติม: การโทรอย่างปลอดภัย
เพิ่มเติมเกี่ยวกับ?:
Elvis Operator
ผู้ประกอบการ Elvis ช่วยให้คุณสามารถให้ค่าทางเลือกเมื่อการแสดงออกทางด้านซ้ายของผู้ประกอบการคือnull
:
val surelyXyz: Xyz = makeXyzOrNull() ?: DefaultXyz()
มันมีการใช้ความคิดสร้างสรรค์เช่นกันยกตัวอย่างเช่นยกเว้นบางสิ่งเมื่อnull
:
val currentUser = session.user ?: throw Http401Error("Unauthorized")
หรือเพื่อกลับก่อนจากฟังก์ชั่น:
fun foo(key: String): Int {
val startingCode: String = codes.findKey(key) ?: return 0
// ...
return endingValue
}
อ่านเพิ่มเติม: Elvis Operator
ผู้ประกอบการที่ไม่มีฟังก์ชั่นที่เกี่ยวข้อง
Kotlin stdlib มีชุดของฟังก์ชั่นที่ใช้งานได้ดีจริง ๆ กับตัวดำเนินการที่กล่าวถึงข้างต้น ตัวอย่างเช่น:
// use ?.let() to change a not null value, and ?: to provide a default
val something = possibleNull?.let { it.transform() } ?: defaultSomething
// use ?.apply() to operate further on a value that is not null
possibleNull?.apply {
func1()
func2()
}
// use .takeIf or .takeUnless to turn a value null if it meets a predicate
val something = name.takeIf { it.isNotBlank() } ?: defaultName
val something = name.takeUnless { it.isBlank() } ?: defaultName
หัวข้อที่เกี่ยวข้อง
ใน Kotlin แอปพลิเคชันส่วนใหญ่พยายามหลีกเลี่ยงnull
ค่าต่างๆ แต่ไม่สามารถทำได้เสมอไป และบางครั้งก็null
สมเหตุสมผลดี แนวทางที่ควรพิจารณา:
ในบางกรณีมันรับประกันประเภทผลตอบแทนที่แตกต่างกันซึ่งรวมถึงสถานะของการเรียกวิธีการและผลถ้าประสบความสำเร็จ ไลบรารี่เช่นผลลัพธ์จะให้ผลลัพธ์ที่สำเร็จหรือล้มเหลวซึ่งสามารถแยกรหัสของคุณได้ และห้องสมุดสัญญาสำหรับ Kotlin ที่ชื่อว่าKovenantก็ทำเช่นเดียวกันในรูปแบบของสัญญา
สำหรับคอลเลกชันที่เป็นประเภทส่งคืนจะส่งคืนคอลเลกชันที่ว่างเปล่าแทนที่จะเป็นnull
เว้นแต่คุณจะต้องการสถานะที่สามของ "ไม่แสดง" Kotlin มีฟังก์ชันผู้ช่วยเช่นemptyList()
หรือemptySet()
เพื่อสร้างค่าว่างเหล่านี้
เมื่อใช้วิธีการที่ส่งคืนค่า nullable ที่คุณมีค่าเริ่มต้นหรือทางเลือกให้ใช้ตัวดำเนินการ Elvis เพื่อระบุค่าเริ่มต้น ในกรณีของการMap
ใช้งานgetOrElse()
ซึ่งอนุญาตให้สร้างค่าเริ่มต้นแทนMap
วิธีการget()
ที่ส่งกลับค่า nullable เหมือนกันสำหรับgetOrPut()
เมื่อแทนที่เมธอดจาก Java โดยที่ Kotlin ไม่แน่ใจเกี่ยวกับความสามารถในการลบล้างโค้ด Java คุณสามารถทิ้งความสามารถในการ?
ลบล้างจากการแทนที่ของคุณหากคุณแน่ใจว่าลายเซ็นและการทำงานควรเป็นอย่างไร ดังนั้นวิธีการแทนที่ของคุณจะnull
ปลอดภัยกว่า เช่นเดียวกับการใช้งานอินเทอร์เฟซ Java ใน Kotlin ให้เปลี่ยนค่าความสามารถเป็นสิ่งที่คุณรู้ว่าถูกต้อง
ดูฟังก์ชั่นที่สามารถช่วยได้อยู่แล้วเช่น for String?.isNullOrEmpty()
และString?.isNullOrBlank()
ซึ่งสามารถทำงานกับค่า nullable อย่างปลอดภัยและทำสิ่งที่คุณคาดหวัง ในความเป็นจริงคุณสามารถเพิ่มส่วนขยายของคุณเองเพื่อเติมช่องว่างใด ๆ ในไลบรารีมาตรฐาน
ฟังก์ชั่นการยืนยันเช่นcheckNotNull()
และrequireNotNull()
ในห้องสมุดมาตรฐาน
ฟังก์ชั่นตัวช่วยเช่นการfilterNotNull()
ลบค่า null จากคอลเลกชันหรือlistOfNotNull()
ส่งคืนรายการศูนย์หรือรายการเดียวจากnull
ค่าที่เป็นไปได้
มีโอเปอเรเตอร์การโยนที่ปลอดภัย (เป็นโมฆะ)เช่นกันที่อนุญาตให้ส่งคืนคาสต์ในประเภทที่ไม่เป็นโมฆะหากไม่สามารถทำได้ แต่ฉันไม่มีกรณีการใช้งานที่ถูกต้องสำหรับสิ่งนี้ที่ไม่ได้รับการแก้ไขโดยวิธีการอื่น ๆ ที่กล่าวถึงข้างต้น