Kotlin基礎 - 第十章抽象類和接口

Kotlin中的對象和接口



#### [kotlin官方文檔 https://www.kotlincn.net/docs/reference/](https://www.kotlincn.net/docs/reference/) ####

Kotlin的類和接口與Java的類和接口是有一定的區別的。

Kotlin編譯器能夠生成有用的方法來避免冗餘。比如將一個類聲明爲data類可以讓編譯器生成若干標準方法,同時也可以避免書寫委託方法(委託模式kotlin原生支持)。

面向對象編程語言(kotlin、java)中接口的理解更像是一種協議和規範,類則相當於規範下的一種具體的產品(當然抽象類則更像是一種半成品),一般的把事物的特性和描述規定成接口,把實物本身定義成一個類,比如

一個漂亮的會做飯會講笑話的小姑娘
可以這樣提取  一個[漂亮的][會做飯][會講笑話]的{小姑娘}
				  接口	 接口	  接口	   接口

kotlin的接口

Kotlin 的接口可以既包含抽象方法的聲明也包含實現。與抽象類不同的是,接口無法保存狀態。它可以有屬性但必須聲明爲抽象或提供訪問器實現。

接口定義

  • 用關鍵字 interface 來定義接口

      /**
       * 使用interface來聲明一個接口
       */
      interface Clickable {
      	val prop: Int // 抽象的,不可以賦值
          fun click();
      	
      	//可以有方法體
      	fun select() {
      	   // 可選的方法體,但是變量無狀態,變量的值需要子類實現
      	}
      }
      
      
      /**
       * 實現接口
       *kotlin在類名後面使用:來代替Java中的extends和implements關鍵字
       *和Java一樣,一個類可以實現多個接口,但是隻能繼承一個類。
       */
      class Button :Clickable {
          override fun click()= println("this is clicked")
      }
      
      
      //調用
      fun main(args: Array<String>) {
          val button:Button = Button();
          println(button.click()); //this is clicked
      }
    

接口中的屬性

	interface MyInterface {
	    val prop: Int // 抽象的,不可以賦值
	
	    val propertyWithImplementation: String
	        get() = "foo"
	
	    fun foo() {
	        print(prop)
	    }
	}
	
	
	class ChildClass : MyInterface {
	    //實現接口,必須重寫抽象屬性
	    override val prop: Int = 122
	}
	

			
	fun main(args: Array<String>) {
	    val ch = ChildClass()
	    println(ch.prop)
	    println(ch.propertyWithImplementation)
	    println(ch.foo())
	}

	//打印結果
	122
	foo
	122kotlin.Unit

接口繼承

	interface named {
	    val name: String
	}
	
	interface newPerson : named {
	
	    var firstName: String
	    var lastName: String
	
	    override val name: String get() = "$firstName $lastName"
	}
	
	data class Emplopee(
	    // 不必實現“name”
	    override var firstName: String,
	    override var lastName: String,
	    val persion: Int
	) : newPerson {
	
	}
	
	
	fun main(args: Array<String>) {
	
	    Emplopee("zhou","bencheng",0).name.println()
	}
	
	//執行結果  zhou bencheng

解決覆蓋衝突

實現多個接口時,可能會遇到同一方法繼承多個實現的問題。例如

	interface A {
	    fun foo():String {
	        println("A foo")
	        return "A foo"
	    }
	
	    fun bar()
	}
	
	
	interface B {
	    fun foo() :String {
	        println("B foo")
	        return "B foo"
	    }
	
	    fun bar(){
	        print("bar")
	    }
	}
	
	
	interface C:A {
	    override fun foo():String {
	        println("C foo")
	        return "C foo"
	    }
	
	
	}
	
	abstract class D {
	    open fun foo() :String {
	        println("方法D foo")
	        return "D foo"
	    }
	
	    open fun bar() {
	        println("方法D bar")
	    }
	}
	
	class E : D(), A, B {
	    override fun foo() :String {
	        //調用接口A中的同名實現
	        super<A>.foo()
	        //調用接口B中的同名實現
	        super<B>.foo()
	        //調用類D中的同名實現
	        super<D>.foo()
	        println("類 E 中的實現")
	        return "類 E 中的實現"
	    }
	
	    override fun bar() {
	        super<D>.bar()
	    }
	
	
	}
	
	
	fun main(args: Array<String>) {
	    E().foo()
	
	}

	// 返回結果
	A foo
	B foo
	方法D foo
	類 E 中的實現	

