Java 中接口和抽象类的区别

培训教学 潘老师 9个月前 (09-01) 186 ℃ (0) 扫码查看

抽象类与接口是Java API 的两个主要构建模块。本文将讨论接口与抽象类之间最显著的区别。

1. 抽象类

简单来说,抽象类是通过使用关键字 abstract 声明的类。它可能包含或不包含任何抽象方法。

在下面的示例中,TestAbstractClass 包含两个方法,第一个方法是抽象的,而第二个方法是普通方法。

public abstract class TestAbstractClass {
    public abstract void abstractMethod();
    public void normalMethod()  { ... method body ... }
}

请注意,如果在类中有一个抽象方法,我们必须将类本身声明为抽象类。抽象方法使得类不完整,因此编译器要求将整个类声明为抽象的。

JVM将抽象类识别为未定义其完整行为的不完整类。声明类为抽象类只强制执行一件事:我们不能创建此类的实例。

那么为什么要创建一个根本不能实例化的类呢?答案在于它在解决一些关键的设计问题时的用途。

在应用程序中使用抽象类的唯一方法是扩展该类。如果未再次声明为抽象,那么它的子类可以实例化。

class ChildClass extends TestAbstractClass {
  @Override
  public void abstractMethod() {
    //method body
  }
}

2. 接口

接口是大多数 Java API 的另一个基本构建模块。接口定义了实现类需要遵守的契约。这些契约本质上是未实现的方法。Java 已经有一个用于未实现方法的关键字,即 `abstract`。在 Java 中,一个类可以实现任何公共接口,因此接口中声明的所有方法都需要是public的。

public interface TestInterface {
    void implementMe();
}

在上面的示例中,任何实现类都需要覆盖 implementMe() 方法。

public class TestMain implements TestInterface {
    @Override
    public void implementMe() {
        //...
    }
}

3. 抽象类实现接口

只有一种情况下我们实现接口但不重写其方法,即将类本身声明为抽象。由于 AbstractClass 是抽象的且不能被初始化,因此类的完整性没有被破坏。

public abstract class AbstractClass implements TestInterface {
    //不需要重写 implementMe()
}

当一个类继承上述的 AbstractClass 时,它必须重写 implementMe() 方法,以使自身成为一个完整的类。

public class ChildClass extends AbstractClass {
    @Override
    public void implementMe() {
        //...
    }
}

4. 抽象类 vs 接口的区别

让我们总结一下抽象类和接口之间的区别,以便进行快速回顾:

  • 接口中的所有方法都默认为公共和抽象。我们无法通过减少方法的访问权限来覆盖这种行为,甚至无法声明静态方法。只允许公共和抽象方法。而抽象类在声明方法时更加灵活。我们可以使用受保护的访问权限定义抽象方法。此外,我们还可以定义静态方法,只要它们不是抽象的。非抽象的静态方法是允许的。
  • 接口除了默认方法外,不能有完全定义的方法。根据定义,接口旨在提供唯一的契约。抽象类可以有非抽象方法,没有任何限制。
  • 子类只能继承一个父类,但可以实现任意数量的接口。这种特性通常被称为 Java 中多继承的模拟。
  • 接口是绝对抽象的,不能被实例化;Java 抽象类也不能被实例化,但如果存在 main() 方法,可以被调用。

5. 何时使用?

永远记住,接口和抽象类的选择不是二选一的,在没有适当分析的情况下,选择任何一个都会产生相同的结果。在了解了手头的问题之后,必须非常明智地做出选择。让我们来了解一些相关信息。

5.1. 使用抽象类添加部分行为

抽象类允许你定义一些行为,使它们成为应用程序框架内的优秀候选项。

让我们以 HttpServlet 为例。如果您正在使用 Servlets 技术开发 Web 应用程序,那么这是您必须继承的主要类。正如我们所知,每个 Servlet 都有明确的生命周期阶段,即初始化、服务和销毁。如果我们每次创建一个 Servlet,都必须一次又一次地编写关于初始化和销毁的相同代码,那会很痛苦。

JDK 设计人员通过将 HttpServlet 声明为抽象类来解决这个问题。它已经预先编写了有关初始化 Servlet 及其销毁的所有基本代码。您只需要重写某些方法,在其中编写与应用程序处理相关的代码,就这么简单。有意义,对吧!

你能使用接口添加上述功能吗?即使可以,对于大多数无辜的程序员来说,这种设计将会非常混乱。

5.2. 仅用于契约的接口

现在,让我们来看看接口的用法。接口只提供契约,由实现类负责实现每个单独的契约。

接口最适合的情况是,您只想定义类的特性,并希望强制所有实现实体实现这些特性。

我想以集合框架中的 Map 接口为例。它仅提供规则,即地图在实践中应该如何工作。例如,它应该存储键-值对,应该能够使用键访问值等。这些规则以接口中的抽象方法形式存在。

所有实现类(例如 HashMapHashTableTreeMapWeakHashMap)都以不同的方式实现所有方法,从而展示与其他实现类不同的特性。

此外,接口还可用于定义责任的分离。例如,HashMap 实现了 3 个接口:MapSerializableCloneable。每个接口定义了独立的责任;因此,实现类可以选择要实现的内容,并提供有限的功能。

6. Java 8 中的接口默认方法

Java 8 中,您现在可以在接口中定义方法。这些被称为默认方法。默认方法使您可以向库的接口添加新功能,并确保与为旧版本接口编写的代码具有二进制兼容性。

顾名思义,Java 8 中的默认方法只是默认方法。如果您不对其进行覆盖,它们就是调用类将调用的方法。

public interface Moveable {
    default void move(){
        System.out.println("I am moving");
    }
}

在上面的例子中,Moveable 接口定义了一个 move() 方法,并且还提供了一个默认实现。如果任何类实现了这个接口,那么它就不需要再实现自己版本的 move() 方法。它可以直接调用 instance.move()

public class Animal implements Moveable {
    public static void main(String[] args){
        Animal tiger = new Animal();
        tiger.move();   //I am moving
    }
}

如果该类希望自定义行为,那么它可以提供自己的自定义实现并重写这个方法。现在将会调用它自己的自定义方法。

public class Animal implements Moveable {
    public void move(){
        System.out.println("I am running");
    }
    public static void main(String[] args){
        Animal tiger = new Animal();
        tiger.move();   //I am running
    }
}

Java 8中抽象类和接口的区别

自从Java 8以来,我们可以使用默认方法在接口中提供部分实现,就像抽象类一样。因此,接口和抽象类之间的界限变得非常模糊。它们现在几乎提供了相同的功能。

然而,一个重要的区别仍然存在,那就是我们不能同时扩展多个类,而可以实现多个接口。除了这个区别之外,您可以通过接口实现与抽象类可能实现的任何功能,反之亦然。

希望您在Java中的接口和抽象类方面找到了足够的信息。

教程 Java基础教程

文章目录 前言  第1章 Java语言基础 第2章 流程控制语句 第3章 面向对象编程 第4章 Java 字符 […]


版权声明:本站文章,如无说明,均为本站原创,转载请注明文章来源。如有侵权,请联系博主删除。
本文链接:https://www.panziye.com/teach/8529.html
喜欢 (0)
请潘老师喝杯Coffee吧!】
分享 (0)
用户头像
发表我的评论
取消评论
表情 贴图 签到 代码

Hi,您需要填写昵称和邮箱!

  • 昵称【必填】
  • 邮箱【必填】
  • 网址【可选】