美文网首页
Java Interface私有方法你真的会用吗?

Java Interface私有方法你真的会用吗?

作者: 黄二狗V | 来源:发表于2018-12-20 17:03 被阅读0次

    【本文更新于2019年5月20日】

    更新内容是将例子讲的更通俗易懂。

    由于本文属于中高阶知识,其中涉及interface知识、类的继承、抽象类、方法重载等基础知识,同学可以先自行了解一下。

    1. interface简介

    interface是java中的一个关键字,用于定义接口类,它最主要的作用封装一定功能的集合,被定义为接口的类不能实例化。

    它的另外一个常见的用法,是用于回调功能。

    通俗点讲,接口类它本身不具备具体的功能,只定义一些模版或者规范,由子类去实现具体的功能。

    Java 7 版本以前,接口类只能定义一些抽象方法与常量,子类必须重写接口类中定义的全部方法。

    这样就有一个明显的弊端,不需要实现的方法,也要在子类重写。

    在Java 8 的时候,JDK支持接口类定义静态方法和默认方法。

    在Java 9 的时候,又进行了改进,支持私有方法和私有静态方法的定义。

    注意,这里我说的是方法,而不是抽象方法。也就是说JDK8与JDK9可以在接口类中定义方法体了。

    2. 三个JDK版本的接口设计功能对比

    Java 7 Java 8 Java 9
    常量 常量 常量
    抽象方法 抽象方法 抽象方法
    默认方法 默认方法
    静态方法 静态方法
    私有方法
    私有静态方法

    2.1 常量与抽象方法

    常量与抽象方法是接口类的基础功能。

    接口中定义的变量默认是public static final 型,也就是一个常量,且必须给其初值,实现类中不能重新定义,也不能改变其值

    抽象方法使用abstract修饰,通常abstract省略不写。

    如下代码,是演示定义一个接口类Test以及它的常量与抽象方法。

    public interface Test {
        String str = "abc";
        int a = 10;
        abstract void test();
        abstract int sum();
        void test2();
    }
    

    2.2 默认方法

    java 8开始支持,也称扩展方法。

    默认方法使用default 关键字修饰、定义。

    默认方法需要写方法体,实现具体的逻辑。

    实现类可以不重写默认方法,在需要的时候进行重写。

    以下是实现的示例代码:

    //接口定义,并定一个默认方法
    public interface Test {
        //默认方法
        default void testDefault(){
            System.out.println("default method");
        }
    }
    
    //第一个实现类,不重写默认方法
    public class TestImpl implements Test {
    }
    //第二个实现类,重写默认方法
    public class TestImpl2 implements Test {
       @Override
        public void testDefault() {
            System.out.println("TestImpl2 method");
        }
    }
    
    //测试运行
    public class Run {
        public static void main(String[] args) {
            Test test = new TestImpl();
            test.testDefault();
    
            Test test2 = new TestImpl2();
            test2.testDefault();
        }
    }
    
    

    运行结果:

    没有重写父类的方法:default method
    重写了父类的方法:TestImpl2 method

    【代码解释说明】

    接口类Test中定义了一个默认的方法testDefault(),实现了一些具体的功能,示例代码中是打印一句话“default method”。

    接着,TestImpl与TestImpl2都实现了接口类Test。

    其中只有TestImpl2重写了默认方法并修改了它的功能,示例代码中是打印一句话“TestImpl2 method”。

    运行后的结果,也正如所见,默认方法,可以不被子类重写,被子类重写后,会执行子类所实现的具体逻辑。

    总结一下:

    1.JDK后它允许我们在接口类里添加一个默认方法。
    2.默认方法不会破坏实现这个接口的已有类的兼容性,也就是不会强迫接口的实现类去重写默认方法。
    3.java.util.Collection包中添加的stream(), forEach()等方法就是最好的例子。

    2.3 静态方法

    JDK 8开始支持

    静态方法使用static关键字修饰、定义。

    静态方法需要写方法体,实现具体的逻辑。

    静态方法不可以被子类实现或继承。

    
    //定义接口、定义静态方法
    public interface TestFactory {
    
          static Test createTest(int type){
            if(type == 1){
                return new TestImpl();
            }else{
                return new TestImpl2();
            }
        }
    }
    
    //测试运行
    public class Run {
        public static void main(String[] args) {
           Test test = TestFactory.createTest(1);
           test.testDefault();
           Test test2 = TestFactory.createTest(2);
           test2.testDefault();
        }
    }
    
    

    运行结果:

    default method
    TestImpl2 method

    【代码解释说明】

    接口类TestFactory 定义了一个静态方法 createTest(),功能是创建Test的对象实例并返回给调用者。Test类的代码定义见2.2节。

    测试运行代码中,通过得到的Test类的对象实例,成功调用其内部的方法。

    2.4 默认方法与静态方法的关系

    • 他们都是一个完整的方法体,各自都有具体的功能实现。
    • 静态方法使用static修饰,默认方法使用default修饰。
    • 默认方法内部可以调用静态方法,静态方法内部不可以调用默认方法,因为静态方法只能调用静态方法,这是语法上的限制,很容易理解,下面是默认方法调用静态方法的示例。
    public interface Test {
        
    //调用静态方法
        default void testDefault(){
            testStatic();
            System.out.println("default method");
        }
        static void testStatic(){
            System.out.println("static method");
        }
    }
    
    //测试运行
    public class Run {
        public static void main(String[] args) {
           Test test = TestFactory.createTest(1);
           test.testDefault();
        }
    }
    

    运行结果:

    static method
    default method

    2.5 私有方法与私有静态方法

    • java 9开始支持
    • 私有方法使用private关键字修饰、定义。
    • 私有静态方法使用private static关键字修饰、定义。
    • private与private static方法,只能接口自身内部调用,实现类或子类不可重写重载。
    • 都需要写方法体,实现具体的逻辑。

    他们的定义示例如下:

    public interface Test {
       private void test(){
            System.out.println(" private method");
        }
       private static void test2(){
            System.out.println(" private static method");
        }
    }
    

    如果你问我,私有方法与私有静态方法只能自身内部调用,是不是没什么意义?

    答案肯定是,不是的!

    其实,它同class类型对象的私有方法功能是一样的。

    • 可以提高代码的重用性
    • 不让子类或实现类调用,也有很好的安全性。

    就拿一个数字签名来说,有PKCS#1与PKCS#7签名。他们有共性就是签名与验签,这两个功能的实现都需要数字证书,其中数字证书的解析功能等,不需要子类去实现,也不希望他们调用,避免遭破坏。那么就可以定义为私有方法。

    大致过程如下:

    • 1.需要实现类实现抽象方法,设置签名的类型、证书等
    • 2.接口将签名的具体逻辑使用私有方法达到保护以及可重用
      下面拿代码说明,首先定义签名接口类
    public interface Signature {
    
        int SIGN_PKCS1 = 0;
        int SIGN_PKCS7 = 1;
    
        //设置签名类型 p1或者P7
        int setType();
        //设置签名的数字证书
        String setCert();
    
        //具体的签名方法,签名逻辑用私有方法
        default String sign(){
            if(!checkCert()) return null;
            int type = setType();
            if(type == SIGN_PKCS1){
                return pkcs1();
            }else {
                return pkcs7();
            }
        }
    
        private Object parseCert(){
            //解析证书
            return obj;
        }
        private boolean checkCert(){
            //检验证书是否过期、是否吊销
            parseCert();
            return true;
        }
        private String pkcs1(){
            return "P1 签名结果";
        }
        private String pkcs7(){
            return "P7 签名结果";
        }
    }
    
    

    接口类定义了签名类型、签名方法、检验证书等具体的私有方法, sign()方法为默认方法,实现类可以重写,也可不写。这样子类只需要设置类型和证书即可,没有额外的代码。看代码:

    public class SignatureP1 implements Signature {
        @Override
        public int setType() {
            return SIGN_PKCS1;
        }
        @Override
        public String setCert() {
            return null;
        }
    }
    
    public class SignatureP7 implements Signature {
        @Override
        public int setType() {
            return SIGN_PKCS7;
        }
        @Override
        public String setCert() {
            return null;
        }
    }
    

    有了默认方法和私有方法,实现类不需要强制实现各自公共的逻辑。交由接口类来实现。最后使用的方式为:

    public class Run {
        public static void main(String[] args) {
            Signature signature1 = new SignatureP1();
            signature1.sign();
            Signature signature7 = new SignatureP7();
            signature7.sign();
        }
    }
    

    运行结果:

    P1 签名结果
    P7 签名结果

    有人说,这他喵的不就是抽象类吗!别说,还真像。

    他们有什么区别呢?请看下面我总结的几点:

    • 他们都是抽象类型,都可以定义抽象方法,都有默认方法,不强制实现类实现。

    • 类是单继承,接口可以多实现。

    • 设计理念的不同,抽象类所表现的关系是"is"关系,接口所表现的是"like"关系,有点像python中的鸭子类型,当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。而抽象类不行,必须是鸭子。

    • 前文中提到过,接口中定义的变量公开的静态常量,且必须给其初值,实现类中不能重新定义,也不能改变其值;抽象类中的变量其值可以在子类中重新定义,也可以重新赋值。

    说到这里,另外强调接口的两个问题,一个是自身方法调用以及多实现的规则。

    3. 方法互相调用问题

    默认方法(default)可以调用abstract/private/static/private static方法。
    而static方法只能调用static/private static方法。
    调用关系不对,编译会不过。

    4. 多实现的冲突问题

    因为一个类可以实现多个接口,若是这些接口定义的方法存在一样的,便会有冲突。因为这样,子类在调用的时候,不知道调用哪一个接口定义的方法。

    像这样,有两种情况下会发生:

    • 两个接口没有任何关系,有相同的方法,一个类同时实现了这两个接口
    • 一个接口继承了另一个接口,一个类同时实现这两个接口

    4.1 一个类实现多个没有任何关系的接口

    如下代码,TestC实现了TestA,TestB,而TestA,TestB中存在相同的方法

    public interface TestA {
        default void test(){
            System.out.println("TestA");
        }
    }
    public interface TestB{
        default void test(){
            System.out.println("TestB");
        }
    }
    
    public class TestC implements TestA,TestB {
    }
    

    此时,会出现编译错误:

    inherits unrelated defaults for test() from TestA and TestB

    也就是说TestA 和 TestB 都有这个 test()方法,不知道要实现哪一个。

    解决方法是重写这个相同的方法,方法内部指定一个具体的接口作为实现方法,下面代码指定TestA 作为实现方法。

    当然也可以不指定父类接口的实现,重写自己的逻辑。

    public class TestC implements TestA,TestB {
        @Override
        public void test() {
            TestA.super.test();
        }
    }
    

    进行测试:

    public class Run {
        public static void main(String[] args) {
            TestC testC = new TestC();
            testC.test();
        }
    }
    

    运行结果

    TestA

    4.2 同时实现继承关系的两个接口

    这种冲突,不能指定一个父接口来解决了,要么重写实现逻辑,要么按照默认的规则来调用,默认规则如下:

    • 声明在类里面的方法优先于任何默认的方法,也就是,在实现类中重写这个相同的方法,调用的时候,优先级最高。
    • 如果默认方法没有在类中实现,优先选取最具体的实现

    重写的优先级最高,我们来看优先选取最具体的实现,接口定义如下:

    public interface TestA {
        default void test(){
            System.out.println("TestA");
        }
    }
    public interface TestB extends TestA{
        default void test(){
            System.out.println("TestB");
        }
    }
    public class TestC implements TestA,TestB {
    }
    

    TestC 实现了TestB,TestB并且没用重写test()方法,TestB继承了TestA。测试运行:

    public class Run {
        public static void main(String[] args) {
            TestC testC = new TestC();
            testC.test();
        }
    }
    

    运行结果

    TestB

    鉴于此,接口不能提供对Object类的任何方法的默认实现,如接口里不能提供对equals,hashCode以及toString的默认实现。

    因为,一个类实现了这个接口并且是Object的子类,已经有了equals/hashCode/toString等方法的实现,那么接口定义的就没有意义了。

    在类里实现的方法,调用优先级最高。

    5. 总结

    • 掌握JDK7\8\9三个版本中接口类的变化
    • JDK 9之后,接口中支持定义方法可以包括私有的、抽象的、默认的、静态的。
    • 一个类实现了多接口时,若存在相同方法,注意他们的调用方式和冲突。

    相关文章

      网友评论

          本文标题:Java Interface私有方法你真的会用吗?

          本文链接:https://www.haomeiwen.com/subject/eygqkqtx.html