一、接口是什么

接口(interface)是Java中定义行为规范的抽象契约,仅描述“能做什么”,不规定“怎么做”,用来统一一类事物的行为标准,实现代码解耦、多态拓展。

  • 核心定位:行为模板,不存储对象状态(无普通实例变量)
  • 关键字interface 定义接口,implements 让类实现接口
  • 核心特点:Java允许一个类同时实现多个接口,弥补单继承的局限

二、接口基础语法与成员规则

2.1 基础定义格式

// 定义行为接口:可飞行
public interface Fly {
    // 抽象方法(标准行为)
    void fly();
}

2.2 接口内所有成员默认修饰符(强制隐式,写不写都一样)

  1. 抽象方法:默认 public abstract

    • JDK7及以前:接口只能写抽象方法,无方法体
  2. 常量:默认 public static final

    • 必须定义时直接赋值,后续不能修改
public interface Game {
    // 常量:全局固定数值
    int MAX_PLAYER = 10;
    void startGame();
}
  1. 接口不能包含实例变量、构造方法,无法直接new创建对象

三、接口的实现

3.1 单个接口实现

类使用implements关键字实现接口,必须重写接口全部抽象方法,实现方法权限必须是public(接口方法默认public,不能缩小访问权限)。

interface Swim {
    void swim();
}

// 鱼类实现游泳接口
public class Fish implements Swim {
    @Override
    public void swim() {
        System.out.println("鱼靠鱼尾摆动游泳");
    }
}

3.2 多实现(接口核心优势)

Java类只能单继承父类,但可以同时实现多个接口,用逗号分隔,需要实现所有接口的全部抽象方法。

interface Fly {
    void fly();
}

interface Swim {
    void swim();
}

// 鸟类:继承动物父类,同时实现飞行、游泳两个接口
class Bird extends Animal implements Fly, Swim {
    @Override
    public void fly() {
        System.out.println("鸟类展翅飞翔");
    }
    
    @Override
    public void swim() {
        System.out.println("鸟类在水面游泳");
    }
}

3.3 接口与接口继承

接口支持使用extends继承另一个接口,且支持多继承,子接口会继承父接口所有抽象方法,实现子接口的类需要实现全部父、子接口抽象方法。

interface Action {
    void doAction();
}

interface Move extends Action, Fly {
    void move();
}

四、JDK8+接口新增三种带方法体的方法

4.1 默认方法 default

  • 格式default 返回值 方法名(){方法体}
  • 作用:给接口新增功能时,不用修改所有实现类,实现类可直接继承使用,也可重写覆盖
  • 调用:通过实现类对象调用
interface Play {
    // 默认方法,自带实现逻辑
    default void relax() {
        System.out.println("休闲娱乐");
    }
    
    void game();
}

⚠️ 冲突问题:类同时实现多个接口,多个接口存在同名default方法,必须手动重写该方法消除冲突。

4.2 静态方法 static

  • 格式static 返回值 方法名(){方法体}
  • 特点:属于接口本身,不能被实现类继承、重写
  • 调用规则:只能用接口名.静态方法()调用,不能通过对象调用
interface Tool {
    static void printTip() {
        System.out.println("工具接口静态提示");
    }
}

// 正确调用
Tool.printTip();

4.3 私有方法 private(JDK9新增)

  • 仅接口内部可见,用于抽取接口内默认方法、静态方法的重复公共逻辑,对外隐藏,实现代码复用。
interface Log {
    default void info() {
        print("信息日志");
    }
    
    default void error() {
        print("错误日志");
    }
    
    // 私有内部复用方法,外部无法访问
    private void print(String msg) {
        System.out.println("日志:" + msg);
    }
}

五、函数式接口(Lambda配套核心)

5.1 定义规则

仅有一个抽象方法的接口称为函数式接口,可添加注解@FunctionalInterface校验,注解会强制检查接口是否只存在一个抽象方法,编译报错规避失误。

@FunctionalInterface
interface Calculate {
    int count(int a, int b);
}

5.2 核心用途:搭配Lambda表达式简化代码

无需创建匿名内部类,直接用()->快速实现接口逻辑:

