乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • 学习路径
  • 第1章 Java概述与环境搭建

    • Java概述与环境搭建
    • Java语言概述
    • 解释型语言与编译型语言对比
    • JDK安装与配置
    • JDK、JRE、JVM 详解
    • HelloWorld程序详解
    • IDE 介绍
  • 第2章 标识符与基本数据类型

    • 章节导读
    • 变量概述
    • 常量概述
    • 基本类型与包装类
    • 字节型 byte
    • 短整型 short
    • 整型 int
    • 长整型 long
    • 单精度浮点型 float
    • 双精度浮点型 double
    • 字符型 char
    • 布尔型 boolean
    • 类型转换
  • 第3章 运算符与表达式

    • 章节导读
    • 算术运算符
    • 赋值运算符
    • 关系运算符
    • 逻辑运算符
    • 位运算符
    • 条件运算符
    • 运算符优先级
    • 表达式
  • 第4章 流程控制

    • 章节导读
    • 常见的程序运行流程
    • if-else 选择结构
    • switch 多分支选择
    • while 循环
    • do-while 循环
    • for 循环
    • break 与 continue
  • 第5章 数组

    • 章节导读
    • 一维数组
    • 多维数组
    • Arrays 工具类
  • 第6章 类与对象

    • 章节导读
    • 类与对象
    • 方法定义与调用
    • 构造方法
    • 封装
    • 访问修饰符
    • package 与 import
    • static 关键字
    • this 关键字
    • 参数传递 详解
    • 枚举
    • 成员内部类
    • 局部内部类
    • 静态内部类
    • 匿名内部类
  • 第7章 接口与继承

    • 章节导读
    • 继承
    • super 关键字
    • final 关键字
    • 多态
    • 向上转型与向下转型
    • 抽象类
    • 接口
    • 抽象类与接口对比
  • 第8章 注解

    • 章节导读
    • 注解基础
    • 元注解详解
    • 自定义注解
  • 第9章 常用类

    • 章节导读:Java 常用类
    • Object 类:万类之祖
    • 包装类:基本类型的对象化
    • String:不可变的字符串
    • StringBuffer:线程安全的可变字符串
    • StringBuilder:可变的字符串构建器
    • Math:数学运算工具类
    • Random:伪随机数生成器
    • 大数值运算 详解
    • 日期时间API 详解
  • 第10章 异常机制

    • 章节导读
    • 异常体系与分类
    • try-catch-finally
    • try-with-resources
    • throws 与 throw
    • 自定义异常
  • 第11章 泛型

    • 章节导读
    • 泛型基础
    • 通配符与PECS原则
    • 类型擦除
  • 第12章 集合框架

    • 章节导读
    • 集合框架概述
    • ArrayList
    • LinkedList
    • HashMap 详解
    • LinkedHashMap 详解
    • TreeMap 详解
    • HashSet
    • TreeSet 详解
    • TreeSet 与 Comparable
    • Collections 工具类详解
  • 第13章 IO流

    • 章节导读
    • IO流概述
    • 字节流
    • 字符流
    • 缓冲流
    • 转换流 详解
    • 序列化 详解
    • NIO与Files 详解
    • NIO与Files工具类
  • 第14章 多线程与并发

    • 第十六章 多线程与并发 —— 章节导读
    • 线程基础详解
    • synchronized 详解
    • Lock 与显式锁详解
    • volatile 详解
    • wait 与 notify 详解
    • ThreadLocal详解
    • 原子类详解
    • 并发工具类详解
    • 线程池详解
  • 第15章 反射

    • 章节导读
    • 反射概述与 Class 对象
    • Constructor 与对象创建
    • Field与Method详解
    • 反射应用详解
  • 第16章 JDK8新特性

    • 章节导读
    • Lambda 表达式
    • Stream API 基础
    • Stream API 高级详解
    • Optional 详解
    • 新日期时间API详解
  • 第17章 JDK9-11新特性

    • 章节导读
    • 模块化系统 — Project Jigsaw(JDK 9)
    • var 局部变量类型推断(JDK 10)
    • 集合工厂方法与增强(JDK 9 / 10 / 11)
    • 接口增强:private 方法(JDK 9)
    • Stream API 增强(JDK 9)
    • Optional 增强(JDK 9 / 10 / 11)
    • String 新增方法(JDK 11)
    • HTTP Client 与 Files 增强(JDK 11)
    • 直接运行 Java 源文件 — JEP 330(JDK 11)
  • 第18章 JDK12-17新特性

    • 章节导读
    • Switch 表达式(JDK 12 预览 / JDK 14 正式)
    • 文本块 Text Blocks(JDK 13 预览 / JDK 15 正式)
    • Records 记录类(JDK 14 预览 / JDK 16 正式)
    • 密封类 Sealed Classes(JDK 15 预览 / JDK 17 正式)
    • instanceof 模式匹配(JDK 14 预览 / JDK 16 正式)
    • Switch 模式匹配 — Pattern Matching for switch(JDK 17 预览 / JDK 21 正式)
    • Helpful NPE 与 String 增强(JDK 12 / JDK 14 / JDK 15)
    • Stream 增强(JDK 12 / JDK 16)
    • 日期时间增强 — Day Period 支持(JDK 16)
  • 第19章 JDK18-21新特性

    • 章节导读
    • 虚拟线程(JDK 19 预览 / JDK 20 第二预览 / JDK 21 正式)
    • 序列集合(JDK 21 正式)
    • Switch 模式匹配(JDK 17 预览 / JDK 18 第二预览 / JDK 20 第四预览 / JDK 21 正式)
    • Record 模式匹配(JDK 19 预览 / JDK 20 第二预览 / JDK 21 正式)
    • 未命名模式与变量(JDK 21 预览 / JDK 22 正式)
  • 第20章 JDK 22-25 新特性

    • 章节导读
    • 字符串模板(JDK 22 预览 / JDK 23 第二预览 / JDK 24 第三预览)
    • Stream Gatherers(JDK 22 预览 / JDK 24 第二预览)
    • 隐式声明类与实例方法(JDK 23 预览 / JDK 24 第二预览)
    • 原始类型模式匹配(JDK 24 预览)
  • 附录

    • Java 核心知识点
    • Java SE 专业术语
    • Java特性索引(JDK 8 → 25)

接口

飞翔科技·设计评审会:架构师白歌在黑板上画了一张图:"我们的薪资系统要支持多种支付方式——银行卡、支付宝、微信。同时,薪资单要支持多种打印格式——PDF 报表、Excel 报表、甚至直接打印到控制台。Employee 已经继承了抽象类,没法再继承 PaymentMethod 和 ReportFormat 了。" 后端开发小崔举手:"用接口!一个类可以实现多个接口,完美解决 Java 单继承的局限。"


什么是接口

接口(Interface) 是一种纯抽象契约。它只定义"能做什么"(方法签名),不关心"怎么做"(方法实现)。一个类通过 implements 关键字签署接口契约,承诺提供接口中所有方法的具体实现。

Java 的类只能继承一个父类(单继承),但可以实现多个接口——这是 Java 解决多继承局限的核心机制。

// 接口:定义支付能力
public interface Payable {
    void pay(double amount);     // 支付
    String getPaymentMethod();   // 获取支付方式名
}

// 接口:定义打印能力
public interface Printable {
    void print();                // 打印
    String getFormat();          // 获取打印格式
}

// 类可以实现多个接口
public class FullTimeEmployee extends Employee implements Payable, Printable {
    // 继承 Employee,同时拥有 Payable 和 Printable 两种能力
}

核心语法(JDK 8)

接口的定义与实现

语法元素写法说明
接口声明public interface InterfaceName { }interface 关键字声明
抽象方法(JDK 7-)void methodName();默认 public abstract,可省略
实现接口class A implements I1, I2 { }一个类可实现多个接口
常量int MAX_COUNT = 100;默认 public static final
default 方法(JDK 8)default void method() { ... }接口中的默认实现,子类可重写
static 方法(JDK 8)static void method() { ... }接口的静态工具方法

接口方法修饰符的省略规则

public interface Payable {
    // 以下三种写法完全等价:
    void pay(double amount);                           // 写法一:最简
    public abstract void pay(double amount);           // 写法二:完整形式(编译器会自动补全)
    abstract void pay(double amount);                  // 写法三:省略 public

    // 常量
    double MIN_AMOUNT = 0.01;
    // 等价于:public static final double MIN_AMOUNT = 0.01;
}
接口成员省略修饰符时的默认值完整形式
抽象方法public abstractpublic abstract void method();
常量public static finalpublic static final int X = 1;
default 方法publicpublic default void method() {}
static 方法publicpublic static void method() {}

深度原理分析

1. 接口如何解决 Java 单继承的局限?

C++ 支持多继承,但带来了菱形继承(Diamond Problem)的复杂性。Java 选择单继承 + 多接口的模型:

关键设计思想:接口只定义方法签名,不包含实现逻辑(JDK 8 之前)。实现类负责提供所有方法的具体代码,因此不存在"两个父类方法打架"的问题。JDK 8 引入 default 方法后,如果多个接口有同名 default 方法,编译器会强制实现类自行解决冲突。

2. 接口方法的字节码表示

在 .class 文件中,接口被标记为 ACC_INTERFACE 和 ACC_ABSTRACT:

// javap -v Payable.class
public interface Payable
  flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT

  public abstract void pay(double);
    flags: ACC_PUBLIC, ACC_ABSTRACT

  public static final double MIN_AMOUNT;
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL

接口的抽象方法在字节码层面与抽象类的抽象方法几乎一致——都标记了 ACC_ABSTRACT。区别在于接口的方法默认还标记了 ACC_PUBLIC。

3. JDK 8 default 方法的原理

JDK 8 允许在接口中定义 default 方法,核心动机是接口演化:

Java 核心类库中 Collection 接口新增了 stream() 方法。如果这是一个抽象方法,所有自定义 Collection 实现类都会编译失败。因此 JDK 8 引入 default 方法——接口可以提供默认实现,旧代码无需修改即可通过编译。

JVM 调用 default 方法的机制:

// 字节码层面,调用接口的 default 方法使用 invokeinterface 指令
// JVM 会在当前类的虚方法表(vtable)中查找该方法的入口
// 如果当前类没有重写,则跳转到接口的 default 实现

invokeinterface #5   // InterfaceMethod Payable.printReceipt:()V

JVM 的 invokeinterface 指令在 JDK 8 中进行了升级,能够正确分发到接口的 default 方法实现——这是 JDK 8 在 JVM 层面的重要改动。

4. 为什么接口不能有构造方法?

接口是纯契约,不包含实例字段(所有变量都是 static final 常量)。构造方法的作用是初始化实例字段,而接口没有需要初始化的实例状态,因此构造方法对接口没有意义。

更深层的原因:一个类可以实现多个接口,如果每个接口都有构造方法,那么实例化时调用谁的构造?以什么顺序调用?这完全不可行。


完整示例:飞翔科技多接口系统

示例一:Payable + Printable 多接口实现

架构师白歌要求薪资单既支持多种支付方式,又支持多种打印格式。

// ============ Payable.java ============
/**
 * 支付能力接口——任何需要支付的业务对象都实现它
 */
public interface Payable {
    /** 执行支付 */
    void pay(double amount);

    /** 获取支付方式名称 */
    String getPaymentMethod();

    /** 获取收款账户 */
    String getAccountInfo();
}
// ============ Printable.java ============
/**
 * 打印能力接口——任何需要输出报表的对象都实现它
 */
public interface Printable {
    /** 打印内容 */
    void print();

    /** 获取打印格式 */
    String getFormat();
}
// ============ BankPayment.java ============
/**
 * 银行卡支付实现
 */
public class BankPayment implements Payable {
    private String bankName;
    private String cardNumber;

    public BankPayment(String bankName, String cardNumber) {
        this.bankName = bankName;
        this.cardNumber = cardNumber;
    }

    @Override
    public void pay(double amount) {
        System.out.println("【银行卡支付】从 " + bankName
                + " 卡号 " + maskCard(cardNumber)
                + " 扣款 " + String.format("%.2f", amount) + " 元");
    }

    @Override
    public String getPaymentMethod() {
        return "银行卡(" + bankName + ")";
    }

    @Override
    public String getAccountInfo() {
        return "卡号:" + maskCard(cardNumber);
    }

    private String maskCard(String card) {
        // 银行卡号脱敏:显示前4后4,中间星号
        return card.substring(0, 4) + " **** **** " + card.substring(card.length() - 4);
    }
}
// ============ AlipayPayment.java ============
/**
 * 支付宝支付实现
 */
public class AlipayPayment implements Payable {
    private String alipayAccount;

    public AlipayPayment(String alipayAccount) {
        this.alipayAccount = alipayAccount;
    }

    @Override
    public void pay(double amount) {
        System.out.println("【支付宝支付】从账户 " + maskAccount(alipayAccount)
                + " 扣款 " + String.format("%.2f", amount) + " 元");
    }

