跳至主要內容

Java

LincZero大约 8 分钟

Java

目录

接口类和抽象类的区别

参考:

抽象基类

目的: 在面向对象领域,抽象类主要用来进行类型隐藏

那什么是类型隐藏呢? 我们可以构造出一个固定的一组行为的抽象描述,但是这组行为却能够有任意个可能的具体实现方式。 这个抽象描述就是抽象类,而这一组任意个可能的具体实现则表现为所有可能的派生类。

举例:动物是一个抽象类,人、猴子、老虎就是具体实现的派生类。我们就可以用动物类型来隐藏人、猴子和老虎的类型。 ( 参考英语中的doSome方法)

接口

接口定义:

Java中的接口是一系列方法的声明,是一些方法特征的集合,一个接口只有方法的特征没有方法的实现,因此这些方法可以在不同的地方被不同的类实现,而这些实现可以具有不同的行为(功能)。

区别

区别

  • 概念上

    • 接口不是类,而是对类的一组需求描述,所以一般不叫 “接口类”。 这点与抽象基类不同,虽然抽象基类也不能被直接实例化。
  • 单多继承

    • 抽象基类:抽象类在Java语言中表示的是一种继承关系,一个类只能使用一次继承关系
    • 接口  :一个类却可以实现多个接口。也许,这是Java语言的设计者在考虑Java对于多重继承的支持方面的一种折中考虑吧。
  • 方法默认行为

    • 抽象基类: 在抽象类的定义中,我们可以赋予方法的默认行为
    • 接口  : 但是在接口的定义中,方法却不能拥有默认行为(Java SE 8前)。 为了绕过这个限制,必须使用委托,但是这会 增加一些复杂性,有时会造成很大的麻烦。 或者在Java SE 8后,在接口中添加静态方法。
  • 父子关系(选用参考)

    • 抽象基类: 对抽象基类的实现我们叫 “继承”,抽象类在Java语言中体现了一种继承关系。 父类和派生类之间必须存在**”is a”关系**,即父类和派生类在概念本质上应该是相同的。
    • 接口  : 对 接口 的实现一般叫 “实现”,接口仅仅是实现了接口定义的契约而已。 父类和派生类之间的关系是**”like a”关系**,并不要求接口的实现者和接口定义在概念本质上是一致的。
  • 修改类(选用参考)

    • 抽象基类: 使用抽象类来定义允许多个实现的类型,比使用接口有一个明显的优势:抽象类的演化比接口的演化要容易的多。 在后续的发行版中,如果希望在抽象类中增加一个方法,只增加一个默认的合理的实现即可,抽象类的所有实现都自动提供了这个新的方法。
    • 接口  : 对于接口,这是行不通的。虽然可以在骨架实现类中增加一方法的实现来解决部分问题,但这不能解决不从骨架实现类继承的接口实现的问题。 由此,设计公有的接口要非常谨慎,一旦一个接口被公开且被广泛实现,对它进行修改将是不可能的
    • 所以,使用接口还是抽象类,取决于我们对问题的概念的本质理解和设计的意图。
  • 写法上

    • 抽象基类

      // 抽象基类定义用 abstract class
      abstract class Comparable
      {
          public abstract int compareTo(Object other);
      }
      
      // 继承用关键字 extends
      class Employee extends Comparable
      {
          public int compareTo(Object other){...}
      }
      
    • 接口

      // 接口定义用 interface
      public interface Comparable
      {
          int compareTo(Object other);
      }
      
      // 实现用关键字 implements
      class Employee implements Comparable
      {
          public int compareTo(Object otherObject)
          {
              Employee other = (Employee) ohterObject;
              return Double.compare(salary, other.salary);
          }
      }
      
      // 或者结合泛型
      class Employee implements Comparable<Employee>
      {
          public int compareTo(Employee otherObject)
          {
              return Double.compare(salary, other.salary);
          }
      }
      
      // 对接口进行扩展用关键字 extends
      public interface Comparable2 extends Comparable{
          double milesPerGallon();
      }
      
    • 一起用

      class Student extends Person implements Named {...}
      

相同点