public class TestLambda {
    public static void main(String[] args) {
        // Lambda快速实现函数式接口
        Calculate add = (x, y) -> x + y;
        System.out.println(add.count(3, 5)); // 输出:8
    }
}

5.3 实战案例:使用 Predicate 和 Function 配合 Stream API

在实际开发中,函数式接口常与 Stream API 结合,用于集合的过滤、转换等操作。下面通过两个贴近实际开发的例子来演示。

示例1:使用 Predicate 过滤集合

Predicate<T> 是一个函数式接口,接收一个参数并返回布尔值,常用于集合过滤。

import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;

public class PredicateExample {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("张三", "李四", "王五", "赵六", "孙七");
        
        // 定义过滤条件:名字长度大于2
        Predicate<String> lengthGreaterThanTwo = name -> name.length() > 2;
        
        // 使用 Stream API 过滤集合
        List<String> filteredNames = names.stream()
                .filter(lengthGreaterThanTwo)  // 使用 Predicate 过滤
                .collect(Collectors.toList());
        
        System.out.println("过滤后的名字: " + filteredNames); // 输出: [张三, 王五, 赵六, 孙七]
        
        // 更复杂的条件组合:名字包含"三"且长度大于2
        Predicate<String> containsThree = name -> name.contains("三");
        List<String> complexFiltered = names.stream()
                .filter(lengthGreaterThanTwo.and(containsThree)) // 组合多个条件
                .collect(Collectors.toList());
        
        System.out.println("复杂条件过滤: " + complexFiltered); // 输出: [张三]
    }
}

【运行结果】

过滤后的名字: [张三, 王五, 赵六, 孙七]
复杂条件过滤: [张三]

运行结果截图如下:

过滤后的名字: [张三, 王五, 赵六, 孙七]
复杂条件过滤: [张三]
示例2:使用 Function<T,R> 转换集合

Function<T,R> 也是一个函数式接口,接收一个类型 T 的参数,返回类型 R 的结果,常用于数据转换。

import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

public class FunctionExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        
        // 定义转换函数:将数字转换为平方
        Function<Integer, Integer> squareFunction = num -> num * num;
        
        // 使用 Stream API 转换集合
        List<Integer> squaredNumbers = numbers.stream()
                .map(squareFunction)  // 使用 Function 转换
                .collect(Collectors.toList());
        
        System.out.println("平方数: " + squaredNumbers); // 输出: [1, 4, 9, 16, 25]
        
        // 更复杂的转换:数字转字符串并添加前缀
        Function<Integer, String> toStringWithPrefix = num -> "数字" + num;
        List<String> stringNumbers = numbers.stream()
                .map(toStringWithPrefix)
                .collect(Collectors.toList());
        
        System.out.println("带前缀的字符串: " + stringNumbers);
        // 输出: [数字1, 数字2, 数字3, 数字4, 数字5]
    }
}

【运行结果】

平方数: [1, 4, 9, 16, 25]
带前缀的字符串: [数字1, 数字2, 数字3, 数字4, 数字5]

运行结果截图如下:

平方数: [1, 4, 9, 16, 25]
带前缀的字符串: [数字1, 数字2, 数字3, 数字4, 数字5]
实际应用场景
  1. 数据清洗:使用 Predicate 过滤无效数据
  2. 数据转换:使用 Function 将数据库实体转换为 DTO
  3. 条件筛选:电商系统中筛选符合条件的商品
  4. 数据映射:将用户列表映射为用户名列表

这些实战案例展示了函数式接口在实际开发中的强大能力,通过 Lambda 表达式和 Stream API 的组合,可以写出更简洁、可读性更高的代码。

5.4 常见错误与排查

使用 Lambda 表达式实现函数式接口时,初学者常遇到一些编译错误或运行时问题。以下是几个典型错误及其解决方案:

错误1:变量捕获问题(非 final 或 effectively final 变量)

Lambda 表达式只能捕获 final 或 effectively final(事实不可变)的局部变量。尝试修改捕获的变量会导致编译错误。

❌ 错误代码示例:

public class VariableCaptureError {
    public static void main(String[] args) {
        int counter = 0;
        
        Runnable task = () -> {
            counter++;  // 编译错误:Variable used in lambda expression should be final or effectively final
            System.out.println("Counter: " + counter);
        };
        
        task.run();
    }
}

