การส่งสมาร์ทเป็น 'ประเภท' เป็นไปไม่ได้เนื่องจาก 'ตัวแปร' เป็นคุณสมบัติที่ไม่แน่นอนที่อาจเปลี่ยนแปลงได้ในเวลานี้


275

และมือใหม่ Kotlin ถามว่า "ทำไมรหัสต่อไปนี้ไม่คอมไพล์?":

    var left: Node? = null

    fun show() {
         if (left != null) {
             queue.add(left) // ERROR HERE
         }
    }

การส่งสมาร์ทไปที่ 'โหนด' เป็นไปไม่ได้เพราะ 'ซ้าย' เป็นคุณสมบัติที่ไม่แน่นอนที่อาจเปลี่ยนแปลงได้ในเวลานี้

ฉันได้รับนั่นleftคือตัวแปรที่ผันแปรได้ แต่ฉันกำลังตรวจสอบอย่างชัดเจนleft != nullและleftเป็นประเภทNodeดังนั้นทำไมจึงไม่สามารถส่งไปที่ประเภทนั้นได้อย่างชาญฉลาด

ฉันจะแก้ไขสิ่งนี้ได้อย่างสวยงามได้อย่างไร :)


3
อยู่ระหว่างเธรดอื่นอาจเปลี่ยนค่าเป็น null อีกครั้ง ฉันค่อนข้างแน่ใจว่าคำตอบสำหรับคำถามอื่น ๆ พูดถึงเช่นกัน
nhaarman

3
คุณสามารถใช้การโทรที่ปลอดภัยเพื่อเพิ่ม
Whymarrh

ขอบคุณ @nhaarman ที่เหมาะสมแล้ว Whymarrh ทำยังไงดีล่ะ? ฉันคิดว่าการโทรที่ปลอดภัยนั้นมีไว้สำหรับวัตถุที่ไม่ใช่วิธีการเท่านั้น
FRR

6
บางอย่างที่ชอบ: n.left?.let { queue.add(it) }ฉันคิดว่า?
Jorn Vernee

คำตอบ:


358

ระหว่างการดำเนินการของleft != nullและqueue.add(left)หัวข้ออื่นอาจมีการเปลี่ยนแปลงค่าของการleftnull

เพื่อหลีกเลี่ยงปัญหานี้คุณมีตัวเลือกมากมาย นี่คือบางส่วน:

  1. ใช้ตัวแปรโลคัลพร้อมตัวส่งอัจฉริยะ:

    val node = left
    if (node != null) {
        queue.add(node)
    }
  2. ใช้การโทรที่ปลอดภัยเช่นหนึ่งในสิ่งต่อไปนี้:

    left?.let { node -> queue.add(node) }
    left?.let { queue.add(it) }
    left?.let(queue::add)
  3. ใช้โอเปอเรเตอร์ Elvisด้วยreturnเพื่อย้อนกลับก่อนจากฟังก์ชั่นการปิดล้อม:

    queue.add(left ?: return)

    โปรดทราบว่าbreakและcontinueสามารถนำไปใช้ในทำนองเดียวกันสำหรับการตรวจสอบภายในลูป


8
4. คิดถึงวิธีแก้ปัญหาที่ใช้งานได้มากกว่าสำหรับปัญหาของคุณที่ไม่ต้องการตัวแปรที่ไม่แน่นอน
Good Night Nerd Pride

1
@sak มันเป็นตัวอย่างของNodeระดับที่กำหนดไว้ในรุ่นเดิมของคำถามที่มีโค้ดมีความซับซ้อนมากขึ้นด้วยแทนที่จะn.left leftฉันได้อัพเดตคำตอบแล้ว ขอบคุณ
mfulton26

