เปลี่ยนโค้ด Kotlin ให้ Java-friendly มากขึ้นด้วย JVM Annotation กันเถอะ

Akexorcist
Black Lens
Published in
2 min readApr 4, 2020

--

ใจมันอยากจะ Pure Kotlin นะ แต่บางกรณีก็ยังต้องเจอกับ Java อยู่ดี

Photo by Ancaro Project on Unsplash

ผมก็เป็นคนหนึ่งที่มีความอวยในภาษา 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 นั้นๆด้วยทุกครั้ง

ใส่ไว้ข้างบน Static 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 ได้เลย ไม่ต้องสร้าง Getter/Setter Method ขึ้นมา
ฝั่ง 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 แล้วล่ะ…

แหล่งข้อมูลอ้างอิง

--

--

Akexorcist
Black Lens

Lovely android developer who enjoys learning in android technology, habitual article writer about Android development for Android community in Thailand.