✅ 修正后的正确代码:

public class VariableCaptureFixed {
    public static void main(String[] args) {
        // 方案1:使用数组或容器包装(引用不可变,内容可变)
        int[] counter = {0};
        
        Runnable task = () -> {
            counter[0]++;  // 合法:修改的是数组元素,不是数组引用
            System.out.println("Counter: " + counter[0]);
        };
        
        task.run();  // 输出:Counter: 1
        
        // 方案2:使用 AtomicInteger(线程安全)
        java.util.concurrent.atomic.AtomicInteger atomicCounter = new java.util.concurrent.atomic.AtomicInteger(0);
        
        Runnable atomicTask = () -> {
            atomicCounter.incrementAndGet();
            System.out.println("Atomic Counter: " + atomicCounter.get());
        };
        
        atomicTask.run();  // 输出:Atomic Counter: 1
    }
}
错误2:目标类型不匹配(Lambda 表达式与函数式接口不兼容)

Lambda 表达式必须与函数式接口的抽象方法签名匹配,包括参数类型、数量和返回类型。

❌ 错误代码示例:

@FunctionalInterface
interface StringProcessor {
    String process(String input);
}

public class TypeMismatchError {
    public static void main(String[] args) {
        // 错误:Lambda 表达式返回 int,但接口要求返回 String
        StringProcessor processor = (s) -> s.length();  // 编译错误:不兼容的类型
        
        // 错误:Lambda 表达式参数数量不匹配
        StringProcessor processor2 = (s1, s2) -> s1 + s2;  // 编译错误:参数数量不匹配
    }
}

✅ 修正后的正确代码:

@FunctionalInterface
interface StringProcessor {
    String process(String input);
}

@FunctionalInterface
interface StringBiProcessor {
    String process(String s1, String s2);
}

public class TypeMismatchFixed {
    public static void main(String[] args) {
        // 正确:返回类型匹配
        StringProcessor toUpperCase = s -> s.toUpperCase();
        System.out.println(toUpperCase.process("hello"));  // 输出:HELLO
        
        // 正确:使用正确的函数式接口
        StringBiProcessor concat = (s1, s2) -> s1 + s2;
        System.out.println(concat.process("Hello, ", "World!"));  // 输出:Hello, World!
        
        // 正确:方法引用方式
        StringProcessor lengthAsString = s -> String.valueOf(s.length());
        System.out.println(lengthAsString.process("hello"));  // 输出:5
    }
}
错误3:空指针异常(NPE)与 Optional 处理

使用 Lambda 处理可能为 null 的数据时,容易产生空指针异常。

❌ 错误代码示例:

import java.util.function.Function;

public class NPEError {
    public static void main(String[] args) {
        String input = null;
        
        Function<String, Integer> lengthFunction = s -> s.length();  // 当 s 为 null 时抛出 NPE
        
        try {
            Integer length = lengthFunction.apply(input);
            System.out.println("Length: " + length);
        } catch (NullPointerException e) {
            System.out.println("发生空指针异常: " + e.getMessage());
        }
    }
}

✅ 修正后的正确代码:

import java.util.function.Function;
import java.util.Optional;

public class NPEfixed {
    public static void main(String[] args) {
        String input = null;
        
        // 方案1:使用 Optional 避免 NPE
        Function<String, Integer> safeLengthFunction = s -> 
            Optional.ofNullable(s)
                   .map(String::length)
                   .orElse(0);
        
        System.out.println("安全长度: " + safeLengthFunction.apply(input));  // 输出:0
        System.out.println("安全长度: " + safeLengthFunction.apply("hello"));  // 输出:5
        
        // 方案2:在 Lambda 内部显式检查 null
        Function<String, Integer> explicitCheckFunction = s -> {
            if (s == null) {
                return 0;
            }
            return s.length();
        };
        
        System.out.println("显式检查长度: " + explicitCheckFunction.apply(input));  // 输出:0
        
        // 方案3:使用 Objects.requireNonNull 提前验证
        Function<String, Integer> requireNonNullFunction = s -> {
            java.util.Objects.requireNonNull(s, "输入字符串不能为 null");
            return s.length();
        };
        
        try {
            System.out.println("要求非空长度: " + requireNonNullFunction.apply(input));
        } catch (NullPointerException e) {
            System.out.println("验证失败: " + e.getMessage());  // 输出:验证失败:输入字符串不能为 null
        }
    }
}
排查建议
  1. 编译错误排查

    • 检查 Lambda 表达式参数类型和数量是否与函数式接口的抽象方法匹配
    • 确认 Lambda 表达式返回类型是否兼容
    • 验证捕获的局部变量是否为 final 或 effectively final
  2. 运行时问题排查

    • 使用调试器逐步执行 Lambda 表达式
    • 添加日志输出,跟踪 Lambda 表达式的执行流程
    • 对于复杂的 Lambda 表达式,考虑先拆分为独立方法,再使用方法引用
  3. 最佳实践

    • 为复杂的 Lambda 逻辑添加注释
    • 考虑使用方法引用替代简单的 Lambda 表达式
    • 使用 @FunctionalInterface 注解明确标识函数式接口
    • 对于可能为 null 的参数,使用 Optional 进行包装处理

