[Java 基础]抽象类和接口
在继承体系中,父类有时不需要实现某些方法,或者某些方法的实现不确定,应该如何处理?抽象类就是解决这个问题的。
抽象类
抽象类的定义和特性
定义: 使用 abstract 关键字修饰的类。
特性:
抽象类不能被实例化(不能使用 new 关键字创建对象)。
抽象类可以包含抽象方法和非抽象方法。
抽象类可以包含成员变量、构造方法和静态成员。
如果一个类包含抽象方法,那么这个类必须声明为抽象类。
子类继承抽象类后,必须实现父类中所有的抽象方法,除非子类也是抽象类。
抽象方法的概念和定义
定义: 使用 abstract 关键字修饰的方法,没有方法体(以分号结尾)。
作用: 声明一个方法的存在,要求子类必须提供具体的实现。
语法: public abstract void methodName(参数列表); (访问修饰符可以是 public、protected,不能是 private、static、final、native、synchronized)
抽象类的使用场景和意义
定义共同的模板: 抽象类可以作为一组相关类的通用父类,定义它们的共同行为和属性,将具体的实现延迟到子类。
强制子类实现某些行为: 通过抽象方法,强制子类必须实现特定的功能。
代码重用: 抽象类可以包含非抽象方法,提供一些通用的实现,供子类直接使用,提高代码的复用性。
案例分析:图形类
创建一个抽象类 Shape,包含一个抽象方法 calculateArea() 用于计算面积。
创建 Circle 和 Rectangle 类继承 Shape,并实现 calculateArea() 方法。
演示如何使用父类引用指向子类对象,并调用 calculateArea() 方法(多态)。
// 抽象类 Shape
abstract class Shape {protected String color;public Shape(String color) {this.color = color;}public String getColor() {return color;}// 抽象方法,计算面积public abstract double calculateArea();// 非抽象方法,显示信息public void display() {System.out.println("这是一个" + getColor() + "的形状。");}
}// 圆形类
class Circle extends Shape {private double radius;public Circle(String color, double radius) {super(color);this.radius = radius;}public double getRadius() {return radius;}@Overridepublic double calculateArea() {return Math.PI * radius * radius;}
}// 矩形类
class Rectangle extends Shape {private double width;private double height;public Rectangle(String color, double width, double height) {super(color);this.width = width;this.height = height;}public double getWidth() {return width;}public double getHeight() {return height;}@Overridepublic double calculateArea() {return width * height;}
}public class AbstractClassDemo {public static void main(String[] args) {// Shape s = new Shape("红色"); // 错误:抽象类不能被实例化Circle circle = new Circle("蓝色", 5.0);Rectangle rectangle = new Rectangle("绿色", 4.0, 6.0);circle.display();System.out.println("圆的面积:" + circle.calculateArea());rectangle.display();System.out.println("矩形的面积:" + rectangle.calculateArea());Shape s1 = new Circle("黄色", 3.0); // 父类引用指向子类对象System.out.println("形状1的面积:" + s1.calculateArea());Shape s2 = new Rectangle("橙色", 2.0, 8.0); // 父类引用指向子类对象System.out.println("形状2的面积:" + s2.calculateArea());}
}
Java 中常见的抽象类
AbstractList:帮助你快速构建自己的 List 类型
接口
继承一般是子类和父类是有种关联的,比如父类是一种更大的概念。抽象类解决的是父类有时不需要实现某些方法,或者某些方法的实现不确定这种问题。 如果一个类需要同时具备多种不相关的能力(行为),例如既能跑又能飞,单继承机制如何实现?
接口的概念和特性
- 定义: 使用
interface
关键字声明的类型。 - 特性:
- 接口不是类,它是一种规范或契约
- 接口中只能包含常量(
public static final
修饰)和抽象方法(public abstract
修饰,可以省略这两个关键字,Java 9 之后可以包含default
和static
方法) - 接口不能被实例化
- 一个类可以实现(
implements
)多个接口 - 实现接口的类必须实现接口中所有的抽象方法
public interface InterfaceName {// 常量声明 (public static final)数据类型 CONSTANT_NAME = 值;// 抽象方法声明 (public abstract)返回类型 methodName(参数列表);// Java 8 允许定义 default 方法default 返回类型 defaultMethodName(参数列表) {// 默认实现}// Java 8 允许定义 static 方法static 返回类型 staticMethodName(参数列表) {// 静态实现}
}
接口的实现
- 使用
implements
关键字实现一个或多个接口 - 实现类必须提供接口中所有抽象方法的具体实现
- 如果一个类实现了多个接口,需要实现所有接口中的所有抽象方法
接口的使用场景和意义
- 实现多继承的效果: 通过实现多个接口,一个类可以拥有多个接口定义的能力
- 定义规范和契约: 接口定义了一组方法签名,任何实现该接口的类都必须遵守这些规范
- 实现松耦合: 接口将行为的定义与具体的实现分离,使得代码更加灵活和可维护
- 实现回调机制: 接口可以作为回调函数的类型,实现不同模块之间的交互
// 可飞翔接口
interface Flyable {void fly();
}// 可游泳接口
interface Swimmable {void swim();
}// 鸟类
class Bird implements Flyable {private String name;public Bird(String name) {this.name = name;}public String getName() {return name;}@Overridepublic void fly() {System.out.println(name + "正在飞翔。");}
}// 鱼类
class Fish implements Swimmable {private String name;public Fish(String name) {this.name = name;}public String getName() {return name;}@Overridepublic void swim() {System.out.println(name + "正在游泳。");}
}// 鸭子类,同时实现 Flyable 和 Swimmable 接口
class Duck implements Flyable, Swimmable {private String name;public Duck(String name) {this.name = name;}public String getName() {return name;}@Overridepublic void fly() {System.out.println(name + "正在用翅膀飞翔。");}@Overridepublic void swim() {System.out.println(name + "正在水中游泳。");}
}public class InterfaceDemo {public static void main(String[] args) {Bird bird = new Bird("老鹰");Fish fish = new Fish("金鱼");Duck duck = new Duck("唐老鸭");bird.fly();fish.swim();duck.fly();duck.swim();Flyable f = new Duck("小黄鸭"); // 接口引用指向实现类对象f.fly();Swimmable s = new Duck("大白鹅"); // 接口引用指向实现类对象s.swim();}
}
Java 中常用的接口
Comparable 接口(rt.jar 下的 java.lang.Comparable)
List 接口(rt.jar 下的 java.util.List)
还有很多 Java 自带的接口,都是定义了一个规范,实现这个接口就代表了你遵循了某种规范。
接口和抽象类的区别
如何选择抽象类和接口
- 关注“是什么”的关系 (Is-A): 如果你正在定义一个类的层次结构,并且子类在本质上是父类的一种特殊类型,并且需要共享一些共同的实现,那么应该使用抽象类
- 关注“能做什么”的能力 (Can-Do): 如果你正在定义一组类应该具备的某种能力或行为,而这些类在本质上可能没有继承关系,那么应该使用接口
- 需要定义共同的实现: 如果需要在父类中提供一些通用的方法实现,供子类直接使用,那么应该使用抽象类
- 需要实现多继承的效果: 如果一个类需要同时具备多种不同的能力,那么应该使用接口
- 设计 API 规范: 接口非常适合定义 API 的规范,强制实现类遵循特定的方法签名
总结
- 抽象类和接口都是 Java 中实现多态的重要机制
- 抽象类更侧重于代码的复用和类型的继承关系
- 接口更侧重于定义行为的规范和实现类具备的能力
- 在实际开发中,需要根据具体的需求选择合适的机制