java8里面,接口迎来两个重大变化,一是可以定义static方法,二是可以定义default方法,也叫作默认方法或虚拟扩展方法或防护方法。
相比于普通类里面的静态方法,接口里面定义static方法没有特殊要求,和普通类里面定义静态方法规则基本都是一样的。接口的静态方法可以通过接口直接调用,而且静态方法必须是public的。
下面主要介绍默认方法。
先给一个例子:
public interface A {
default A createA(){
return new AImpl();
}
}
public class AImpl implements A{}
默认方法前面多了一个关键字default,而且可以对默认方法提供实现,实现类可以不实现该方法。
默认方法有点类似与抽象类的非抽象方法,不同的是在接口里面提供了默认实现,实现类可以根据需要决定是否覆盖。默认方法必须都是public的。
默认方法不能覆盖Object中的方法,但可以重载Object中的方法。比如:toString()、equals()、hashCode()不能在接口中被覆盖,但可以重载。如果强行覆盖会报如下编译错误:
java: 接口 interfaceDefault.A 中的默认方法toString覆盖了 java.lang.Object 的成员
为什么不能覆盖?
这是因为有一个“类优先”原则 ,这个原则是这么说的,若一个接口中定义了一个默认方法,而另外一个父类又定义了一个同名方法,那么调用时会调用父类中的同名方法而忽略接口中的默认方法。大家都知道,所有的类都继承自Object,Object是所有类的父类,基于上述原则,如果在接口中覆盖了Object的方法,那么这些方法永远都不会被调用到,所以允许接口里面覆盖Object的方法也就没有任何意义了。
类优先原则下文会通过实例再进行介绍。
下面来看一下,关于接口、父类中同时有相同的方法实现的几个问题。
一、接口、父类中同时有相同方法实现,调用时会调用哪个方法?
这个在上面已经介绍过了,基于“类优先”原则,调用时会调用父类的方法,下面通过一个例子看一下:
//接口
public interface A {
default void sayHello(){
System.out.println("i am a");
}
}
//父类
public class Alphabet {
public void sayHello(){
System.out.println("i am alphabet");
}
}
//实现类和子类
public class AImpl extends Alphabet implements A{
public AImpl(){
super();
super.sayHello();
}
}
public class Main {
public static void main(String argv[]){
A a=new AImpl();
a.sayHello();
}
}
运行结果为:
i am alphabet
i am alphabet
可以看到,接口的默认方法被屏蔽了。
二、接着上面这个问题,如果子类中提供了相同的方法,那么会调用哪个方法?
基于对java的了解,肯定是调用子类的方法了。子类修改如下,其他类的代码不变:
public class AImpl extends Alphabet implements A{
public AImpl(){
super();
super.sayHello();
}
public void sayHello(){
System.out.println("i am a");
}
}
运行结果为:
i am a
三、如果多个接口都提供了相同的默认方法,那么实现类会调用哪个方法?
接口定义如下:
public interface A {
default void sayHello(){
System.out.println("i am a");
}
}
public interface B {
default void sayHello(){
System.out.println("i am b");
}
}
接口A和接口B都提供了sayHello()的默认方法,如果ABImpl类实现了接口A和接口B,那么ABImpl必须提供sayHello()的方法实现,以覆盖接口的方法,否则编译报错。
//ABImpl必须提供sayHello的实现
public class ABImpl implements A,B{
public void sayHello(){
System.out.println("i am ABImpl");
}
}
当调用ABImpl的实例时,会执行该类中的实现方法,忽略接口的默认方法。
四、接着第三个问题,如果ABImpl的父类中有相同的sayHello方法,那么ABImpl是否可以不用实现sayHello方法?
答案是肯定的。
//父类
public class Alphabet {
public void sayHello(){
System.out.println("i am alphabet");
}
}
//ABImpl可以不用实现sayHello()方法
public class ABImpl extends Alphabet implements A,B{}
五、如果有两个父接口都提供了相同的默认方法,子接口继承了这两个接口,那么子接口必须重写该默认方法。
像下面这个例子,编译会报错:
public interface A {
default void sayHello(){
System.out.println("i am a");
}
}
public interface B {
default void sayHello(){
System.out.println("i am b");
}
}
public interface AB extends A,B{}
接口AB必须实现sayHello()方法:
public interface AB extends A,B{
default void sayHello(){
System.out.println("i am ab");
}
}
还可以这么实现:
public interface AB extends A,B{
default void sayHello(){
A.super.sayHello();
}
}
还可以让实现类实现:
下面这种写法,将sayHello()重新变成抽象方法,这样实现类必须实现该抽象方法。
public interface AB extends A,B{
void sayHello();
}
六、子接口可以使用默认方法来实现父接口的抽象方法。
下面代码不会报错:
public interface A {
void sayHello();
}
public interface AB extends A{
default void sayHello(){
System.out.println("i am ab");
}
}
public class ABImpl implements AB{}