03 接口和抽象类的区别

上一节学习了面向对象。其中学习了面向对象的四大特性,封装还好理解,但是抽象和多态看简单的例子还可以,但是具体到项目中,需要对"为什么"进一步了解。

1.抽象类和接口的差异点?

  1. 实现方式:

    • 抽象类: 可以有实例变量(字段),可以有构造方法,可以包含具体的方法实现(非抽象方法)。使用 abstract 关键字声明抽象方法。
    • 接口: 不能包含实例变量(除非是 staticfinal),不能有构造方法,所有的方法都是抽象的,不包含方法的具体实现。
  2. 多继承:

    • 抽象类: 一个类只能继承一个抽象类。
    • 接口: 一个类可以实现多个接口。
  3. 构造方法:

    • 抽象类: 可以有构造方法,用于初始化实例变量。
    • 接口: 不能有构造方法,因为接口不能被实例化。
  4. 成员类型:

    • 抽象类: 可以包含实例变量,普通方法(具体或抽象),静态方法,常量。
    • 接口: 可以包含常量,抽象方法,静态方法(Java 8+),默认方法(Java 8+)。
  5. 访问修饰符:

    • 抽象类: 可以有各种访问修饰符的成员(public、private、protected、default/package-private)。
    • 接口: 所有成员默认是 public,并且不允许使用其他访问修饰符。
  6. 使用场景:

    • 抽象类: 适用于有一些通用实现的情况,以及需要包含实例变量的情况。
    • 接口: 适用于定义规范、实现多继承、以及不包含实例变量的情况。
  7. 继承与实现:

    • 抽象类: 使用 extends 关键字进行继承。
    • 接口: 使用 implements 关键字进行实现。
  8. 构造方法调用:

    • 抽象类: 在子类构造方法中使用 super 关键字调用父类构造方法。
    • 接口: 接口没有构造方法,因此不需要在实现类中调用接口的构造方法。

2.抽象类和继承的区别?

继承本身就能达到代码复用的目的, 而继承也并不要求父类一定是抽象类,那我们不使用抽象类,照样也可以实现继承和复用。从这个角度上来讲,我们貌似并不需要抽象类这种语法呀。那抽象类除了解决代码复用的问题,还有什么其他存在的意义吗?

js 复制代码
class Animal {
    void makeSound() {
        System.out.println("Some generic sound");
    }
}

class Dog extends Animal {
    void makeSound() {
        System.out.println("Bark");
    }
}

class Cat extends Animal {
    void makeSound() {
        System.out.println("Meow");
    }
    
     void makeJump() {
        System.out.println("Jump");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal dog = new Dog();
        Animal cat = new Cat();

        dog.makeSound();  // 输出: Bark
        cat.makeSound();  // 输出: Meow
        //报错!!!!! animal里没有这个方法。
        cat.makeJump();
    }
}

虽然抽象类和继承都可以实现代码复用,但是无法使用多态特性了。像下面这样编写代码,就会出现编译错误。

3.什么时候应该使用抽象,什么时候使用接口?

如果我们要表示一种 is-a 的关系,并且是为了解决代码复用的问题,我们就用抽象类;

抽象类的应用场景:

  1. 有一些通用的方法或属性需要被多个子类共享:

    • 如果存在一组子类,它们有一些通用的方法或属性,而又有一些需要在每个子类中实现的抽象方法,那么抽象类是一个合适的选择。
    csharp 复制代码
    javaCopy code
    abstract class Shape {
        int x, y;
        abstract void draw();  // 子类必须实现的抽象方法
    }
  2. 代码复用性较高:

    • 如果你希望在不同的类之间共享一些通用的代码,包括字段和方法的实现,抽象类可以提供一定程度的代码复用。
  3. 构造方法和初始化块:

    • 抽象类可以有构造方法,用于初始化实例变量。这在一些情况下是很有用的。
    csharp 复制代码
    javaCopy code
    abstract class Shape {
        int x, y;
    
        Shape(int x, int y) {
            this.x = x;
            this.y = y;
        }
    
        abstract void draw();
    }
  4. 访问修饰符的使用:

    • 如果你想使用不同的访问修饰符(public、protected、private)来限定抽象类的成员,抽象类提供了这样的灵活性。

如果我们要表示 一种 has-a 关系,并且是为了解决抽象而非代码复用的问题,那我们就可以使用接口。

  1. 定义规范而不关心具体实现:

    • 如果你希望定义一组规范,而不关心具体的实现细节,接口是更合适的选择。接口只包含抽象方法的声明,没有提供具体的实现。
    csharp 复制代码
    javaCopy code
    interface Drawable {
        void draw();  // 只有方法的声明,没有实现
    }
  2. 多继承情况:

    • 当一个类需要继承多个接口时,接口是更合适的选择,因为Java不支持多继承,而一个类可以实现多个接口。
    csharp 复制代码
    javaCopy code
    interface Shape {
        void draw();
    }
    
    interface Color {
        void fill();
    }
    
    class Circle implements Shape, Color {
        // 实现 draw 和 fill 方法
    }
  3. Java 8+的默认方法:

    • 接口从Java 8开始支持默认方法,允许在接口中提供方法的默认实现。这为向现有接口添加新的方法提供了一种方式,而不会影响现有的实现类。
    csharp 复制代码
    javaCopy code
    interface Drawable {
        void draw();  // 抽象方法
    
        default void resize() {
            System.out.println("Resizing the drawing");
        }
    }
  4. 实现组件化设计:

    • 接口的灵活性使得它们非常适合实现组件化设计,每个组件只需要实现相应的接口,而不需要关心其他组件的具体实现。

从类的继承层次上来看,抽象类是一种自下而上的设计思路,先有子类的代码重复,然后再抽象成上层的父类(抽象类)。

而接口正好相反,它是一种自上而下的设计思路。我们在编程的时候,一般都是先设计接口,再去考虑具体的实现。

前者是代码优化,后者是在代码设计过程中就应该考虑到的问题,这与经验和知识积累也有直接的关系。 那怎么样在一开始编程的时候就确定是否要使用接口呢? 后面会继续积累。

相关推荐
李少兄1 小时前
Unirest:优雅的Java HTTP客户端库
java·开发语言·http
此木|西贝2 小时前
【设计模式】原型模式
java·设计模式·原型模式
可乐加.糖2 小时前
一篇关于Netty相关的梳理总结
java·后端·网络协议·netty·信息与通信
s9123601012 小时前
rust 同时处理多个异步任务
java·数据库·rust
9号达人2 小时前
java9新特性详解与实践
java·后端·面试
cg50172 小时前
Spring Boot 的配置文件
java·linux·spring boot
啊喜拔牙2 小时前
1. hadoop 集群的常用命令
java·大数据·开发语言·python·scala
anlogic3 小时前
Java基础 4.3
java·开发语言
非ban必选3 小时前
spring-ai-alibaba第七章阿里dashscope集成RedisChatMemory实现对话记忆
java·后端·spring
A旧城以西3 小时前
数据结构(JAVA)单向,双向链表
java·开发语言·数据结构·学习·链表·intellij-idea·idea