Bootstrap

Java的抽象类和接口

在Java的面向对象编程中,抽象类接口是两个非常重要的概念,它们都帮助我们实现了类的功能扩展和多态性。这两者看似相似,实际上各有不同的应用场景和优势。

一、抽象类

抽象类是不能实例化的类。它被设计成让其他类继承并实现其抽象方法。抽象类可以包含抽象方法(没有方法体)和具体方法(有方法体),它是对其他类的通用模板或基础,即子类共性内容的提取。

1、为什么要有抽象类

1.提供代码复用

抽象类允许将多个类之间共享的代码提取到一个地方,从而避免了重复编写相同的代码,这样不仅能减少冗余,还能增强系统的可维护性。

假设我们有多个不同类型的动物,它们都有 eat sleep 的行为,但它们的具体实现方式可能不同。

abstract class Animal {
    public void eat() {
        System.out.println("Animal的吃");
    }

    public void sleep() {
        System.out.println("Animal的睡");
    }

    abstract void makeSound();
}

class Dog extends Animal {
    @Override
    void makeSound() {
        System.out.println("汪汪汪!");
    }
}

class Cat extends Animal {
    @Override
    void makeSound() {
        System.out.println("我又是谁的小猫咪呢~");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal dog = new Dog();
        dog.eat();  // Animal的吃
        dog.makeSound();  // 汪汪汪!
        
        Animal cat = new Cat();
        cat.sleep();  // Animal的睡
        cat.makeSound();  // 我又是谁的小猫咪呢~
    }
}

 Animal 类是抽象的,定义了eat、sleep和一个抽象方法makeSound。不同的动物类只需要实现makeSound方法,而eat和sleep方法是共享的。

2. 实现部分功能的继承

抽象类可以将一些功能的实现推迟到子类中去完成,不仅提供了抽象方法(没有实现),还允许提供一些已经实现的功能。子类可以继承这些功能并在此基础上进行扩展。

在银行应用中,可能会有各种类型的账户(如储蓄账户、信用账户)。每种账户的利率计算可能不同,但它们都有一个共同的存款和取款功能。可以通过抽象类提供存款和取款的通用实现,而让每个子类实现利率计算。

abstract class Account {
    double balance;

    public void deposit(double amount) {
        balance += amount;
        System.out.println("Deposited: " + amount);
    }

    public void withdraw(double amount) {
        if (balance >= amount) {
            balance -= amount;
            System.out.println("Withdrew: " + amount);
        } else {
            System.out.println("Insufficient funds");
        }
    }

    abstract double calculateInterest();  // 子类必须实现此方法
}

class SavingsAccount extends Account {
    @Override
    double calculateInterest() {
        return balance * 0.05;  // 假设储蓄账户年利率为5%
    }
}

class CreditAccount extends Account {
    @Override
    double calculateInterest() {
        return balance * 0.15;  // 假设信用账户年利率为15%
    }
}

public class Main {
    public static void main(String[] args) {
        Account savings = new SavingsAccount();
        savings.deposit(1000);
        System.out.println("Interest: " + savings.calculateInterest());
        
        Account credit = new CreditAccount();
        credit.deposit(1000);
        System.out.println("Interest: " + credit.calculateInterest());
    }
}

Account类是一个抽象类,定义了通用的deposit和withdraw方法,并且要求子类实现calculateInterest方法,这样能在不同的账户类型中重用通用代码,同时保持灵活性。

3. 提供默认行为的实现

抽象类不仅可以定义抽象方法,还可以提供一些带有默认实现的方法。这使得子类可以选择继承这些方法,或者重写它们,进一步增强代码复用。

假设有多个设备类,其中某些设备有启动和关闭功能。通过抽象类提供默认的启动和关闭实现,子类可以根据需要进行修改。

abstract class Device {
    public void turnOn() {
        System.out.println("Device is now ON.");
    }

    public void turnOff() {
        System.out.println("Device is now OFF.");
    }

    abstract void operate();
}

class TV extends Device {
    @Override
    void operate() {
        System.out.println("Watching TV...");
    }
}

class Radio extends Device {
    @Override
    void operate() {
        System.out.println("Listening to Radio...");
    }
}

public class Main {
    public static void main(String[] args) {
        Device tv = new TV();
        tv.turnOn();  // Device is now ON.
        tv.operate(); // Watching TV...

        Device radio = new Radio();
        radio.turnOff();  // Device is now OFF.
        radio.operate();  // Listening to Radio...
    }
}

