目录
1.1 什么是内部类?
1.2 内部类的分类
1.2.1 匿名内部类
1.2.2 成员内部类(普通内部类)
⓵正确获取实例内部类对象
方法一:通过现有外部类实例创建
方法二:同时创建外部类和内部类实例
💡 核心知识点
⓶ 实力内部类中不能定义静态的成员变量
⓷ 变量访问规则遵循:就近原则
🧐注意事项
1.2.3 静态内部类
1.2.4 局部内部类
1.3 总结
🧐📌 前言:为什么Java要设计"套娃式"的内部类结构? 想象你有一个俄罗斯套娃,大娃肚子里装着小娃。Java内部类就是这样的设计哲学——在一个类的内部再定义另一个类,就像在客厅里隔出一个储物间,既保持关联性又节省空间。
1.1 什么是内部类?
内部类(Inner Class) 是定义在另一个类内部的类。它可以定义在另一个类或者一个方法的内部,增强封装性,避免代码臃肿,同时直接访问外部类的私有成员。
代码形态 class OuterClass {
//实例内部类或者叫做普通成员内部类或者非静态内部类
//关于分类后面会讲解
class InnerClass {
//...
}
}
ps:我们以后在说明一个类是内部类的时候,尽量都说明这个类是什么内部类,说清楚比较好;并且大家需要知道一个类一个字节码文件。
1.2 内部类的分类
java中内部类主要分为以下四种,接下来将分别详细讲解:
成员内部类
静态内部类
局部内部类
匿名内部类
1.2.1 匿名内部类
定义:匿名内部类是没有名字的局部内部类,通常用于简化接口或抽象类的实现。语法 new 接口/抽象类() {
// 必须实现所有抽象方法
@Override
void method() { ... }
}; 示例
interface A {
void testA();
}
public class Test {
public static void main(String[] args) {
new A(){
@Override
public void testA() {
}
};
}
}
下面这个是加了注释版本的,更方便友友们的理解:
//定义一个接口A,接口中声明了一个抽象方法testA()
interface A {
void testA();//这是一个抽象方法,没有方法体,需要实现类来具体实现
}
public class Test {
public static void main(String[] args) {
//创建一个A接口的匿名内部类实例
new A(){
// 在匿名内部类中重写(实现)接口A的testA()方法
@Override
public void testA() {
//这里可以填写方法的具体实现
//当前为空实现,即方法体中没有代码
}
};//注意这里的分号,表示匿名内部类实例创建的语句结束
}
}
此时我们对代码进行小小的改动:
interface A {
void testA();
}
public class Test {
public static void main(String[] args) {
int b = 9;
b = 999; //变量重新赋值
A a = new A() { // ✅ 创建A接口的匿名实现类
@Override
public void testA() {
System.out.println("b的值:" + b); //此时会出现错误
}
}; //分号不能丢
a.testA(); // 调用方法
}
}
我们定义了一个局部变量b,并且将它重新赋值了,但是此时匿名类中使用 b 会导致编译错误,需要注意,在匿名内部类当中,能够访问的是没有被修改过的数据。默认访问的是final修饰的常量,修改后b就不再是等效final了。
1.2.2 成员内部类(普通内部类)
定义:定义在外部类的成员位置(与字段、方法同级),也可以说是定义在类中,方法外的非静态类。语法: class OuterClass {
// 外部类成员
class InnerClass {
// 内部类成员
}
}
比如说:
class OuterClass {
// 外部类实例成员变量
public int data1 = 1;
public int data2 = 2;
public static int data3 = 3;
class InnerClass2 {
//成员内部类实例成员变量
public int data4 = 4;
private int data5 = 5;
}
}
成员内部类所处的位置和外部类的成员变量是同一个位置的,在这个内部类里面我们也可以有构造方法,也可以有普通的成员变量,普通的成员方法:
class OuterClass {
// 外部类实例成员变量
public int data1 = 1;
public int data2 = 2;
public static int data3 = 3;
class InnerClass2 {
//成员内部类实例成员变量
public int data4 = 4;
private int data5 = 5;
public void test() {
System.out.println(data4);
System.out.println(data5);
System.out.println("内部类的test方法");
}
}
}
当然我们的外部类也是可以有自己的方法,比如说:
public void test() {
System.out.println("外部类的test方法");
}
目前的完整的代码如下呀:
class OuterClass {
//外部类实例成员变量
public int data1 = 1;
public int data2 = 2;
public static int data3 = 3;
class InnerClass2 {
//成员内部类实例成员变量
public int data4 = 4;
private int data5 = 5;
//public static int data6 = 6;
//内部类的测试方法
public void test() {
System.out.println(data4);
System.out.println(data5);
System.out.println("成员内部类的test方法");
}
}
//外部类的测试方法
public void test() {
System.out.println("外部类的test方法");
}
}
🧐到现在为止,我们定义好了一个内部类,但是我们定义好了,又该如何获取实例内部类对象?接下来进行讲解~
⓵正确获取实例内部类对象
如果按照之前直接实例化成员内部类是会出现错误的:
在等号的左边是需要获取内部类类型的,那么如何获取内部类的类型呢?语法有点点奇怪(等号左边部分):
在等号左边就是获取内部类类型 ,此时等号左边就是内部类类型,是通过外部类类名点内部类来获取到。但是并没有结束,我们会发现等号右边出现了问题。
那么我们需要知道的是:成员内部类(非静态内部类)必须通过外部类实例创建。此时等号左半部分语法正确,那么等号右边呢?接下来我们详细讲解:
首先我们在外部类OuterClass中我们如何去访问一个普通的成员变量data1?相信大家都知道,只需要需先创建实例再输出即可:
那么类比的去学习,data1是一个普通的成员变量,我们的InnerClass2和data1都属于这个OuterClass的一个成员,而且是一个实例成员。所以InnerClass也依靠外部类对象的引用去调用。
此时代码就变成:
public class Test {
public static void main(String[] args) {
//创建外部类实例
OuterClass outerClass = new OuterClass();
System.out.println(outerClass.data1);// 通过对象实例访问
//创建非静态内部类实例(必须通过外部类实例创建)
OuterClass.InnerClass2 innerClass2 = outerClass.new InnerClass2();
//错误示例InnerClass innerClass = new InnerClass();
//不能直接创建非静态内部类实例,必须通过外部类对象创建
}
}
有这一行代码后,就成功的获取实例内部类对象
所以说我们需要外部类对象的引用去点(.),才能把这部分点出来~
当然我们也有另一种写法:
public class Test {
public static void main(String[] args) {
// 1. 创建外部类实例(必须先有外部类才能创建非静态内部类)
OuterClass outerClass = new OuterClass();
// 2. 通过对象实例访问外部类的成员变量
System.out.println(outerClass.data1);
//创建内部类实例的另一种方式
// 方法:同时创建外部类和内部类实例
OuterClass.InnerClass2 innerClass2 = new OuterClass().new InnerClass2();
}
}
也就是下面这样啦~
✍️那么我们总结一下上面的内容:
首先是两种创建内部类实例的方法:
方法一:通过现有外部类实例创建
// 🌟 方法一:通过现有外部类实例创建
// 语法:外部类实例.new 内部类构造方法()
OuterClass outer = new OuterClass();
OuterClass.InnerClass2 inner = outer.new InnerClass2();
方法二:同时创建外部类和内部类实例
// 🌟 方法二:同时创建外部类和内部类实例
// 语法:new 外部类构造方法().new 内部类构造方法()
OuterClass.InnerClass2 inner = new OuterClass().new InnerClass2();
💡 核心知识点
依赖关系
非静态内部类必须依附于外部类实例存在(就像"心脏不能离开身体")内部类实例自动持有外部类实例的引用(可通过OuterClass.this访问)
访问权限
内部类可以直接访问外部类的所有成员(包括private)外部类访问内部类成员需要通过内部类实例
我们一定要记住:获取实例内部类对象的时候,依赖于外部类对象。也就是说我们要想获得一个实例内部类对象,一定要有外部类对像!如果没有,我们的实例内部类对象是构造不了的。是先有外部类对象,再有内部类对象的~(再强调一遍,说内部类要带前缀,说明是什么内部类)
ok,下面是刚刚讲解时所涉及的代码,希望能帮助友友们的理解啦~
class OuterClass {
// 外部类实例成员变量
public int data1 = 1;
public int data2 = 2;
public static int data3 = 3;
class InnerClass2 {
//成员内部类实例成员变量
public int data4 = 4;
private int data5 = 5;
//内部类的测试方法
public void test() {
System.out.println(data4);
System.out.println(data5);
System.out.println("成员内部类的test方法");
}
}
//外部类的测试方法
public void test() {
System.out.println("外部类的test方法");
}
}
//定义一个接口A,接口中声明了一个抽象方法testA()
interface A {
void testA();//这是一个抽象方法,没有方法体,需要实现类来具体实现
}
public class Test {
public static void main(String[] args) {
// ========== 第一部分:外部类操作 ==========
// 1. 创建外部类实例(必须先有外部类才能创建非静态内部类)
OuterClass outerClass = new OuterClass();
// 2. 通过对象实例访问外部类的成员变量
System.out.println(outerClass.data1);
// ========== 第二部分:创建内部类实例的两种方式 ==========
// 🌟 方法一:通过现有外部类实例创建
// 语法:外部类实例.new 内部类构造方法()
OuterClass.InnerClass2 innerClass1 = outerClass.new InnerClass2();
// 🌟 方法二:同时创建外部类和内部类实例
// 语法:new 外部类构造方法().new 内部类构造方法()
OuterClass.InnerClass2 innerClass2 = new OuterClass().new InnerClass2();
}
}
拿到了内部类对象后我们就可以调用我们的方法了,比如说:
public class Test {
public static void main(String[] args) {
OuterClass outerClass = new OuterClass();
System.out.println(outerClass.data1);
OuterClass.InnerClass2 innerClass1 = outerClass.new InnerClass2();
innerClass1.test();
OuterClass.InnerClass2 innerClass2 = new OuterClass().new InnerClass2();
innerClass2.test();
}
}
此时输出结果为:
⓶ 实力内部类中不能定义静态的成员变量
如果上述内容都没有问题了,接下来我们对代码进行小小的改动,在内部类InnerClass中添加一个静态成员:
会发现是不可以的,会出现报错。但是我们再做一个改动,加上一个final:
此时就可以啦,这说明非静态内部类(成员内部类)中 不能声明非 final 的静态成员。没有加final的时候,我们知道static修饰的成员是最早被执行的,不依赖于任何对象的,也就是说不依赖于InnerClass2的,但是我们的内部类InnerClass2是依赖于外部类对象的,两者之间就矛盾了,存在了问题。换句话说就是我们将一个不依赖于任何对象的变量定义在了一个需要依赖外部类对象的一个类当中,当然是矛盾的啦~
那为什么加上final后就可以了呢?这是因为final修饰的是常量,不需要被加载,编译的时候就能被通过,马上就能知道这个值就是6。
现在我们在外部类和内部类中同时添加一个data1,外部类中data1是1,内部类中data是111。并且输出data1的值:
class OuterClass {
// 外部类实例成员变量
public int data1 = 1;
public int data2 = 2;
public static int data3 = 3;
class InnerClass2 {
//成员内部类实例成员变量
public int data1 = 111;
public int data4 = 4;
private int data5 = 5;
public static final int data6 = 6;
//内部类的测试方法
public void test() {
System.out.println(data1);
System.out.println(data4);
System.out.println(data5);
System.out.println("成员内部类的test方法");
}
}
//外部类的测试方法
public void test() {
System.out.println("外部类的test方法");
}
}
public class Test {
public static void main(String[] args) {
OuterClass outerClass = new OuterClass();
OuterClass.InnerClass2 innerClass1 = outerClass.new InnerClass2();
innerClass1.test();
}
}
那么最后输出的data1的值到底是1还是000呢?看结果:
输出的是111。 我们知道,内部类可以访问外部类的成员,但如果有同名的变量,可能会发生隐藏现象。也就是说,如果内部类和外部类有同名的变量,内部类中的变量会覆盖外部类的变量。这时候,如果内部类的方法直接使用变量名,会优先访问内部类自己的变量data1。
⓷ 变量访问规则遵循:就近原则
此时Java 在访问变量时,会按照以下顺序查找:
当前方法的局部变量 → 内部类成员变量 → 外部类成员变量
如果我们内部类中没有data1,再访问data1的时候,查找到并且输出的就是外部类中的data1了,输出的结果就是1了。
如果我们非要访问外部类的data1,那么必须显式指定:OuterClass.this.data1 通过:
外部类名 . this . 变量名
class OuterClass {
// 外部类实例成员变量
public int data1 = 1;
public int data2 = 2;
public static int data3 = 3;
class InnerClass2 {
//成员内部类实例成员变量
public int data1 = 111;
public int data4 = 4;
private int data5 = 5;
public static final int data6 = 6;
//内部类的测试方法
public void test() {
System.out.println(data1);
System.out.println(OuterClass.this.data1);
System.out.println(data4);
System.out.println(data5);
System.out.println("成员内部类的test方法");
}
}
//外部类的测试方法
public void test() {
System.out.println("外部类的test方法");
}
}
public class Test {
public static void main(String[] args) {
OuterClass outerClass = new OuterClass();
OuterClass.InnerClass2 innerClass1 = outerClass.new InnerClass2();
innerClass1.test();
}
}
此时data1输出结果就是1了。
🧐注意事项
总结一下对于实例内部类中的这些注意事项:
1.外部类中的任何成员变量是都可以在实例内部类中直接访问的。
2.实例内部类和外部类成员所处的位置相同,也受访问限定修饰符public、public等的约束。
3.实例内部类对象必须在先有外部类对象前提下才可以创建。
4.在外部类当中,不能直接访问实例内部类中的成员,如果要访问必须先要创建内部类对象。
5.在实例内部类方法中访问同名的成员时,先优先访问内部类自己的,如果要访问外部类同名的成员,必须通过 外部类名称.this.同名成员 来进行访问。
6.实例内部类的非静态方法中包含了一个指向外部类对象的引用。
1.2.3 静态内部类
定义:被static修饰的内部成员类称为静态内部类。
例子:
//定义一个外部类
OuterClass2class OuterClass2 {
public int data1 = 1;
public int data2 = 2;
public static int data3 = 3;
//静态内部类(可以直接通过外部类名访问)
static class InnerClass {
public int data4 = 4;
private int data5 = 5;
public static int data6 = 6;
public void test() {
//访问自己的实例变量
System.out.println(data4);
System.out.println(data5);
//访问静态变量
System.out.println(data6);
System.out.println("静态内部类的test方法");
}
}
}
//外部类的测试方法
public void test() {
System.out.println("外部类的test方法");
}
}
public class Test {
public static void main(String[] args) {
//示例1:静态内部类的使用
//直接通过外部类名创建静态内部类实例
OuterClass2.InnerClass innerClass = new OuterClass2.InnerClass();
innerClass.test();//调用静态内部类的方法
}
}
创建静态内部类实例的方式:
OuterClass2.InnerClass innerClass = new OuterClass2.InnerClass();
同样的,若是我们此时输出data1会发现出现了问题:
若是刚刚的实例内部类,这种情况就会先查询自己的data1,若是内部类中没有,就去外部类找。但是现在不行,这是为什么呢?
data1 是外部类 OuterClass2 的实例成员变量,而静态内部类不依赖外部类实例。静态内部类只能直接访问外部类的静态成员。
也就是说:
✅ 可以直接访问外部类的静态成员(如 data3)。
❌ 不能直接访问外部类的实例成员(如 data1、data2),因为它们属于外部类的具体实例。
所以不依赖于外部类对象,那么它怎么知道里面有没有data1呢,当然是不清楚的啦~
静态内部类的对象可以独立存在,而外部类的实例变量(如 data1)必须依赖外部类的具体实例。如果静态内部类需要访问外部类的实例成员,必须手动创建一个外部类实例。
class OuterClass2 {
public int data1 = 1; // 外部类实例变量
static class InnerClass { // 静态内部类
// 1️⃣ 在静态内部类中创建外部类实例
OuterClass2 outerClass2 = new OuterClass2();
public void test() {
// 2️⃣ 通过外部类实例访问其成员变量
System.out.println(outerClass2.data1); // 输出:1
}
}
}
类比一下:就像你要知道某个人的年龄(实例变量),必须找到具体的人(实例),而不能直接问“人类”这个类别(静态内部类)。
🐱知识点对比表格
特性静态内部类非静态内部类依赖关系不依赖外部类实例必须依赖外部类实例访问外部类实例成员必须手动创建外部类实例才能访问可直接访问(自动绑定外部类实例)访问外部类静态成员可直接访问(如 OuterClass2.data3)可直接访问实例化语法OuterClass2.InnerClass inner = new OuterClass2.InnerClass()OuterClass.Inner inner = outer.new Inner()内存占用更独立,不隐含外部类引用隐含外部类引用(占用更多内存)
1.2.4 局部内部类
定义:定义在 方法体内部 或 代码块内部的类。
作用域:仅在定义它的方法/代码块内有效(类似局部变量)
public void func() {
class Inner { /* ... */ } // ✅ 正确:在方法内部定义
// 这里可以访问Inner类
}
// 这里不能访问Inner类 ❌ 访问权限:不能被访问限定修饰符修饰(public/private/protected)还有static等修饰符修饰 public void func() {
public class Inner { /* ... */ } // ❌ 错误:不能加访问修饰符
} 示例:
public class Test {
public void func() {
// 定义局部内部类(在方法内部)
class Inner {
public int data1 = 1; // 实例成员变量
}
// 创建局部内部类实例
Inner inner = new Inner();
// 访问内部类成员
System.out.println(inner.data1);
}
}
1.3 总结
类型定义位置访问外部类成员常见用途成员内部类类内部可访问所有成员紧密关联的功能组件静态内部类类内部+static只能访问静态成员工具类、独立模块局部内部类方法/代码块内只能访问final变量方法内部专用匿名内部类new对象时同局部内部类快速实现接口/抽象类
小贴士:内部类编译后会生成 外部类$内部类.class文件(如 Outer$Inner.class),这也是代码结构清晰的体现哦~
制作不易,更多内容加载中~感谢友友们的点赞收藏关注~~
如有问题欢迎批评指正,祝友友们生活愉快,学习工作顺顺利利!