• 【Java】抽象类和接口详解


    抽象类

    一、抽象类的概述

    现在请思考一个问题:假如我现在又Dog、Cat、Pig等实例对象,现在我们把它们都抽象成一个Animal类,这个类应该包含了这些Dog、Cat、Pig等实例对象eat的功能,所以我们按照之前的思路会在Animal类当中定义一个eat方法,但是有个问题Dog、Cat、Pig的eat行为都有所不同,所以我们按照之前的方式自然会覆盖重写Animal类中eat方法。但是这样就导致Animal的eat方法中的方法体和定义方法的方式没有了任何存在的意义,对吗?

    public class Animal {
        public void eat(){
    
        }
    }
    
    public class Dog extends Animal{
        @Override
        public void eat(){
            System.out.println("啃骨头!");
        }
    }
    
    public class Cat extends Animal{
        @Override
        public void eat(){
            System.out.println("吃猫粮!");
        }
    }

    那么如何解决这种无效代码的问题呢?其实抽象类就可以:

    public abstract class Animal {
        public abstract void eat();
    }

    从形式上来看是不是就已经解决了上述中的问题?其实这个时候的Animal类就是一个抽象类。所以:

    父类中的方法,被它的子类们重写,子类各自的实现都不尽相同。那么父类的方法声明和方法主体,只有声明还有意义,而方法主体则没有存在的意义了。我们把没有方法主体的方法称为抽象方法(如Animal类中的eat方法)。Java语法规定,包含抽象方法的类就是抽象类(如Animal类)。

    抽象方法 : 没有方法体的方法。
    抽象类:包含抽象方法的类。

    二、使用方式

    1、抽象方法

    使用 abstract 关键字修饰方法,该方法就成了抽象方法,抽象方法只包含一个方法名,而没有方法体。

    定义格式:修饰符 abstract 返回值类型 方法名 (参数列表);

    如 public abstract void eat();

    2、抽象类

    如果一个类包含抽象方法,那么该类必须是抽象类。

    定义格式:

    修饰符 abstract class 类名字 {
    }

    如 public abstract class Animal {}

    3、使用方式

    继承抽象类的子类必须重写父类所有的抽象方法。否则,该子类也必须声明为抽象类。最终,必须有子类实现该父类的抽象方法,否则,从最初的父类到最终的子类都不能创建对象,失去意义。如下:

    public abstract class Animal {
        public abstract void eat();
    }
    
    public class Dog extends Animal{
        @Override
        public void eat(){
            System.out.println("啃骨头!");
        }
    }
    
    public class Demo {
        public static void main(String[] args) {
            Dog dog=new Dog();
            dog.eat(); // 啃骨头
        }
    }

    此时的方法重写(也可以不用写 @Override )是子类对父类抽象方法的完成实现,我们将这种方法重写的操作,也叫做实现方法。

    注意事项:

    (1) 抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。

    // 错误写法
    Animal animal = new Animal();

    假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。

    (2)抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。

    子类的构造方法中,有默认的super(),需要访问父类构造方法。

    (3)抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。

    public abstract class MyAbstract {
    
    }

    未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计。

    (4)抽象类的子类,必须重写抽象父类中所有的抽象方法,否则,编译无法通过而报错。除非该子类也是抽象类。

    public abstract class Animal {
    
        public abstract void eat();
    
    }
    
    public abstract class Dog extends Animal {
    
        @Override
        public void eat() {
            System.out.println("狗吃骨头");
        }
    
        public abstract void sleep();
    
    }
    
    public class DogGolden extends Dog {
    
        @Override
        public void sleep() {
            System.out.println("呼呼呼……");
        }
    
    }
    
    public class Demo {
    
        public static void main(String[] args) {
    
            // Animal animal = new Animal(); // 错误!
            // Dog dog = new Dog(); // 错误!
    
            DogGolden golden = new DogGolden(); // 正确!
            golden.eat();
            golden.sleep();
        }
    
    }

    假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有意义。

    接口

    接口,是Java语言中一种引用类型,是方法的集合,如果说类的内部封装了成员变量、构造方法和成员方法,那么接口的内部主要就是封装了方法。

    接口包含了抽象方法、默认方法、静态方法(JDK 8+)、私有方法(JDK 9+)。它也会被编译成.class文件,但一定要明确它并不是类,而是另外一种引用数据类型。

    引用数据类型:数组,类,接口。

    一、接口定义与使用

    1、定义

    它与定义类方式相似,但是使用 interface 关键字。再次声明接口并不是一个类。其格式为:

    public interface 接口名称 {
      // 抽象方法
      // 默认方法
      // 静态方法
      // 私有方法
    }

    2、使用

    接口在使用时不能创建对象,但是可以被实现( implements ,类似于被继承)。其格式为:

    public class 实现类名称 implements 接口名称 {
      // ...
    }
    接口不能直接使用,必须有一个“实现类”来“实现”该接口。
    类与接口的关系为实现关系,即类实现接口,该类可以称为接口的实现类,也可以称为接口的子类。实现的动作类似继承,格式相仿只是关键字不同,实现使用 implements 关键字。

    二、基本实现方式

    1、抽象方法的使用

    定义接口:

    public interface MyInterfaceAbstract {
    
        // 这是一个抽象方法
        public abstract void methodAbs1();
    
        // 这也是抽象方法
        abstract void methodAbs2();
    
        // 这也是抽象方法
        public void methodAbs3();
    
        // 这也是抽象方法
        void methodAbs4();
    } 

    上面代码的四种定义方法的方式都是抽象方法的定义方式。

    定义实现类:

    public class MyInterfaceAbstractImpl implements MyInterfaceAbstract {
        @Override
        public void methodAbs1() {
            System.out.println("这是第一个方法!");
        }
    
        @Override
        public void methodAbs2() {
            System.out.println("这是第二个方法!");
        }
    
        @Override
        public void methodAbs3() {
            System.out.println("这是第三个方法!");
        }
    
        @Override
        public void methodAbs4() {
            System.out.println("这是第四个方法!");
        }
    }

    MyInterfaceAbstractImpl 为 MyInterfaceAbstract 接口的实现类,并且推荐命名规则为:接口名Impl。

    实现类必须重写接口中所有抽象方法。

    定义测试类:

    public class Demo {
    
        public static void main(String[] args) {
    // 错误写法!不能直接new接口使用 // MyInterfaceAbstract inter = new MyInterfaceAbstract(); // 创建实现类的对象使用 MyInterfaceAbstractImpl impl = new MyInterfaceAbstractImpl(); impl.methodAbs1(); impl.methodAbs2();
         // ... } }

    注意事项:如果实现类并没有覆盖重写接口中所有的抽象方法,那么这个实现类自己就必须是抽象类。

    2、默认方法的使用

    从Java 8开始,接口里允许定义默认方法。
    定义接口:
    public interface MyInterfaceDefault {
    
        // 默认方法
        public default void methodDefault() {
            System.out.println("这是接口的默认方法");
        }
    
    }

    定义实现类:

    public class MyInterfaceDefaultImpl implements MyInterfaceDefaultImpl {
        // 什么都不用写,后面直接调用
    }

    定义测试类:

    public class Demo{
    
        public static void main(String[] args) {
    
            // 创建了实现类对象
            MyInterfaceDefaultImpl a = new MyInterfaceDefaultImpl();
    
            // 调用默认方法,如果实现类当中没有会向上找接口
            a.methodDefault();
        }
    }
    接口的默认方法,通过接口实现类对象直接调用。
    接口的默认方法,也可以被接口实现类进行覆盖重写,代码如下:
    实现类中代码有所变化,如下:
    public class MyInterfaceDefaultImpl implements MyInterfaceDefaultImpl {
    
        @Override
        public void methodDefault() {
            System.out.println("实现类中覆盖重写了接口的默认方法");
        }
    
    }

    测试类中代码如下:

    public class Demo {
    
        public static void main(String[] args) {
    
            // 创建了实现类对象
            MyInterfaceDefaultImpl a = new MyInterfaceDefaultImpl();
    a.methodDefault();
    // 实现类中覆盖重写了接口的默认方法 } }
    因此,接口的默认方法可以继承,可以重写,二选一,但是只能通过实现类的对象来调用。从作用上来看接口当中的默认方法,可以解决接口升级的问题。

    3、静态方法的使用

    从Java 8开始,接口当中允许定义静态方法。
    定义接口:
    public interface MyInterfaceStatic {
    
        public static void methodStatic() {
            System.out.println("这是接口的静态方法!");
        }
    
    }

    定义实现类(也可以不用写):

    public class MyInterfaceStaticImpl implements MyInterfaceStatic {
       
    }

    定义测试类:

    public class Demo {
    
        public static void main(String[] args) {
            // 创建了实现类对象
            MyInterfaceStaticImpl impl = new MyInterfaceStaticImpl();
            // 错误写法!
            // impl.methodStatic();
    
            // 直接通过接口名称调用静态方法
            MyInterfaceStatic.methodStatic();
        }
    
    }
    注意事项:不能通过接口实现类的对象来调用接口当中的静态方法。
    因为静态与.class 文件相关,只能使用接口名调用,不可以通过实现类的类名或者实现类的对象调用。

    4、私有方法的使用

    从Java 9开始,接口当中允许定义私有方法。如果一个接口中有多个默认方法,并且方法中有重复的内容,那么可以抽取出来,封装到私有方法中,供默认方法去调用。从设计的角度讲,私有的方法是对默认方法和静态方法的辅助。

    定义接口:

    public interface MyInterface {
    
        public default void methodDefault1() {
            System.out.println("默认方法1");
            methodCommon();
        }
    
        public default void methodDefault2() {
            System.out.println("默认方法2");
            methodCommon();
        }
    
        public default void methodCommon() {
            System.out.println("AAA");
            System.out.println("BBB");
            System.out.println("CCC");
        }
    
    }

    定义实现类:

    public class MyInterfaceImpl implements MyInterface {
    
        public void methodAnother() {
            // 直接访问到了接口中的默认方法
            methodCommon();
        }
    
    }

    定义测试类:

    public class Demo {
    
        public static void main(String[] args) {
            MyInterfaceImpl impl =new MyInterfaceImpl();
            impl.methodDefault1();
            impl.methodDefault2();
            impl.methodAnother();
       }  
    }

    但是该方法应该只能使用在接口当中,因此不能将其暴露出来。为了避免破坏这种私有性,所以咱们可以采用私有方法来解决这个问题,因此在接口中的代码如下:

    public interface MyInterface {
    
        public default void methodDefault1() {
            System.out.println("默认方法1");
            methodCommon();
        }
    
        public default void methodDefault2() {
            System.out.println("默认方法2");
            methodCommon();
        }
    
        private void methodCommon() {
            System.out.println("AAA");
            System.out.println("BBB");
            System.out.println("CCC");
        }
    
    }

    其实上述代码中的 private 关键字对应的方法属于普通私有方法,普通私有方法可以解决多个默认方法之间重复代码以及私有性问题。

    还有一种解决多个静态方法之间重复代码以及私有性问题的方式,这种方式我们叫它私有静态方法

    定义测试类:

    public interface MyInterface {
    
        public static void methodStatic1() {
            System.out.println("静态方法1");
            methodStaticCommon();
        }
    
        public static void methodStatic2() {
            System.out.println("静态方法2");
            methodStaticCommon();
        }
    
        private static void methodStaticCommon() {
            System.out.println("AAA");
            System.out.println("BBB");
            System.out.println("CCC");
        }
    
    }

    在这里我们可以不用写接口的实现类,直接调用即可。代码如下:

    public class Demo {
    
        public static void main(String[] args) {
    
            MyInterface.methodStatic1();
            MyInterface.methodStatic2();
            // 错误写法!
            // MyInterface.methodStaticCommon();
        }
    
    }        

    注意区分:

    普通私有方法:只有默认方法可以调用。
    私有静态方法:默认方法和静态方法都可以调用。

    三、多实现方式

    在继承体系中,一个类只能继承一个父类。而对于接口而言,一个类是可以实现多个接口的,这叫做接口的多实现。并且,一个类能继承一个父类,同时实现多个接口。

    同时在使用接口的时候需要注意:

    (1)接口是没有静态代码块或者构造方法的;
    (2)一个类的直接父类是唯一的,但是一个类可以同时实现多个接口;
    (3)如果实现类所实现的多个接口当中,存在重复的抽象方法,那么只需要覆盖重写一次即可;
    (4)如果实现类没有覆盖重写所有接口当中的所有抽象方法,那么实现类就必须是一个抽象类;
    (5)如果实现类所实现的多个接口当中,存在重复的默认方法,那么实现类一定要对冲突的默认方法进行覆盖重写;
    (6)实现类如果父类当中的方法和接口当中的默认方法产生了冲突,优先用父类当中的方法;
    (7)接口中,存在同名的静态方法并不会冲突,原因是只能通过各自接口名访问静态方法。

    实现格式:

    class 类名 [extends 父类名] implements 接口名1,接口名2,接口名3... {
      // 重写接口中抽象方法【必须】
      // 重写接口中默认方法【不重名时可选】
    }

    代码如下,定义接口:

    // 定义父类
    public class Parent {
    
        public void method(){
            System.out.println("Parent");
        }
    }
    
    // 定义接口A
    public interface MyInterfaceA {
    
        // 错误写法!接口不能有静态代码块
      // static {}
    
        // 错误写法!接口不能有构造方法
      // public MyInterfaceA() {}
    
        public abstract void methodA();
    
        public abstract void methodAbs();
    
        public default void methodDefault() {
            System.out.println("默认方法AAA");
        }
    
        public default void method() {
            System.out.println("MyInterfaceA");
        }
    }
    
    // 定义接口B
    public interface MyInterfaceB {
    
        public abstract void methodB();
    
        public abstract void methodAbs();
    
        public default void methodDefault() {
            System.out.println("默认方法BBB");
        }
    
    }

    定义实现类:

    public class MyInterfaceImpl extends Parent implements MyInterfaceA, MyInterfaceB {
    
        @Override
        public void methodA() {
            System.out.println("覆盖重写了A方法");
        }
    
    
        @Override
        public void methodB() {
            System.out.println("覆盖重写了B方法");
        }
    
        @Override
        public void methodAbs() {
            System.out.println("覆盖重写了AB接口都有的抽象方法");
        }
    
        @Override
        public void methodDefault() {
            System.out.println("对多个接口当中冲突的默认方法进行了覆盖重写");
        }
    }

    定义测试类:

    public class Demo01Interface {
    
        public static void main(String[] args) {
        
            MyInterfaceImpl impl = new MyInterfaceImpl();
    
            impl.methodA(); // 覆盖重写了A方法
    
            impl.methodB(); // 覆盖重写了B方法
    
            impl.methodAbs(); // 覆盖重写了AB接口都有的抽象方法
    
            impl.methodDefault(); // 对多个接口当中冲突的默认方法进行了覆盖重写
    
            impl.method(); // Parent
        }
    
    }    

    四、多继承方式

     之前在讲类的继承性的时候我们知道Java类是不能实现多继承的。因为如果类要实现多继承就要面临一些问题,如:

    (1)如果有两个父类,两个父类里有一个相同的方法,那么作为子类应该怎么继承这个方法?父类1的还是父类2的? 当然编译器可以报错,不允许出现相同的方法。

    (2)两个父类继承自同一个基类,则子类中会包含两份祖父类的内容,不合并重复内容会引起一些歧义,而合并重复内容又会导致类成员的内存布局不能简单复制地从父类复制。

    如上所述,这样其实增大了程序语言实现的复杂度,也没有带来很多的优化,但是接口能实现多继承。

    一个接口能继承另一个或者多个接口,这和类之间的继承比较相似。接口的继承也是使用 extends 关键字,子接口继承父接口的方法。

    定义父接口:

    public interface MyInterfaceA {
    
        public abstract void methodA();
    
        public abstract void methodCommon();
    
        public default void methodDefault() {
            System.out.println("AAA");
        }
    
    }
    
    public interface MyInterfaceB {
    
        public abstract void methodB();
    
        public abstract void methodCommon();
    
        public default void methodDefault() {
            System.out.println("BBB");
        }
    
    }

    定义子接口:

    public interface MyInterface extends MyInterfaceA, MyInterfaceB {
    
        public abstract void method();
    
        @Override
        public default void methodDefault() {
        // 如果父接口中的默认方法有重名的,那么子接口需要重写一次
        }
    } 

    定义实现类:

    public class MyInterfaceImpl implements MyInterface {
        @Override
        public void method() {
    
        }
    
        @Override
        public void methodA() {
    
        }
    
        @Override
        public void methodB() {
    
        }
    
        @Override
        public void methodCommon() {
    
        }
    }

    子接口重写默认方法时,default关键字必须要保留。
    子类重写默认方法时,default关键字不可以保留。

    通过上面代码可以看出接口实现多继承,不管哪个接口调用的都是同一个实现。因为继承父类包括实现,继承接口只包括接口,就是这样。

    五、接口总结

    接口中,无法定义成员变量,但是可以定义常量,其值不可以改变,默认使用public static final修饰。
    接口中,没有构造方法,不能创建对象。
    接口中,没有静态代码块。

  • 相关阅读:
    sed
    zabbix时间不同步
    zabbix-agent安装
    zabbix安装(网络)
    sendmail启动报错
    12306:被骂十年不吭声,终成大器
    一文带你看清HTTP所有概念(转)
    为什么 K8s 在阿里能成功(转)
    一文解读融资方式
    一文解读工业互联网 (转)
  • 原文地址:https://www.cnblogs.com/onebox/p/10426153.html
Copyright © 2020-2023  润新知