2016-02-20 13 views
11

Tôi có một kịch bản trong mã của tôi mà tôi muốn một lớp học để thực hiện một giao diện cho hai loại riêng biệt, như ví dụ sau:Bất kỳ cách nào để kế thừa từ cùng một giao diện chung hai lần (với các kiểu riêng biệt) trong Kotlin?

interface Speaker<T> { 
    fun talk(value: T) 
} 

class Multilinguist : Speaker<String>, Speaker<Float> { 
    override fun talk(value: String) { 
     println("greetings") 
    } 

    override fun talk(value: Float) { 
     // Do something fun like transmit it along a serial port 
    } 
} 

Kotlin là không hài lòng với điều này, với lý do:

Type parameter T of 'Speaker' has inconsistent values: kotlin.String, kotlin.Float 
A supertype appears twice 

Tôi biết rằng một giải pháp có thể là triển khai mã sau đây, nơi tôi triển khai giao diện với <Any> và sau đó kiểm tra các loại bản thân và ủy quyền chúng cho các chức năng của chúng.

interface Speaker<T> { 
    fun talk(value: T) 
} 

class Multilinguist : Speaker<Any> { 
    override fun talk(value: Any) { 
     when (value) { 
      is String -> 
       internalTalk(value) 
      is Float -> 
       internalTalk(value) 
     } 
    } 

    fun internalTalk(value: String) { 
     println(value) 
    } 

    fun internalTalk(value: Float) { 
     // Do something fun like transmit it along a serial port 
    } 
} 

Tuy nhiên, điều đó có nghĩa là tôi đang xóa loại an toàn và thông tin liên lạc về lớp học được sử dụng và yêu cầu sự cố xuống dòng. Có cách nào tốt hơn để thực hiện điều này trong Kotlin? Ngoài ra - lý do đằng sau nó không được cho phép theo cách tôi đã chỉ ra trong mẫu đầu tiên là gì? Không phải giao diện chỉ là một hợp đồng chữ ký mà tôi được yêu cầu để thực hiện, hoặc có cái gì đó tôi đang thiếu liên quan đến generics ở đây?

Trả lời

14

Có, bạn đang thiếu một chi tiết quan trọng về việc triển khai Generics trên JVM: the type erasure. Tóm lại, bytecode được biên dịch của các lớp không thực sự chứa bất kỳ thông tin nào về các kiểu generic (ngoại trừ một số siêu dữ liệu về thực tế là một lớp hoặc một phương thức là generic). Tất cả các loại kiểm tra xảy ra tại thời gian biên dịch, và sau đó không có loại chung giữ lại trong mã, chỉ có Object.

Để phát hiện sự cố trong trường hợp của bạn, chỉ cần xem mã bytecode (trong IDEA, Tools -> Kotlin -> Show Kotlin Bytecode hoặc bất kỳ công cụ nào khác). Chúng ta hãy xem xét ví dụ đơn giản này:

interface Converter<T> { 
    fun convert(t: T): T 
} 

class Reverser(): Converter<String> { 
    override fun convert(t: String) = t.reversed() 
} 

Trong bytecode của Converter kiểu generic được xoá hoàn toàn:

// access flags 0x401 
// signature (TT;)TT; 
// declaration: T convert(T) 
public abstract convert(Ljava/lang/Object;)Ljava/lang/Object; 

Và đây là những phương pháp được tìm thấy trong bytecode của Reverser:

// access flags 0x1 
public convert(Ljava/lang/String;)Ljava/lang/String; 
    ... 

// access flags 0x1041 
public synthetic bridge convert(Ljava/lang/Object;)Ljava/lang/Object; 
    ... 
    INVOKEVIRTUAL Reverser.convert (Ljava/lang/String;)Ljava/lang/String; 
    ... 

Để kế thừa giao diện Converter, Reverser phải có phương thức bằng cùng một chữ ký, tức là một loại đã xóa. Nếu phương thức triển khai thực tế có chữ ký khác nhau, thì thêm bridge method. Ở đây chúng ta thấy rằng phương thức thứ hai trong bytecode chính xác là phương thức bridge (và nó gọi phương thức đầu tiên).

Do đó, nhiều triển khai giao diện chung chung sẽ xung đột với nhau, vì có thể chỉ có một phương thức cầu cho một chữ ký nhất định. Ngoài ra, nếu có thể, không phải Java cũng như Kotlin has method overloading based on return value type, và đôi khi cũng có sự mơ hồ trong các đối số, vì vậy thừa kế đa số sẽ khá hạn chế.

Mọi thứ, tuy nhiên, sẽ thay đổi với Project Valhalla (generics được đổi lại sẽ giữ nguyên loại thực tế khi chạy), nhưng tôi vẫn không mong đợi nhiều kế thừa giao diện chung.

Các vấn đề liên quan