4. 限制类的实例化

抽象类的另一个重要作用是,它可以用来限制类的实例化。通过将类声明为抽象的,我们确保它不能被直接实例化,这有助于防止创建不完整的对象。

Shape类可以是抽象的,它不能直接实例化,因为它是所有具体形状的基类。只能创建Circle、Rectangle等具体形状对象。

abstract class Shape {
    abstract void draw();  // 每种具体形状都应该有绘制的方法
}

class Circle extends Shape {
    @Override
    void draw() {
        System.out.println("Drawing a Circle.");
    }
}

class Rectangle extends Shape {
    @Override
    void draw() {
        System.out.println("Drawing a Rectangle.");
    }
}

public class Main {
    public static void main(String[] args) {
        // Shape s = new Shape();  // 错误!抽象类不能实例化

        Shape circle = new Circle();
        circle.draw();  // Drawing a Circle.

        Shape rectangle = new Rectangle();
        rectangle.draw();  // Drawing a Rectangle.
    }
}

2、抽象类的特点

  1. abstract 修饰的类叫抽象类,修饰的方法叫做抽象方法
  2. abstract 修饰的抽象方法,不在抽象类中实现,更多的作为子类必须实现的方法的定义
  3. 抽象类中才有抽象方法,普通类中不能有抽象方法
  4. 抽象类中可以有抽象方法,也可以有普通方法,普通方法在子类中可以不用重写。
  5. 不可创建抽象类的对象,即抽象类不能被实例化,原因是没有构造器
  6. final 和 abstract 不能同时使用。final关键字作用在类上不能被继承,作用在方法上不能被重写,和 abstract 冲突。
  7. 抽象方法不能使用 static,static 是针对层次 ,抽象方法是针对对象层次,所以不能一起使用
  8. 子类继承抽象类后,如果不想实现抽象类中的抽象方法,那么子类必须是个抽象类

3、抽象类和抽象方法的关系

1. 定义不同

抽象类是一个包含抽象方法或者是一些通用行为的类,不能直接实例化,只能被继承。

抽象方法是一个没有方法体的方法,通常定义在抽象类中。它的目的是要求子类必须提供对该方法的具体实现。

换句话说,抽象类是一个容器,它可以包含抽象方法,也可以包含普通(具体)方法。而抽象方法则是一个没有实现的声明,专门用于在抽象类中声明,要求所有继承这个抽象类的子类必须实现这些抽象方法。

2. 目的不同

抽象类提供一个共同的基类,允许子类继承和共享其中的通用方法或属性,并强制子类实现某些特定的方法(即抽象方法)。抽象类可以是部分实现的类,可以包含既有方法实现,也有抽象方法。

抽象方法为子类提供一个方法的“模板”或者“接口”,强制子类提供具体的实现。抽象方法本身不包含任何实现,它只是一个方法签名(声明),并且必须在子类中被实现。

3. 抽象方法对类的夺舍

 抽象类包含一个或多个抽象方法。如果一个类中包含抽象方法,那么该类必须被声明为抽象类

4. 简单表格总结

特性抽象类抽象方法
定义是一个不能被实例化的类,可能包含抽象方法、普通方法和构造方法。是一个没有方法体的方法,只能在抽象类中定义。
是否有方法体可以包含方法体(具体方法)和抽象方法。没有方法体。
是否需要子类实现不强制要求子类实现所有方法,除非方法是抽象的必须在子类中实现抽象方法。
实例化不能实例化一个抽象类。抽象方法不能单独实例化,它必须依附于抽象类。

 二、接口

接口定义了一组方法的集合,但接口中的方法默认都是抽象的(在Java 8及其以后版本,可以有默认方法和静态方法)。接口是一种完全抽象化的类,它只定义了行为的规范,没有实现。

1、为什么要有接口

接口本身是抽象的,在其中的方法是抽象方法,变量是抽象变量。

支持多重继承:一个类可以实现多个接口,弥补了Java类单继承的限制。解决Java单继承的问题,一个类只能有一个子类,一个接口可以由多个类实现。

interface Rechargeable {
    void recharge();
}

interface Flyable {
    void fly();
}

class Drone implements Rechargeable, Flyable {
    @Override
    public void recharge() {
        System.out.println("Drone is recharging...");
    }

    @Override
    public void fly() {
        System.out.println("Drone is flying...");
    }
}

public class Main {
    public static void main(String[] args) {
        Drone drone = new Drone();
        drone.recharge();  // Drone is recharging...
        drone.fly();       // Drone is flying...
    }
}

