第9章:接口、lambda表达式与内部类
本章主要探讨Java中用于实现抽象、函数式编程和代码组织的三个关键特性。

第一部分:接口
接口是Java中一种引用数据类型,它是一系列方法的声明,但没有方法体,它规定了类必须做什么,但规定不了如何去做,这完美地体现了“面向接口编程”的思想。
接口的概念与作用
- 抽象行为的契约:接口定义了一组方法,任何实现该接口的类都必须提供这些方法的具体实现,它像一份“契约”,承诺了类具备某种能力。
- 实现多继承:Java不支持类的多继承(一个类不能直接继承多个类),但一个类可以实现多个接口,这弥补了Java在多继承上的不足,是一种“多实现”。
- 解耦:当你的代码依赖于一个接口而不是具体的实现类时,代码的耦合度会降低,你可以轻松地更换接口的实现,而无需修改调用方的代码。
- 多态:接口是实现多态的重要方式,可以将接口类型的变量引用到任何实现了该接口的对象上。
接口的定义与实现
定义一个接口:
使用 interface 关键字。
public interface Comparable {
// 抽象方法 (在Java 7及之前,接口中所有方法都是抽象的)
int compareTo(Object other);
}
实现一个接口:
使用 implements 关键字,一个类可以实现多个接口,用逗号隔开。
public class Employee implements Comparable, Cloneable { // 实现了两个接口
private String name;
private double salary;
// 必须实现接口中的所有抽象方法
@Override
public int compareTo(Object otherObject) {
Employee other = (Employee) otherObject;
return Double.compare(this.salary, other.salary); // 按工资比较
}
// ... 其他方法和构造器 ...
}
接口的演化 (Java 8+ 的重要增强)
Java 8为接口带来了巨大的变化,使其功能更加强大。

| 特性 | Java 7及之前 | Java 8+ |
|---|---|---|
| 方法类型 | 只有抽象方法 | 抽象方法 + 默认方法 + 静态方法 |
| 字段 | 只能是 public static final 的常量 |
仍然是 public static final 的常量 |
| 方法体 | 抽象方法没有方法体 | 默认方法和静态方法有方法体 |
a) 默认方法
- 目的:在不破坏现有实现类代码的前提下,为接口添加新的方法,这是为了解决Java API(如
Collection接口)向后兼容的问题。 - 语法:使用
default关键字修饰。 - 调用规则:当一个类实现多个接口,且这些接口中存在同名的默认方法时,编译器会报错,要求程序员在类中显式地覆盖这个冲突的默认方法。
public interface Resizable {
void setWidth(int width);
void setHeight(int height);
// Java 8+ 默认方法
default void setSize(int width, int height) {
setWidth(width);
setHeight(height);
}
}
public class Rectangle implements Resizable {
@Override
public void setWidth(int width) { /* ... */ }
@Override
public void setHeight(int height) { /* ... */ }
// 不需要实现 setSize(),因为它有默认实现
}
b) 静态方法
- 目的:提供与接口相关的工具性方法,无需创建接口的实例即可调用。
- 语法:使用
static关键字修饰。 - 调用:通过接口名直接调用,如
Collections.sort(...)。
public interface MathUtils {
static int add(int a, int b) {
return a + b;
}
}
// 调用方式
int sum = MathUtils.add(10, 20); // 直接通过接口调用
接口与抽象类的区别
这是一个非常经典和重要的面试题。
| 特性 | 接口 | 抽象类 |
|---|---|---|
| 继承 | implements (实现) |
extends (继承) |
| 构造器 | 没有 | 有,用于子类初始化 |
| 字段 | 只能是 public static final |
任意类型 (private, protected, static, final等) |
| 方法 | Java 8+: 抽象方法、默认方法、静态方法 | 抽象方法、具体方法、静态方法 |
| 多继承 | 一个类可以实现多个接口 | 一个类只能继承一个抽象类 |
| 设计目的 | 定义能力或行为 (Can do) | 定义是什么 (Is a) |
简单总结:

