在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、抽象类的特点
- abstract 修饰的类叫抽象类,修饰的方法叫做抽象方法
- abstract 修饰的抽象方法,不在抽象类中实现,更多的作为子类必须实现的方法的定义
- 抽象类中才有抽象方法,普通类中不能有抽象方法
- 抽象类中可以有抽象方法,也可以有普通方法,普通方法在子类中可以不用重写。
- 不可创建抽象类的对象,即抽象类不能被实例化,原因是没有构造器
- final 和 abstract 不能同时使用。final关键字作用在类上不能被继承,作用在方法上不能被重写,和 abstract 冲突。
- 抽象方法不能使用 static,static 是针对层次 ,抽象方法是针对对象层次,所以不能一起使用
- 子类继承抽象类后,如果不想实现抽象类中的抽象方法,那么子类必须是个抽象类
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、接口的特点
- interface 实现
- 接口中所有的成员变量都是 public static final 修饰的,即公有的静态常量。
- 接口中的所有方法默认都是public abstract 修饰的,抽象方法
- 接口也没有构造器,不能被实例化
- 接口当中的方法全部都是抽象方法,和抽象类不一样
- 接口和接口之间可以互相继承(继承的本质:代码的复用),即支持多继承
- 接口是对方法的抽象,子类必须实现父类的抽象方法
- 可以在接口中定义带有实现的方法,使用default关键字定义接口的默认方法和静态方法(Java 8 及以后)
三、接口和抽象类的区别
特点 | 抽象类 | 接口 |
能否实例化 | 不能实例化 | 不能实例化 |
继承方式 | 只能继承一个抽象类 | 可以实现多个接口 |
方法实现 | 可以有方法的实现(具体方法) | 只能包含抽象方法(Java 8 之后可包含默认方法和静态方法) |
构造方法 | 可以有构造方法 | 没有构造方法 |
成员变量 | 可以有实例变量,变量可以有不同的访问权限 | 只能有常量,且默认为 public static final |
访问修饰符 | 可以使用private、protected、public | 默认是public,不能是private或protected |
目的 | 定义一组相关的类的通用模板 | 定义类必须实现的行为规范 |
是否支持多继承 | 不支持多重继承 | 支持多重实现 |
能否实现多态 | 能够实现多态 | 能够实现多态 |