定义行为规范:接口为类提供了一种统一的标准,强制类实现接口中的方法。接口只关心“做什么”,而不关心“如何做”。

interface Shape {
    void draw();  // 所有形状类都必须实现此方法
}

class Circle implements Shape {
    @Override
    public void draw() {
        System.out.println("Drawing a Circle.");
    }
}

class Rectangle implements Shape {
    @Override
    public void draw() {
        System.out.println("Drawing a Rectangle.");
    }
}

public class Main {
    public static void main(String[] args) {
        Shape circle = new Circle();
        circle.draw();  // Drawing a Circle.

        Shape rectangle = new Rectangle();
        rectangle.draw();  // Drawing a Rectangle.
    }
}

松耦合:接口使得代码更加灵活,类之间的依赖关系变得更加松散,有助于系统扩展。类之间的依赖关系被最小化,代码变得更加灵活和可维护。一个类只需要依赖接口,而不需要依赖具体的实现类。

interface StorageDevice {
    void read();
    void write();
}

class HardDrive implements StorageDevice {
    @Override
    public void read() {
        System.out.println("Reading from Hard Drive.");
    }

    @Override
    public void write() {
        System.out.println("Writing to Hard Drive.");
    }
}

class SSD implements StorageDevice {
    @Override
    public void read() {
        System.out.println("Reading from SSD.");
    }

    @Override
    public void write() {
        System.out.println("Writing to SSD.");
    }
}

class Computer {
    private StorageDevice storageDevice;

    public Computer(StorageDevice storageDevice) {
        this.storageDevice = storageDevice;
    }

    public void performReadWrite() {
        storageDevice.read();
        storageDevice.write();
    }
}

public class Main {
    public static void main(String[] args) {
        StorageDevice hdd = new HardDrive();
        Computer computer = new Computer(hdd);
        computer.performReadWrite();  // Reading from Hard Drive. Writing to Hard Drive.

        StorageDevice ssd = new SSD();
        computer = new Computer(ssd);
        computer.performReadWrite();  // Reading from SSD. Writing to SSD.
    }
}

支持多态:接口能够实现不同类的多态性,使得我们可以通过统一的方式处理不同类型的对象。

interface Animal {
    void sound();
}

class Dog implements Animal {
    @Override
    public void sound() {
        System.out.println("Woof!");
    }
}

class Cat implements Animal {
    @Override
    public void sound() {
        System.out.println("Meow!");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal myAnimal1 = new Dog();
        Animal myAnimal2 = new Cat();
        
        myAnimal1.sound();  // Woof!
        myAnimal2.sound();  // Meow!
    }
}

默认方法:Java 8引入的默认方法使得接口在提供行为规范的同时,还能提供方法的默认实现,提高了接口的灵活性和兼容性。

interface Printer {
    default void print() {
        System.out.println("Printing document...");
    }
}

class LaserPrinter implements Printer {
    @Override
    public void print() {
        System.out.println("Laser Printer printing document...");
    }
}

public class Main {
    public static void main(String[] args) {
        Printer printer = new LaserPrinter();
        printer.print();  // Laser Printer printing document...
    }
}

2、接口的特点

  1. interface 实现
  2. 接口中所有的成员变量都是 public static final 修饰的,即公有的静态常量
  3. 接口中的所有方法默认都是public abstract 修饰的,抽象方法
  4. 接口也没有构造器,不能被实例化
  5. 接口当中的方法全部都是抽象方法,和抽象类不一样
  6. 接口和接口之间可以互相继承(继承的本质:代码的复用),即支持多继承
  7. 接口是对方法的抽象,子类必须实现父类的抽象方法
  8. 可以在接口中定义带有实现的方法,使用default关键字定义接口的默认方法和静态方法(Java 8 及以后)

三、接口和抽象类的区别

特点抽象类接口
能否实例化不能实例化不能实例化
继承方式只能继承一个抽象类可以实现多个接口
方法实现可以有方法的实现(具体方法)只能包含抽象方法(Java 8 之后可包含默认方法和静态方法)
构造方法可以有构造方法没有构造方法
成员变量可以有实例变量,变量可以有不同的访问权限只能有常量,且默认为 public static final
访问修饰符可以使用privateprotectedpublic默认是public,不能是private或protected
目的定义一组相关的类的通用模板定义类必须实现的行为规范
是否支持多继承不支持多重继承支持多重实现
能否实现多态能够实现多态能够实现多态

;