上例中,接口 A 和 B 都定義了方法 foo() 和 bar()。 兩者都實現了 foo(), 但是隻有 B 實現了 bar() (bar() 在 A 中沒有標記爲抽象, 因爲沒有方法體時默認爲抽象)。因爲 C 是一個實現了 A 的具體類,所以必須要重寫 bar() 並實現這個抽象方法。

然而,如果我們從 A 和 B 派生 D,我們需要實現我們從多個接口繼承的所有方法,並指明 D 應該如何實現它們。這一規則既適用於繼承單個實現(bar())的方法也適用於繼承多個實現(foo())的方法。

  • 思考一下,上面的示例是函數名相同,返回值相同 ,如果函數名相同返回值不同會怎樣呢

結果很明顯,那就沒法完成實現關係了,這個衝突問題無解

接口代理

kotlin代理模式官方文檔地址:http://kotlinlang.org/docs/reference/delegation.html

一.代理模式

代理是實現代碼複用的一種方法。
在面向對象的語言中,代理模式是通過組合的方式來達到和繼承一樣的效果的。

代理模式定義:給某個對象提供一個代理對象,並由代理對象控制對於原對象的訪問,即客戶不直接操控原對象,而是通過代理對象間接地操控原對象。

下面是代理模式UML圖:

代理模式三要素:1.RealSubject 原對象 2.Proxy 代理對象 3.Subject 接口

RealSubject和Proxy都實現了Subject接口,這樣兩者就具有了公共方法Request。

通過執行Proxy中的Request方法,間接的執行RealSubject中的Request方法。

先來個🌰瞭解一下如何實現:

	interface Subject {
	    void request();
	}
	class RealSubject implements Subject{
	
	    @Override
	    public void request() {
	        System.out.println("RealSubject");
	    }
	}
	class Proxy implements Subject{
	    private RealSubject realSubject;
	
	    public Proxy(RealSubject realSubject) {
	        this.realSubject = realSubject;
	    }
	
	    @Override
	    public void request() {
	        System.out.println("Proxy start");
	        realSubject.request();
	        System.out.println("Proxy end");
	    }
	}
	public static void main(String[] args){
	    RealSubject realSubject = new RealSubject();
	    Proxy proxy=new Proxy(realSubject);
	    proxy.request();
	}

以上代碼就是代理模式的實現原理。

通過代理模式:
功能1. 我們可以複用RealSubject類的代碼。
功能2. 在執行RealSubject的request方法執行之前和執行之後,插入一段代碼(比如打印出來request方法的執行時間)。

對於功能1 接下來讓我們思考一個問題:
假如Subject接口聲明瞭2個方法。而我們需要複用RealSubject其中的1個方法:

	interface Subject {
	    void request1();
	    void request2();
	}
	class RealSubject implements Subject{
	    @Override
	    public void request1() {
	        System.out.println("RealSubject request1");
	    }
	
	    @Override
	    public void request2() {
	        System.out.println("RealSubject request2");
	    }
	}
	class Proxy implements Subject{
	    private RealSubject realSubject;
	
	    public Proxy(RealSubject realSubject) {
	        this.realSubject = realSubject;
	    }
	
	    @Override
	    public void request1() {
	        realSubject.request1();
	    }
	
	    @Override
	    public void request2() {
	        System.out.println("Proxy request2");
	    }
	}

我們需要手動書寫Proxy類,然後重載request1和request2方法,我們可以很快的把代碼敲完。

