Java8特性第四讲:Java 8的接口默认方法实现

简介: Java8特性第四讲:Java 8的接口默认方法实现

1、什么是默认方法,为什么要有默认方法

1.1、案例

一个接口A,Clazz类实现了接口A。

public interface A {
    default void foo(){
       System.out.println("Calling A.foo()");
    }
}
public class Clazz implements A {
    public static void main(String[] args){
       Clazz clazz = new Clazz();
       clazz.foo();//调用A.foo()
    }
}

代码是可以编译的,即使Clazz类并没有实现foo()方法。在接口A中提供了foo()方法的默认实现。

1.2、什么是默认方法

简单说,就是接口可以有实现方法,而且不需要实现类去实现其方法。只需在方法名前面加个default关键字即可。

1.3、为什么出现默认方法

为什么要有这个特性? 首先,之前的接口是个双刃剑,好处是面向抽象而不是面向具体编程,缺陷是,当需要修改接口时候,需要修改全部实现该接口的类,目前的java 8之前的集合框架没有foreach方法,通常能想到的解决办法是在JDK里给相关的接口添加新的方法及实现。然而,对于已经发布的版本,是没法在给接口添加新方法的同时不影响已有的实现。所以引进的默认方法。他们的目的是为了解决接口的修改与现有的实现不兼容的问题

背景:Java8中给很多API提供了新的方法,比如常用的java.util.List接口增加了replaceAll、sort、spliterator这三个方法;根据现行的Java语法规范,List的所有子类都需要对这三个方法进行实现,这显然是不能接受的,尤其是众多第三方类库对JDK有依赖,要求他们一同修改也是做不到的

  • 为了解决这个问题,也保证JDK的兼容性,Java8引入了一种新的机制:接口可以声明带有实现的方法

语法规则

  • 1、使用default关键字
  • 2、一个接口中可以含有多个带有默认实现的方法

java.util.List中新增的三个方法源码

public interface List<E> extends Collection<E> {
    default void replaceAll(UnaryOperator<E> operator) {
        Objects.requireNonNull(operator);
        final ListIterator<E> li = this.listIterator();
        while (li.hasNext()) {
            li.set(operator.apply(li.next()));
        }
    }
    default void sort(Comparator<? super E> c) {
        Object[] a = this.toArray();
        Arrays.sort(a, (Comparator) c);
        ListIterator<E> i = this.listIterator();
        for (Object e : a) {
            i.next();
            i.set((E) e);
        }
    }
    default Spliterator<E> spliterator() {
        return Spliterators.spliterator(this, Spliterator.ORDERED);
    }
}

2、java 8抽象类与接口对比

这一个功能特性出来后,有人反馈,java 8的接口都有实现方法了,跟抽象类岂不是差不多?其实还是有的,请看下表对比。。

相同点 不同点
都是抽象类型 抽象类不可以多重继承,接口可以(无论是多重类型继承还是多重行为继承)
都可以有实现方法(以前接口不行) 抽象类和接口所反映出的设计理念不同。其实抽象类表示的是”is-a”关系,接口表示的是”like-a”关系
都可以不需要实现类或者继承者去实现所有方法,(以前不行,现在接口中默认方法不需要实现者实现) 接口中定义的变量默认是public static final 型,且必须给其初值,所以实现类中不能改变其值;抽象类中的变量默认是 friendly 型,其值可以在子类中重新定义,也可以重新赋值。

3、多重继承的冲突

由于同一个方法可以从不同接口引入,自然而然的会有冲突的现象,同一个类可能会从不同的接口中继承了具有相同的签名的方法,这又该如何解决呢?

默认方法判断冲突的规则如下:

1.一个声明在类里面的方法优先于任何默认方法(classes always win)

2.否则,则会优先选取路径最短的。

3.1、举例子

场景1:

public interface A {
    default String test() {
        return "A";
    }
}
public class B {
    public String test() {
        return "B";
    }
}
public class C extends B implements A {
    public static void main(String[] args) {
        System.out.println(new C().test());
    }
}
  • 这个例子中C分别中A和B中都继承了test()方法,但是B是类,而A是接口,所以B中的test()方法优先级更高,因此此次输出结果应该是:B

场景二:

public interface D extends A {
    default String test() {
        return "D";
    }
}
public class E implements A, D {
    public static void main(String[] args) {
        System.out.println(new E().test());
    }
}
  • 这个例子中E分别中A和D这两个接口中继承了test()方法,但是D是A的子接口,所以D接口中的test()方法优先级更高,因此此处输出应该是:D

场景三:

public class F implements A {
}
public class G extends F implements A, D {
    public static void main(String[] args) {
        System.out.println(new G().test());
    }
}
  • 这种场景下虽然F实现了A接口,但是由于它并没有重写test()方法,所以优先级依然是D接口高,因此此处输出依然是:D

场景四:

public interface H {
    default String test() {
        return "H";
    }
}
public class K implements A, H {
    public static void main(String[] args) {
        System.out.println(new K().test());
    }
    @Override
    public String test() {
        return H.super.test();
    }
}
  • 这个场景中,由于A和H没有继承关系,所以在JVM无法识别出到底要调用哪一个接口中的方法,所以需要在类K中显式的覆盖test()方法,指明具体调用哪个接口中的方法,当然也可以自己重写方法实现;

4、默认方法在项目中的使用

场景1:在规则引擎实现类中,我们使用默认方法标识执行阶段,这样子类不用重写该方法,可以节省代码量。

/**
 * 业务检查
 **/
public interface BizChecker extends Checker {
    /**
     * 适用什么阶段
     *
     * @return
     */
    @Override
    @NotNull
    default PhaseEnum phase() {
        return PhaseEnum.BIZ_CHECK;
    }
}

