前言
在上一篇文章我们详细的介绍了Kotlin中的基础知识类与继承,类与继承的细节知识点有很多,希望每个读者都能牢牢的掌握它。本篇文章我们继续讲解Kotlin中的基础知识接口,由于语法上的差异,Kotlin中的接口和Java中的接口还是有一些区别的,下面我们开始本篇文章的学习。
1.接口
和Java语言一样在Kotlin中我们使用关键字interface
来定义一个接口。
kotlin
interface CallBack { }
在Kotlin中接口既可以声明一个抽象方法也可以包含该抽象方法的实现,这听起来好像不太对。接口中的方法不是都是抽象的吗?为什么还能在定义的时候实现该抽象方法,下面我们就来看一下这个问题,我们在接口CallBack中定义一个抽象方法onSuccess,不实现该抽象方法,并定义一个HttpRequest的类来实现CallBack接口,如下示例代码:
很显然编译器会提示我们要去实现onSuccess这个抽象方法。当我们给onSuccess方法添加一个默认实现的时候,编译器不再报语法错误。
看到这里,是不是感到奇怪呢?为什么可以在接口CallBack中给抽象方法onSuccess添加实现呢?而且不需要我们在接口的实现类中去实现这个抽象方法呢?在Android Studio中依次打开Tools -> Kotlin -> Show Kotlin Bytecode,在右边弹窗框中我们点击Decompile按钮。 我们来看一下反编译成Java的代码:
java
public final class HttpRequest implements CallBack {
public void onSuccess() {
CallBack.DefaultImpls.onSuccess(this);
}
}
public interface CallBack {
void onSuccess();
public static final class DefaultImpls {
public static void onSuccess(CallBack callback) {
String var1 = "onSuccess called";
System.out.println(var1);
}
}
}
为了方便阅读这里对反编译的Java代码做了适当的删减和更改。我们可以看到在CallBack接口中默认帮我们声明了一个静态内部类DefaultImpls,并在该类中声明了一个同名的静态方法onSuccess,该方法内有我们在Kotlin中定义的CallBack接口中的onSuccess方法的具体实现,而HttpRequest类也帮我们默认实现了onSuccess这个抽象方法,通过调用DefaultImpls中的同名方法来完成具体的实现。现在你应该能理解Kotlin中抽象方法的默认实现了吧。
kotlin
interface CallBack {
fun onSuccess() {
println("onSuccess called")
}
}
这种写法编译器已经帮我们在其实现类中默认的实现了该抽象方法,所以我们如果不需要每个子类都去显示的实现接口中的抽象方法,我们就可以采用这种默认的实现方式。
2.接口中的属性
在接口中我们定义的属性都是抽象的,和方法一样他们默认都是可覆盖的。我们无法给一个抽象的属性直接初始化,但是我们可以为其提供访问器get和set方法的实现。如下代码示例,如果我们强行给一个抽象的属性初始化,编译器会报语法错误:
在上一篇类与继承的文章中我们介绍到,可以在子类中使用var
来覆盖一个声明为val
的属性,但反之却不行。因为将一个声明为val
的属性覆盖时声明为var
就代表我们在子类中给该属性添加了set方法的实现。
如上示例代码我们将在CallBack接口中声明为val
的isFailure属性在HttpRequest类中覆盖的时候声明为var
,并为其提供set访问器的实现。
3.接口的继承
一个接口可以从其它接口派生,如果派生出的接口实现了其父接口中的属性或者方法。在该派生接口的子类中就可以选择的覆盖该方法或者属性,因为我们已经在父类中实现了该抽象方法或者属性。
如果该派生接口没有实现父接口中定义的属性或者方法,那么很显然该派生接口的子类必须要实现其父接口中的抽象属性或者方法,否则编译器会提示错误,强行让你去实现该属性或者方法。
当我们在该派生接口的子类中去实现它父接口中的抽象方法或者属性的时候,编译器不再报语法错误。
4.多个父接口中有重名的方法
关于多个父类中有重名的方法,我们已经在上一篇类与继承的文章中介绍了,接口亦是如此。我们使用<>
限制的super
语句来区分要调用那个父类中的同名方法。如下示例代码:
kotlin
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()
}
}
5.函数式接口
关于函数式接口 ,其实我们已经在高阶函数和Lambda表达式的文章中详细的介绍了。这里既然介绍了接口,我觉得有必要再将这部分内容再重新介绍一下。我们先来看下函数式接口在官方文档中的定义:只有一个抽象方法的接口称为函数式接口 或 SAM(单一抽象方法) 接口。函数式接口可以有多个非抽象成员,但只能有一个抽象成员。事实上在Kotlin中定义一个函数类型就相当于在Java中定义了一个接口,例如我们在MyInterface.kt文件中定义一个如下可空的函数类型block:
kotlin
val block: (() -> Unit)? = null
在Android Studio中打开Tools -> Kotlin -> Show Kotlin Bytecode,在弹出框中我们点击Decompile按钮:
我们得到如下反编译的Java代码:
我的函数类型参数block,被转换成了Function0,我们将鼠标选中Fuction0,然后点击定位到Kotlin中的Functions.kt文件中,如下:
kotlin
public interface Function0<out R> : Function<R> {
public operator fun invoke(): R
}
public interface Function1<in P1, out R> : Function<R> {
public operator fun invoke(p1: P1): R
}
public interface Function2<in P1, in P2, out R> : Function<R> {
public operator fun invoke(p1: P1, p2: P2): R
}
...
为了方便阅读这里我只贴出了前三个接口,我们定义的每一个函数类型在Java中都有对应的Function接口与之对应。事实上Kotlin中函数类型的出现确实替代了Java中接口回调的能力。比如我们举一个常见的例子,OnClickListener接口就是一个函数式接口。
csharp
public interface OnClickListener {
void onClick(View v);
}
在Java中当我们要给一个View设置点击事件的时候,通常我们会用new
关键字创建一个匿名类的方式来实现:
less
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
而上面的OnClicklistener接口对等的转换成函数类型就是:
kotlin
(View) -> Unit
对于接口匿名类实现的方式我们可以对等的使用Lambda表达式来完成函数类型的初始化。
sql
{ view: View -> }
现在我们再使用Lambda表达式的方式来简化上面对View点击事件的设置:
css
view.setOnClickListener({ view: View -> println("view = $view")})
Lambda表达式是该方法的唯一参数,可以在调用时省略 (),将花括号 { } 直接放在方法名后。
css
view.setOnClickListener { view: View -> println("view = $view") }
函数类型只有一个参数,可以在Lambda表达式中省略参数的声明和 ->。该参数将被隐式的声明为it。
bash
view.setOnClickListener { println("view = $it") }
总结
本篇文章我们主要介绍了Kotlin中接口的使用,接口中方法的默认实现、属性的定义、接口的继承、多个父接口中同名方法的调用、以及函数式接口。下篇文章我们将继续介绍Kotlin中的基础知识object
关键字的使用。如果你觉得本篇文章对你有帮助,麻烦留个赞再走。你的支持就是我创作的最大动力。我们下期再见~