Kotlin - Need to know - part 2

Part 1: https://viblo.asia/p/kotlin-need-to-know-part-1-gDVK2pbnlLj

Kotlin được thiết kế với khả năng tương thích với Java. Code Java hiện tại có thể được gọi từ Kotlin theo cách tự nhiên và ngược lại, phía code Kotlin, bạn cũng hoàn toàn có thể gọi code từ Java. Tuy nhiên, có một số khác biệt nhất định giữa Java và Kotlin nên cần lưu ý chúng khi tích hợp giữa Kotlin và Java.

Bài viết này focus việc gọi Java code từ Kotlin class.

Hầu hết code Java có thể được sử dụng bởi Kotlin mà không gặp phải quá nhiều issues

Getters & Setters

Lưu ý 1:

Method phải theo Java convention cho gettersetter:

  • Get: không chứa argument và tên method phải bắt đầu bằng get hoặc is nếu là Boolean
  • Set: chứa 1 argument, và tên bắt đầu bằng set

Phía Kotlin, gettersetter của Java sẽ được biểu diễn như là một property. Ví dụ:

import java.util.Calendar
fun calendarDemo() {
    val calendar = Calendar.getInstance()
    if (calendar.firstDayOfWeek == Calendar.SUNDAY) {  // call getFirstDayOfWeek()
        calendar.firstDayOfWeek = Calendar.MONDAY      // call setFirstDayOfWeek()
    }
    if (!calendar.isLenient) {                         // call isLenient()
        calendar.isLenient = true                      // call setLenient()
    }
}

Lưu ý 2:

Nếu trong code Java chỉ có setter, nó sẽ không là property bên Kotlin, vì Kotlin hiện chưa support set-only properties.

Keyword in, object, is as Java method name