    @Override
    public String getPaymentMethod() {
        return "支付宝";
    }

    @Override
    public String getAccountInfo() {
        return "支付宝账户:" + maskAccount(alipayAccount);
    }

    private String maskAccount(String account) {
        if (account.contains("@")) {
            // 邮箱:只显示第一个字符和域名
            int atIndex = account.indexOf('@');
            return account.charAt(0) + "***" + account.substring(atIndex);
        }
        // 手机号:显示前3后4
        return account.substring(0, 3) + "****" + account.substring(account.length() - 4);
    }
}
// ============ SalaryPrinter.java ============
/**
 * 薪资单打印服务——面向接口编程,不依赖具体支付方式
 */
public class SalaryPrinter {
    /**
     * 打印薪资单
     * @param employeeName 员工姓名
     * @param amount 工资金额
     * @param payable 支付方式(面向接口,不关心具体是银行卡还是支付宝)
     * @param printable 打印格式(面向接口,不关心具体格式)
     */
    public void printSalarySlip(String employeeName, double amount,
                                 Payable payable, Printable printable) {
        System.out.println("========== 飞翔科技薪资单 ==========");
        System.out.println("员工:" + employeeName);
        System.out.println("支付方式:" + payable.getPaymentMethod());
        System.out.println("收款账户:" + payable.getAccountInfo());
        printable.print();
        payable.pay(amount);
        System.out.println("====================================");
        System.out.println();
    }
}
// ============ PdfPrintable.java ============
public class PdfPrintable implements Printable {
    @Override
    public void print() {
        System.out.println("【PDF 格式】正在生成薪资单 PDF 文件...");
    }

    @Override
    public String getFormat() {
        return "PDF";
    }
}

// ============ ExcelPrintable.java ============
public class ExcelPrintable implements Printable {
    @Override
    public void print() {
        System.out.println("【Excel 格式】正在生成薪资单 Excel 文件...");
    }

    @Override
    public String getFormat() {
        return "Excel";
    }
}
// ============ Main.java ============
public class Main {
    public static void main(String[] args) {
        SalaryPrinter printer = new SalaryPrinter();

        // 大翔:银行卡 + PDF
        Payable bankPay = new BankPayment("招商银行", "6222021234567890");
        Printable pdfPrint = new PdfPrintable();
        printer.printSalarySlip("大翔", 28926.00, bankPay, pdfPrint);

        // 小崔:支付宝 + Excel
        Payable alipay = new AlipayPayment("xiaocui@feixiang.cn");
        Printable excelPrint = new ExcelPrintable();
        printer.printSalarySlip("小崔", 3982.75, alipay, excelPrint);

        // 白歌:银行卡 + Excel
        printer.printSalarySlip("白歌", 32000.00, bankPay, excelPrint);
    }
}

运行输出:

========== 飞翔科技薪资单 ==========
员工:大翔
支付方式:银行卡(招商银行)
收款账户:卡号:6222 **** **** 7890
【PDF 格式】正在生成薪资单 PDF 文件...
【银行卡支付】从 招商银行 卡号 6222 **** **** 7890 扣款 28926.00 元
====================================

========== 飞翔科技薪资单 ==========
员工:小崔
支付方式:支付宝
收款账户:支付宝账户:x***@feixiang.cn
【Excel 格式】正在生成薪资单 Excel 文件...
【支付宝支付】从账户 x***@feixiang.cn 扣款 3982.75 元
====================================

========== 飞翔科技薪资单 ==========
员工:白歌
支付方式:银行卡(招商银行)
收款账户:卡号:6222 **** **** 7890
【Excel 格式】正在生成薪资单 Excel 文件...
【银行卡支付】从 招商银行 卡号 6222 **** **** 7890 扣款 32000.00 元
====================================

示例二:JDK 8 default 方法——接口演化

JDK 8 发布后,小崔想在 Payable 接口中新增一个"打印回执"功能,但不希望破坏已有的 BankPayment 和 AlipayPayment 实现。

// ============ Payable.java (JDK 8 增强版) ============
public interface Payable {
    void pay(double amount);
    String getPaymentMethod();
    String getAccountInfo();

