วิธีการขยายแบบคงที่ใน Kotlin


145

คุณกำหนดวิธีการขยายแบบคงที่ใน Kotlin ได้อย่างไร? เป็นไปได้หรือไม่? ขณะนี้ฉันมีวิธีการขยายดังที่แสดงด้านล่าง

public fun Uber.doMagic(context: Context) {
    // ...
}

ส่วนขยายข้างต้นสามารถเรียกใช้ในอินสแตนซ์ได้

uberInstance.doMagic(context) // Instance method

แต่ฉันจะทำให้มันเป็นวิธีการคงที่ดังที่แสดงด้านล่างได้อย่างไร

Uber.doMagic(context)         // Static or class method

1
"วิธีการขยายแบบคงที่" หมายความว่าอย่างไร
Andrey Breslav

3
วิธีที่ฉันสามารถเรียกได้โดยไม่ต้องใช้อินสแตนซ์ แต่ยังคงเป็นส่วนเสริมของคลาส (วิธีการคงที่ Java ปกติ)
Ragunath Jawahar

ฉันคิดว่าอาจไม่ใช่การใช้คำถามที่ดีสำหรับส่วนขยายของKotlinฉันคิดถึงสิ่งเดียวกันในขณะที่พยายามคาดคะเนแนวคิด C # แต่ในทางปฏิบัติการใช้งานค่อนข้างคล้ายกัน ส่วนขยายของ Kotlin ในขณะที่พวกเขาอ้างว่ามีการส่งแบบคงที่ แต่พวกเขาก็รู้สึกเหมือน "แนบ" แบบไดนามิกหากมีสิ่งนั้นหรืออยู่ในขอบเขตที่ล่าช้ากับอินสแตนซ์ ถ้าฉันจำไม่ผิด ... หรือบางทีฉันก็เข้าใจผิดไปหมด :)
แดน

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

@AndreyBreslav คุณช่วยอัพเดทเราได้ไหมว่าจะอนุญาตให้ใช้ฟังก์ชันส่วนขยายแบบคงที่? ฉันคิดถึงมันเหมือนกัน
Lars Blumberg

คำตอบ:


148

เพื่อให้บรรลุUber.doMagic(context)คุณสามารถเขียนส่วนขยายไปยังวัตถุที่แสดงร่วมของUber(จำเป็นต้องมีการประกาศวัตถุร่วม):

class Uber {
    companion object {}
}

fun Uber.Companion.doMagic(context: Context) { }

80
นี่เป็นสิ่งที่ดี แต่ถ้าเป็นคลาส Java หรือคลาสที่ไม่มีคู่หูล่ะ?
Jire

31
@ Jire สำหรับกรณีดังกล่าวไม่มีการสนับสนุนใน Kotlin 1.0
Andrey Breslav

2
ฉันเห็น usecase ในการขยายคลาสเฟรมเวิร์ก JVM ด้วยค่าเริ่มต้นเช่น String.DEFAULT หรือ Int.DEFAULT ในกรณีที่ตรรกะของโดเมนของคุณต้องการสิ่งนี้ (ของฉันทำร่วมกับคลาสข้อมูลว่างเปล่าในปัจจุบัน)
Steffen Funke

36
ใช้งานได้ตราบเท่าที่Uberเป็นคลาสKotlinเท่านั้น คงจะดีถ้ามีความเป็นไปได้ที่จะขยายคลาสJavaแบบคงที่ด้วย
kosiara - Bartosz Kosarzycki

6
ยังไม่สามารถดูได้ที่นี่: youtrack.jetbrains.com/issue/KT-11968มีเธรด SO ที่เกี่ยวข้องอยู่ที่นี่: stackoverflow.com/questions/33911457/…
narko

12

นี่คือสิ่งที่เอกสารอย่างเป็นทางการกล่าวว่า:

Kotlin สร้างวิธีการคงที่สำหรับฟังก์ชันระดับแพ็คเกจ Kotlin ยังสามารถสร้างวิธีการแบบคงที่สำหรับฟังก์ชันที่กำหนดในอ็อบเจ็กต์ที่มีชื่อหรืออ็อบเจ็กต์ที่แสดงร่วมกันหากคุณใส่คำอธิบายประกอบฟังก์ชันเหล่านั้นเป็น @JvmStatic ตัวอย่างเช่น:

วิธีการคงที่ของ Kotlin

class C {
  companion object {
    @JvmStatic fun foo() {}
    fun bar() {}
  }
}

ตอนนี้ foo () เป็นแบบคงที่ใน Java ในขณะที่ bar () ไม่ใช่:

C.foo(); // works fine
C.bar(); // error: not a static method

8

ฉันมีคำถามที่แน่นอนนี้เมื่อ 30 นาทีที่แล้วดังนั้นฉันจึงเริ่มขุดคุ้ยและไม่พบวิธีแก้ปัญหาหรือวิธีแก้ไขใด ๆ สำหรับสิ่งนี้ แต่ในขณะที่ค้นหาฉันพบส่วนนี้ในเว็บไซต์ Kotlinglang ที่ระบุว่า:

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

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