六、两大核心比较接口:Comparable & Comparator

6.1 Comparable 内部比较器

  • 接口定义public interface Comparable<T>
  • 使用方式:实体类自身实现该接口,重写compareTo()方法,定义对象默认排序规则
  • 适用:集合Collections.sort()、数组排序直接使用
class Student implements Comparable<Student> {
    String name;
    int age;
    
    @Override
    public int compareTo(Student o) {
        // 按年龄升序
        return this.age - o.age;
    }
}

6.2 Comparator 外部比较器

  • 接口定义public interface Comparator<T>
  • 使用方式:单独创建比较类/用Lambda临时实现,不修改实体类代码,灵活切换多种排序规则
  • 优势:同一实体可自定义多种排序(年龄、姓名、分数),无需改动原类
// 按姓名排序,独立外部比较器
Comparator<Student> sortByName = (s1, s2) -> s1.name.compareTo(s2.name);

// 按年龄降序排序
Comparator<Student> sortByAgeDesc = (s1, s2) -> s2.age - s1.age;

两者对比

特性 Comparable Comparator
实现位置 实体类内部实现 独立外部类/Lambda
排序数量 仅一套默认排序 支持多套灵活排序
是否修改实体 需要修改实体代码 无需改动原有实体

七、接口与抽象类核心区别(高频考点)

对比维度 接口 interface 抽象类 abstract class
继承实现 类用implements多实现;接口可多extends 类用extends单继承,仅一个父类
构造方法 无构造方法 拥有构造方法,供子类调用
成员变量 public static final常量,必须初始化 普通成员变量、静态变量均可
方法权限 抽象方法默认public;default/static/private 支持public/protected/private所有权限
使用目的 定义行为规范,解耦多态 抽取子类通用属性+逻辑,代码复用

八、接口实战案例:设备行为规范

// 设备通用行为接口
interface Device {
    // 常量:设备最大功率
    int MAX_POWER = 1000;
    
    // 抽象方法:开机
    void turnOn();
    
    // 默认方法:通用待机逻辑
    default void standby() {
        System.out.println("设备低功耗待机");
    }
    
    // 接口静态工具方法
    static void showTip() {
        System.out.println("所有电子设备需遵守设备接口规范");
    }
}

// 电视实现设备接口
public class TV implements Device {
    @Override
    public void turnOn() {
        System.out.println("电视通电启动,屏幕亮起");
    }
}

// 测试
class TestDevice {
    public static void main(String[] args) {
        Device tv = new TV();
        tv.turnOn();        // 电视通电启动,屏幕亮起
        tv.standby();       // 设备低功耗待机
        Device.showTip();   // 所有电子设备需遵守设备接口规范
    }
}

九、接口核心设计思想总结

  1. 解耦分层:面向接口编程,业务依赖抽象接口而非具体实现类,更换实现类无需改动上层代码,符合开闭原则;
  2. 多态拓展:同一接口多种实现类,父接口引用指向任意实现子类,实现统一调用、差异化执行;
  3. 行为标准化:统一一类事物的能力标准,强制实现类补齐核心功能;
  4. 弥补单继承缺陷:通过多实现让一个类同时具备多种独立行为。

更多推荐