面试者必看:Java8中的默认方法

封面图

背景

在Java8以前,定义在接口中的全部方法都须要在接口实现类中提供一个实现,若是接口的提供者须要升级接口,添加新的方法,那么全部的实现类都须要把这个新增的方法实现一遍,若是说全部的实现类可以本身控制的话,那么还能接受,可是现实状况是实现类可能不受本身控制。好比说Java中的集合框架中的List接口添加一个方法,那么Apache Commons这种框架就会很难受,必须修改全部实现了List的实现类面试


如今的接口有哪些不便

  1. 向已经发布的接口中添加新的方法是问题的根源,一旦接口发生变化,接口的实现者都须要更新代码,实现新增的接口
  2. 接口中有些方法是可选的,不是全部的实现者都须要实现,这个时候实现类不得不实现一个空的方法,或者是提供一个Adapter对接口中全部的方法作空实现,在Spring中咱们可看到不少这种例子,好比WebMvcConfigurerAdapter
@Deprecated
public abstract class WebMvcConfigurerAdapter implements WebMvcConfigurer {

    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
    }


    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
    }

    @Override
    public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
    }
    
    省略其余代码...
}

在Java8之后这些类都被标注成了过时@Deprecatedspring


默认方法的简介

为了解决上述问题,在Java8中容许指定接口作默认实现,未指定的接口由实现类去实现。如何标识出接口是默认实现呢?方法前面加上default关键字。好比Spring中的WebMvcConfigurersegmentfault

public interface WebMvcConfigurer {

    default void configurePathMatch(PathMatchConfigurer configurer) {
    }

    default void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
    }

    default void configureAsyncSupport(AsyncSupportConfigurer configurer) {
    }
    
    省略其余代码...
}

从如今看来,可能你们都会有个疑问,默认方法和抽象类有什么区别呢?架构

    1. 默认方法不能有实例变量,抽象类能够有
    1. 一个类只能继承一个抽象类,当时能够实现多个接口

默认方法的使用场景

  • 可选方法

为接口提供可选的方法,给出默认实现,这样实现类就不用显示的去提供一个空的方法;这种场景刚才咱们在上面已经说到了,spring中有大量的例子框架

  • 实现多继承

继承是面向对象的特性之一,在Java中一直以来都是单继承的原则,Java8中默认方法为实现多继承提供了可能(因为接口中不能有实例对象,因此可以抽象的到接口中的行为通常都是比较小的模块);从我的的经从来看,作游戏是训练本身面向对象思惟的最好方式(之后有机会分享一下小游戏的制做),由于如今大部分学Java的同窗学完Java基础后就直接进入JavaWeb的学习,整合各类框架,只能在通用的三层架构(Controller、Service、Dao)中写本身的逻辑。ide

相信不少人在都作个坦克大战的游戏,若是用Java8中的默认方法如何设计好多继承呢?
类设计图学习

这里咱们举个简单的例子,定义了三个接口:spa

  1. Moveable:容许移动的物体,把移动的逻辑放入到这个接口中的默认方法
  2. Attackable: 容许攻击的物体,把攻击的通用逻辑放入到默认方法,不通用的逻辑经过模板方法给实现类处理
  3. Location: 获取物体的坐标

经过这些接口的组合方式,咱们就能够为游戏建立不一样的实现类,好比说坦克、草地、墙壁...设计


解决默认方法冲突规则(面试者必看)

经过上面的例子,咱们体验的默认方法给咱们带来了多继承的便利,可是让咱们思考下,若是出现了不一样的类出现的相同签名的默认方法,实际在运行的时候应该如何选择呢?客官不慌,有办法的3d

    1. 类中的方法优先级最高。若是类或者父类(抽象类也OK)中声明了相同签名的方法,那么优先级最高
    1. 若是第一条没法肯定,那么最具体的的实现的默认方法;很绕,举例子:B接口继承了A,B就更加具体,那么B中的方法优先级最高
    1. 若是上面两个都没法判断,那么编译会报错,须要在实现接口,而后手动显式调用
public class C implements A, B  {
    void pint() {
        B.supper.print();  // 显式调用
    }
}

菱形继承问题

为了说明上面的三个原则,咱们直接来看看最复杂的菱形继承问题

菱形继承问题

public interface A {
    default void print(){
        System.out.println("Class A");
    }
}

public interface B extend A {}

public interface C extend A {}

public class D implement B, C {
    public static void main(String[] args) {
       new D().print()
    }
}

这种状况下B,C都没有本身的实现,实际上就只有A有实现,那么会打印Class A

若是说这个时候把接口B接口改一下

public interface B extends A {
    default void print(){
        System.out.println("Class B");
    }
}

根据原则(2),B继承于A,更加具体,因此打印结果应该是B

若是说把接口C修改一下

public interface C extends A {
    default void print(){
        System.out.println("Class C");
    }
}

这时候咱们发现编译报错,须要咱们本身手动指定,修改D中的代码

public class D implements B, C {

    @Override
    public void print() {
        C.super.print();
    }

    public static void main(String[] args) {
        new D().print();
    }
}

总结

  • Java8中的默认方法须要使用default来修饰
  • 默认方法的使用场景可选方法和多继承
  • 三个原则解决相同签名的默认方法冲突问题
相关文章
相关标签/搜索