Vì các từ trên đều hợp lệ để đặt tên trong Java nhưng lại là keyword bên phía Kotlin, nên cần phải thêm dấu backtick (`) trong các keyword này, Ví dụ:

foo.`is`(bar) // call method is() in Java from Kotlin.

Annotating type parameters

Phía Java có thể chú thích (annotate) kiểu các function, argument hay generic type để cung cấp thông tin nullability , Ví dụ:

@NotNull
Set<@NotNull String> toSet(@NotNull Collection<@NotNull String> elements) { ... }

và từ phía Kotlin sẽ nhìn với thông tin:

fun toSet(elements: (Mutable)Collection<String>) : (Mutable)Set<String> { ... }

Lưu ý, việc vô tình hoặc cố tinh thiếu các nullability annotation từ phía Java, phía Kotlin sẽ nhận được các kiểu Platform Type (hiểu đơn giản là kiểu có thể null hoặc không null trong Kotlin).

// without @NotNull from Java code
fun toSet(elements: (Mutable)Collection<String!>) : (Mutable)Set<String!> { ... }

Java Arrays

Không giống như Java, array trong Kotlininvariant, nghĩa là Kotlin không cho phép bạn gán một Array<String> cho một Array<Any>, để tránh các lỗi lúc runtime. Và Kotlin cũng cấm luôn việc gán 1 array của một subclass cho array của superclass (Java cho phép).

Các array kiểu nguyên thủy ở Javaperformance tốt do hạn chế thao tác boxing/unboxing kiểu. Phía Kotlin có hỗ trợ array tất cả các kiểu nguyên thủy (IntArray, DoubleArray...) để handle trường hợp này. Nó không liên quan gì đến class Array và được compile thành array kiểu nguyên thủy như Java.

public class JavaArrayExample {
    public void removeIndices(int[] indices) {
        // code here...
    }
}

Gọi từ Kotlin để có performance tốt nhất:

val javaObj = JavaArrayExample()
val array = intArrayOf(0, 1, 2, 3) // create an IntArray object
javaObj.removeIndices(array)  // passes int[] to method

Java Varargs

public class JavaArrayExample {
    public void removeIndicesVarArg(int... indices) { // varargs parameter
        // code here...
    }
}

Phía Kotlin có thể dùng spread operator (* ) để truyền một IntArray

val javaObj = JavaArrayExample()
val array = intArrayOf(0, 1, 2, 3)
javaObj.removeIndicesVarArg(*array)

Lưu ý: Hiện tại null không thể truyền vào method khai báo varargs

Checked Exceptions

Trong Kotlin, tất cả exception đều là kiểu unchecked, nghĩa là compiler sẽ không bắt buộc bạn phải catch chúng. Vậy nên, khi gọi một Java method có khai báo một checked exception, Kotlin không yêu cầu bạn phải catch hay gì cả.

fun render(list: List<*>, to: Appendable) {
    for (item in list) {
        to.append(item.toString()) // Java would require us to catch IOException here
    }
}

Object Methods

Khi import các kiểu của Java sang Kotlin, tất cả các tham chiếu của kiểu java.lang.Object sẽ thành Any. Vì Any không phải là một platform-specific, nó chỉ định nghĩa 3 method toString(), hashCode(), equals(), nên để khai báo các đặc tính khác của java.lang.Object, Kotlin dùng các extensions function.

wait()/notify()

Chúng không tồn tại trong Any, và cũng không được khuyến khích sử dụng tron java.util.concurrent, tuy nhiên nếu cần thì vẫn có thể gọi trong Kotlin bằng cách cast về java.lang.Object

(foo as java.lang.Object).wait()

getClass()

Để get class của một Object, ta có thể dùng extensions property java trong KClass hoặc javaClass

val fooClass = foo::class.java
val fooClass2 = foo.javaClass

clone()

Để override clone, classKotlin cần phải extends từ kotlin.Cloneable

class Example : Cloneable {
    override fun clone(): Any { ... }
}

finalize()

Để override finalize, bạn chỉ cần viết method finalizekhông kèm từ khóa overridekhông được là private

class MyClass {
    protected fun finalize() {
        // finalization logic
    }
}

Accessing static members

Các thành phần static của* Java class* tạo thành companion object cho các class này trong Kotlin. Chúng ta không thể truyền một companion object như là giá trị, nhưng có thể truy cập thành phần static cụ thể, ví dụ:

if (Character.isLetter(a)) { ... }

Để truy cập thành phần static của một kiểu Java được map sang một kiểu Kotlin, sử dụng full qulified name của nó:

java.lang.Boolean.getBoolean("false"); // call static method getBoolean() from Java Boolean

Java Reflection

Java Reflection sử dụng được trong Kotlin class và ngược lại. Để truy cập Java Reflection thông qua java.lang.Class, có thể sử dụng 1 trong các cách sau:

foo::class.java // instance::class.java
foo.javaClass // instance.javaClass
Foo::class.java // ClassName::class.java

Using JNI with Kotlin

Để khai báo một function được implement trong mã native (C or C++), cần đánh dấu nó với từ khóa external, còn lại các thủ tục khác hoàn toàn giống java khi sử dụng với JNI, ví dụ:

external fun foo(x: Int): Double

SAM conversions

Giống như Java 8, Kotlin support SAM - Single Abstract Method - conversions, nghĩa là Kotlin function literals có thể tự động convert thành triển khai (implementation) của Java interface với một non-default method, miễn sao các kiểu parameter của Java interface method trùng khớp với kiểu parameter trong Kotlin function. (Đọc thêm SAM tại đây)

Bạn có thể sử dụng để tạo instance của các SAM interface:

val runnable = Runnable { println("This runs in a runnable") }

và trong method gọi:

val executor = ThreadPoolExecutor()
// Java signature: void execute(Runnable command) { ... }
executor.execute { println("This runs in a thread pool") }

nếu trong Java class có nhiều method sử dụng nhiều functional interface, bạn có thể chọn một method cần gọi bằng cách sử dụng một adapter function chuyển đổi một lambda sang một kiểu SAM cụ thể. Những adapter function này cũng được compiler tạo ra khi cần:

executor.execute(Runnable { println("This runs in a thread pool") })

Chú ý:

  1. SAM conversion chỉ hoạt động cho interface chứ không cho abstract class, ngay cả khi các abstract class chỉ có một Single Abstract Method.
  2. SAM cũng chỉ hoạt động với Java interop (gọi từ Kotlin), Vì Kotlin có các kiểu function phù hợp, việc tự động chuyển các function thành các triển khai của Kotlin interface là không cần thiết và do đó không được hỗ trợ.

TO BE CONTINUED

References: