Java的抽象类和接口
Java的抽象类
抽象类是 Java 语言中被 abstract 关键字修饰的特殊类,属于引用类型范畴,其核心本质是为子类提供共性封装与行为约束的模板类,自身不具备直接实例化能力,仅可作为继承体系中的父类存在,是面向对象编程中实现代码复用与行为规范化的核心工具。
当多个类具有共同的属性和部分共同行为,且需要为子类提供基础实现时,适合用抽象类。
抽象类的特点
一、实例化约束:无法直接实例化,支持多态引用
- 抽象类的核心语法约束为无法通过 new 关键字直接创建实例对象(即 new 抽象类名(...) 会触发编译错误);
- 抽象类虽无法实例化自身,但可声明抽象类类型的引用变量,该引用可指向其子类的实例(多态的核心用法);
其设计定位是作为继承体系的父类,仅可被其他类继承后使用,此约束的本质是抽象类为子类提供模板,而非作为可独立运行的实例载体。
二、方法体系:支持抽象方法与具体方法共存,抽象方法有明确约束
抽象类的方法体系包含两类方法,且对抽象方法的定义、子类实现有严格语法规则:
- 抽象方法:由 abstract 关键字修饰,仅包含方法签名(返回值类型、方法名、参数列表),无方法体(无 {} 包裹的实现逻辑);
- 包含抽象方法的类必须被声明为抽象类(反之不成立,抽象类可包含 0 个抽象方法);
- 抽象方法不可使用 private、final、static 修饰(private 导致子类无法访问重写,final 禁止方法重写,static 方法属于类而非实例,均违背抽象方法约束子类实现的设计目标);
- 抽象方法需由子类实现:若子类为非抽象类,必须重写抽象类中所有抽象方法;若子类为抽象类,可部分 / 完全不重写,未重写的抽象方法将约束传递至其子类(孙类)。
- 抽象类的核心特征是无法实例化,而非必须包含抽象方法;若仅需禁止某类被实例化,即使无抽象方法,也可将其声明为抽象类。
- 具体方法(非抽象方法)
具备完整方法体(包含 {} 实现逻辑)的方法,与普通类的方法语法一致;
子类可直接继承使用该方法,也可根据业务需求重写(重写遵循 Java 方法重写的通用规则)。
三、构造方法:存在且用于子类初始化父类成员,非自身实例化
抽象类可声明构造方法(无参 / 有参),其作用并非实例化自身,而是为子类继承时初始化抽象类的成员变量:
- 子类构造方法执行时,会隐式调用父类(抽象类)的无参构造方法(通过 super());
- 若抽象类仅定义有参构造方法,子类构造方法必须显式调用该有参构造(super(参数)),否则编译报错;
抽象类的构造方法不会被直接调用以创建自身实例,但会作为子类初始化流程的一部分,完成抽象类中成员变量的赋值,符合 Java 继承体系中子类必先初始化父类成员的语义规则。
四、继承约束:遵循 Java 单继承机制,抽象类自身可继承其他类
抽象类的继承行为受 Java 单继承规则约束,且具备双向继承能力:
- 子类继承限制:一个类只能继承一个抽象类(或普通类),这是 Java 单继承机制的必然结果,区别于接口的多实现特性;
- 抽象类自身的继承能力:抽象类可继承其他类(包括普通类或另一个抽象类),继承后可复用父类的成员(变量、方法),并可新增自身的抽象方法 / 具体方法。
五、子类实现规则:按子类类型区分,非抽象子类有强制重写要求
子类继承抽象类时,需根据自身是否为抽象类遵循不同的实现规则,且规则由编译器强制校验:
- 非抽象子类:必须重写抽象类中所有未被实现的抽象方法,否则编译报错(确保子类具备完整的行为实现,符合 "非抽象类可实例化" 的基础要求);
- 抽象子类:可选择性重写抽象类的部分抽象方法,也可完全不重写;未重写的抽象方法将保留抽象属性,由该抽象子类的非抽象子类(孙类)完成最终实现。
例:定义一个 Animal 抽象类,包含所有动物共有的属性和部分共同行为,以及必须由子类实现的抽象行为
java
// 抽象类
abstract class Animal {
protected String name; // 成员变量
// 构造方法
public Animal(String name) {
this.name = name;
}
// 具体方法(有实现)
public void sleep() {
System.out.println(name + "在睡觉");
}
// 抽象方法(无实现,必须由子类重写)
public abstract void eat();
}
// 子类继承抽象类
class Dog extends Animal {
public Dog(String name) {
super(name); // 调用父类构造方法
}
// 必须重写抽象方法
@Override
public void eat() {
System.out.println(name + "吃骨头");
}
}
Java的接口
接口(Interface)是 Java 语言中一种特殊的引用类型,由 interface 关键字定义,其核心本质是为不同类提供行为规范:仅声明类具备的行为能力,不约束行为的具体实现逻辑,是面向对象编程中实现行为抽象、解耦及突破单继承限制的核心工具。
接口的设计语义为 can-do(具备某种能力),区别于抽象类 is-a(属于某类)的继承语义,它不依赖类的继承关系,可跨继承体系为无关联的类赋予统一的行为标准。
接口的特点
一、实例化约束:无法直接实例化,支持多态引用
- 接口的核心语法约束为无法通过 new 关键字直接创建实例对象(即 new 接口名(...) 会触发编译错误);
- 接口虽无法实例化自身,但可声明接口类型的引用变量,该引用可指向其实现类的实例(多态的核心用法);
其设计定位是作为跨类的行为契约,仅可被其他类实现后使用,此约束的本质是接口仅定义行为规范,而非作为可独立运行的实例载体。
二、成员体系:变量仅为常量,方法分类型有严格约束
接口的成员体系包含常量与多类型方法,且各类成员均有严格的语法规则:
- 成员变量(仅常量):默认且只能是
public static final修饰的常量(可省略修饰符,编译器自动补充);- 常量必须显式初始化(不可处于未赋值状态),如
int MAX_SPEED;编译报错,需int MAX_SPEED = 10;; - 常量不可修改( final 特性),仅能通过 接口名.变量名 或 实现类名.变量名 访问(静态常量特性);
- 不可使用 private/protected 等其他修饰符,违背接口常量的公共访问特性。
- 常量必须显式初始化(不可处于未赋值状态),如
- 方法体系(按类型区分)
- 抽象方法:由 public abstract 修饰(可省略),仅包含方法签名,无方法体;
非抽象实现类必须重写接口中所有抽象方法,是接口行为契约的核心要求;
不可使用 private/final/static 修饰,否则违背约束实现类完成行为的设计目标。 - 默认方法(Java 8+):由 public default 修饰,具备完整方法体;
实现类可直接复用该方法,也可重写(重写时需移除 default 修饰符);
若实现类同时实现多个含同名默认方法的接口,必须显式重写该方法解决冲突。 - 静态方法(Java 8+):由 public static 修饰,具备完整方法体;
属于接口本身,仅能通过 接口名.方法名 调用,不可被实现类重写(仅可隐藏);
静态方法不参与接口继承,子接口无法继承父接口的静态方法。 - 私有方法(Java 9+):由 private 修饰,具备完整方法体;
仅用于接口内部辅助默认方法,不可被外部访问,避免默认方法代码冗余;
私有静态方法(private static)仅辅助接口静态方法,同样不可外部访问。
- 抽象方法:由 public abstract 修饰(可省略),仅包含方法签名,无方法体;
三、构造方法:无构造方法,无实例化相关初始化逻辑
接口不存在构造方法,核心原因在于其无实例化语义,且成员变量为静态常量(类加载时已完成初始化):
- 接口无需构造方法完成实例化,因其仅定义行为规范,无创建实例的语义基础;
- 接口的常量初始化依赖类加载机制,而非构造方法,因此无构造方法的设计符合接口的本质定位;
这是接口与抽象类的核心区别之一,抽象类因需为子类初始化成员变量而存在构造方法,接口则无此需求。
四、继承/实现约束:支持接口多继承、类多实现,突破单继承限制
接口的继承与实现行为突破了 Java 单继承的限制,具备灵活的行为组合能力:
- 接口的继承规则:接口可通过 extends 关键字继承多个其他接口(如
interface C extends A, B);
子接口会继承父接口的抽象方法与默认方法(静态方法不继承),若父接口存在同名默认方法,子接口需显式重写该方法解决冲突。 - 类的实现规则:一个类可通过 implements 关键字实现多个接口(如
class D implements A, B);
这是 Java 突破单继承限制的核心方式,使类可同时具备多种行为能力,实现多个接口时,需重写所有接口的抽象方法,若存在同名默认方法需显式重写。
五、实现类规则:按实现类类型区分,非抽象实现类有强制重写要求
类实现接口时,需根据自身是否为抽象类遵循不同的实现规则,且规则由编译器强制校验:
- 非抽象实现类:必须重写接口中所有未被实现的抽象方法,否则编译报错;
- 抽象实现类:可选择性重写接口的部分抽象方法,也可完全不重写;
未重写的抽象方法将保留抽象属性,由该抽象实现类的非抽象子类完成最终实现,抽象实现类可直接复用接口的默认方法,无需额外处理。
例:定义一个 Runnable 接口,表示 "可运行" 的功能,无论 Dog、Car 等类是否有继承关系,只要实现 Runnable 就能具备 "运行" 能力。
java
// 接口
interface Runnable {
// 抽象方法
void run();
}
// 类实现接口
class Dog extends Animal implements Runnable {
public Dog(String name) {
super(name);
}
// 重写继承于Animal的抽象方法
@Override
public void eat() {
System.out.println(name + "吃骨头");
}
// 实现接口的抽象方法
@Override
public void run() {
System.out.println(name + "在跑");
}
}
实现Comparable接口定义的排序
我们来看这组代码,演示如何通过 Comparable 接口定义对象的比较规则,并结合冒泡排序和快速排序算法实现对象数组的排序。
Cat.java
java
public class Cat implements Comparable<Cat>{
public String name;
public int age;
public int height;
public Cat(){}
public Cat(String name,int age, int height) {
this.name = name;
this.age = age;
this.height = height;
}
@Override
public String toString() {
return "Cat{" +"name='" + name + '\'' +", age=" + age +", height=" + height +'}';
}
@Override
public int compareTo(Cat o) {
return age - o.age;
}
}
Student.java
java
public class Student implements Comparable<Student>{
public String name;
public int age;
public int height;
public Student(){}
public Student(String name,int age, int height) {
this.name = name;
this.age = age;
this.height = height;
}
@Override
public String toString() {
return "Student{" +"name='" + name + '\'' +", age=" + age +", height=" + height +'}';
}
@Override
public int compareTo(Student o) {
return age - o.age;
}
}
Arrays2.java
java
public class Arrays2 {
public static void sort(Object[] o){
Comparable[] arr = (Comparable[]) o;
bubbleSort(arr);
//QuickSort(arr,0,arr.length-1);
}
//冒泡排序
public static void bubbleSort(Comparable[] arr){
for (int j = 0; j < arr.length; j++){
for(int i = 0;i<arr.length-1;i++){
if(arr[i].compareTo(arr[i+1]) > 0){
Comparable temp = arr[i];
arr[i] = arr[i+1];
arr[i+1] = temp;
}
}
}
}
//快速排序
public static void QuickSort(Comparable[] arr,int left,int right) {
if(left>=right) {
return;
}
Comparable base = arr[left];
int i = left;
int j = right;
while(i!=j) {
while(arr[i].compareTo(base)>=0 && i<j) {
j--;
}
while(arr[j].compareTo(base)<=0 && i<j) {
i++;
}
Comparable temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
arr[left] = arr[i];
arr[i] = base;
QuickSort(arr,left,i-1);
QuickSort(arr,i+1,right);
}
}
Test.java
java
public class Test {
public static void main(String[] args) {
Student[] students = {
new Student("张三",18,188),
new Student("李四",17,180),
new Student("王五",19,190)
};
Cat[] cats = {
new Cat("小猫",18,19),
new Cat("小猫",17,18),
new Cat("小猫",19,20)
};
Arrays2.sort(students);
Arrays2.sort(cats);
for (Cat cat: cats) {
System.out.println(cat.toString());
}
for (Student student: students) {
System.out.println(student.toString());
}
}
}
输出结果:

Arrays2.java 类是自定义的排序工具类,实现了冒泡排序和快速排序两个算法内容,用于对自定义对象数组的排序,让类实现 Comparable 接口指定比较规则,并且必须要重写 compareTo 抽象方法,在 compareTo 抽象方法内写排序规则,并且需要注意 Comparable 接口的泛型内要传入对应的实现类参数。
Cat.java 和 Student.java 类实现了 Comparable<Cat> 接口,这意味着 Cat 和 Student 对象可以进行比较和排序,重写 Comparable 接口中的抽象方法 compareTo 方法:通过 return age - o.age 定义比较规则,按照 age 升序排序,当前对象年龄 - 目标对象年龄,结果 > 0 则当前对象值更大,实现了按年龄排序的规则。
- 为什么用 Object[] 作为 sort 方法的参数?
根据 Java 的继承规则,所有类都直接或间接继承自 Object 类,子类对象可向上转型为 Object 类型(多态的基础特性)。因此,Object[] 类型的数组可以接收任意自定义对象的数组,这是实现排序方法适配所有对象类型的基础,保证了方法参数的通用性。 - 为什么不能直接用 Object[] 实现排序逻辑?
若 bubbleSort/quickSort 方法直接接收 Object[] 数组,无法完成对象比较:Object 类仅提供通用的 equals/hashCode 等方法,没有定义对象大小比较的行为,也无法直接访问子类的 age 等属性,比如无法通过 Object 类型调用 arr[i].age,自然无法判断对象大小。 - 接口如何解决通用比较的核心问题?
为了让不同自定义类能被统一比较,我们需要一个标准化的行为契约:让所有需要排序的类统一实现 Comparable 接口,Comparable 接口定义了 compareTo 抽象方法,所有实现该接口的类必须重写 compareTo 方法,在方法内自定义比较规则;
利用接口多态特性 Comparable 接口类型的引用可以指向任意实现类的对象,因此 Comparable[] 数组可以接收所有实现类的数组,且能调用重写后的 compareTo 方法完成比较。 - 强制类型转换的逻辑与目的
sort 方法的参数是通用的 Object[],但排序方法需要 Comparable[] 才能调用 compareTo 方法,因此需要将 Object[] 强制转换为 Comparable[](Comparable[] arr = (Comparable[]) o;)。
传入 sort 方法的 Object[] 必须是实现了 Comparable 接口的对象数组,否则会抛出 ClassCastException,将通用的 Object 类型还原为具备比较能力的 Comparable 类型,保证排序方法能调用 compareTo 方法。 - 排序判断逻辑的核心
若直接通过属性比较(如 arr[i].age > arr[i+1].age),需要依赖具体类的属性,新增类排序时需修改排序方法;而通过 Comparable 接口的 compareTo 方法(arr[i].compareTo(arr[i+1]) > 0):Comparable[] 类型虽无法直接访问子类的 age 等属性,但能调用接口约定的 compareTo 方法;比较逻辑由实现类自身定义,排序方法只需关注比较结果,无需关心具体比较的属性,实现了排序逻辑与对象属性的解耦。