zoukankan      html  css  js  c++  java
  • Java8系列 (四) 静态方法和默认方法

    静态方法和默认方法

    我们可以在 Comparator 接口的源码中, 看到大量类似下面这样的方法声明

        //default关键字修饰的默认方法
        default Comparator<T> thenComparingInt(ToIntFunction<? super T> keyExtractor) {
            return thenComparing(comparingInt(keyExtractor));
        }
        //Comparator接口中的静态方法
        public static <T extends Comparable<? super T>> Comparator<T> naturalOrder() {
            return (Comparator<T>) Comparators.NaturalOrderComparator.INSTANCE;
        }

    其中 thenComparingInt() 就是一个默认方法, 它使用 default 关键字修饰。这是Java8引入的新功能: 接口中可以声明默认方法和静态方法。

    默认方法带来的多继承问题

    在此之前, Java中的类只支持多重继承, 不支持多继承。现在有了默认方法, 你可以以另一种方式来实现类的多继承行为, 即一个类实现多个接口, 而这几个接口都有声明自己的默认方法。

    这里面引发了一个多继承的问题, 设想一下, 假如一个类从多个接口中继承了它们声明的默认方法, 而这几个默认方法使用的都是相同的函数签名, 那么程序运行时, 类会选择调用哪一个方法呢?

    代码清单一:

        @Test
        public void test2() {
            new C().hello();//result: hello from D
        }
    
        interface A {
            default void hello() {
                System.out.println("heelo from A");
            }
        }
    
        interface B extends A {
            default void hello() {
                System.out.println("heelo from B");
            }
        }
    
        class D implements A{
            public void hello() {
                System.out.println("hello from D");
            }
        }
    
        class C extends D implements A, B{
        }

    代码清单一的输出结果是 hello from D,  可以看到, C类的父类D、父接口A、父接口B都定义了一个相同函数签名的  hello() , 最后实际调用的是父类D中声明的方法。

    代码清单二:

        @Test
        public void test4() {
            new I().hello();//result: heelo from G
        }
    
        class I implements G, H { }
        
        interface G extends E {
            default void hello() {
                System.out.println("heelo from G");
            }
        }
        
        interface H extends E { }
    
        interface E {
            default void hello() {
                System.out.println("heelo from E");
            }
        }

    代码清单二的输出结果是 hello from G,  可以看到, I类的父接口G、父接口E都定义了一个相同函数签名的 hello() ,  最后实际调用的是父接口G中声明的方法。

    代码清单三:

        @Test
        public void test3() {
            new F().hello(); //result: heelo from E
        }
    
        interface A {
            default void hello() {
                System.out.println("heelo from A");
            }
        }
    
        interface E {
            default void hello() {
                System.out.println("heelo from E");
            }
        }
    
        class F implements A, E {
            public void hello() {
                //这里接口A和E不再具有继承关系,需显式的选择调用接口E或A中的方法,否则无法通过编译
                E.super.hello();
            }
        }

    代码清单三中, 类F必须显式的覆盖父接口的 hello() 方法, 否则无法通过编译器的检测, 因为编译器无法确定父接口A和父接口E中的默认方法哪一个优先。

    这种情况下, 如果你想调用某个父接口的默认方法, 可以使用  接口名.super.默认方法名 这种方式进行调用。

    总结

    Java8的新特性: 接口中可以声明默认方法和静态方法。

    另外, 接口默认方法带来的多继承问题, 即如果一个类使用相同的函数签名从多个地方(比如另一个类或接口)继承了方法, 通过三条规则可以进行判断:

    • 类中的方法优先级最高。类或父类中声明的方法的优先级高于任何声明为默认方法的优先级。
    • 如果无法依据第一条进行判断,那么子接口的优先级更高:函数签名相同时,优先选择有最具体实现的默认方法的接口,即如果B继承了A,那么B就比A更加具体。
    • 最后, 如果还是无法判断, 继承了多个接口的类必须通过显式覆盖和调用期望的方法, 显式地选择使用哪一个默认方法的实现(调用语法:  接口名.super.默认方法名 )。

    作者:张小凡
    出处:https://www.cnblogs.com/qingshanli/
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。如果觉得还有帮助的话,可以点一下右下角的【推荐】。

  • 相关阅读:
    android 本地字符串存取
    2020-07-17:线上一个服务有4个实例突然变得访问很慢,你会从什么地方入手找原因?
    2020-07-16:如何获得一个链表的倒数第n个元素?
    2020-07-15:死锁与活锁的区别,死锁与饥饿的区别?
    2020-07-14:es用过冷热分离吗?假如现在有些数据热变冷,有些数据冷变热,怎么解决?
    2020-07-28:已知sqrt (2)约等于 1.414,要求不用数学库,求sqrt (2)精确到小数点后 10 位。
    2020-07-29:从 innodb 的索引结构分析,为什么索引的 key 长度不能太长?
    2020-07-27:如何设计一个分布式文件系统,如何设计动态扩容和数据定位?
    2020-07-26:如何用 socket 编程实现 ftp 协议?
    2020-07-25:如何实现一个高效的单向链表逆序输出?
  • 原文地址:https://www.cnblogs.com/qingshanli/p/11774668.html
Copyright © 2011-2022 走看看