乐趣区

关于java:面试者必看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 当前这些类都被标注成了过期@Deprecated


默认办法的简介

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

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)中写本人的逻辑。

置信很多人在都做个坦克大战的游戏,如果用 Java8 中的默认办法如何设计好多继承呢?

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

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

通过这些接口的组合形式,咱们就能够为游戏创立不同的实现类,比如说坦克、草地、墙壁 …


解决默认办法抵触规定(面试者必看)

通过下面的例子,咱们体验的默认办法给咱们带来了多继承的便当,然而让咱们思考下,如果呈现了不同的类呈现的雷同签名的默认办法,理论在运行的时候应该如何抉择呢?客官不慌,有方法的

    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 来润饰
  • 默认办法的应用场景可选办法和多继承
  • 三个准则解决雷同签名的默认办法抵触问题
退出移动版