1
@sak ใช้แนวคิดเดียวกัน คุณสามารถสร้างใหม่valสำหรับแต่ละvarซ้อนหลาย?.letงบหรือใช้หลาย?: returnงบขึ้นอยู่กับฟังก์ชันของคุณ MyAsyncTask().execute(a1 ?: return, a2 ?: return, a3 ?: return)เช่น นอกจากนี้คุณยังสามารถลองหนึ่งของการแก้ปัญหาสำหรับ"Let ตัวแปรหลายรายการ"
mfulton26

1
@FARID ที่อ้างถึงใคร
mfulton26

3
ใช่มันปลอดภัย เมื่อตัวแปรถูกประกาศเป็น class global เธรดใด ๆ สามารถแก้ไขค่าของมันได้ แต่ในกรณีของตัวแปรโลคัล (ตัวแปรที่ประกาศภายในฟังก์ชัน) ตัวแปรนั้นไม่สามารถเข้าถึงได้จากเธรดอื่นดังนั้นจึงปลอดภัยที่จะใช้
Farid

31

1)นอกจากนี้คุณยังสามารถใช้lateinitถ้าคุณแน่ใจว่าจะเริ่มต้นในภายหลังonCreate()หรือที่อื่น ๆ

ใช้สิ่งนี้

lateinit var left: Node

แทนที่จะเป็นแบบนี้

var left: Node? = null

2)และมีวิธีอื่นที่ใช้!!จุดสิ้นสุดของตัวแปรเมื่อคุณใช้มันเช่นนี้

queue.add(left!!) // add !!

มันทำอะไร?
c-an

@ c-an ทำให้ตัวแปรของคุณเริ่มต้นเป็นโมฆะ แต่คาดว่าจะเริ่มต้นในภายหลังในรหัส
Radesh

งั้นมันก็ไม่เหมือนกันเหรอ? @Radesh
c-an

@ c-an เดียวกันกับอะไร
Radesh

1
ฉันตอบคำถามข้างต้นว่า Smart cast to 'Node' เป็นไปไม่ได้เพราะ 'left' เป็นคุณสมบัติที่ไม่แน่นอนที่สามารถเปลี่ยนแปลงได้ในเวลานี้รหัสนี้ป้องกันข้อผิดพลาดนั้นโดยระบุชนิดของตัวแปร คอมไพเลอร์จึงไม่จำเป็นต้องมีนักแสดงอัจฉริยะ
Radesh

27

มีตัวเลือกที่สี่นอกเหนือจากคำตอบของ mfulton26

โดยการใช้?.โอเปอเรเตอร์มันเป็นไปได้ที่จะเรียกเมธอดและฟิลด์โดยไม่ต้องจัดการletหรือใช้ตัวแปรโลคอล

รหัสบางส่วนสำหรับบริบท:

var factory: ServerSocketFactory = SSLServerSocketFactory.getDefault();
socket = factory.createServerSocket(port)
socket.close()//smartcast impossible
socket?.close()//Smartcast possible. And works when called

มันใช้งานได้กับวิธีการทุ่งนาและสิ่งอื่น ๆ ที่ฉันพยายามทำให้มันใช้งานได้

ดังนั้นเพื่อแก้ไขปัญหาแทนที่จะต้องใช้การส่งคำสั่งแบบแมนนวลหรือการใช้ตัวแปรโลคัลคุณสามารถใช้?.เพื่อเรียกเมธอด

สำหรับการอ้างอิงนี้ได้รับการทดสอบใน Kotlin 1.1.4-3แต่ยังผ่านการทดสอบในและ1.1.51 1.1.60ไม่มีการรับประกันว่าใช้งานได้กับเวอร์ชันอื่นมันอาจเป็นคุณสมบัติใหม่

การใช้?.โอเปอเรเตอร์ไม่สามารถใช้ในกรณีของคุณเนื่องจากเป็นตัวแปรที่ส่งผ่านซึ่งเป็นปัญหา ตัวดำเนินการ Elvis สามารถใช้เป็นทางเลือกและอาจเป็นรหัสที่ต้องใช้จำนวนน้อยที่สุด แทนที่จะใช้continueแม้ว่าreturnสามารถใช้งานได้เช่นกัน

การใช้การคัดเลือกนักแสดงด้วยตนเองอาจเป็นตัวเลือก แต่สิ่งนี้ไม่ปลอดภัย:

queue.add(left as Node);

หมายความว่าถ้าซ้ายมีการเปลี่ยนแปลงในเธรดอื่นโปรแกรมจะหยุดทำงาน


เท่าที่ฉันเข้าใจ '?.' โอเปอเรเตอร์กำลังตรวจสอบว่าตัวแปรทางด้านซ้ายเป็นโมฆะหรือไม่ในตัวอย่างด้านบนจะเป็น 'คิว' ข้อผิดพลาด 'ไม่สามารถส่งสมาร์ท' หมายถึงพารามิเตอร์ "ซ้าย" ที่ถูกส่งผ่านไปยังวิธีการ "เพิ่ม" ... ฉันยังคงได้รับข้อผิดพลาดหากฉันใช้วิธีนี้
FRR

ใช่ข้อผิดพลาดเปิดอยู่leftและไม่queueทำงาน จำเป็นต้องตรวจสอบนี้จะแก้ไขคำตอบในนาที
โซอี้

4

เหตุผลจริงที่ว่าทำไมสิ่งนี้ถึงใช้งานไม่ได้ไม่เกี่ยวข้องกับกระทู้ ประเด็นก็คือว่าจะแปลอย่างมีประสิทธิภาพในnode.leftnode.getLeft()

ผู้ได้รับทรัพย์สินนี้อาจถูกกำหนดเป็น:

val left get() = if (Math.random() < 0.5) null else leftPtr

ดังนั้นการโทรสองครั้งอาจไม่ส่งคืนผลลัพธ์เดียวกัน



1

ทำเช่นนี้:

var left: Node? = null

fun show() {
     val left = left
     if (left != null) {
         queue.add(left) // safe cast succeeds
     }
}

ซึ่งดูเหมือนจะเป็นตัวเลือกแรกจากคำตอบที่ยอมรับ แต่นั่นคือสิ่งที่คุณกำลังมองหา


นี่คือเงาของตัวแปร "ซ้าย" หรือไม่
AFD

ซึ่งก็โอเคโดยสิ้นเชิง ดูreddit.com/r/androiddev/comments/fdp2zq/…
EpicPandaForce

1

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


เช่นบน Android

เป็น:

class MyVM : ViewModel() {
    fun onClick() {}
}

สารละลาย:

From: private lateinit var viewModel: ViewModel
To: private lateinit var viewModel: MyVM

การใช้งาน:

viewModel = ViewModelProvider(this)[MyVM::class.java]
viewModel.onClick {}

GL


1

ทางออกที่ดีที่สุดของคุณจะต้อง:

var left: Node? = null

fun show() {
    left?.also {
        queue.add( it )
    }
}

จากนั้นคุณไม่จำเป็นต้องกำหนดตัวแปรเฉพาะที่ใหม่และไม่จำเป็นและคุณไม่มีคำยืนยันหรือ casts ใหม่ (ซึ่งไม่ใช่ DRY) ฟังก์ชันขอบเขตอื่น ๆ สามารถใช้งานได้ดังนั้นโปรดเลือกรายการที่คุณชื่นชอบ


0

ลองใช้ตัวดำเนินการยืนยันที่ไม่ใช่ค่าว่าง ...

queue.add(left!!) 

3
เป็นอันตราย ด้วยเหตุผลเดียวกันการหล่ออัตโนมัติไม่ทำงาน
จาค็อบซิมเมอร์แมน

3
อาจทำให้แอปขัดข้องหากปล่อยทิ้งไว้เป็นโมฆะ
Pritam Karmakar

0

ฉันจะเขียนมันอย่างไร:

var left: Node? = null

fun show() {
     val left = left ?: return
     queue.add(left) // no error because we return if it is null
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.