一、抽象类(abstract)

1.1 为什么要有抽象类?

在面向对象的继承体系中,父类往往需要定义子类的通用行为,但某些行为在父类中无法确定具体实现

举个例子:定义一个Animal类,其中eat()(吃)和speak()(叫)方法是所有动物的通用行为,但不同动物的 “吃” 和 “叫” 完全不同(猫吃鱼、狗吃骨头;猫喵喵叫、狗汪汪叫)。如果在父类中给eat()/speak()写死方法体,显然不符合逻辑;但如果去掉方法体,普通类又不允许空方法体 —— 这就是抽象类的诞生背景。

抽象类的核心价值:封装子类的共性属性 / 行为,同时将差异化行为声明为抽象方法,交由子类各自实现

1.2 abstract 关键字语法

abstract关键字可修饰方法

  • 修饰类:该类为抽象类,无法实例化,仅用于被继承;
  • 修饰方法:该方法为抽象方法,只有方法声明,无方法体,需子类重写。

java

运行

// 抽象类定义语法
public abstract class 类名 {
    // 抽象方法(无方法体,以分号结尾)
    public abstract void 方法名();
    
    // 抽象类中可包含普通方法(有方法体)
    public void commonMethod() {
        System.out.println("这是抽象类中的普通方法");
    }
}

1.3 抽象类核心特点

特点 1:抽象类不能实例化,仅用于被继承

抽象类的设计初衷是作为 “父类模板”,直接new抽象类会编译报错:

java

运行

public class AbstractTest {
    public static void main(String[] args) {
        // 编译报错:'Person' is abstract; cannot be instantiated
        Person p1 = new Person(); 
    }
}

// 抽象类
abstract class Person {}
特点 2:抽象类可以有构造器,便于子类实例化调用

抽象类虽不能自己实例化,但构造器可被子类通过super()调用,用于初始化父类属性:

java

运行

abstract class Person {
    String name;
    int age;

    // 无参构造器
    public Person() {}

    // 有参构造器
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

// 子类继承抽象类
class Chinese extends Person {
    // 子类构造器调用父类有参构造器
    public Chinese(String name, int age) {
        super(name, age);
    }

    // 子类无参构造器(默认调用父类无参构造器)
    public Chinese() {}
}
特点 3:子类必须重写父类所有抽象方法(否则子类也需声明为抽象类)

如果子类不想成为抽象类,就必须实现父类的全部抽象方法:

java

运行

public class AbstractTest {
    public static void main(String[] args) {
        // 子类可正常实例化
        Chinese chinese = new Chinese();
        chinese.eat();
        chinese.breath();
    }
}

// 抽象父类:定义抽象方法
abstract class Animal {
    public abstract void breath(); // 呼吸(抽象方法)
    public abstract void eat();    // 吃(抽象方法)
}

// 中间抽象子类:仅实现部分抽象方法,仍需声明为抽象类
abstract class Person extends Animal {
    // 实现eat方法
    @Override
    public void eat() {
        System.out.println("人类吃有营养的食物");
    }
}

// 具体子类:必须实现剩余的抽象方法
class Chinese extends Person {
    @Override
    public void breath() {
        System.out.println("中国人呼吸新鲜空气");
    }
}

1.4 抽象类常见面试题(核心练习)

问题 1:为什么抽象类不可以使用 final 关键字声明?

答:final修饰的类表示 “不可被继承”,而抽象类的核心设计目的是被继承(子类需重写抽象方法)。两者语义冲突,因此抽象类不能被final修饰。

问题 2:一个抽象类中可以定义构造器吗?

答:可以。抽象类的构造器并非用于自身实例化,而是供子类通过super()调用,完成父类属性的初始化。

问题 3:是否可以这样理解:抽象类就是比普通类多定义了抽象方法,除了不能直接实例化之外,没有任何不同?

答:不可以。抽象类和普通类的核心区别是 “设计意图”:

