Java的抽象类和接口

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)仅辅助接口静态方法,同样不可外部访问。

三、构造方法:无构造方法,无实例化相关初始化逻辑

接口不存在构造方法,核心原因在于其无实例化语义,且成员变量为静态常量(类加载时已完成初始化):

  • 接口无需构造方法完成实例化,因其仅定义行为规范,无创建实例的语义基础;
  • 接口的常量初始化依赖类加载机制,而非构造方法,因此无构造方法的设计符合接口的本质定位;

这是接口与抽象类的核心区别之一,抽象类因需为子类初始化成员变量而存在构造方法,接口则无此需求。

四、继承/实现约束:支持接口多继承、类多实现,突破单继承限制

接口的继承与实现行为突破了 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 则当前对象值更大,实现了按年龄排序的规则。

  1. 为什么用 Object[] 作为 sort 方法的参数?
    根据 Java 的继承规则,所有类都直接或间接继承自 Object 类,子类对象可向上转型为 Object 类型(多态的基础特性)。因此,Object[] 类型的数组可以接收任意自定义对象的数组,这是实现排序方法适配所有对象类型的基础,保证了方法参数的通用性。
  2. 为什么不能直接用 Object[] 实现排序逻辑?
    若 bubbleSort/quickSort 方法直接接收 Object[] 数组,无法完成对象比较:Object 类仅提供通用的 equals/hashCode 等方法,没有定义对象大小比较的行为,也无法直接访问子类的 age 等属性,比如无法通过 Object 类型调用 arr[i].age,自然无法判断对象大小。
  3. 接口如何解决通用比较的核心问题?
    为了让不同自定义类能被统一比较,我们需要一个标准化的行为契约:让所有需要排序的类统一实现 Comparable 接口,Comparable 接口定义了 compareTo 抽象方法,所有实现该接口的类必须重写 compareTo 方法,在方法内自定义比较规则;
    利用接口多态特性 Comparable 接口类型的引用可以指向任意实现类的对象,因此 Comparable[] 数组可以接收所有实现类的数组,且能调用重写后的 compareTo 方法完成比较。
  4. 强制类型转换的逻辑与目的
    sort 方法的参数是通用的 Object[],但排序方法需要 Comparable[] 才能调用 compareTo 方法,因此需要将 Object[] 强制转换为 Comparable[](Comparable[] arr = (Comparable[]) o;)。
    传入 sort 方法的 Object[] 必须是实现了 Comparable 接口的对象数组,否则会抛出 ClassCastException,将通用的 Object 类型还原为具备比较能力的 Comparable 类型,保证排序方法能调用 compareTo 方法。
  5. 排序判断逻辑的核心
    若直接通过属性比较(如 arr[i].age > arr[i+1].age),需要依赖具体类的属性,新增类排序时需修改排序方法;而通过 Comparable 接口的 compareTo 方法(arr[i].compareTo(arr[i+1]) > 0):Comparable[] 类型虽无法直接访问子类的 age 等属性,但能调用接口约定的 compareTo 方法;比较逻辑由实现类自身定义,排序方法只需关注比较结果,无需关心具体比较的属性,实现了排序逻辑与对象属性的解耦。
相关推荐
while(1){yan}2 小时前
SpringDI
java·jvm·spring·java-ee
陈平安安2 小时前
设计一个秒杀功能
java·数据库·sql
TAEHENGV2 小时前
基本设置模块 Cordova 与 OpenHarmony 混合开发实战
android·java·数据库
wadesir2 小时前
Go语言中高效读取数据(详解io包的ReadAll函数用法)
开发语言·后端·golang
千寻技术帮2 小时前
10422_基于Springboot的教务管理系统
java·spring boot·后端·vue·教务管理
milanleon2 小时前
使用Spring Security进行登录认证
java·前端·spring
毕设源码-朱学姐2 小时前
【开题答辩全过程】以 三叶草中石油信息管理系统的设计与实现为例,包含答辩的问题和答案
java·eclipse
小高不明3 小时前
前缀和一维/二维-复习篇
开发语言·算法