接口

接口是用于实现多态编程的重要组成部分。我们都知道,Java是单继承结构的语言,任何一个类最多只能继承一个父类,但是却可以实现任意多个接口,Kotlin也是如此。

我们可以在接口中定义一系列的抽象行为,然后由具体的类去实现。下面还是通过具体的代码来学习一下,首先创建一个Study接口,并在其中定义几个学习行为。右击包→New→Kotlin File/Class,在弹出的对话框中输入“Study”,创建类型选择“Interface”。

然后在Study接口中添加几个学习相关的函数,注意接口中的函数不要求有函数体,代码如下所示:

interface Study {
    fun readBooks()
    fun doHomework()
}

接下来就可以让Student类去实现Study接口了,这里我将Student类原有的代码调整了一下,以突出继承父类和实现接口的区别:

class Student(name: String, age: Int) : Person(name, age), Study {
    override fun readBooks() {
        println(name + " is reading.")
    }

    override fun doHomework() {
        println(name + " is doing homework.")
    }
}

熟悉Java的人一定知道,Java中继承使用的关键字是extends,实现接口使用的关键字是implements,而Kotlin中统一使用冒号,中间用逗号进行分隔。上述代码表示Student类继承了Person类,同时还实现了Study接口。另外接口的后面不用加上括号,因为它没有构造函数可以去调用。

Study接口中定义了readBooks()和doHomework()这两个待实现函数,因此Student类必须实现这两个函数。

现在我们可以在main()函数中编写如下代码来调用这两个接口中的函数:

fun main() {
    val student = Student("Jack", 19)
    doStudy(student)
}

fun doStudy(study: Study) {
    study.readBooks()
    study.doHomework()
}

首先创建了一个Student类的实例,本来是可以直接调用该实例的readBooks()和doHomework()函数的,但是我没有这么做,而是将它传入到了doStudy()函数中。doStudy()函数接收一个Study类型的参数,由于Student类实现了Study接口,因此Student类的实例是可以传递给doStudy()函数的,接下来我们调用了Study接口的readBooks()和doHomework()函数,这种就叫作面向接口编程,也可以称为多态。

现在运行一下代码,结果如图2.20所示。

Snipaste_2020-12-30_13-57-03

为了让接口的功能更加灵活,Kotlin还增加了一个额外的功能:允许对接口中定义的函数进行默认实现。其实Java在JDK 1.8之后也开始支持这个功能了,因此总体来说,Kotlin和Java在接口方面的功能仍然是一模一样的。

下面我们学习一下如何对接口中的函数进行默认实现,修改Study接口中的代码,如下所示:

interface Study {
    fun readBooks()

    fun doHomework() {
        println("do homework default implementation.")
    }
}

可以看到,我们给doHomework()函数加上了函数体,并且在里面打印了一行日志,成为它的默认实现。现在当一个类去实现Study接口时,只会强制要求实现readBooks()函数,而doHomework()函数则可以自由选择实现或者不实现,不实现时就会自动使用默认的实现逻辑。

现在回到Student类当中,你会发现如果我们删除了doHomework()函数,代码是不会提示错误的,而删除readBooks()函数则不行。

接口中的属性

接口中的属性只能是抽象的,不允许初始化值,接口不会保存属性值,实现接口时,必须重写属性:

interface MyInterface{    
    var name:String //name 属性, 抽象的 
}  
class MyImpl:MyInterface{    
    override var name: String = "runoob" //重写属性 
}
实例
interface MyInterface {    
	var name:String //name 属性, 抽象的    
    fun bar()    
    fun foo() {        
        // 可选的方法体        
        println("foo")    
    } 
} 
class Child : MyInterface {
    override var name: String = "runoob" //重写属性   
    override fun bar() {       
        // 方法体        
        println("bar")    
    }
} 
fun main(args: Array<String>) {
    val c =  Child()    
    c.foo();    
    c.bar();   
    println(c.name) 
}

输出结果为:

foo
bar
runoob

函数重写

实现多个接口时,可能会遇到同一方法继承多个实现的问题。例如:

实例
interface A {    
    fun foo() {
        print("A") 
    }   
    // 已实现    fun bar()  
    // 未实现,没有方法体,是抽象的 
}  
interface B {  
    fun foo() {
        print("B")
    }   
    // 已实现    
    fun bar() {
        print("bar")
    } 
    // 已实现 
}  
class C : A {  
    override fun bar() { 
        print("bar")
    }   // 重写 
}  
class D : A, B {  
    override fun foo() { 
        super<A>.foo()  
        super<B>.foo()  
    }     
    override fun bar() {  
        super<B>.bar()  
    }
}  

fun main(args: Array<String>) { 
    val d =  D()    d.foo();    d.bar();
}

输出结果为:

ABbar

实例中接口 A 和 B 都定义了方法 foo() 和 bar(), 两者都实现了 foo(), B 实现了 bar()。因为 C 是一个实现了 A 的具体类,所以必须要重写 bar() 并实现这个抽象方法。