(null as Type?).staticFunction(param1, param2)

ดังนั้นฉันจึงไปรอบ ๆ โดยการสร้าง val ในไฟล์นามสกุลของประเภทตัวรับที่มีค่าเป็นโมฆะแล้วใช้ในคลาสอื่นของฉัน ดังตัวอย่างต่อไปนี้เป็นวิธีที่ฉันใช้ฟังก์ชันส่วนขยาย "คงที่" สำหรับNavigationคลาสใน Android: ในไฟล์ NavigationExtensions.kt ของฉัน:

val SNavigation: Navigation? = null
fun Navigation?.createNavigateOnClickListener(@IdRes resId: Int, args: Bundle? = null, navOptions: NavOptions? = null,
                                                navigationExtras: Navigator.Extras? = null) : (View) -> Unit {
    //This is just implementation details, don't worry too much about them, just focus on the Navigation? part in the method declaration
    return { view: View -> view.navigate(resId, args, navOptions, navigationExtras) }
}

ในรหัสที่ใช้:

SNavigation.createNavigateOnClickListener(R.id.action_gameWonFragment_to_gameFragment)

เห็นได้ชัดว่านี่ไม่ใช่ชื่อคลาส แต่เป็นเพียงตัวแปรของประเภทคลาสที่มีค่า null เห็นได้ชัดว่าน่าเกลียดในฝั่งผู้สร้างส่วนขยาย (เพราะต้องสร้างตัวแปร) และฝั่งนักพัฒนา (เพราะต้องใช้STypeรูปแบบแทนชื่อคลาสจริง) แต่เป็นสิ่งที่ใกล้เคียงที่สุดที่สามารถทำได้ในตอนนี้ เมื่อเทียบกับฟังก์ชันคงที่จริง หวังว่าผู้ผลิตภาษา Kotlin จะตอบสนองต่อปัญหาที่สร้างขึ้นและเพิ่มคุณลักษณะนั้นในภาษา


2
แฮ็กนี่อาจเป็น แถมยังฉลาดเท่อีกด้วย นี่เป็นการตอบคำถามที่เจาะลึกที่สุด Kotlin พยายามอย่างดีที่สุดเท่าที่จะทำได้ในการปลอมแปลงที่ให้บริการส่วนขยายชั้นหนึ่งดังนั้น IMO คุณจึงใช้โซลูชันที่ดีที่สุดที่คุณสามารถทำได้เพื่อจัดการกับความไม่สมบูรณ์ของมัน ดี.
Travis Griggs

5

คุณสามารถสร้างวิธีการแบบคงที่โดยใช้วัตถุคู่หูเช่น:

class Foo {
    // ...
    companion object {
        public fun bar() {
            // do anything
        }
    }
}

จากนั้นคุณสามารถเรียกมันว่า:

class Baz {
    // ...
    private fun callBar() {
        Foo.bar()
    }
}

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

1
platformStaticถูกเปลี่ยนชื่อJvmStaticใน Kotlin ปัจจุบัน
Jayson Minard

0

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

package strings
public fun joinToString(...): String { ... }

นี่เท่ากับ

package strings;

public class JoinKt {
    public static String joinToString(...) { ... }
}

ด้วยค่าคงที่ทุกอย่างจะเหมือนกัน คำประกาศนี้

val UNIX_LINE_SEPARATOR = "\n"

เท่ากับ

public static final String UNIX_LINE_SEPARATOR = "\n";

-3

ฉันค่อนข้างชอบที่จะมีความเป็นไปได้ที่จะเพิ่มวิธีการขยายแบบคงที่ใน Kotlin วิธีแก้ปัญหาสำหรับตอนนี้ฉันกำลังเพิ่มเมธอด exntension ให้กับหลายคลาสแทนที่จะใช้วิธีการขยายแบบคงที่เดียวในทั้งหมด

class Util    

fun Util.isDeviceOnline(context: Context): Boolean {
    val connMgr = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
    val networkInfo = connMgr.activeNetworkInfo
    return networkInfo != null && networkInfo.isConnected
}

fun Activity.isDeviceOnline(context: Context) = { Util().isDeviceOnline(context) }
fun OkHttpClient.isDeviceOnline(context: Context) = { Util().isDeviceOnline(context) }

2
คำถามเดิมเกี่ยวกับวิธี javaclass สามารถขยายได้ด้วยวิธีการคง ในกรณีของคุณนั่นหมายถึงความสามารถในการโทรActivity.isDeviceOnline(...)โดยไม่ต้องมีอินสแตนซ์ของActivity.
Fabian Zeindl

-4

ในการสร้างวิธีการขยายใน kotlin คุณต้องสร้างไฟล์ kotlin (ไม่ใช่คลาส) จากนั้นประกาศเมธอดของคุณในไฟล์เช่น:

public fun String.toLowercase(){
    // **this** is the string object
}

นำเข้าฟังก์ชันในคลาสหรือไฟล์ที่คุณกำลังทำงานและใช้งาน


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