    /**
     * JDK 8 default 方法:打印支付回执
     * 提供默认实现,旧代码无需修改
     */
    default void printReceipt(double amount) {
        System.out.println("--------- 支付回执 ---------");
        System.out.println("支付方式:" + getPaymentMethod());
        System.out.println("收款账户:" + getAccountInfo());
        System.out.println("支付金额:" + String.format("%.2f", amount));
        System.out.println("支付时间:" + java.time.LocalDateTime.now());
        System.out.println("状态:支付成功");
        System.out.println("---------------------------");
    }

    /**
     * JDK 8 static 方法:工厂方法——根据支付类型创建支付实例
     */
    static Payable createPayment(String type, String account) {
        switch (type.toUpperCase()) {
            case "BANK":
                return new BankPayment("默认银行", account);
            case "ALIPAY":
                return new AlipayPayment(account);
            default:
                throw new IllegalArgumentException("不支持的支付类型:" + type);
        }
    }

    /**
     * JDK 8 static 方法:验证支付金额是否合法
     */
    static boolean isValidAmount(double amount) {
        return amount > 0 && amount < 1000000;
    }
}
// ============ WechatPayment.java(新实现类,重写 default 方法) ============
public class WechatPayment implements Payable {
    private String wechatId;

    public WechatPayment(String wechatId) {
        this.wechatId = wechatId;
    }

    @Override
    public void pay(double amount) {
        System.out.println("【微信支付】从微信 " + maskWechat(wechatId)
                + " 扣款 " + String.format("%.2f", amount) + " 元");
    }

    @Override
    public String getPaymentMethod() {
        return "微信支付";
    }

    @Override
    public String getAccountInfo() {
        return "微信账号:" + maskWechat(wechatId);
    }

    /**
     * 微信支付重写回执格式——加上二维码信息
     */
    @Override
    public void printReceipt(double amount) {
        System.out.println("--------- 微信支付回执 ---------");
        System.out.println("支付方式:" + getPaymentMethod());
        System.out.println("支付金额:" + String.format("%.2f", amount));
        System.out.println("电子发票二维码:wx_receipt_"
                + java.util.UUID.randomUUID().toString().substring(0, 8));
        System.out.println("-------------------------------");
    }

    private String maskWechat(String id) {
        return id.substring(0, 3) + "****";
    }
}
// ============ Main.java ============
public class Main {
    public static void main(String[] args) {
        // 使用 static 工厂方法创建支付实例
        Payable bankPay = Payable.createPayment("BANK", "6222021234567890");
        Payable alipay = Payable.createPayment("ALIPAY", "xiaocui@feixiang.cn");
        Payable wechatPay = new WechatPayment("wxid_xiaocui");

        // 使用 static 工具方法验证金额
        System.out.println("金额验证 5000:" + Payable.isValidAmount(5000));
        System.out.println("金额验证 -100:" + Payable.isValidAmount(-100));

        System.out.println();

        // 银行卡:使用默认回执格式
        bankPay.pay(10000);
        bankPay.printReceipt(10000);

        System.out.println();

        // 支付宝:使用默认回执格式
        alipay.pay(3982.75);
        alipay.printReceipt(3982.75);

        System.out.println();

        // 微信:使用自定义回执格式
        wechatPay.pay(5000);
        wechatPay.printReceipt(5000);
    }
}

运行输出:

金额验证 5000:true
金额验证 -100:false

【银行卡支付】从 默认银行 卡号 6222 **** **** 7890 扣款 10000.00 元
--------- 支付回执 ---------
支付方式:银行卡(默认银行)
收款账户:卡号:6222 **** **** 7890
支付金额:10000.00
支付时间:2024-06-30T10:30:15.123
状态:支付成功
---------------------------

【支付宝支付】从账户 x***@feixiang.cn 扣款 3982.75 元
--------- 支付回执 ---------
支付方式:支付宝
收款账户:支付宝账户:x***@feixiang.cn
支付金额:3982.75
支付时间:2024-06-30T10:30:15.125
状态:支付成功
---------------------------

【微信支付】从微信 wxi**** 扣款 5000.00 元
--------- 微信支付回执 ---------
支付方式:微信支付
支付金额:5000.00
电子发票二维码:wx_receipt_a3f8b2c1
-------------------------------

接口层次结构图


易错场景

反例一:试图给接口定义构造方法

// ❌ 错误:接口不能有构造方法
public interface Payable {
    public Payable() {  // 编译错误!
        // 初始化代码...
    }
    void pay(double amount);
}
编译错误:
Interfaces cannot have constructors

