Java 抽象类、接口与对象排序
一、抽象类(Abstract Class)
1. 什么是抽象类?
抽象类是对所有子类公有方法的共性提取,把子类都会实现的、重复的方法,抽取到父类中统一定义,避免代码冗余。
当父类本身不应该被实例化(比如 Animal 只是动物的概念,不应该直接创建 Animal 对象),且只需要定义子类必须实现的方法规范时,就可以把父类定义为抽象类。
2. 抽象类与抽象方法的定义
- 抽象类:用
abstract关键字修饰的类,不能被实例化(不能直接new)。 - 抽象方法:用
abstract关键字修饰的方法,没有方法体(连大括号都没有,直接以分号结尾),定义在抽象类中,强制子类必须重写实现。
代码示例
// 抽象父类:动物
abstract class Animal {
// 抽象方法:run,没有方法体,强制子类实现
public abstract void run();
// 抽象类也可以有普通方法(非抽象方法)
public void sleep() {
System.out.println("动物都需要睡觉");
}
}
// 子类:猫,必须实现抽象父类的所有抽象方法
class Cat extends Animal {
@Override
public void run() {
System.out.println("小猫在跑");
}
}
// 子类:狗,必须实现抽象父类的所有抽象方法
class Dog extends Animal {
@Override
public void run() {
System.out.println("小狗在跑");
}
}
3. 抽象类的核心特点
- 只有抽象类中才能包含抽象方法,普通类中不能定义抽象方法。
- 抽象方法必须在子类中重写实现,否则子类也必须声明为抽象类。
- 抽象类可以包含普通方法、构造方法、成员变量,但不能被实例化。
- 抽象类存在的意义:作为模板父类,强制子类遵循统一的方法规范,同时实现代码复用。
- 抽象类可以使用多态:
Animal animal = new Cat();,调用方法时会执行子类重写后的实现。 - 修饰符冲突规则:
final和abstract不能同时修饰类 / 方法:final禁止继承 / 重写,abstract强制继承 / 重写,两者矛盾。private和abstract不能同时修饰方法:private方法子类不可见,无法重写。static和abstract不能同时修饰方法:static方法属于类,不依赖对象,而抽象方法需要子类实例重写实现。
二、接口(Interface)
1. 为什么需要接口?
Java 不支持类的多继承(一个类只能有一个直接父类),但多继承会带来 “菱形继承” 的歧义问题(比如多个父类有同名方法,子类不知道该调用哪个)。
为了解决 “无法多继承” 和 “多继承歧义” 的矛盾,Java 引入了接口:
- 接口本身不包含具体实现,不会造成代码调用混乱。
- 一个类可以实现多个接口,从而获得多个接口定义的行为规范。
2. 接口的定义与核心特性
- 接口用
interface关键字定义,本身不能被实例化。 - 接口中的成员变量默认是
public static final(静态常量,必须初始化)。 - 接口中的方法默认是
public abstract(抽象方法,没有方法体,强制实现类重写)。 - 接口没有构造方法。
- 一个类实现接口时,必须重写接口中所有的抽象方法;如果不重写,该类必须声明为抽象类。
- 接口与接口之间可以多继承,用
extends关键字。
代码示例
// 接口:定义“可奔跑”的行为规范
interface Runnable {
// 静态常量(默认 public static final)
int SPEED = 10;
// 抽象方法(默认 public abstract)
void run();
}
// 接口:定义“可叫”的行为规范
interface Barkable {
void bark();
}
// 实现类:狗,同时实现多个接口
class Dog implements Runnable, Barkable {
@Override
public void run() {
System.out.println("小狗以" + SPEED + "km/h的速度奔跑");
}
@Override
public void bark() {
System.out.println("小狗汪汪叫");
}
}
// 接口继承示例:Person接口继承Animal接口
interface Animal {
void eat();
}
interface Person extends Animal {
void study();
}
// 实现类:Student,必须实现Person和Animal的所有抽象方法
class Student implements Person {
@Override
public void eat() {
System.out.println("学生需要吃饭");
}
@Override
public void study() {
System.out.println("学生需要学习");
}
}
3. 抽象类 vs 接口
| 对比维度 | 抽象类(Abstract Class) | 接口(Interface) |
|---|---|---|
| 核心定位 | 对 “类” 的抽象,提取子类的共性特征(属性 + 行为) | 对 “行为” 的抽象,定义类需要具备的行为规范 |
| 继承 / 实现 | 类只能单继承一个抽象类 | 类可以实现多个接口 |
| 成员变量 | 可以包含普通成员变量 | 只能包含 public static final 静态常量 |
| 方法 | 可以包含抽象方法、普通方法、构造方法 | 只能包含抽象方法(JDK8+ 可包含默认方法 / 静态方法) |
| 构造方法 | 有构造方法,供子类初始化父类使用 | 没有构造方法 |
三、对象排序:Comparable 接口
1. 为什么对象需要实现 Comparable 接口?
Arrays.sort() 可以直接对基本数据类型(如 int[]、String[])排序,但无法直接对自定义类的对象数组排序。因为程序不知道两个对象之间如何比较大小,需要通过 Comparable 接口定义比较规则。
2. Comparable 接口的使用
Comparable接口位于java.lang包中,只有一个抽象方法compareTo(T o)。- 自定义类实现
Comparable接口,并重写compareTo方法,定义对象的比较规则。 - 实现后,
Arrays.sort()就可以直接对该类的对象数组进行排序。
核心规则:compareTo 方法
this.compareTo(o) 的返回值决定排序规则:
- 返回 正整数:表示
this > o,排序时this排在o后面(默认升序)。 - 返回 0:表示
this == o。 - 返回 负整数:表示
this < o,排序时this排在o前面(默认升序)。
代码示例:按年龄升序排序
import java.util.Arrays;
// 自定义类:Cat,实现Comparable接口
class Cat implements Comparable<Cat> {
private String name;
private int age;
public Cat(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Cat{name='" + name + "', age=" + age + "}";
}
// 重写compareTo方法,定义按age升序排序
@Override
public int compareTo(Cat o) {
// 升序:this.age - o.age
return this.age - o.age;
// 降序:o.age - this.age
// return o.age - this.age;
}
}
public class SortTest {
public static void main(String[] args) {
Cat[] cats = {
new Cat("咪咪", 3),
new Cat("花花", 1),
new Cat("小白", 2)
};
// 排序前
System.out.println("排序前:" + Arrays.toString(cats));
// 排序(调用重写后的compareTo方法)
Arrays.sort(cats);
// 排序后
System.out.println("排序后:" + Arrays.toString(cats));
}
}
四、自定义排序算法(泛型 / 接口实现通用排序)
1. 需求背景
Arrays.sort() 只能处理基本类型和实现了 Comparable 接口的对象。我们可以自己实现一个通用的冒泡排序 / 快速排序算法,支持所有实现了 Comparable 接口的对象数组排序。
2. 核心思路
- 方法参数用
Comparable[]接收,而不是具体的Cat[]或Dog[],保证算法的泛化能力(支持所有实现了Comparable的类)。 - 利用
Comparable接口的compareTo方法进行对象间的大小比较,替代基本类型的>/<运算符。
代码示例:通用冒泡排序
public class MyArrays {
// 通用冒泡排序:支持所有实现Comparable接口的对象数组
public static void bubbleSort(Comparable[] arr) {
if (arr == null || arr.length < 2) {
return;
}
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
// 用compareTo方法比较,实现通用逻辑
if (arr[j].compareTo(arr[j + 1]) > 0) {
// 交换位置
Comparable temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
}
// 测试:用自定义冒泡排序对Cat对象数组排序
public class MySortTest {
public static void main(String[] args) {
Cat[] cats = {
new Cat("咪咪", 3),
new Cat("花花", 1),
new Cat("小白", 2)
};
System.out.println("自定义排序前:" + Arrays.toString(cats));
MyArrays.bubbleSort(cats);
System.out.println("自定义排序后:" + Arrays.toString(cats));
}
}
3. 注意
-
为什么要转成
Comparable[],不直接用Object[]?Object类中没有compareTo方法,如果直接用Object[]接收数组,后续无法调用compareTo进行比较。而所有实现了Comparable接口的类,都可以向上转型为Comparable类型,保证算法的通用性。 -
为什么不直接写死为
Cat[]?如果写死为Cat[],这个排序方法就只能处理Cat对象,无法复用在Dog、Student等其他需要排序的类上。用Comparable[]接收,所有实现了该接口的类都可以使用同一个排序方法,大幅提升代码复用性。
五、易错点
- 抽象类不能被实例化:直接
new Animal()会编译报错,只能通过多态创建子类对象。 - 接口的方法默认是 public abstract:实现类重写时必须用
public修饰,否则会降低访问权限。 compareTo方法的返回值:升序和降序的返回值容易写反,记住核心规则:this.compareTo(o) > 0时交换,就是升序排序。- 泛化排序的类型转换:自定义排序算法中,不能直接用
Object[]调用compareTo,必须转成Comparable[]才能调用该方法。
更多推荐




所有评论(0)