  • 普通类是 “可实例化的具体类”,侧重封装具体功能;
  • 抽象类是 “模板类”,侧重定义子类的规范,强制子类实现抽象方法;
  • 此外,抽象类可包含抽象方法,普通类则不能。
问题 4:抽象类中可以有非抽象的成员方法吗?

答:可以。抽象类既可以包含抽象方法(强制子类实现),也可以包含普通方法(封装子类的共性行为,减少代码冗余)。

1.5 抽象类的匿名子类

当抽象类的子类仅需临时使用一次,无需单独定义类文件时,可使用匿名子类(匿名内部类),语法如下:

java

运行

abstract class Person {
    public abstract void breath();
    public abstract void eat();
}

public class PersonTest {
    public static void main(String[] args) {
        // 方式1:传统子类(需单独定义类)
        // Worker worker = new Worker();
        // worker.eat();
        // worker.breath();

        // 方式2:匿名子类(临时使用,无需定义类)
        Person person = new Person() {
            @Override
            public void eat() {
                System.out.println("匿名子类:吃健康餐");
            }

            @Override
            public void breath() {
                System.out.println("匿名子类:呼吸新鲜空气");
            }
        };
        person.eat();
        person.breath();
    }
}

// 传统子类(可选)
// class Worker extends Person {
//     @Override
//     public void eat() {
//         System.out.println("Worker:吃工作餐");
//     }
//
//     @Override
//     public void breath() {
//         System.out.println("Worker:好好呼吸");
//     }
// }

二、接口(interface)

2.1 为什么要有接口?

Java 是单继承语言(一个类只能继承一个父类),但实际开发中,一个类往往需要具备多种 “能力”(如:一个类既要 “飞”,又要 “跑”)。

接口的核心价值:

  1. 突破单继承限制,实现 “多实现” 效果;
  2. 定义统一行为标准:强制多个类遵循相同的行为规范,而不限制具体实现方式(“规定做什么,不规定怎么做”)。

2.2 接口的定义与语法

接口是特殊的抽象类型,使用interface关键字定义,子类通过implements实现接口:

java

运行

// 1. 接口定义
public interface 接口名 {
    // 全局常量(默认public static final)
    int MAX_SPEED = 100;
    
    // 抽象方法(默认public abstract)
    void method();
}

// 2. 类实现单个接口
public class 实现类名 implements 接口名 {
    @Override
    public void method() {
        // 实现接口方法
    }
}

// 3. 类实现多个接口(突破单继承)
public class 实现类名 implements 接口1, 接口2 {
    // 实现所有接口的抽象方法
}

// 4. 类继承父类 + 实现接口
public class 实现类名 extends 父类名 implements 接口名 {
    // 重写父类抽象方法 + 实现接口方法
}

2.3 接口的核心特点

特点 1:接口中仅能定义全局常量和抽象方法(JDK8 前)

接口中的成员有默认修饰符:

  • 变量:默认public static final(全局常量);
  • 方法:默认public abstract(抽象方法)。

java

运行

interface Flyable {
    // 等价于 public static final int MAX_SPEED = 7900;
    int MAX_SPEED = 7900; 
    // 等价于 public static final int MIN_SPEED = 1;
    int MIN_SPEED = 1;     

    // 等价于 public abstract void fly();
    void fly();
    // 等价于 public abstract void stop();
    void stop();
}
特点 2:接口不能定义构造器,无法实例化

接口是 “行为规范”,而非 “对象模板”,因此无构造器,也不能new

java

运行

interface Flyable {
    // 编译报错:Interfaces cannot have constructors
    public Flyable() {} 
}
特点 3:Java 类可实现多个接口

java

运行

interface AA { void method1(); }
interface BB { void method2(); }

// 实现多个接口,需重写所有抽象方法
class CC implements AA, BB {
    @Override
    public void method1() {
        System.out.println("实现AA接口的method1");
    }