如果Subject接口聲明瞭100個方法,而我們想複用RealSubject類中的90個方法呢,這敲代碼花費的時間不可忽視。

kotlin成功的解決了這個問題。

二.kotlin代理模式的實現

kotlin實現代理模式非常簡單,看一個官網的🌰:

	interface Base {
	    fun print()
	}
	
	class BaseImpl(val x: Int) : Base {
	    override fun print() { print(x) }
	}
	
	class Derived(b: Base) : Base by b
	
	fun main(args: Array<String>) {
	    val b = BaseImpl(10)
	    Derived(b).print()
	}
	
	
	
	運行結果是:10
  • 轉爲java代碼看一下廬山真面目:

      // Base.java
      import kotlin.Metadata;
      
      public interface Base {
         void print();
      }
      // Derived.java
      import kotlin.Metadata;
      import kotlin.jvm.internal.Intrinsics;
      import org.jetbrains.annotations.NotNull;
      
      public final class Derived implements Base {
         private final Base $$delegate_0;
      
         public Derived(@NotNull Base b) {
            Intrinsics.checkParameterIsNotNull(b, "b");
            super();
            this.$$delegate_0 = b;
         }
      
         public void print() {
            this.$$delegate_0.print();
         }
      }
      // TestKt.java
      import kotlin.Metadata;
      import kotlin.jvm.internal.Intrinsics;
      import org.jetbrains.annotations.NotNull;
      
      public final class TestKt {
         public static final void main(@NotNull String[] args) {
            Intrinsics.checkParameterIsNotNull(args, "args");
            BaseImpl b = new BaseImpl(10);
            (new Derived((Base)b)).print();
         }
      }
      // BaseImpl.java
      import kotlin.Metadata;
      
      public final class BaseImpl implements Base {
         private final int x;
      
         public void print() {
            int var1 = this.x;
            System.out.print(var1);
         }
      
         public final int getX() {
            return this.x;
         }
      
         public BaseImpl(int x) {
            this.x = x;
         }
      }
    

其實就是在編譯期自動生成了Derived類,解放了雙手。

我們可以按照需求複寫print()方法

	interface Base {
	    fun printMessage()
	    fun printMessageLine()
	}
	
	class BaseImpl(val x: Int) : Base {
	    override fun printMessage() { print(x) }
	    override fun printMessageLine() { println(x) }
	}
	
	class Derived(b: Base) : Base by b {
	    override fun printMessage() { print("abc") }
	}
	
	fun main(args: Array<String>) {
	    val b = BaseImpl(10)
	    Derived(b).printMessage()
	    Derived(b).printMessageLine()
	}
	

	輸出:abc10
  • Derived除了可以實現Base接口,還可以繼承其他父類,方法名字遇到衝突怎麼辦

比如Derived繼承了父類Parent,而父類同樣擁有print方法:

	interface Base {
	    fun print()
	}
	
	class BaseImpl(val x: Int) : Base {
	    override fun print() {
	        print(x)
	    }
	}
	
	open class Parent {
	    open fun print() {
	        println("Parent print")
	    }
	}
	
	class Derived(b: Base) : Parent(),Base by b
	
	fun main(args: Array<String>) {
	    val b = BaseImpl(10)
	    Derived(b).print()
	}


	輸出:10

可以看到父類print方法會被覆蓋。

三.kotlin代理模式的總結

  • 1.只能實現對接口方法的代理,即Base類不能爲抽象類。
  • 2.不方便對所有的代理方法進行統一處理。比如說在執行每個方法前都執行相同的邏輯,而java動態代理可以方便的實現這個功能。
  • 3.方法名稱有衝突時,代理類方法優先級較高。
  • 4.編譯期自動生成代理模式。不會影響運行效率。

四.繼承和代理的選擇