场景2:在错误码接口中,将获取错误字符串方法设置为默认方法,可以节省代码量。

/**
 * 错误码接口
 **/
public interface ErrorCode {
    /**
     * 错误码 必须在{@link ErrorCodeEnum} 中定义
     * @return
     * @see {@link ErrorCodeEnum#getErrCode()}
     */
    String getErrCode();
    /**
     * 错误描述
     * @return
     */
    String getErrDesc();
    /**
     * 是否用在response里对外提示
     * @return
     */
    boolean isFirstTip();
    /**
     * 获取错误字符串:code+desc
     * @return
     */
    default String getErrString() {
        return "code:" + getErrCode() + ",desc:" + getErrDesc() + ",useInResp:" + isFirstTip();
    }
}

场景3:限流器默认方法

/**
 * @Description 限流器
 */
public interface RateLimiter {
    default void acquire() {
        acquire(1);
    }
    /**
     * 限流
     * @param permits 请求资源数
     */
    void acquire(int permits);
}

场景4:处理商品限价,默认方法放在接口上

public interface GoodsFirmHighestPricePretreated {
    /**
     * 处理最高限价针对商品简化
     */
    default Response<Boolean> handleFirmHighestPrice(FullItemDTO fullItemDTO, GoodsPriceContext context) {
        if (CollectionUtils.isEmpty(fullItemDTO.getBaseItem().getSkus())) {
            return Response.ok(true);
        }
    }
}

5、总结

默认方法给予我们修改接口而不破坏原来的实现类的结构提供了便利,目前java 8的集合框架已经大量使用了默认方法来改进了,当我们最终开始使用Java 8的lambdas表达式时,提供给我们一个平滑的过渡体验。也许将来我们会在API设计中看到更多的默认方法的应用。

相关文章
|
3天前
|
存储 Java 开发者
什么是java的Compact Strings特性,什么情况下使用
Java 9引入了紧凑字符串特性,优化了字符串的内存使用。它通过将字符串从UTF-16字符数组改为字节数组存储,根据内容选择更节省内存的编码方式,通常能节省10%至15%的内存。
|
12天前
|
存储 Java 数据挖掘
Java 8 新特性之 Stream API:函数式编程风格的数据处理范式
Java 8 引入的 Stream API 提供了一种新的数据处理方式,支持函数式编程风格,能够高效、简洁地处理集合数据,实现过滤、映射、聚合等操作。
31 5
|
16天前
|
安全 Java 开发者
Java中WAIT和NOTIFY方法必须在同步块中调用的原因
在Java多线程编程中,`wait()`和`notify()`方法是实现线程间协作的关键。这两个方法必须在同步块或同步方法中调用,这一要求背后有着深刻的原因。本文将深入探讨为什么`wait()`和`notify()`方法必须在同步块中调用,以及这一机制如何确保线程安全和避免死锁。
30 4
|
14天前
|
Java 数据处理 数据安全/隐私保护
Java处理数据接口方法
Java处理数据接口方法
21 1
|
IDE Java 关系型数据库
Java14发布,16大新特性,代码更加简洁明快
Java14发布,16大新特性,代码更加简洁明快
323 0
Java14发布,16大新特性,代码更加简洁明快
|
13天前
|
设计模式 Java 开发者
Java多线程编程的陷阱与解决方案####
本文深入探讨了Java多线程编程中常见的问题及其解决策略。通过分析竞态条件、死锁、活锁等典型场景,并结合代码示例和实用技巧,帮助开发者有效避免这些陷阱,提升并发程序的稳定性和性能。 ####
|
11天前
|
存储 监控 小程序
Java中的线程池优化实践####
本文深入探讨了Java中线程池的工作原理,分析了常见的线程池类型及其适用场景,并通过实际案例展示了如何根据应用需求进行线程池的优化配置。文章首先介绍了线程池的基本概念和核心参数,随后详细阐述了几种常见的线程池实现(如FixedThreadPool、CachedThreadPool、ScheduledThreadPool等)的特点及使用场景。接着,通过一个电商系统订单处理的实际案例,分析了线程池参数设置不当导致的性能问题,并提出了相应的优化策略。最终,总结了线程池优化的最佳实践,旨在帮助开发者更好地利用Java线程池提升应用性能和稳定性。 ####
|
13天前
|
缓存 Java 开发者
Java多线程编程的陷阱与最佳实践####
本文深入探讨了Java多线程编程中常见的陷阱,如竞态条件、死锁和内存一致性错误,并提供了实用的避免策略。通过分析典型错误案例,本文旨在帮助开发者更好地理解和掌握多线程环境下的编程技巧,从而提升并发程序的稳定性和性能。 ####
|
7天前
|
安全 算法 Java
Java多线程编程中的陷阱与最佳实践####
本文探讨了Java多线程编程中常见的陷阱,并介绍了如何通过最佳实践来避免这些问题。我们将从基础概念入手,逐步深入到具体的代码示例,帮助开发者更好地理解和应用多线程技术。无论是初学者还是有经验的开发者,都能从中获得有价值的见解和建议。 ####
|
7天前
|
Java 调度
Java中的多线程编程与并发控制
本文深入探讨了Java编程语言中多线程编程的基础知识和并发控制机制。文章首先介绍了多线程的基本概念,包括线程的定义、生命周期以及在Java中创建和管理线程的方法。接着,详细讲解了Java提供的同步机制,如synchronized关键字、wait()和notify()方法等,以及如何通过这些机制实现线程间的协调与通信。最后,本文还讨论了一些常见的并发问题,例如死锁、竞态条件等,并提供了相应的解决策略。
24 3