当前位置 博文首页 > 龚厂长的博客:Java 8 新特性之接口默认方法和静态方法

    龚厂长的博客:Java 8 新特性之接口默认方法和静态方法

    作者:[db:作者] 时间:2021-07-26 17:41

    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{}
    
    cs