0

Writing Java-friendly Kotlin code (Phần 3)

Generics , @JvmWildcard, @JvmSuppressWildcards

Như chúng ta đã biết, Kotlin có declaration-site variance trong hộp công cụ của nó. Điều này có nghĩa rằng List<Dog> có thể được sử dụng thay cho List<Animal> mà không có bất kỳ kí tự đặc biệt nào, ngay cả <? extends …> hoặc <? super …> cũng không. Nhưng trong Java chúng ta chỉ có sự khác biệt về vị trí, biến sử dụng và Kotlin phải giải quyết vấn đề đó. Vì vậy, có những gì xảy ra đối với List<out E>? Trong trường hợp của các tham số đầu vào,

fun addPlugins(plugs: List<Plugin>)

sẽ được trả về trong

public final void addPlugins(@NotNull List<? extends Plugin> plugs)

Để dễ dàng truyền giá trị thông qua phần thừa kế của Plugin. Bởi vì đó là trường hợp phổ biến việc xử lý các tham số trong Java. Nếu chúng ta muốn ngăn chặn việc sử dụng wildcards. Chúng ta nên khai báo method như một trong các cách sau:

// Suppress only for this param.
fun addPlugins(plugs: List<@JvmSuppressWildcards Plugin>)
// Suppress for the whole method.
@JvmSuppressWildcards fun addPlugins(plugs: List<Plugin>)
// Or even for the whole class.
@JvmSuppressWildcards
object Analytics {
  fun addPlugins(plugs: List<Plugin>)
}

Nhưng trong trường hợp có giá trị trả về

fun getPlugins(): List<Plugin>

sẽ không có kiểu đại diện

public final List<Plugin> getPlugins()

Bởi vì đó là một trường hợp phổ biến trong Java, và nếu không sẽ cần phải xử lý một kiểu đại diện bằng tay. Nếu chúng ta cần trả về kiểu đại diện nào đó, chúng ta có thể sử dụng tùy chọn sau

fun getPlugins(): List<@JvmWildcard Plugin>

và nó sẽ biến thành

public final List<? extends Plugin> getPlugins()

Ví dụ, nếu chúng ta muốn ngăn chặn việc thêm Plugins mới cho List trả về tại thời gian biên dịch (tốt hơn cũng làm một bản sao trong trường hợp đó, bởi vì plugin được hỗ trợ bởi ArrayList).

CHÚ THÍCH! Trong trường hợp tham số kiểu contravariant sửa đổi, giống như Comparable<in T>, kiểu đại diện sẽ được sinh ra như <? super …>.

Working with java.lang.Class

Nếu chúng ta cũng cần phải xử lý một chút tham chiếu trong thư viện, có lẽ chúng ta muốn hỗ trợ hầu hết Class<?> and KClass<*> Và thực sự không cần thực hiện những điều này riêng biệt - chúng ta có thể chuyển đổi từ một cái khác khá dễ dàng với các thuộc tính mở rộng được xây dựng:

val <T> KClass<T>.java: Class<T>
// and 
val <T : Any> Class<T>.kotlin: KClass<T>

Ví dụ: chúng tôi đang xây dựng phiên bản Retrofit Kotlin và chúng tôi có một phương pháp để xây dựng thực hiện giao diện REST API thực tế trông như sau:

fun <T: Any> create(service: KClass<T>): T

Sau đó, phương thức tương ứng cho Java Class<?> Sẽ là như sau:

fun <T: Any> create(service: Class<T>): T {
    return create(service.kotlin)
}

Builders Thông thường, trong Kotlin không có những thứ như là Builders, bởi vì chúng ta có các tham số được đặt tên và các chức năng apply. Nhưng để sử dụng Java tốt hơn, chúng tôi có thể xem xét triển khai . Với Retrofit đơn giản của chúng tôi, nó có thể trông như thế này:

class Builder {
  private lateinit var baseUrl: String
  private var client: Client = Client()

  fun baseUrl(baseUrl: String) = this.also { it.baseUrl = baseUrl }

  fun client(client: Client) = this.also { it.client = client }

  fun build() = Retrofit(baseUrl, client)
}

Đối với các tham số yêu cầu, chúng ta có thể sử dụng trình sửa đổi lateinit - đối với những cái khác chỉ cần đặt các giá trị mặc định. Ngoài ra, để loại bỏ tất cả* return this từ mỗi setter, chúng ta có thể sử dụng also hoặc apply. Khá súc tích, phải không?


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.