Kotlin: อินเทอร์เฟซ ... ไม่มีตัวสร้าง


140

ฉันกำลังแปลงรหัส Java ของฉันเป็น Kotlin และฉันไม่ค่อยเข้าใจวิธีอินสแตนซ์อินเทอร์เฟซที่กำหนดไว้ในโค้ด Kotlin ตัวอย่างเช่นฉันมีอินเทอร์เฟซ (กำหนดในโค้ด Java):

public interface MyInterface {
    void onLocationMeasured(Location location);
}

จากนั้นต่อไปในรหัส Kotlin ของฉันฉันสร้างอินเทอร์เฟซนี้:

val myObj = new MyInterface { Log.d("...", "...") }

และใช้งานได้ดี อย่างไรก็ตามเมื่อฉันแปลง MyInterface เป็น Kotlin:

interface MyInterface {
    fun onLocationMeasured(location: Location)
}

ฉันได้รับข้อความแสดงข้อผิดพลาด: Interface MyListener does not have constructorsเมื่อฉันพยายามสร้างอินสแตนซ์ - แม้ว่าฉันจะดูเหมือนว่าไม่มีอะไรเปลี่ยนแปลงนอกจากไวยากรณ์ ฉันเข้าใจผิดว่าอินเทอร์เฟซทำงานอย่างไรใน Kotlin หรือไม่

คำตอบ:


227

โค้ด Java ของคุณอาศัยการแปลง SAM ซึ่งเป็นการแปลงแลมด้าโดยอัตโนมัติให้เป็นอินเทอร์เฟซด้วยวิธีนามธรรมเดียว ขณะนี้ยังไม่รองรับการแปลง SAM สำหรับอินเทอร์เฟซที่กำหนดไว้ใน Kotlin คุณต้องกำหนดอ็อบเจ็กต์ที่ไม่ระบุชื่อเพื่อใช้งานอินเทอร์เฟซ:

val obj = object : MyInterface {
    override fun onLocationMeasured(location: Location) { ... }
}

14
ขอบคุณมาก. จากลิงค์ที่คุณโพสต์ฉันเข้าใจว่าควรใช้ประเภทการทำงาน (เช่นLocation -> Unit) แทนอินเตอร์เฟสวิธีเดียวถ้าเป็นไปได้ - ถูกต้องหรือไม่?
Aleph Aleph

4
ถูกต้อง. คุณควรใช้ประเภทการทำงานทุกที่ที่เป็นไปได้
Yoav Sternberg

อย่างไรก็ตามในกรณีของฉันอินเทอร์เฟซ (SurfaceTextureListener) มีหลายวิธี
Tash Pemhiwa

ขอบคุณมาก. คำตอบนี้จะต้องมีจำนวนไลค์มากกว่าหรือเครื่องหมายพิเศษเนื่องจากเป็นข้อมูลที่มีประโยชน์มากและน่าเสียดายที่ผู้เรียนบางคนอาจสับสนมากเมื่อเรียนรู้ Kotlin จากบทความหรือโดย "Kotlin in Action" เมื่อดูหัวข้อ SAM
TT_W

จาก "Kotlin in Action" ใช่คุณสามารถใช้ lambda ในพารามิเตอร์ SAM ของ Java เพื่อย่อและทำความสะอาดโค้ด แต่ไม่ใช่ SAM ของ Kotlin ประเภทฟังก์ชันเป็นคลาสแรกใน Kotlin ดังนั้น SAM จึงไม่มีความหมายสำหรับ Kotlin ประเภทฟังก์ชันที่มี typealias เป็นสไตล์ Kotlin มากขึ้น
vg0x00

18

ทางออกที่ดีที่สุดคือใช้ typealias แทนอินเทอร์เฟซ Java ของคุณ

typealias MyInterface = (Location) -> Unit

fun addLocationHandler(myInterface:MyInterface) {

}

ลงทะเบียนดังนี้:

val myObject = { location -> ...}
addLocationHandler(myObject)

หรือแม้กระทั่งสะอาดกว่า

addLocationHandler { location -> ...}

วิงวอนเช่นนี้:

myInterface.invoke(location)