如果僅僅是代碼複用和方法重寫,繼承能達到和代理一樣的效果。

  • 繼承和代理的使用都存在條件限制:

    • 如果使用繼承的話,父類必須爲可繼承的,並且想要覆蓋的方法也必須爲可重寫的,即java中類和方法都不能存在 final 修飾符,kotlin 中明確使用 open 修飾符。

    • 使用代理的話,兩者需要存在公共接口,比如上面例子中類 DerivedParent 都需要實現 Base 接口。

    • 由於 kotlinjava 存在單繼承的約束(每個類只能存在一個父類),在使用繼承或者代理均可的情況下,推薦使用代理。

可見性修飾符

類、對象、接口、構造函數、方法、屬性和它們的 setter 都可以有 可見性修飾符。 getter 總是與屬性有着相同的可見性。)Kotlin 中有這四個可見性修飾符:privateprotectedinternalpublic。 如果沒有顯式指定修飾符的話,默認可見性是 public。

kotlin修飾符與java修飾符對比

kotlin java 作用
private private 類內部方法和成員可見,外部不可見
protected protected 繼承他的子類可見
- default 包內可見
internal(模塊內可見) -
public public 公共可見

在本頁可以學到這些修飾符如何應用到不同類型的聲明作用域。

函數、屬性和類、對象和接口可以在頂層聲明,即直接在包內:

	// 文件名:example.kt
	package foo
	
	fun baz() { …… }
	class Bar { …… }
  • kotlin中的變量可見性

    • 如果你不指定任何可見性修飾符,默認爲 public,這意味着你的聲明將隨處可見;
    • 如果你聲明爲 private,它只會在聲明它的文件內可見;
    • 如果你聲明爲 internal,它會在相同模塊內隨處可見;
    • protected 不適用於頂層聲明。

注意:要使用另一包中可見的頂層聲明,仍需將其導入進來。

例如:

	// 文件名:example.kt
	package foo
	
	private fun foo() { …… } // 在 example.kt 內可見
	
	public var bar: Int = 5 // 該屬性隨處可見
	    private set         // setter 只在 example.kt 內可見
	    
	internal val baz = 6    // 相同模塊內可見

類和接口

  • 對於類內部聲明的成員:

    • private 意味着只在這個類內部(包含其所有成員)可見;
    • protected—— 和 private一樣 + 在子類中可見。
    • internal —— 能見到類聲明的 本模塊內 的任何客戶端都可見其 internal 成員;
    • public —— 能見到類聲明的任何客戶端都可見其 public 成員。

請注意在 Kotlin 中,外部類不能訪問內部類的 private 成員。

如果你覆蓋一個 protected 成員並且沒有顯式指定其可見性,該成員還會是 protected 可見性。

例子:

	open class Outer {
	    private val a = 1
	    protected open val b = 2
	    internal val c = 3
	    val d = 4  // 默認 public
	    
	    protected class Nested {
	        public val e: Int = 5
	    }
	}
	
	class Subclass : Outer() {
	    // a 不可見
	    // b、c、d 可見
	    // Nested 和 e 可見
	
	    override val b = 5   // “b”爲 protected
	}
	
	class Unrelated(o: Outer) {
	    // o.a、o.b 不可見
	    // o.c 和 o.d 可見(相同模塊)
	    // Outer.Nested 不可見,Nested::e 也不可見
	}

構造函數可見性

要指定一個類的的主構造函數的可見性,使用以下語法(注意你需要添加一個顯式 constructor 關鍵字):

	//私有化類的構造
	class C private constructor(a: Int) { …… }

這裏的構造函數是私有的。默認情況下,所有構造函數都是 public,這實際上等於類可見的地方它就可見(即 一個 internal 類的構造函數只能在相同模塊內可見).

局部聲明

局部變量、函數和類不能有可見性修飾符。

模塊可見性

  • 可見性修飾符 internal 意味着該成員只在相同模塊內可見。更具體地說, 一個模塊是編譯在一起的一套 Kotlin 文件:

    • 一個 IntelliJ IDEA 模塊;
    • 一個 Maven 項目;
    • 一個 Gradle 源集(例外是 test 源集可以訪問 main 的 internal 聲明);
    • 一次 Ant 任務執行所編譯的一套文件。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章