เปลี่ยนโค้ด Kotlin ให้ Java-friendly มากขึ้นด้วย JVM Annotation กันเถอะ
ใจมันอยากจะ Pure Kotlin นะ แต่บางกรณีก็ยังต้องเจอกับ Java อยู่ดี
ผมก็เป็นคนหนึ่งที่มีความอวยในภาษา Kotlin เป็นอย่างมาก และในทุกวันนี้ก็เลือกที่จะเขียนโค้ดเป็น Kotlin มากกว่า Java และเดาว่านักพัฒนาหลายๆคนที่ได้ลิ้มลองภาษา Kotlin กันก็คงคิดเช่นเดียวกับผม
แต่สิ่งหนึ่งที่เลี่ยงไม่ได้ก็คือเราไม่สามารถเขียนแต่ Kotlin อย่างเดียว โดยไม่สนใจ Java เลย เพราะในตอนที่เขียน Kotlin นั้น เราจะรู้สึกว่า Syntax มันช่างสวยงามเหลือเกิน แต่หารู้ไม่ว่าเมื่อเราเรียกโค้ดเหล่านั้นใน Java จะพบว่ามันไม่ได้สวยงามอย่างที่คิดเลยซักนิด
ยกตัวอย่างเช่น HomeFragment
ที่มี Static Method ชื่อว่า newInstance()
ที่สามารถพบเจอได้ทั่วไป
ตอนที่เรียกใช้งาน HomeFragment
ใน Kotlin ด้วยกันก็ไม่มีปัญหาอะไรหรอก จนกระทั่งเอาไปเรียกใช้งานใน Java เท่านั้นแหละ
จะเห็นว่าจากเดิมที่เรียก HomeFragment.newInstance()
ได้เลย ต้องใช้ HomeFragment.Companion.newInstance()
ใน Java ซะงั้น
ก็ช่างมันสิ ทำไมเราต้องแคร์ Java ในเมื่อเขียน Kotlin อยู่แล้ว?
ถ้าเราไม่ได้ต้องไปยุ่งกับโค้ด Java เลย เราก็ไม่ต้องซีเรียสกับปัญหานี้เลย แต่ทว่าถ้า
- คุณต้องเขียน Library ขึ้นมาซักตัวให้คนอื่นใช้ — ไม่สามารถรู้ได้ว่าคนที่เอาไปใช้จะเขียนโค้ดด้วย Java หรือ Kotlin
- คุณต้องทำงานกับโปรเจคที่มีโค้ด Java อยู่ — ถ้าสามารถ Migrate ได้ทั้งหมดก็ดี แต่ถ้าทำไม่ได้ก็แปลว่ามีโอกาสที่โค้ด Java ต้องเรียกใช้งานโค้ด Kotlin อย่างแน่นอน
ซึ่งกรณีของผมคือต้องเขียน Library ขึ้นมา โดยผมต้องการใช้ Kotlin แต่ก็ต้องคิดเผื่อว่าต้องใช้กับโค้ด Java ได้ด้วย เพราะมันไม่สนุกเลยถ้าเขียนโค้ด Java แล้วต้องเรียกใช้ Library ที่เขียนเป็น Kotlin ทั้งหมด
และนี่ก็คือที่มาของบทความนี้ที่จะแนะนำวิธีการเขียนโค้ด Kotlin ที่จะช่วยทำให้โค้ดของเรามีความเป็น Java-friendly มากขึ้นนั่นเอง
โดยพระเอกของเราในบทความนี้ก็คือ
JVM Annotation ใน Kotlin Standard Library
ใน Kotlin Standard Library มีชุดคำสั่งมากมายเพื่ออำนวยความสะดวกให้กับนักพัฒนาอย่างเรา และหนึ่งในนั้นก็มี JVM Annotation อยู่ด้วย เพื่อให้เราสามารถใช้ Annotation เพื่อทำให้โค้ดของเราดูดีมากขึ้นเวลาเรียกใช้งานใน Java
โดย JVM Annotation ที่จำเป็นต่อการทำ Java-friendly จะมีด้วยกันดังนี้
และเมื่อนักพัฒนาใส่ Annotation เหล่านี้ลงไปในโค้ด มันจะทำงานทันทีโดยไม่ต้อง Build ใหม่ให้เสียเวลา
@JvmStatic สำหรับ Static Method
สำหรับนักพัฒนาที่สร้าง Static Method ไว้ใน Class ใดๆก็ตามของ Kotlin ควรใส่ @JvmStatic
ให้กับ Method นั้นๆด้วยทุกครั้ง
โดย Annotation ตัวนี้จะช่วยให้เราสามารถเรียก Static Method ได้โดยตรงเหมือนปกติ ไม่ต้องมีคำว่า Companion
ให้รำคาญใจอีกต่อไป
Companion
@JvmOverloads สำหรับ Overload Constructor
การสร้าง Constructor ใน Kotlin จะสั้นและกระชับมาก และเมื่อใช้ Default Value ด้วย ก็จะช่วยลดโค้ดที่จะต้องสร้าง Constuctor เยอะๆในตอนที่เขียน Java ได้เยอะมาก
การเขียนแบบนี้จะช่วยให้นักพัฒนาสามารถเลือกได้ว่าจะใช้ Writer(name: String)
หรือ Writer(name: String, job: String)
ก็ได้
แต่พอเรียกใช้ Writer
บน Java กลับพบว่าไม่เห็น Overload Constructor และต้องกำหนด Argument ให้ครบทั้ง 2 ตัวซะงั้น
เพื่อให้ Overload Constructor ทำงานอย่างที่เราต้องการใน Java ด้วย จะต้องใส่ @JvmOverloads
เข้าไปแบบนี้ด้วย
เพียงเท่านี้ก็สามารถใช้ Overload Constructor ใน Class นั้นได้ตามใจชอบแล้ว
@JvmField สำหรับเปลี่ยน Getter/Setter กลายเป็น Property Access
โดยปกติแล้วเวลาเราสร้าง Property ใดๆก็ตามไว้ใน Class ที่เป็น Kotlin เวลาที่เรียกใช้งานใน Java จะมองเห็นเป็น Getter/Setter Method ให้ทันที
อันนี้แล้วแต่ความชอบของแต่ละคนเลย เพราะสำหรับผมแล้วชอบ Property Access เวลาเขียน Kotlin แต่ถ้า Java จะชอบ Getter/Setter Method มากกว่า เลยไม่ค่อยได้ JVM Annotation ตัวนี้ซักเท่าไร
โดย @JvmField
จะบอกให้ Properties ตัวนั้นๆไม่ต้องสร้าง Getter/Setter Method สำหรับ Java ให้ใช้เป็น Property Access เหมือนเดิมซะ
@JvmName สำหรับเปลี่ยนชื่อให้เป็นตามที่ต้องการ
Annotation ตัวนี้เหมาะกับกรณีที่ต้องการปรับเปลี่ยนชื่อสำหรับแสดงให้แค่ฝั่ง Java เห็นเท่านั้น เช่น ใน Publisher
มีคำสั่ง assign(writer: Writer)
แบบนี้
ถ้าเรียก Publisher ใน Kotlin ก็จะเห็นเป็นคำสั่ง assign(writer: Writer)
ตามปกติ แต่ถ้าเรียกใน Java จะกลายเป็น assignTo(Writer writer)
ตามที่กำหนดไว้ใน @JvmName
แทน
โดย @JvmName
สามารถใช้ได้ใน Function, File, Set, Get หรือ Parameter แต่ไม่สามารถใช้กับ Class หรือ Interface ได้
ซึ่งเอาเข้าจริงก็ไม่ค่อยได้ใช้ซักเท่าไรหรอก แต่รู้ไว้ใช่ว่าใส่บ่าแบกหามเนอะ
Extension Function ที่ไม่สามารถทำให้ Java-friendly ได้เลย
เรื่องน่าเศร้าอย่างหนึ่งของชาว Java ที่ต้องเรียกโค้ด Kotlin มาใช้งาน แล้วพบว่าโค้ดที่ต้องการเรียกใช้งานดันเป็น Extension Function
ถ้าผู้อ่านเขียน Kotlin อยู่ตลอดเวลา ก็จะพบว่า Extension Function มันเป็นอะไรที่ดีงามมากๆ ช่วยชีวิตได้บ่อยมาก
แต่สำหรับ Java นั้นกลับกลายเป็น Pain ซะงั้น เพราะว่าเวลาเรียก Publisher
จะมองไม่เห็น Extension Function ที่สร้างไว้ซะงั้น
ทำให้การเรียก Extension Function บน Java จะต้องเรียกผ่าน Class ที่ Kotlin สร้างไว้ให้อัตโนมัติ โดย Class จะชื่อ <Extension function's filename> + kt
เช่น ผมสร้าง Extension Function ไว้ใน Publisher.kt
ดังนั้นถ้าต้องการเรียกใช้งานบน Java ก็จะต้องเรียกจาก PublisherKt
แทน
ซึ่งผมก็หาวิธีที่ทำให้ Java-friendly ไม่ได้จริงๆ เศร้าชะมัด
อย่างดีก็ทำได้แค่เปลี่ยนชื่อ Class ให้ดูดีขึ้นมาหน่อยด้วยการสร้างไฟล์แยกขึ้นมาเพื่อเก็บ Extension Function ตัวนั้นไว้ แล้วใช้ @JvmName ที่ File-level เพื่อเปลี่ยนเป็นชื่อที่เหมาะสมกว่านี้
จากเดิมที่เรียกผ่าน PublisherKt
ก็จะเปลี่ยนเป็น PublisherExtension
แทน ทำให้รู้สึกผิดน้อยลงกว่าเดิมนิดนึง
ทำให้ Java-friendly เท่าที่จำเป็น
อย่างที่บอกไปในตอนแรกว่าถ้าโปรเจคของคุณเป็น Kotlin ทั้งหมด และโค้ดที่เขียนก็ไม่ได้เอาไปใช้ที่อื่นที่อาจจะเป็นโค้ด Java ก็ไม่จำเป็นต้องสนใจเรื่องนี้เลยซักนิด
เพราะ JVM Annotation จะมีประโยชน์มาก ในเวลาที่ใจคุณอยากเขียน Kotlin แบบสุดๆ แต่โค้ดนั้นจะต้องเรียกใช้งานใน Java ด้วย ดังนั้นก็จงเขียน Kotlin ได้เลย แล้วใช้ JVM Annotation เหล่านี้ เพื่อช่วยให้โค้ดดูสวยงามเมื่อเรียกใช้งานในโค้ด Java
แต่จงจำไว้ว่า Extension Function เป็นอะไรที่เจ็บปวดมากที่สุดสำหรับการเรียกใช้บน Java แล้วล่ะ…