相同点

  1. 都不能被实例化
  2. 都能包含抽象方法
  3. 接口是一种特殊形式的抽象类。 抽象类与接口紧密相关,然而接口又比抽象类更抽象,这主要体现在它们的差别上:
    1. 类可以实现无限个接口,但仅能从一个抽象(或任何其他类型)类继承,从抽象类派生的类仍可实现接口,从而得出接口是用来解决多重继承问题的。
    2. 抽象类当中可以存在非抽象的方法,可接口不能且它里面的方法只是一个声名必须用public来修饰没有具体实现的方法。
    3. 抽象类中的成员变量可以被不同的修饰符来修饰,可接口中的成员变量默认的都是静态常量(static fainl)。
    4. 这一点也是最重要的一点本质的一点"抽象类是对象的抽象,然接口是一种行为规范"。

例如每个接口可以代表一种最顶层的抽象,可以理解为代表一类东西,如果一个类实现了多个接口,那这个类就有了多种类型,即接口是定义混合类型的理想工具最后:有一种设计模式,就是,默认适配模式,意思就是说,首先定义一个接口,通过抽象类实现这个接口,并实现一些子类不需要一定实现的方法,然后,子类就可以选择是继承接口,实现所有方法,还是直接继承这个抽象类实现具体需要的方法,

选用

为什么用抽象类/接口

定义成抽象类是为了以后要其子类来继承的,因为父类里有很多方法是无法定义具体的实现的,只能定义一个原型,让子类来分别实现!所以要定义成抽象的!

简单来说就是 抽象类是在接口和实体类之间的一个桥梁

使用场景举例:

做一个接口叫做飞行FlyAnimalAction,里面定义一个方法叫做flying,再定义一个方法叫做eat 做一个类叫做蚊子实现接口(多继承),蚊子要实现flying方法,实现自己的eat方法 做一个类叫做苍蝇实现接口(多继承),苍蝇也要实现flying方法,实现自己的eat方法

你发现所有会飞的动物都要实现这个接口,很麻烦,不如 做一个抽象类FlyAnimal,然后实现上面的接口 在里面实现flying这个方法,因为大部分的飞行动作是一样的,而eat方法则继续写成抽象方法,因为大部分的动物吃东西是不一样的

下面你再写蚊子类就简单了,可以直接继承这个抽象类,然后实现自己的吃方法就行了 而且苍蝇类的制作也是一样,这个时候抽象类的功能就显示出来了,当然抽象类的功能远远不是这些,只是初期理解到这里就够了

为什么用接口(多重继承问题)

Q:为什么Java程序设计语言还要不辞辛苦地引入接口概念?抽象基类都能做到抽象基类的功能吗? A:实际上,接口可以提供多重继承的大多数好处,同时还能避免多重继承的复杂性和低效性。

  • 继承一般不允许多继承
    • 有些程序设计语言允许一个类有多个超类,例如C++。我们将此特性称为多重继承(multiple inheritance)
    • Java的设计者选择了不支持多继承,其主要原因是多继承会让语言本身变得非常复杂(如同C++),效率也会降低(如同Eiffel)
  • 多重继承的复杂性
    • 虚基类、控制规则、横向指针类型转换等
    • 很少有C++程序员使用多继承
    • 有些程序员建议只对“混合”风格的继承使用多继承。在“混合”风格中,一个主要的基类描述父对象,其他的基类(因此称为混合)扮演辅助的角色。这种风格类似于Java类中从一个基类派生,然后实现若干个辅助接口

与C/C++不同

  • Java

    • Java有抽象基类,也有接口的概念
    • 在Java程序设计语言中,接口不是类,而是对类的一组需求描述,这些类要遵从接口描述的统一格式进行定义
    • Java不支持多重继承
  • C++

    • C++没有接口这的概念,不区分接口和纯虚基类 仅使用 “纯虚基类+多重继承” 可以完成相同的需求
  • Python

    • 没有纯虚积累或接口,但是可以在基类中抛出一个没有被实现的异常,以迫使使用者去实现接口

      raise NotImplemented("This method has to be override in a child class")