纠正:

// ✅ 正确:接口不需要构造方法,如有初始化逻辑用 static 工厂方法
public interface Payable {
    void pay(double amount);

    // 用 static 方法创建实例(JDK 8)
    static Payable createBankPayment(String cardNumber) {
        return new BankPayment("默认银行", cardNumber);
    }
}

反例二:接口方法不是 public

// ❌ 错误:接口方法不能是 protected 或 private(JDK 8 中)
public interface Payable {
    protected void pay(double amount);  // 编译错误!
    private void log();                 // 编译错误!(JDK 9 才支持 private 接口方法)
}
编译错误(JDK 8):
Illegal modifier for the interface method pay; only public, abstract,
default, static and strictfp are permitted

纠正:接口中的方法必须是 public(可省略,编译器自动补全)。

反例三:多个接口 default 方法冲突

// 接口A
public interface A {
    default void greet() {
        System.out.println("Hello from A");
    }
}

// 接口B
public interface B {
    default void greet() {
        System.out.println("Hello from B");
    }
}

// ❌ 错误:同时实现 A 和 B,但两个接口有同名 default 方法
public class MyClass implements A, B {
    // 编译错误:Duplicate default methods named greet
    // 编译器不知道用哪个
}
编译错误:
Duplicate default methods named greet with the parameters () and ()
are inherited from the types B and A

纠正:实现类必须自己重写冲突的方法:

// ✅ 正确:实现类自行解决冲突
public class MyClass implements A, B {
    @Override
    public void greet() {
        // 方案一:自己实现
        System.out.println("Hello from MyClass");

        // 方案二:指定调用某个接口的 default 实现
        // A.super.greet();
        // B.super.greet();
    }
}

反例四:接口变量被修改

// ❌ 错误:接口中的变量是 public static final,不能修改
public interface Constants {
    int MAX_RETRY = 3;  // 默认 public static final
}

public class Main {
    public static void main(String[] args) {
        Constants.MAX_RETRY = 5;  // 编译错误!
    }
}
编译错误:
The final field Constants.MAX_RETRY cannot be assigned

纠正:接口中的变量是常量,只能读取不能修改。


面试考点

Q1:接口和抽象类在 JDK 8 之后是否趋同了?它们本质区别是什么?

JDK 8 的 default 方法确实模糊了接口和抽象类的边界,但本质区别未变:

  • 抽象类是类,可以有状态(成员变量)、构造方法、非 public 方法;代表 is-a 关系,是一种模板继承。
  • 接口是契约,不能有实例状态(只有常量)、不能有构造方法;代表 can-do 能力,是一种行为约定。
  • 一个类只能继承一个抽象类,但可以实现多个接口——这个核心差异永不会变。

Q2:JDK 8 的 default 方法解决了什么问题?

解决了接口向后兼容性问题。Java 核心库(如 java.util.Collection)需要新增方法(如 stream()),如果只加抽象方法,所有现有的第三方实现类都会编译失败。default 方法允许在接口中提供默认实现,旧代码无需修改即可通过编译,新代码可以按需重写。

Q3:为什么 class Object 中的方法(如 toString、equals)在接口中定义 default 版本无效?

这是 Java 语言规范(JLS 9.4.1.2)的明确规定:接口中的 default 方法不能覆写 java.lang.Object 的 public 方法。因为方法调用的优先级是 superclass > interface,而所有类都继承 Object,接口中的 default 版本永远不会被调用——编译器直接禁止这种写法,避免开发者误解。

Q4:接口中的 static 方法和类的 static 方法有什么区别?

  • 调用方式:接口的 static 方法只能通过 InterfaceName.method() 调用,不能通过实现类的实例或子接口调用。
  • 继承关系:接口的 static 方法不会被实现类继承,这与类的 static 方法可被继承不同。
  • 设计意图:接口的 static 方法通常用作工具方法或工厂方法,将相关功能内聚在接口本身。

Q5:一个类既继承了抽象类又实现了接口,初始化顺序是怎样的?

  1. 父类(抽象类)的静态代码块/静态变量初始化
  2. 接口的静态字段初始化(按 implements 声明顺序)
  3. 父类构造方法 super()
  4. 子类构造方法
  5. 由于接口不能有实例字段和构造方法,它们在实例化阶段不参与 <init> 流程。
上一页
抽象类
下一页
抽象类与接口对比