    @Override
    public void method2() {
        System.out.println("实现BB接口的method2");
    }
}
特点 4:接口之间可多继承

java

运行

interface AA { void method1(); }
interface BB { void method2(); }

// 接口DD继承AA和BB,拥有两者的抽象方法
interface DD extends AA, BB {}

// 实现DD需重写method1和method2
class EE implements DD {
    @Override
    public void method1() {}
    @Override
    public void method2() {}
}

2.4 JDK8 接口新特性

JDK8 打破了 “接口只有常量和抽象方法” 的限制,新增默认方法静态方法

(1)默认方法(default)
  • 有方法体,实现类可直接调用,也可重写;
  • 解决 “接口升级兼容问题”(新增方法时,无需修改所有实现类)。
(2)静态方法(static)
  • 有方法体,只能通过 “接口名。方法名” 调用,实现类无法重写。

java

运行

interface Fly {
    // 默认方法
    default void defaultMethod() {
        System.out.println("接口默认方法:实现类可直接调用");
    }

    // 静态方法
    static void staticMethod() {
        System.out.println("接口静态方法:仅接口可调用");
    }
}

class SubClass implements Fly {
    // 可选:重写默认方法
    @Override
    public void defaultMethod() {
        System.out.println("子类重写接口默认方法");
    }
}

public class Test01 {
    public static void main(String[] args) {
        SubClass sc = new SubClass();
        sc.defaultMethod(); // 调用重写后的默认方法
        Fly.staticMethod(); // 调用接口静态方法
    }
}

2.5 接口实战练习:USB 接口案例

需求:定义 USB 接口,实现键盘、鼠标的 USB 功能,并通过设备类调用 USB 设备。

java

运行

// 1. 定义USB接口(统一行为标准)
public interface USB {
    // USB设备的使用方法
    void useUSB();
}

// 2. 实现类:键盘
class KeyBoard implements USB {
    @Override
    public void useUSB() {
        System.out.println("键盘:通过USB接口输入文字");
    }
}

// 3. 实现类:鼠标
class Mouse implements USB {
    @Override
    public void useUSB() {
        System.out.println("鼠标:通过USB接口控制光标");
    }
}

// 4. 设备类:调用USB设备
class Device {
    // 使用USB设备的方法(参数为USB接口,面向接口编程)
    public void useDevice(USB usb) {
        usb.useUSB();
    }
}

// 5. 测试类
public class USBTest {
    public static void main(String[] args) {
        Device device = new Device();
        
        // 调用键盘
        device.useDevice(new KeyBoard());
        // 调用鼠标
        device.useDevice(new Mouse());
        
        // 匿名实现类调用USB
        device.useDevice(new USB() {
            @Override
            public void useUSB() {
                System.out.println("匿名USB设备:临时测试用");
            }
        });
    }
}

输出结果

plaintext

键盘:通过USB接口输入文字
鼠标:通过USB接口控制光标
匿名USB设备:临时测试用

三、抽象类 vs 接口(核心区别)

维度 抽象类(abstract class) 接口(interface)
继承 / 实现 单继承(extends) 多实现(implements)
构造器 可以有 不能有
方法 抽象方法 + 普通方法 JDK8 前:仅抽象方法;JDK8 后:默认 / 静态方法
变量 普通变量 + 常量 仅全局常量(public static final)
设计意图 封装子类共性(属性 + 行为) 定义行为标准(仅行为)

四、总结

  1. 抽象类:是 “模板类”,用于封装子类共性,抽象方法强制子类实现差异化行为,不能实例化但可定义构造器;
  2. 接口:是 “行为标准”,突破单继承限制,JDK8 后支持默认 / 静态方法,核心是 “统一规范、多实现”;
  3. 核心区别:抽象类侧重 “复用共性”,接口侧重 “定义规范”,实际开发中常结合使用(类继承抽象类 + 实现多个接口)。

掌握抽象类和接口的本质,不仅能写出更优雅的面向对象代码,也是应对 Java 基础面试的关键!

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