Java内部类与匿名内部类
Java内部类:重点掌握匿名内部类
一、内部类概述
内部类(Inner Class):定义在另一个类(称为外部类(Outer Class))内部的类。与之相对,直接定义在包下的类称为顶级类(Top-level Class)。
内部类是 Java 语言的一项重要特性,主要用于实现:
- 更强的封装性
- 逻辑上的关联性
- 间接的多重继承支持
- 简化回调函数的实现
Java 中的内部类分为四种类型:
成员内部类(Member Inner Class)
静态内部类(Static Nested Class)
局部内部类(Local Inner Class)
匿名内部类(Anonymous Inner Class)
匿名内部类是较常用的一种。
二、基本格式与用法
1. 成员内部类
基本格式:
public class Outer {
// 成员内部类
class Inner {
public void method() {
System.out.println("成员内部类方法");
}
}
}
基本用法:
// 必须先创建外部类实例,再创建内部类实例
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
inner.method();
核心特点:可访问外部类所有成员,依赖外部类实例存在。
2. 静态内部类
基本格式:
public class Outer {
// 静态内部类
static class StaticInner {
public void method() {
System.out.println("静态内部类方法");
}
}
}
基本用法:
// 直接创建,无需外部类实例
Outer.StaticInner inner = new Outer.StaticInner();
inner.method();
核心特点:只能访问外部类静态成员,不依赖外部类实例。
3. 局部内部类
基本格式:
public class Outer {
public void outerMethod() {
// 局部内部类(定义在方法内部)
class LocalInner {
public void method() {
System.out.println("局部内部类方法");
}
}
// 只能在定义它的方法内部使用
LocalInner inner = new LocalInner();
inner.method();
}
}
核心特点:作用域仅限于定义它的方法,不能使用访问修饰符。
三、匿名内部类(重点详解)
3.1 本质与定义
匿名内部类:没有显式类名的局部内部类。
本质:创建一个继承指定父类或实现指定接口的匿名子类的实例。它将"定义类"和"创建对象"两个步骤合并为一步完成。
3.2 基本语法
new 父类/接口(构造参数列表) {
// 类体:重写父类/接口的方法,定义成员变量和方法
};
3.3 三种核心使用场景
场景1:实现接口(最常用)
// 定义一个接口
interface Greeting {
void sayHello(String name);
}
public class Test {
public static void main(String[] args) {
// 使用匿名内部类实现Greeting接口
Greeting greeting = new Greeting() {
@Override
public void sayHello(String name) {
System.out.println("Hello, " + name + "!");
}
};
greeting.sayHello("Java"); // 输出:Hello, Java!
}
}
场景2:继承抽象类
// 定义一个抽象类
abstract class Animal {
public abstract void makeSound();
}
public class Test {
public static void main(String[] args) {
// 使用匿名内部类继承Animal抽象类
Animal dog = new Animal() {
@Override
public void makeSound() {
System.out.println("汪汪汪!");
}
};
dog.makeSound(); // 输出:汪汪汪!
}
}
场景3:作为方法参数传递(最实用)
这是匿名内部类在实际开发中最常见的用法,用于向方法传递回调函数。
import java.util.Arrays;
import java.util.Comparator;
public class Test {
public static void main(String[] args) {
Integer[] numbers = {5, 2, 9, 1, 5, 6};
// 将匿名内部类作为Comparator参数传递给sort方法
Arrays.sort(numbers, new Comparator<Integer>() {
@Override
public int compare(Integer a, Integer b) {
// 降序排序
return b - a;
}
});
System.out.println(Arrays.toString(numbers)); // 输出:[9, 6, 5, 5, 2, 1]
}
}
3.4 核心特性详解
-
无类名特性
- 匿名内部类没有显式的类名
- 编译器自动生成类名,格式为:
外部类名$数字.class - 只能在定义处创建一个实例,无法在其他地方使用
-
继承/实现限制
- 只能继承一个父类或实现一个接口
- 不能同时继承类和实现接口,也不能实现多个接口
-
构造方法限制
- 不能定义显式的构造方法(因为没有类名)
- 若需要初始化操作,可以使用实例初始化块
public class InitExample { public static void main(String[] args) { Person person = new Person() { // 实例初始化块,替代构造方法 { setName("张三"); setAge(20); } @Override public void introduce() { System.out.println("我叫" + getName() + ",今年" + getAge() + "岁"); } }; person.introduce(); } } class Person { private String name; private int age; public void introduce() {} // getter和setter方法 public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } -
变量访问特性
- 可以访问外部类的所有成员(包括私有成员)
- 可以访问方法中的
final或effectively final局部变量(Java 8+) - effectively final:变量虽然没有被
final修饰,但在初始化后没有被重新赋值
四: Lambda表达式
1. 先认识:什么是 Lambda 表达式?
Lambda 是 JDK8 推出的极简代码写法,核心作用只有一个:
简化「只需要重写一个方法」的代码,省去所有模板代码,只保留核心业务逻辑。
它的固定格式:
(参数) -> { 方法体 }
最简单直观案例:
// 传统写法一大堆
// Lambda 极简写法
Runnable task = () -> System.out.println("任务执行中");
你会发现:
没有类、没有方法名、没有重写注解,只剩干活的代码。
但 Lambda 不是随便用的,它有一个铁律:
只能用于只有一个抽象方法的接口。
2. Lambda 的前置条件:函数式接口
2.1 什么是函数式接口?
只要接口中,有且仅有一个抽象方法,它就是函数式接口。
只有函数式接口,才能使用 Lambda 表达式。
标准函数式接口:
interface Runnable {
// 唯一抽象方法
void run();
}
非函数式接口(不能用Lambda):
interface Calculator {
int add(int a, int b);
int subtract(int a, int b);
}
两个抽象方法 → 编译器不知道你要重写哪个 → 无法用 Lambda。
2.2 为什么 Lambda 只能用在单方法接口?
Lambda 的本质是:
省略所有可被编译器自动推断的内容,只保留方法参数和方法体。
当接口只有一个方法时:
- 类型(左边接口)编译器知道
- 方法名编译器知道
- 参数列表、返回值编译器全部知道
既然编译器全懂,就不需要你手写模板代码!
如果有两个方法,编译器无法推断你 Lambda 代码对应哪个方法,因此 Lambda 直接失效。
3. 搞懂 Lambda 后,再反推:匿名内部类到底是什么?
3.1 匿名内部类核心本质
一句话总结:
匿名内部类 = 临时定义一个类 + 立刻 new 对象
它专门用来 实现接口 / 继承类,且只用一次。
3.2 单方法接口下:匿名内部类极度冗余
我们用函数式接口 Runnable 写匿名内部类:
Runnable task = new Runnable() {
@Override
public void run() {
// 真正有用的只有这一行
System.out.println("任务执行中");
}
};
我们拆分所有代码:
new Runnable()声明实现接口- 类体
{} @Override- 方法签名
public void run() - 方法体结构
- 唯一业务代码:打印语句
90% 都是模板仪式代码,只有一行是真正干活的。
3.3 Lambda 就是为了消灭这种冗余而生
编译器已知:
- 实现的是 Runnable 接口
- 只能重写 run 方法
- 方法无参、无返回值
所以全部可以省略,最终简化为:
Runnable task = () -> System.out.println("任务执行中");
结论:Lambda 不是新语法,它只是「单方法接口匿名内部类」的极致精简版。
4. 完整演变链路(新手必懂:臃肿 → 简洁)
方式1:普通实现类(最繁琐)
interface Greeting {
void sayHello(String name);
}
// 单独写实现类
class EngGreet implements Greeting {
@Override
public void sayHello(String name) {
System.out.println("Hello " + name);
}
}
缺点:只用一次还要单独建类,极度冗余。
方式2:匿名内部类(省去单独类)
Greeting g = new Greeting() {
@Override
public void sayHello(String name) {
System.out.println("Hello " + name);
}
};
优点:不用单独建类
缺点:模板代码太多
方式3:Lambda 表达式(最终极简形态)
Greeting g = name -> System.out.println("Hello " + name);
5. 关键总结:为什么多方法接口不能用 Lambda?
interface Calculator {
int add(int a, int b);
int sub(int a, int b);
}
匿名内部类可以正常写:重写两个方法。
但 Lambda 完全无法实现:
因为 Lambda 只能写一段逻辑,编译器无法判断这段代码对应 add 还是 sub。
全文核心总结
- 先有函数式接口(单抽象方法),才有 Lambda 表达式
- Lambda 本质:编译器自动推断,干掉所有模板代码
- 匿名内部类是完整写法,Lambda 是它的单方法场景精简版
- 多方法接口、继承类场景,只能用匿名内部类
- 继承一个类:Lambda表达式只能实现接口,不能继承类
- 定义成员变量和方法:匿名内部类可以有自己的成员变量和方法
- 重写多个方法:匿名内部类可以重写父类的多个方法
更多推荐



所有评论(0)