抽象类和接口是面向对象的高级特性,Kotlin 中的抽象类和接口与 Java 8 类似。
1. 抽象类
Kotlin 中将一个类声明为抽象类的方式与 Java 相同,使用 abstract 关键字:
abstract class 抽象类名[(主构造函数)][: 继承父类和实现接口] {……}
我们之前提到,Kotlin 中的类默认不可继承,但抽象类本身就是为继承设计的,因此 即使不用 open 关键字修饰,抽象类也是可以被继承的 ,同样,使用 abstract 关键字修饰的抽象函数也可以不加 open。但是抽象类中的非抽象方法如果不用 open 修饰的话,还是不能被子类覆盖。
除了上面两点外,Kotlin 的抽象类与 Java 抽象类的用法基本相同。
2. 接口
Kotlin 的接口与 Java 8 中的接口类似,使用 interface 关键字定义接口,允许方法有默认实现:
interface KotlinInterface {
var prop: Int
fun foo(prop: Int)
fun bar() {
println("bar")
}
}
从上面代码可以发现 Kotlin 接口的几个细节:Kotlin 接口不能有构造函数,也就是说 Kotlin 接口定义时,接口名后是不能加主构造函数的;
Kotlin 接口中函数可以有默认实现,也可以没有,区别只在于函数有没有函数体,这与 Java 8 中需要用 default 关键字声明有默认实现的方法有所不同;
Kotlin 接口允许存在抽象变量,上面的定义的 prop 变量,实际上会被编译为 getProp(): Int 和 setProp(Int) 两个抽象方法,Kotlin 子类在实现接口时只需要用 override 覆盖这个属性,Java 子类在实现接口时需要先定义一个字段,然后实现这两个方法;
Kotlin 接口的属性只能是抽象的,也就是说 Kotlin 不允许像 Java 那样,在接口中定义 public static final 的全局常量。这点是因为 Kotlin 会把接口中定义的属性编译为 getter 和 setter 方法,而 Kotlin 字节码的版本只相当于 Java 6,不支持 Java 8 中的接口默认实现,所以不能给变量加上初始化语句。
------- 2017.5.27 更新 ----------
有小伙伴问到了默认方法的实现,在这里回答一下,让更多的人看到。
Kotlin 目前的目标字节码还停留在 JDK 1.6 版本,而 Java 直到 JDK 1.8 才支持接口添加默认实现,所以 Kotlin 在编译有默认实现的接口方法时,必须用一些“奇技淫巧”,用一些妥协的办法:
特殊的编译方式:静态内部类
没有默认实现的 Kotlin 接口方法,编译后与 JDK 1.8 之前的接口方法并无区别。而有默认实现的 Kotlin 接口方法会被编译为两个方法:一个是在接口里,没有默认实现,用来让子类继承;另一个在名为 DefaultImpls 的静态内部类里,我们给方法定义的默认实现就被编译器搬到了这里,同时添加了一个接口类型的参数。
比如我们上面举的 KotlinInterface 接口就会被编译下面的样子:
public interface KotlinInterface {
public abstract int getProp();
public abstract void setProp(int value);
public abstract void foo(int prop);
public abstract void bar();
public static final class DefaultImpls {
public static void bar(KotlinInterface $this) {
System.out.println("bar");
}
}
}
特殊的实现方式:自动实现默认方法
在实现有默认实现方法的 Kotlin 接口时,如果没有覆盖默认实现方法,Kotlin 编译器会自动添加一个实现方法:直接调用 DefaultImpls 类的方法;如果覆盖了默认实现方法,就不会再添加这个方法了。
比如,我们添加一个 KotlinInterfaceImpl 类,它实现了 KotlinInterface 接口,但没有实现 bar() 方法:
class KotlinInterfaceImpl : KotlinInterface {
// getProp(), setProp() override fun foo(value: Int) = value
}
反编译这个类,发现编译器已经帮我们实现了 bar() 方法:
public class KotlinInterfaceImpl implements KotlinInterface {
// getProp(), setProp() @Override
public int foo(int value) {
return value;
}
@Override
public void bar(KotlinInterface $this) {
return KotlinInterface.DefaultImpls.bar(this);
}
}
这样,我们就能直接调用 KotlinInterfaceImpl 类对象的 bar() 方法了,它实际上调用的是 KotlinInterface.DefaultImpls.bar() 方法。
总结一下,Kotlin 通过静态内部类 + 添加默认实现方法的方式实现了 JDK 1.6 中不支持的接口默认方法,给接口的设计带来了便利。当用 Java 编写 Kotlin 接口的实现类时,需要注意到这一点,可以通过调用 DefaultImpls 类的方法来使用接口提供的默认实现。