likes
comments
collection
share

03 接口和抽象类的区别

作者站长头像
站长
· 阅读数 1

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

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.抽象类和继承的区别?

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


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. 有一些通用的方法或属性需要被多个子类共享:

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

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

    • 抽象类可以有构造方法,用于初始化实例变量。这在一些情况下是很有用的。
    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. 定义规范而不关心具体实现:

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

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

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

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

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

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

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