3 ตัวเลือกปัจจุบันดูเหมือนจะเป็น:

  • typealias (ยุ่งเมื่อเรียกจาก java)
  • อินเทอร์เฟซ kotlin (ยุ่งเมื่อถูกเรียกจาก kotlin คุณต้องสร้างวัตถุ) นี่คือก้าวถอยหลังครั้งใหญ่ของ IMO
  • อินเทอร์เฟซ java (ยุ่งน้อยลงเมื่อเรียกจาก kotlin แลมบ์ดาต้องการชื่ออินเทอร์เฟซที่นำหน้าดังนั้นคุณจึงไม่ต้องการวัตถุนอกจากนี้ยังไม่สามารถใช้แลมบ์ดานอกการประชุมวงเล็บฟังก์ชัน)

เมื่อแปลงไลบรารีของเราเป็น Kotlin เราทิ้งอินเทอร์เฟซทั้งหมดไว้ในโค้ด Java เนื่องจากการเรียก Java จาก Kotlin นั้นสะอาดกว่า Kotlin จาก Kotlin


9

พยายามเข้าถึงอินเทอร์เฟซของคุณดังนี้:

 object : MyInterface {
    override fun onSomething() { ... }
}

7

หากคุณมีคลาส Javaเช่นนี้:

recyclerView.addOnItemTouchListener(new RecyclerTouchListener(getActivity(), recyclerView, new RecyclerTouchListener.ClickListener()
        {
              //Your Code
        }));

คุณ shoud แปลงรหัสนี้จาก Java เป็นKotlinดังนี้:

override fun showJozList (list : List<ResponseGetJuzList.Parameter4>) {
        adapter.addData(list)
        jozlist_recycler.addOnItemTouchListener(RecyclerTouchListener(
                activity ,
                jozlist_recycler ,
                object : RecyclerTouchListener.ClickListener
                    {
                          //Your Code
                    }))

แปลงอินเตอร์เฟส Java :

new RecyclerTouchListener.ClickListener()

เป็นรูปแบบอินเตอร์เฟส Kotlin :

object : RecyclerTouchListener.ClickListener

4

การแปลง SAM ได้รับการสนับสนุนสำหรับอินเทอร์เฟซที่กำหนดไว้ใน Kotlin ตั้งแต่ 1.4.0

มีอะไรใหม่ใน Kotlin 1.4.0

ก่อน Kotlin 1.4.0คุณสามารถใช้การแปลง SAM (Single Abstract Method) เฉพาะเมื่อทำงานกับเมธอด Java และอินเทอร์เฟซ Java จาก Kotlin จากนี้ไปคุณสามารถใช้การแปลง SAM สำหรับอินเตอร์เฟส Kotlin ได้เช่นกัน ในการทำเช่นนั้นให้ทำเครื่องหมายอินเทอร์เฟซ Kotlin อย่างชัดเจนว่าใช้งานได้กับfunตัวปรับแต่ง

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

ตัวอย่างในคำถามของคุณจะมีลักษณะดังนี้:

fun interface MyInterface
{
    fun onLocationMeasured(location: String)
}

fun main()
{
    val myObj = MyInterface { println(it) }

    myObj.onLocationMeasured("New York")
}

1

หากอินเทอร์เฟซใช้สำหรับเมธอด Listener ของคลาสให้เปลี่ยนนิยามอินเตอร์เฟสเป็นประเภทฟังก์ชัน นั่นทำให้โค้ดมีความกระชับมากขึ้น ดูสิ่งต่อไปนี้

คลาสที่มีคำจำกัดความของผู้ฟัง

// A class
private var mLocationMeasuredListener = (location: Location) -> Unit = {}

var setOnLocationMeasuredListener(listener: (location: Location) -> Unit) {
    mLocationMeasuredListener = listener
}

// somewhere in A class
mLocationMeasuredListener(location)

ชั้นเรียนอื่น

// B class
aClass.setOnLocationMeasuredListener { location ->
    // your code
}

0
class YourClass : YourInterface {  
    override fun getTest() = "test"    
}

interface YourInterface {
    fun getTest(): String
}

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