思维导图:
4.6 内部类
在Java中,可以在一个类的内部定义另一个类,这种结构的类被称作内部类,而包含它的类被称为外部类。根据内部类的位置、修饰符和定义方式,内部类可以分为以下四种:
- 成员内部类
- 局部内部类
- 静态内部类
- 匿名内部类
本节将对这四种内部类进行详细的讲解。
4.6.1 成员内部类
在Java中,一个类不仅可以定义成员变量和成员方法,还可以定义类。这种结构的类被称为成员内部类。这种内部类可以访问外部类的所有成员,不论这些成员具有何种访问权限。而想要从外部类访问内部类,需要先创建内部类的对象。
创建内部类对象的基本格式如下:
外部类名 外部类对象 = new 外部类名();
外部类名.内部类名 内部类对象 = 外部类对象.new 内部类名();
为了更清晰地了解如何定义和访问成员内部类,我们可以查看文件Example20.java
的例子。在这个文件中,Outer
类是一个外部类,而在Outer
类中定义了Inner
类作为其成员内部类。
代码示例 (Example20.java
):
class Outer {
int m = 0;
void test1() {
System.out.println("外部类成员方法test1()");
}
class Inner {
int n = 1;
void show1() {
System.out.println("外部类成员变量m=" + m);
test1();
}
void show2() {
System.out.println("内部类成员方法show2()");
}
}
void test2() {
Inner inner = new Inner();
System.out.println("内部类成员变量n=" + inner.n);
inner.show2();
}
public static void main(String[] args) {
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
inner.show1();
outer.test2();
}
}
从代码中我们可以看出,在内部类Inner
的show1()
方法中,我们访问了外部类的成员变量m
和成员方法test1()
。这证明了成员内部类可以访问外部类的所有成员。同时,外部类通过创建内部类对象也可以访问内部类的成员。
当执行这段代码时,输出结果将显示内部类成功地访问了外部类的成员,同时外部类也访问了内部类的成员。
4.6.2 局部内部类
局部内部类,又被称为方法内部类,是在一个方法或者某个局部范围内定义的类。与局部变量类似,它的作用范围仅限于所在的方法或局部范围。由于它的有效范围有限,外部类不能直接访问局部内部类的成员。但局部内部类可以无约束地访问外部类的所有成员。
为了在外部类中访问局部内部类的成员,我们必须在局部内部类所属的方法中创建其对象,并通过这个对象来访问其成员。
考虑以下代码示例,Example21.java
:
class Outer {
int m = 0;
void test1() {
System.out.println("外部类成员方法test1()");
}
void test2() {
// 定义一个局部内部类
class Inner {
int n = 1;
void show() {
System.out.println("外部类成员变量m=" + m);
test1();
}
}
// 创建局部内部类对象并访问其成员
Inner inner = new Inner();
System.out.println("局部内部类变量n=" + inner.n);
inner.show();
}
public static void main(String[] args) {
Outer outer = new Outer();
outer.test2(); // 通过外部类对象调用包含局部内部类的方法
}
}
从Example21.java
中,我们可以观察到在Outer
类的test2()
方法中定义了一个局部内部类Inner
。这个内部类有一个方法show()
,它访问了外部类的变量m
和方法test1()
。在test2()
方法中,我们创建了Inner
类的对象并访问了其成员。
当我们运行这个程序时,控制台将输出:
局部内部类变量n=1
外部类成员变量m=0
外部类成员方法test1()
这证明了我们可以在局部内部类的定义方法中通过创建对象来访问其成员,并且局部内部类可以自由访问外部类的成员。
注意:局部内部类和其他类型的内部类之间的主要区别在于它们的定义位置和访问范围。局部内部类的作用范围非常有限,仅限于定义它的方法或代码块。
4.6.3 静态内部类
静态内部类是一个被static
关键字修饰的成员内部类。它和普通的成员内部类有几点不同:
- 静态内部类不能直接访问外部类的非静态成员,但可以访问外部类的静态成员。
- 实例化静态内部类的对象不需要外部类的对象。
- 在静态内部类中定义的所有成员都隐式地为静态的。
基本语法:
外部类名.静态内部类名 变量名 = new 外部类名.静态内部类名();
示例:
class Outer {
static int m = 0;
static class Inner {
int n = 1;
void show() {
System.out.println("外部类静态变量m=" + m);
}
}
public static void main(String[] args) {
Outer.Inner inner = new Outer.Inner();
inner.show();
}
}
在上述代码中,Outer
类定义了一个静态变量m
和一个静态内部类Inner
。Inner
类中有一个方法show
,该方法访问并打印外部类的静态变量m
。在main
方法中,我们直接实例化了Inner
类的对象并调用其show
方法。
4.6.4 匿名内部类
匿名内部类是一种没有声明明确类名的局部内部类。它们通常用于简化代码,特别是在需要使用一个简单的类来实现接口或继承类时。匿名内部类通常只用一次。
基本语法:
new 父类名或接口名() {
// 实现部分
}
示例:
interface Animal {
void shout();
}
public class Example23 {
public static void main(String[] args) {
String name = "小花";
animalShout(new Animal() {
@Override
public void shout() {
System.out.println(name + "喵喵……");
}
});
}
public static void animalShout(Animal an) {
an.shout();
}
}
在这个示例中,我们定义了一个Animal
接口和一个animalShout
方法。在main
方法中,我们传递了一个实现了Animal
接口的匿名内部类的对象到animalShout
方法。
注意: 从JDK8开始,我们可以在匿名内部类中访问未使用final修饰的局部变量。这是因为编译器在后台隐式地将其视为final的。
总结: 静态内部类和匿名内部类都为Java提供了更大的灵活性,使我们能够更好地组织和管理代码。静态内部类常用于相关类的组织,而匿名内部类则常用于简化代码和回调机制。
4.6 内部类重点:
-
分类:
- 成员内部类:定义在外部类的成员位置。
- 局部内部类:定义在方法中或某个作用域内。
- 静态内部类:用static关键字修饰的内部类。
- 匿名内部类:没有名字的内部类,通常用于简化代码,特别是在回调和事件处理中。
-
访问规则:
- 内部类可以无条件地访问外部类的所有成员。
- 外部类要访问内部类成员需要创建内部类的对象。
- 静态内部类只能访问外部类的静态成员。
-
创建对象:
- 对于成员内部类:
外部类名.内部类名 对象名 = new 外部类名().new 内部类名();
- 对于静态内部类:
外部类名.内部类名 对象名 = new 外部类名.内部类名();
- 对于成员内部类:
难点:
-
匿名内部类的使用:尽管它为代码简化提供了便利,但它也可能让初学者感到困惑,特别是当涉及到接口或抽象类实现时。
-
理解内部类与外部类之间的关系和访问权限:如何创建内部类对象,以及何时可以访问外部类的特定成员。
-
静态与非静态内部类之间的差异:尤其是在访问权限和实例化方式上。
易错点:
-
误用修饰符:比如在局部内部类中使用访问修饰符。
-
创建对象的方式:尤其是对于成员内部类和静态内部类,很容易混淆创建它们的对象的语法。
-
局部内部类对局部变量的访问:在JDK8之前,局部内部类访问局部变量时,该局部变量必须是final的。虽然JDK8以后这一限制放宽了,但仍然要确保在局部内部类中访问的局部变量值不被修改。
-
静态内部类误用非静态外部类成员:静态内部类只能访问外部类的静态成员。
-
匿名内部类在大量使用时可能导致代码混乱和难以读懂,特别是当多个匿名内部类嵌套使用时。
综上,内部类是Java提供的一个高级特性,它允许更好地封装代码,但也带来了一些复杂性。理解和熟悉这些重点、难点和易错点对于有效使用内部类是非常关键的。