一、内部类概述
1. 什么是内部类
把一个类定义在另一个类的里面 ,外面的类叫外部类 ,里面的类叫内部类。 Java 内部类一共 4 种:
- 成员内部类
- 局部内部类
- 静态内部类(静态成员内部类)
- 匿名内部类
核心特点:内部类可以直接访问外部类所有成员(包括私有成员);外部类访问内部类成员,需要先创建内部类对象。 Lambda 表达式:专门简化函数式接口的匿名内部类,属于匿名内部类场景的语法糖。
二、成员内部类
1. 定义位置
写在外部类的成员位置(和成员变量、成员方法同级,不在方法里面)。
class Outer{ //外部类
// 成员变量
private String name = "外部类私有属性";
// 成员内部类
class Inner{
public void show(){
// ✅内部类可以直接访问外部类的私有成员
System.out.println(name);
}
}
}
2. 创建成员内部类对象语法
外部类.内部类 对象名 = new 外部类().new 内部类();
完整演示代码
class Outer{
private String name = "外部类私有属性";
//成员内部类
class Inner{
public void show(){
System.out.println(name);
}
}
}
public class Test{
public static void main(String[] args) {
// 创建成员内部类对象
Outer.Inner inner = new Outer().new Inner();
inner.show(); //输出:外部类私有属性
}
}
3. 重点:重名变量访问(外部类.this)
当内部类成员变量、外部类成员变量重名时:
-
this.变量:访问内部类自己的成员 -
外部类名.this.变量:访问外部类成员class Outer{
String num = "外部类";
class Inner{
String num = "内部类";
public void test(){
String num = "局部变量";
System.out.println(num); //局部变量
System.out.println(this.num); //内部类成员
System.out.println(Outer.this.num); //外部类成员
}
}
}
4. 成员内部类注意事项
-
不能定义static 静态成员(static 属于类,成员内部类依赖外部对象);
-
成员内部类属于外部类实例,必须先有外部类对象,才能创建内部类对象;
-
可以使用
private修饰:私有化之后,外部无法直接创建内部类对象,只能在外部类内部提供方法获取。class Outer{
private class Inner{ //私有成员内部类
public void show(){
System.out.println("私有内部类");
}
}
//外部类提供方法,返回内部类对象
public Inner getInner(){
return new Inner();
}
}
三、局部内部类
1. 定义位置
定义在外部类的方法里面,属于方法局部。
作用域:只能在当前方法内使用,方法外面不可见。
class Outer{
public void method(){
//局部内部类,写在方法内部
class Inner{
public void show(){
System.out.println("局部内部类方法");
}
}
// 只能在当前方法内创建对象使用
Inner inner = new Inner();
inner.show();
}
}
public class Test{
public static void main(String[] args) {
Outer outer = new Outer();
outer.method();
}
}
2. 核心特性
- 作用域仅限于当前方法,外部完全无法访问;
- JDK8 及以后:访问方法的局部变量,变量要求有效 final(变量一旦赋值,不能二次修改);
原因:局部变量存放在栈内存,方法执行完毕就销毁;局部内部类对象存在堆内存,生命周期更长,为防止数据不一致,要求变量不可修改。
class Outer{
public void method(){
int num = 10; //有效final,没有二次赋值
class Inner{
public void show(){
System.out.println(num); //可以访问
}
}
Inner inner = new Inner();
inner.show();
// num = 20; 如果写这句,上面代码直接报错!
}
}
- 不能加访问修饰符(public/private/protected),和局部变量一样。
四、静态内部类(静态成员内部类)
1. 定义
成员内部类前面加static,就是静态内部类。
static 属于外部类本身,不依赖外部类对象。
class Outer{
private static String name = "外部类静态属性";
//静态内部类
static class Inner{
public void show(){
//静态内部类,只能直接访问外部类static成员
System.out.println(name);
}
}
}
2. 创建对象语法
外部类.内部类 对象名 = new 外部类.内部类();
完整示例
class Outer{
private static String staticStr = "外部静态变量";
private String instanceStr = "外部实例变量";
static class Inner{
public void show(){
System.out.println(staticStr);
// System.out.println(instanceStr); 报错!静态内部类不能直接访问外部非static成员
// 如果要访问外部实例成员,必须手动创建外部类对象
Outer outer = new Outer();
System.out.println(outer.instanceStr);
}
}
}
public class Test{
public static void main(String[] args) {
//静态内部类创建,不需要外部类对象
Outer.Inner inner = new Outer.Inner();
inner.show();
}
}
3. 重点总结
- 静态内部类只能直接访问外部类静态成员;
- 访问外部非静态成员,需要手动 new 外部类对象;
- 创建静态内部类对象,不需要外部类实例;
- 静态内部类里面可以定义静态成员。
开发常用场景:工具类、实体类的嵌套(比如 HashMap 里面的 Node 就是静态内部类)
五、匿名内部类 + Lambda 表达式
5.1 匿名内部类
1. 定义
没有名字的内部类,用来简化代码,一次性使用,只创建一次对象。 前提:必须继承一个类 / 实现一个接口。 语法格式:
new 接口/父类(){
//重写抽象方法
@Override
public void 方法名(){
}
};
代码演示
//定义接口
interface Animal{
void eat();
}
public class Test{
public static void main(String[] args) {
//匿名内部类:相当于创建了一个没有名字的类,实现Animal接口,并直接new对象
Animal cat = new Animal(){
@Override
public void eat() {
System.out.println("小猫吃鱼");
}
};
cat.eat();
}
}
匿名内部类特点
- 没有类名,不能写构造方法;
- 适合接口只有少量抽象方法,临时使用;
- 可以访问外部变量,遵守有效 final规则;
- 可以用于有多个抽象方法的接口。
匿名内部类传参写法
interface Run{
void run();
}
class Test{
public static void test(Run r){
r.run();
}
public static void main(String[] args) {
//直接把匿名内部类传入方法
test(new Run(){
@Override
public void run() {
System.out.println("奔跑");
}
});
}
}
5.2 Lambda 表达式(简化匿名内部类)
Lambda 是语法糖 ,只能简化【函数式接口】的匿名内部类。 函数式接口:接口里面有且仅有一个抽象方法,可以用
@FunctionalInterface注解标记,编译校验
语法格式
(参数列表) -> {方法体};
():对应抽象方法的参数列表,无参数就空括号;->:Lambda 操作符,读作箭头;{}:方法体,写实现逻辑。
匿名内部类 VS Lambda 代码对比
//标记函数式接口
@FunctionalInterface
interface Animal{
void eat();
}
public class Test{
public static void main(String[] args) {
// 匿名内部类写法
Animal cat1 = new Animal(){
@Override
public void eat() {
System.out.println("小猫吃鱼");
}
};
cat1.eat();
// Lambda简化写法
Animal cat2 = () -> {
System.out.println("小猫吃鱼");
};
cat2.eat();
}
}
Lambda 省略规则(重点)
-
参数类型可以省略,所有参数统一省略,不能只省一部分;
-
只有一个参数 ,小括号
()可以省略; -
方法体只有一行代码 ,大括号
{}可以省略,同时去掉末尾分号; -
方法体只有一行 return 语句:省略
{}的同时,必须省略 return 关键字。@FunctionalInterface
interface Calc{
int add(int a, int b);
}public class Test{
public static void main(String[] args) {
//完整版
Calc c1 = (int a, int b) -> {return a + b;};
//省略参数类型
Calc c2 = (a, b) -> {return a + b;};
//一行return,省略{}和return
Calc c3 = (a, b) -> a + b;
System.out.println(c3.add(10,20));
}
}
Lambda 访问局部变量
Lambda 访问方法局部变量,变量同样要求有效 final,一旦赋值不能二次修改,否则编译报错。
public class Test{
public static void main(String[] args) {
int num = 10; //有效final
Runnable r = () -> {
System.out.println(num);
};
// num = 20; //打开这行直接报错
}
}
Lambda 与匿名内部类区别
| 对比项 | 匿名内部类 | Lambda 表达式 |
|---|---|---|
| 使用范围 | 普通类、抽象类、接口(不限抽象方法数量) | 仅函数式接口(只有 1 个抽象方法) |
| this 含义 | 指向匿名内部类对象 | 指向外部类对象 |
| 能否定义构造器 | 可以 | 不可以 |
JDK 自带常用函数式接口(开发高频)
java.util.function包,无需自己定义接口:
Runnable:无参数,无返回值void run()Consumer<T>:有参数,无返回值void accept(T t)Supplier<T>:无参数,有返回值T get()Function<T,R>:有参数,有返回值R apply(T t)Predicate<T>:有参数,返回布尔值boolean test(T t)
示例:Predicate
import java.util.function.Predicate;
public class Test{
public static void main(String[] args) {
//判断数字是否大于10
Predicate<Integer> p = num -> num > 10;
System.out.println(p.test(15)); //true
System.out.println(p.test(5)); //false
}
}
六、总对比表
| 类型 | 定义位置 | 创建对象 | 适用场景 |
|---|---|---|---|
| 成员内部类 | 外部类成员位置 | new Outer().new Inner() |
需要访问外部实例成员 |
| 局部内部类 | 外部类方法内部 | 方法内 new | 方法内临时使用,逻辑复杂 |
| 静态内部类 | 外部类成员,加 static | new Outer.Inner() |
不需要外部对象,工具嵌套类 |
| 匿名内部类 | new 的时候定义 | 直接 new | 临时实现接口 / 继承类,可多抽象方法 |
| Lambda 表达式 | 方法内,赋值给函数式接口变量 | 直接赋值 | 仅函数式接口,简化匿名内部类 |
七、高频易错点汇总
- 成员内部类不能写 static 成员;静态内部类可以定义 static;
- 静态内部类不等于成员内部类,创建对象不需要外部实例;
- 局部内部类不能加 public/private 修饰符;
- Lambda不能用于拥有多个抽象方法的接口;
@FunctionalInterface只是校验注解,不加注解,只要只有一个抽象方法,依旧是函数式接口;- Lambda 中
this代表外部类对象,和匿名内部类的 this 不一样。