Kotlin基础 - 第十章抽象类和接口

本文详细介绍了Kotlin中的接口,包括接口定义、属性、继承、解决覆盖冲突和接口代理。讨论了Kotlin如何通过代理模式实现代码复用,并与继承进行了比较。还涵盖了可见性修饰符的应用,如包、类、接口、构造函数和局部声明的可见性规则。
摘要由CSDN通过智能技术生成

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修饰符对比

kotlinjava作用
privateprivate类内部方法和成员可见,外部不可见
protectedprotected继承他的子类可见
-default包内可见
internal(模块内可见)-
publicpublic公共可见

在本页可以学到这些修饰符如何应用到不同类型的声明作用域。

函数、属性和类、对象和接口可以在顶层声明,即直接在包内:

	// 文件名: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 任务执行所编译的一套文件。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值