然而,如果我们从 A 和 B 派生 D,我们需要实现多个接口继承的所有方法,并指明 D 应该如何实现它们。这一规则 既适用于继承单个实现(bar())的方法也适用于继承多个实现(foo())的方法。

数据类与单例类

1.数据类

在一个规范的系统架构中,数据类通常占据着非常重要的角色,它们用于将服务器端或数据库中的数据映射到内存中,为编程逻辑提供数据模型的支持。

数据类通常需要重写equals()、hashCode()、toString()这几个方法。

这里我们新构建一个手机数据类,字段就简单一点,只有品牌和价格这两个字段。如果使用Java来实现这样一个数据类,代码就需要这样写:

public class Cellphone {
    String brand;
    double price;

    public Cellphone(String brand, double price) {
        this.brand = brand;
        this.price = price;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Cellphone) {
            Cellphone other = (Cellphone) obj;
            return other.brand.equals(brand) && other.price == price;
        }
        return false;
    }

    @Override
    public int hashCode() {
        return brand.hashCode() + (int) price;
    }

    @Override
    public String toString() {java
        return "Cellphone(brand=" + brand + ", price=" + price + ")";
    }
}

而同样的功能使用Kotlin来实现就会变得极其简单,右击包→New→Kotlin File/Class,在弹出的对话框中输入“Cellphone”,创建类型选择“Class”。然后在创建的类中编写如下代码:

data class Cellphone(val brand: String, val price: Double)

你没看错,只需要一行代码就可以实现了!神奇的地方就在于data这个关键字,当在一个类前面声明了data关键字时,就表明你希望这个类是一个数据类,Kotlin会根据主构造函数中的参数帮你将equals()、hashCode()、toString()等固定且无实际逻辑意义的方法自动生成,从而大大减少了开发的工作量。

另外,当一个类中没有任何代码时,还可以将尾部的大括号省略。

下面我们来测试一下这个数据类,在main()函数中编写如下代码:

fun main() {
    val cellphone1 = Cellphone("Samsung", 1299.99)
    val cellphone2 = Cellphone("Samsung", 1299.99)
    println(cellphone1)
    println("cellphone1 equals cellphone2 " + (cellphone1 == cellphone2))
}

这里我们创建了两个Cellphone对象,首先直接将第一个对象打印出来,然后判断这两个对象是否相等。运行一下程序,结果如图所示。

Snipaste_2020-12-30_14-05-26

2.单例类

想必你一定听说过单例模式吧,这是最常用、最基础的设计模式之一,它可以用于避免创建重复的对象。比如我们希望某个类在全局最多只能拥有一个实例,这时就可以使用单例模式。当然单例模式也有很多种写法,这里就演示一种最常见的Java写法吧:

public class Singleton {
    private static Singleton instance;

    private Singleton() {}

    public synchronized static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }

    public void singletonTest() {
        System.out.println("singletonTest is called.");
    }
}

这段代码其实很好理解,首先为了禁止外部创建Singleton的实例,我们需要用private关键字将Singleton的构造函数私有化,然后给外部提供了一个getInstance()静态方法用于获取Singleton的实例。在getInstance()方法中,我们判断如果当前缓存的Singleton实例为null,就创建一个新的实例,否则直接返回缓存的实例即可,这就是单例模式的工作机制。

而如果我们想调用单例类中的方法,也很简单,比如想调用上述的singletonTest()方法,就可以这样写:

Singleton singleton = Singleton.getInstance();
singleton.singletonTest();

虽然Java中的单例实现并不复杂,但是Kotlin明显做得更好,它同样是将一些固定的、重复的逻辑实现隐藏了起来,只暴露给我们最简单方便的用法。

在Kotlin中创建一个单例类的方式极其简单,只需要将class关键字改成object关键字即可。现在我们尝试创建一个Kotlin版的Singleton单例类,右击包→New→Kotlin File/Class,在弹出的对话框中输入“Singleton”,创建类型选择“Object”,点击“OK”完成创建,初始代码如下所示:

object Singleton {
}

现在Singleton就已经是一个单例类了,我们可以直接在这个类中编写需要的函数,比如加入一个singletonTest()函数:

object Singleton {
    fun singletonTest() {
        println("singletonTest is called.")
    }
}

可以看到,在Kotlin中我们不需要私有化构造函数,也不需要提供getInstance()这样的静态方法,只需要把class关键字改成object关键字,一个单例类就创建完成了。而调用单例类中的函数也很简单,比较类似于Java中静态方法的调用方式:

Singleton.singletonTest()

这种写法虽然看上去像是静态方法的调用,但其实Kotlin在背后自动帮我们创建了一个Singleton类的实例,并且保证全局只会存在一个Singleton实例。

本文章参考自郭霖的第一行代码——Android(第3版)

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