Kotlin的類和接口與Java的類和接口存在較大區別,本次主要概括Kotlin的接口和類如何定義、繼承以及其一些具體細節,同時查看其對應的Java層實現。android
Kotlin接口能夠包含抽象方法以及非抽象方法的實現(相似Java 8的默認方法)編程
interface MyInterface {
//抽象方法
fun daqi()
//非抽象方法(即提供默認實現方法)
fun defaultMethod() {
}
}
複製代碼
接口也能夠定義屬性。聲明的屬性能夠是抽象的,也能夠是提供具體訪問器實現的(即不算抽象的)。安全
interface MyInterface {
//抽象屬性
var length:Int
//提供訪問器的屬性
val name:String
get() = ""
//抽象方法
fun daqi()
//非抽象方法(即提供默認實現方法)
fun defaultMethod() {
}
}
複製代碼
接口中聲明的屬性不能有幕後字段。由於接口是無狀態的,所以接口中聲明的訪問器不能引用它們。(簡單說就是接口沒有具體的屬性,不能用幕後字段對屬性進行賦值)bash
Kotlin使用 : 替代Java中的extends 和 implements 關鍵字。Kotlin和Java同樣,一個類能夠實現任意多個接口,可是隻能繼承一個類。ide
接口中抽象的方法和抽象屬性,實現接口的類必須對其提供具體的實現。函數
對於在接口中提供默認實現的接口方法和提供具體訪問器的屬性,能夠對其進行覆蓋,從新實現方法和提供新的訪問器實現。post
class MyClass:MyInterface{
//原抽象屬性,提供具體訪問器
//不提供具體訪問器,提供初始值,使用默認訪問器也是沒有問題的
override var length: Int = 0
/*override var length: Int
get() = 0
set(value) {}*/
//覆蓋提供好訪問器的接口屬性
override val name: String
//super.name 實際上是調用接口中定義的訪問器
get() = super.name
//原抽象方法,提供具體實現
override fun daqi() {
}
//覆蓋默認方法
override fun defaultMethod() {
super.defaultMethod()
}
}
複製代碼
不管是從接口中獲取的屬性仍是方法,前面都帶有一個override關鍵字。該關鍵字與Java的@Override註解相似,重寫父類或接口的方法屬性時,都 強制 須要用override修飾符進行修飾。由於這樣能夠避免先寫出實現方法,再添加抽象方法形成的意外重寫。學習
接口也能夠從其餘接口中派生出來,從而既提供基類成員的實現,也能夠聲明新的方法和屬性。ui
interface Name {
val name:String
}
interface Person :Name{
fun learn()
}
class daqi:Person{
//爲父接口的屬性提供具體的訪問器
override val name: String
get() = "daqi"
//爲子接口的方法提供具體的實現
override fun learn() {
}
}
複製代碼
在C++中,存在菱形繼承的問題,即一個類同時繼承具備相同函數簽名的兩個方法,到底該選擇哪個實現呢?因爲Kotlin的接口支持默認方法,當一個類實現多個接口,同時擁有兩個具備相同函數簽名的默認方法時,到底選擇哪個實現呢?this
主要根據如下3條規則進行判斷:
一、類中帶override修飾的方法優先級最高。 類或者父類中帶override修飾的方法的優先級高於任何聲明爲默認方法的優先級。(Kotlin編譯器強制要求,當類中存在和父類或實現的接口有相同函數簽名的方法存在時,須要在前面添加override關鍵字修飾。)
二、當第一條沒法判斷時,子接口的優先級更高。優先選擇擁有最具體實現的默認方法的接口,由於從繼承角度理解,能夠認爲子接口的默認方法覆蓋重寫了父接口的默認方法,子接口比父接口具體。
三、最後仍是沒法判斷時,繼承多個接口的類須要顯示覆蓋重寫該方法,並選擇調用指望的默認方法。
Java繼承自Language,二者都對use方法提供了默認實現。而Java比Language更具體。
interface Language{
fun use() = println("使用語言")
}
interface Java:Language{
override fun use() = println("使用Java語言編程")
}
複製代碼
而實現這兩個接口的類中,並沒有覆蓋重寫該方法,只能選擇更具體的默認方法做爲其方法實現。
class Person:Java,Language{
}
//執行結果是輸出:使用Java語言編程
val daqi = Person()
daqi.use()
複製代碼
接口Java和Kotlin都提供對learn方法提供了具體的默認實現,且二者並沒有明確的繼承關係。
interface Java {
fun learn() = println("學習Java")
}
interface Kotlin{
fun learn() = println("學習Kotlin")
}
複製代碼
當某類都實現Java和Kotlin接口時,此時就會產生覆蓋衝突的問題,這個時候編譯器會強制要求你提供本身的實現:
惟一的解決辦法就是顯示覆蓋該方法,若是想沿用接口的默認實現,能夠super關鍵字,並將具體的接口名放在super的尖括號中進行調用。
class Person:Java,Kotlin{
override fun learn() {
super<Java>.learn()
super<Kotlin>.learn()
}
}
複製代碼
Java 8中也同樣能夠爲接口提供默認實現,但須要使用default關鍵字進行標識。(Kotlin只須要提供具體的方法實現,即提供函數體)
public interface Java8 {
default void defaultMethod() {
System.out.println("我是Java8的默認方法");
}
}
複製代碼
面對覆蓋衝突,Java8的和處理和Kotlin的基本類似,在語法上顯示調用接口的默認方法時有些不一樣:
//Java8 顯示調用覆蓋衝突的方法
Java8.super.defaultMethod()
//Kotlin 顯示調用覆蓋衝突的方法
super<Kotlin>.learn()
複製代碼
衆所周知,Java8以前接口沒有默認方法,Kotlin是如何兼容的呢?定義以下兩個接口,再查看看一下反編譯的結果:
interface Language{
//默認方法
fun use() = println("使用語言編程")
}
interface Java:Language{
//抽象屬性
var className:String
//提供訪問器的屬性
val field:String
get() = ""
//默認方法
override fun use() = println("使用Java語言編程")
//抽象方法
fun absMethod()
}
複製代碼
先查看父接口的源碼:
public interface Language {
void use();
public static final class DefaultImpls {
public static void use(Language $this) {
String var1 = "使用語言編程";
System.out.println(var1);
}
}
}
複製代碼
Language接口中的默認方法轉換爲抽象方法保留在接口中。其內部定義了一個名爲DefaultImpls的靜態內部類,該內部類中擁有和默認方法相同名稱的靜態方法,而該靜態方法的實現就是其同名默認函數的具體實現。也就是說,Kotlin的默認方法轉換爲靜態內部類DefaultImpls的同名靜態函數。
因此,若是想在Java中調用Kotlin接口的默認方法,須要加多一層DefaultImpls
public class daqiJava implements Language {
@Override
public void use() {
Language.DefaultImpls.use(this);
}
}
複製代碼
再繼續查看子接口的源碼
public interface Java extends Language {
//抽象屬性的訪問器
@NotNull
String getClassName();
void setClassName(@NotNull String var1);
//提供具體訪問器的屬性
@NotNull
String getField();
//默認方法
void use();
//抽象方法
void absMethod();
public static final class DefaultImpls {
@NotNull
public static String getField(Java $this) {
return "";
}
public static void use(Java $this) {
String var1 = "使用Java語言編程";
System.out.println(var1);
}
}
}
複製代碼
經過源碼觀察到,不管是抽象屬性仍是擁有具體訪問器的屬性,都沒有在接口中定義任何屬性,只是聲明瞭對應的訪問器方法。(和擴展屬性類似)
抽象屬性和提供具體訪問器的屬性區別是:
Java定義的接口,Kotlin繼承後能爲其父接口的方法提供默認實現嗎?固然是能夠啦:
//Java接口
public interface daqiInterface {
String name = "";
void absMethod();
}
//Kotlin接口
interface daqi: daqiInterface {
override fun absMethod() {
}
}
複製代碼
Java接口中定義的屬性都是默認public static final,對於Java的靜態屬性,在Kotlin中能夠像頂層屬性同樣,直接對其進行使用:
fun main(args: Array<String>) {
println("Java接口中的靜態屬性name = $name")
}
複製代碼
Kotlin的類能夠有一個主構造函數以及一個或多個 從構造函數。主構造函數是類頭的一部分,即在類體外部聲明。
constructor關鍵字能夠用來聲明 主構造方法 或 從構造方法。
class Person(val name:String)
//其等價於
class Person constructor(val name:String)
複製代碼
主構造函數不能包含任何的代碼。初始化的代碼能夠放到以 init 關鍵字做爲前綴的初始化塊中。
class Person constructor(val name:String){
init {
println("name = $name")
}
}
複製代碼
構造方法的參數也能夠設置爲默認參數,當全部構造方法的參數都是默認參數時,編譯器會生成一個額外的不帶參數的構造方法來使用全部的默認值。
class Person constructor(val name:String = "daqi"){
init {
println("name = $name")
}
}
//輸出爲:name = daqi
fun main(args: Array<String>) {
Person()
}
複製代碼
主構造方法同時須要初始化父類,子類能夠在其列表參數中索取父類構造方法所需的參數,以便爲父類構造方法提供參數。
open class Person constructor(name:String){
}
class daqi(name:String):Person(name){
}
複製代碼
當沒有給一個類聲明任何構造方法,編譯器將生成一個不作任何事情的默認構造方法。對於只有默認構造方法的類,其子類必須顯式地調用父類的默認構造方法,即便他沒有參數。
open class View
class Button:View()
複製代碼
而接口沒有構造方法,因此接口名後不加括號。
//實現接口
class Button:ClickListener
複製代碼
當 主構造方法 有註解或可見性修飾符時,constructor 關鍵字不可忽略,而且constructor 在這些修飾符和註解的後面。
class Person public @Inject constructor(val name:String)
複製代碼
構造方法的可見性是 public,若是想將構造方法設置爲私有,可使用private修飾符。
class Person private constructor()
複製代碼
從構造方法使用constructor關鍵字進行聲明
open class View{
//從構造方法1
constructor(context:Context){
}
//從構造方法2
constructor(context:Context,attr:AttributeSet){
}
}
複製代碼
使用this關鍵字,從一個構造方法中調用該類另外一個構造方法,同時也能使用super()關鍵字調用父類構造方法。
若是一個類有 主構造方法,每一個 從構造方法 都應該顯式調用 主構造方法,不然將其委派給會調用主構造方法的從構造方法。
class Person constructor(){
//從構造方法1,顯式調用主構造方法
constructor(string: String) : this() {
println("從構造方法1")
}
//從構造方法2,顯式調用構造方法1,間接調用主構造方法。
constructor(data: Int) : this("daqi") {
println("從構造方法2")
}
}
複製代碼
注意:
初始化塊中的代碼實際上會成爲主構造函數的一部分。顯式調用主構造方法會做爲次構造函數的第一條語句,所以全部初始化塊中的代碼都會在次構造函數體以前執行。
即便該類沒有主構造函數,這種調用仍會隱式發生,而且仍會執行初始化塊。
//沒有主構造方法的類
class Person{
init {
println("主構造方法 init 1")
}
//從構造方法默認會執行全部初始化塊
constructor(string: String) {
println("從構造方法1")
}
init {
println("主構造方法 init 2")
}
}
複製代碼
若是一個類擁有父類,但沒有主構造方法時,每一個從構造方法都應該初始化父類(即調用父類的構造方法),不然將其委託給會初始化父類的構造方法(即便用this調用其餘會初始化父類的構造方法)。
class MyButton:View{
//調用自身的另一個從構造方法,間接調用父類的構造方法。
constructor(context:Context):this(context,MY_STYLE){
}
//調用父類的構造方法,初始化父類。
constructor(context:Context,attr:AttributeSet):super(context,attr){
}
}
複製代碼
Java中容許建立任意類的子類並重寫任意方法,除非顯式地使用final關鍵字。對基類進行修改致使子類不正確的行爲,就是所謂的脆弱的基類。因此Kotlin中類和方法默認是final,Java類和方法默認是open的。
當你容許一個類存在子類時,須要使用open修飾符修改這個類。若是想一個方法能被子類重寫,也須要使用open修飾符修飾。
open class Person{
//該方法時final 子類不能對它進行重寫
fun getName(){}
//子類能夠對其進行重寫
open fun getAge(){}
}
複製代碼
對基類或接口的成員進行重寫後,重寫的成員一樣默認爲open。(儘管其爲override修飾)
若是想改變重寫成員默認爲open的行爲,能夠顯式的將重寫成員標註爲final
open class daqi:Person(){
final override fun getAge() {
super.getAge()
}
}
複製代碼
抽象類的成員和接口的成員始終是open的,不須要顯式地使用open修飾符。
Kotlin和Java的可見性修飾符類似,一樣可使用public、protected和private修飾符。但Kotlin默承認見性是public,而Java默承認見性是包私有。
Kotlin中並無包私有這種可見性,Kotlin提供了一個新的修飾符:internal,表示「只在模塊內部可見」。模塊是指一組一塊兒編譯的Kotlin文件。多是一個Gradle項目,多是一個Idea模塊。internal可見性的優點在於它提供了對模塊實現細節的封裝。
Kotlin容許在頂層聲明中使用private修飾符,其中包括類聲明,方法聲明和屬性聲明,但這些聲明只能在聲明它們的文件中可見。
注意:
Kotlin像Java同樣,容許在一個類中聲明另外一個類。但Kotlin的嵌套類默認不能訪問外部類的實例,和Java的靜態內部類同樣。
若是想讓Kotlin內部類像Java內部類同樣,持有一個外部類的引用的話,須要使用inner修飾符。
內部類須要外部類引用時,須要使用 this@外部類名 來獲取。
class Person{
private val name = "daqi"
inner class MyInner{
fun getPersonInfo(){
println("name = ${this@Person.name}")
}
}
}
複製代碼
在Java中建立單例每每須要定義一個private的構造方法,並建立一個靜態屬性來持有這個類的單例。
Kotlin經過對象聲明將類聲明和類的單一實例結合在一塊兒。對象聲明在定義的時候就當即建立,而這個初始化過程是線程安全的。
對象聲明中能夠包含屬性、方法、初始化語句等,也支持繼承類和實現接口,惟一不容許的是不能定義構造方法(包括主構造方法和從構造方法)。
對象聲明不能定義在方法和內部類中,但能夠定義在其餘的對象聲明和非內部類(例如:嵌套類)。若是須要引用該對象,直接使用其名稱便可。
//定義對象聲明
class Book private constructor(val name:String){
object Factory {
val name = "印書廠"
fun createAppleBooK():Book{
return Book("Apple")
}
fun createAndroidBooK():Book{
return Book("Android")
}
}
}
複製代碼
調用對象聲明的屬性和方法:
Book.Factory.name
Book.Factory.createAndroidBooK()
複製代碼
將對象聲明反編譯成Java代碼,其內部實現也是定義一個private的構造方法,並始終建立一個名爲INSTANCE的靜態屬性來持有這個類的單例,而該類的初始化放在靜態代碼塊中。
public final class Book {
//....
public Book(String name, DefaultConstructorMarker $constructor_marker) {
this(name);
}
public static final class Factory {
@NotNull
private static final String name = "印書廠";
public static final Book.Factory INSTANCE;
//...
@NotNull
public final Book createAppleBooK() {
return new Book("Apple", (DefaultConstructorMarker)null);
}
@NotNull
public final Book createAndroidBooK() {
return new Book("Android", (DefaultConstructorMarker)null);
}
private Factory() {
}
static {
Book.Factory var0 = new Book.Factory();
INSTANCE = var0;
name = "印書廠";
}
}
}
複製代碼
用Java調用對象聲明的方法:
//Java調用對象聲明
Book.Factory.INSTANCE.createAndroidBooK();
複製代碼
通常狀況下,使用頂層函數能夠很好的替代Java中的靜態函數,但頂層函數沒法訪問類的private成員。
當須要定義一個方法,該方法能在沒有類實例的狀況下,調用該類的內部方法。能夠定義一個該類的對象聲明,並在該對象聲明中定義該方法。類內部的對象聲明能夠用 companion 關鍵字標記,這種對象叫伴生對象。
能夠直接經過類名來訪問該伴生對象的方法和屬性,不用再顯式的指明對象聲明的名稱,再訪問該對象聲明對象的方法和屬性。能夠像調用該類的靜態函數和屬性同樣,不須要再關心對象聲明的名稱。
//將構造方法私有化
class Book private constructor(val name:String){
//伴生對象的名稱可定義也能夠不定義。
companion object {
//伴生對象調用其內部私有構造方法
fun createAppleBooK():Book{
return Book("Apple")
}
fun createAndroidBooK():Book{
return Book("Android")
}
}
}
複製代碼
調用伴生對象的方法:
Book.createAndroidBooK()
複製代碼
伴生對象的實現和對象聲明相似,定義一個private的構造方法,並始終建立一個名爲Companion的靜態屬性來持有這個類的單例,並直接對Companion靜態屬性進行初始化。
public final class Book {
//..
public static final Book.Companion Companion = new Book.Companion((DefaultConstructorMarker)null);
//...
public static final class Companion {
//...
@NotNull
public final Book createAppleBooK() {
return new Book("Apple", (DefaultConstructorMarker)null);
}
@NotNull
public final Book createAndroidBooK() {
return new Book("Android", (DefaultConstructorMarker)null);
}
private Companion() {
}
// $FF: synthetic method
public Companion(DefaultConstructorMarker $constructor_marker) {
this();
}
}
}
複製代碼
擴展方法機制容許在任何地方定義某類的擴展方法,但須要該類的實例進行調用。當須要擴展一個經過類自身調用的方法時,若是該類擁有伴生對象,能夠經過對伴生對象定義擴展方法。
//對伴生對象定義擴展方法
fun Book.Companion.sellBooks(){
}
複製代碼
當對該擴展方法進行調用時,能夠直接經過類自身進行調用:
Book.sellBooks()
複製代碼
做爲android開發者,在設置監聽時,建立匿名對象的狀況再常見不過了。
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
複製代碼
object關鍵字除了能用來聲明單例式對象外,還能夠聲明匿名對象。和對象聲明不一樣,匿名對象不是單例,每次都會建立一個新的對象實例。
mRecyclerView.setOnClickListener(object :View.OnClickListener{
override fun onClick(v: View?) {
}
});
複製代碼
當該匿名類擁有兩個以上抽象方法時,才須要使用object建立匿名類。不然儘可能使用lambda表達式。
mButton.setOnClickListener {
}
複製代碼