在java8之前,接口(interface)中只允许存在抽像方法(abstract method),而在java8中允许我们使用default关键字,为接口添加非抽象方法,该方法被称为扩展方法(或者直接叫做默认方法),该方法无须子类实现。如下:
interface Calculate{
int calculate(int i);
default int abs(int i) {
return Math.abs(i);
}
}
在接口Calculate中,除了传统的抽像方法calculate外,还定义了一个默认方法abs,在Calculate的实现类中可以直接使用该默认方法。
Calculate calculate=new Calculate() {
@Override
public int calculate(int i) {
return i>0?i:-1;
}
};
System.out.println(calculate.calculate(-7));
System.out.println(calculate.abs(-5));
在明白了什么是默认方法后,我们来想想为什么要有默认方法。
开发中,我们提倡面向抽象编程,这当然是没有任何问题的。但是在某些情况下,随着业务的改进我们早期的的接口需要进行修改,很不幸的是你需要修改全部的实现类,如果只有几个实现类,修改量还可接受,如果是几十个实现类呢?我们需要为当前接口Calculate的所有子类添加一个print方法并不影响已有的实现,那么此时就可以使用默认方法。在Java8之前,集合方法没有foreach方法,如果在没有默认方法的前提下我们要为其增加该方法的支持,那么就不得不修改所有现有的实现类了。我们可以通过查看Iterable接口的源码来看了解这方面的改进:
public interface Iterable<T> {
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
default Spliterator<T> spliterator() {
return Spliterators.spliteratorUnknownSize(iterator(), 0);
}
}
由此看来,默认方法出现的本质是为了解决接口的修改与现有的实现不兼容的问题。
既然现在的接口中可以有实现方法了,那它与抽象类有什么区别?下面做个简单对比,但是在对比之前,我希望你能先认识接口和抽象类的本质区别(自行google)。
相同 | 不相同 |
---|---|
1、都可以有抽像方法; 2、都是抽象类型 | 1、抽象类不可多继承,而接口可以 2、抽象类表示的是is-a关系,而接口表示的是like-a关系 |
接下来我们来了解一下默认方法在继承关系中的表现?
假设同时存在三个接口:InterfaceA,Interface B,Interface C,其继承关系如下:
InterfaceA和InterfaceB拥有相同的签名的默认方法default String say(String name)
,此时InterfaceC中必须override此方法,否则编译报错.
interface A {
default String say(String name) {
return "hello "+name;
}
}
interface B {
default String say(String name) {
return "hi "+name;
}
}
interface C extends A, B {
@Override
default String say(String name){
return "ok"+name;
}
}
如果InterfaceA和InterfaceB中的方法签名不同,那么InterfaceC将隐式继承了父接口的默认方法,无需override了,其继承关系如下:
interface A {
default String say(String name) {
return "hello " + name;
}
}
interface B {
default String say(byte[] name) {
return new String(name);
}
}
interface C extends A,B{
}
假设同时存在三个接口:InterfaceA,Interface B,Interface C,其继承关系如下:
interface A {
default String say(String name) {
return "hello " + name;
}
}
interface B extends A{
default String say(String name) {
return "hi,"+new String(name);
}
default void print() {
System.out.println("hi,man");
}
}
interface C extends B {
}
这和我们以前的类继承一致,并没有其他不同。
假设同时存在四个接口:InterfaceA,Interface B,Interface C,Interface D其继承关系如下:
假设同时存在InterfaceA ,class C,class B,其继承关系如下图所以:
interface A {
default String say(String name) {
return "hello " + name;
}
}
public class B {
public String say(String name) {
return "hi,"+new String(name);
}
}
public class C extends B implements A {
}
public class InterfaceTest {
public static void main(String[] args) {
C c=new C() {
};
System.out.println(c.say("dong"));
}
输出结构为:hi,dong。可以看出子类优先继承父类的的方法,如果父类不存在该签名的方法,则继承接口的默认方法。
除了为接口增加了默认方法外,现在的接口也支持添加静态方法:
interface Calculate {
int calculate(int i);
default int abs(int i) {
return Math.abs(i);
}
public static void test() {
System.out.println("can add static method in interface");
}
}
当然现在你也可以在接口中添加常量了:
interface Calculate {
public static int VALUE=9;
int calculate(int i);
default int abs(int i) {
return Math.abs(i);
}
public static void test() {
System.out.println("can add static method in interface");
}
}
我们在Calculate接口添加了静态常量VALUE,你可能会感到困惑:final关键字跑哪去了?在接口中添加静态常量可以省略final或者static关键字,编译器在编译时会将其隐式编译为静态常量。这也意味这你无法在其实现类中对其进行修改。
interface Calculate {
public static final int VALUE=9;
/*public static int VALUE=9; public int VALUE=9; 这两种写法等价于public static final int VALUE=9; 尽管如此,我建议你不要这么写,避免让其他人感到困惑 */
int calculate(int i);
default int abs(int i) {
return Math.abs(i);
}
public static void test() {
System.out.println("can add static method in interface");
}
}