- 用接口:当你想定义一种通用的行为能力,希望任何不相关的类都能具备这种能力时。“可比较”、“可克隆”、“可序列化”。
- 用抽象类:当你想定义一个“模板”,让多个相关的类共享代码和状态,并且它们之间有明确的“是一个”的关系时。“图形” -> “圆形”、“矩形”。
第二部分:Lambda表达式
Lambda表达式是Java 8引入的最重要的新特性之一,它允许我们将函数作为方法参数传递,极大地简化了编写匿名内部类(特别是只有一个方法的接口)的代码。
函数式接口
Lambda表达式的基础是函数式接口,一个函数式接口是指只包含一个抽象方法的接口。
@FunctionalInterface注解:这是一个标记注解,用于告诉编译器该接口是一个函数式接口,如果接口中包含多于一个抽象方法,编译器会报错,虽然可以不写这个注解,但强烈建议加上,以增强代码的可读性和安全性。
经典的函数式接口示例:
| 接口名 | 抽象方法 | 描述 |
|---|---|---|
java.lang.Runnable |
run() |
无参数,无返回值 |
java.util.Comparator<T> |
compare(T, T) |
两个参数,返回int |
java.util.function.Supplier<T> |
get() |
无参数,返回T |
java.util.function.Consumer<T> |
accept(T) |
一个参数,无返回值 |
java.util.function.Function<T, R> |
apply(T) |
一个参数,返回R |
Lambda表达式的语法
Lambda表达式本质上是一个匿名函数,其基本语法是:
(参数列表) -> { 方法体 }
- 参数列表:如果方法没有参数,则使用空括号 ,如果只有一个参数,可以省略括号,如
x。 ->:Lambda操作符,读作“goes to”。- 方法体:如果方法体只有一行代码,可以省略大括号 和分号 ,如果这一行代码是
return语句,则return关键字也要省略。
代码简化示例:
假设我们有一个 Arrays.sort() 方法,需要一个 Comparator。
传统方式 (使用匿名内部类):
String[] names = {"Peter", "Paul", "Mary"};
Arrays.sort(names, new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return s1.compareTo(s2);
}
});
使用Lambda表达式:
String[] names = {"Peter", "Paul", "Mary"};
Arrays.sort(names, (s1, s2) -> s1.compareTo(s2));
(s1, s2):对应compare方法的参数列表。->:Lambda操作符。s1.compareTo(s2):对应方法体,因为只有一行,所以省略了 和return。
方法引用
当Lambda表达式只是调用一个已有的方法时,可以使用方法引用,使代码更加简洁。
语法:对象::实例方法 或 类::静态方法
示例:
上面的Lambda表达式 s1.compareTo(s2) 可以用方法引用替换:
Arrays.sort(names, String::compareTo);
这行代码的含义是:“使用 String 类的 compareTo 方法来进行比较”,编译器会自动推断出参数 s1 和 s2。
第三部分:内部类
内部类是指在一个类的内部定义的类,内部类是一个编译器现象,JVM并不知道内部类的存在,编译器会将内部类编译成独立的 .class 文件(如 OuterClass$InnerClass.class)。
使用内部类的目的
- 封装性:内部类可以访问外部类的私有成员,这使得它们可以紧密地绑定在一起,形成一个功能单元。
- 实现多继承:通过内部类,一个类可以“继承”多个具体类或实现多个接口。
- 编写事件处理代码:GUI编程中,事件监听器通常使用内部类来实现,代码结构清晰。
内部类的四种类型
a) 成员内部类
- 定义在外部类的内部,与外部类的成员(字段、方法)处于同一级别。
- 可以访问外部类的所有成员(包括私有的)。
- 创建对象时,必须先有外部类的对象。
public class Outer {
private int x = 10;
class Inner {
public void print() {
System.out.println("x = " + x); // 访问外部类的私有成员
}
}
}
// 创建方式
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner(); // 或 Outer.Inner inner = new Outer().new Inner();
inner.print();
b) 静态内部类
- 使用
static修饰。 - 它只属于外部类本身,而不属于外部类的某个实例。
- 不能访问外部类的非静态成员(实例成员),因为静态内部类不持有外部类的引用。
- 创建时不需要外部类的对象。
public class Outer {
private static int x = 10;
private int y = 20;
static class Inner {
public void print() {
System.out.println("x = " + x); // 可以访问静态成员
// System.out.println("y = " + y); // 编译错误!不能访问非静态成员
}
}
}
// 创建方式
Outer.Inner inner = new Outer.Inner();
inner.print();
c) 局部内部类
- 定义在方法或作用域块(如
for循环、if语句)内部。 - 作用域仅限于定义它的方法或块内。
- 可以访问外部类的成员,以及方法中的
final或 effectively final 的局部变量。
public class Outer {
private int x = 10;
public void method() {
int y = 20; // effectively final
class LocalInner {
public void print() {
System.out.println("x = " + x);
System.out.println("y = " + y);
}
}
LocalInner inner = new LocalInner();
inner.print();
}
}
d) 匿名内部类
- 这是最常见的内部类形式,它没有名字。
- 它是一种特殊的局部内部类,在声明和实例化同时进行。
- 通常用于只需要创建一个类的实例,并且这个类只被使用一次的场景(如事件监听器)。
- 它可以继承一个类,也可以实现一个接口。
语法:
new SuperType() { ... }
示例 (实现接口):
// 传统方式
btn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Button clicked!");
}
});
// 使用Lambda表达式 (更简洁)
btn.addActionListener(e -> System.out.println("Button clicked!"));
这里的 new ActionListener() { ... } 就是一个匿名内部类,Lambda表达式其实就是匿名内部类的一种语法糖。
| 特性 | 核心思想 | 关键点 |
|---|---|---|
| 接口 | 定义行为契约,实现多继承 | Java 8引入默认方法和静态方法,增强了功能。 |
| Lambda表达式 | 将函数作为参数传递,简化代码 | 依赖于函数式接口,是匿名内部类的语法糖。 |
| 内部类 | 增强封装性,实现多继承,组织代码 | 分为成员、静态、局部和匿名四种,匿名内部类常用于事件处理。 |
理解并掌握这三大特性,是迈向Java高级开发者的关键一步,它们让Java代码变得更加灵活、简洁和富有表现力。
