一、介绍
在java中,我们被允许在编写一个类(外部类OuterClass
)时,在其内部再嵌套一个类(嵌套类NestedClass
),java将嵌套类分为两种:非静态内部类(简称内部类) 和 静态内部类,如下所示
public class OuterClass {
class InnerClass {
}
static class StaticInnerClass {
}
}
嵌套类作为外部类的一个成员,无论其是否为静态内部类,我们都可以对其添加任何的访问修饰符(public
、protected
、private
、default
),如下所示
public class OuterClass {
public class InnerClass {
}
protected static class StaticInnerClass {
}
}
非静态内部类对其外部类中的所有成员变量和方法都具有访问权,即便它被private
修饰也可以。但是静态内部类对外部类中的成员变量和方法没有访问权。
二、为什么要使用内部类
内部类为我们提供了以下便利:
它对只在一个地方使用的类进行逻辑分组
如果一个类
A.java
只对另一个类B.java
有用,那么将它嵌入到那个类中并把两个类放在一起岂不合理?即把A.java
作为内部类,而将B.java
作为外部类。它增加了封装性
考虑两个类
A.java
和B.java
,其中B.java
需要访问A.java
的被声明为private
的成员变量或方法。通过将类B.java
隐藏在类A.java
中,A.java
的成员变量或方法可以被声明为private
,B.java
也可以访问它们。另外,B.java
本身也可以对外界隐藏。它使代码更加可读和可维护
将内部类嵌套在外部类中使代码更接近使用它的地方。
三、非静态内部类
非静态内部类有以下特点:
- 对其外部类中的所有成员变量和方法都具有访问权,即便它被
private
修饰也可以。 - 不允许定义任何
static
修饰的成员变量和方法。 - 外部类必须通过非静态内部类的实例对象访问其内部的属性和方法。
- 编译后生成两个
class
文件:外部类.class
和外部类$内部类.class
。
当我们实例化一个非静态内部类的对象时,使用以下方法
在外部类中实例化内部类
public class OuterClass { public void initInnerClass() { InnerClass innerClass = new InnerClass(); innerClass.innerClassMethod_1(); } class InnerClass { public void innerClassMethod_1() { System.out.println("调用内部类方法"); } } }
在其他类中实例化内部类
class InnerClassTest { public static void main(String[] args) { // 方式一:创建内部类innerClass实例 OuterClass outerClass = new OuterClass(); OuterClass.InnerClass innerClass1 = outerClass.new InnerClass(); innerClass1.innerClassMethod_1(); // 方式二:创建内部类innerClass实例 OuterClass.InnerClass innerClass2 = new OuterClass().new InnerClass(); innerClass2.innerClassMethod_1(); } }
非静态内部类还有两种特殊的类型:局部内部类 和 匿名内部类。后面细说。
四、静态内部类
静态内部类在行为上与其他类相似
- 对外部类成员的访问只能通过外部类的实例对象。
- 编译后生成两个
class
文件:外部类.class
和外部类$内部类.class
。
当我们实例化一个静态内部类的对象时,使用以下方法
在外部类中实例化内部类
public class OuterClass { public void initStaticInnerClass() { StaticInnerClass staticInnerClass = new StaticInnerClass(); staticInnerClass.staticInnerClassMethod_1(); } static class StaticInnerClass { public void staticInnerClassMethod_1() { System.out.println("调用静态内部类方法"); } } }
在其他类中实例化内部类
class InnerClassTest { public static void main(String[] args) { OuterClass.StaticInnerClass staticInnerClass = new OuterClass.StaticInnerClass(); staticInnerClass.staticInnerClassMethod_1(); } }
五、局部内部类
在一个代码块声明的类叫局部内部类。此处的代码块指任何{}
内部(如静态代码块、方法、for
循环、if
块)
- 局部内部类可声明在任何代码块中
- 局部内部类内部可以访问其外部的成员变量,但该成员变量的值不允许被修改,因此我们需要使用
final
修饰。 - 不允许定义任何
static
静态成员变量或方法 - 静态方法中的局部内部类,只允许访问外部类中的
static
静态成员变量和方法。 - 不允许被
static
修饰 - 在
{}
代码块中不可以声明接口interface
。因为interface
接口天生就是静态的。 - 局部内部类中仅允许在常量变量声明中使用
static
。从java16
开始允许不被final
修饰 - 编译后生成
外部类.class
和外部类+$+编号+内部类.class
。
创建局部内部类的方式如下:
public class OuterClass {
public void localClassMethod() {
for (int i=0; i<10; i++) {
// for循环中的局部内部类
class LocalClassInFor {
}
}
if (true) {
// if代码块中的局部内部类
class LocalClassInIf {
}
}
// 方法中的局部内部类
class LocalClass {
}
}
}
六、匿名内部类
在一个方法内部声明的类但没有命名该类的名称叫局部内部类。匿名类使您的代码更加简洁。允许我们能够同时声明和实例化一个类。除了没有名字之外,它们类似于局部内部类。如果只需要使用一次局部内部类,就使用它们。
- 本质上是一个表达式。
- 实例化匿名内部类需要借助一个父类 或 接口。
- 可以访问外部类的成员变量和方法。与局部内部类相同。
- 不可以修改外部变量,但该变量的值不允许被修改,因此我们需要使用
final
修饰。与局部内部类相同。 - 与外部变量或方法重名时,默认采用就近原则访问。与非静态内部类相同。
- 不可以声明
static
静态变量或方法。但可以声明final static
常量。 - 编译后生成
外部类.class
和外部类+$+编号.class
。
使用匿名内部类需要借助父类 或 接口实现,其本质相同,都是对方法的重写。如下所示
使用父类
public class AnonymousOuterClass { class InnerClass { private String innerField = "field in InnerClass"; public void getField() { System.out.println(innerField); } } public void anonymousInnerMethod() { // 声明并实例化匿名内部类 InnerClass innerClass = new InnerClass() { private String innerField = "a"; @Override public void getField() { // 输出匿名内部类中的成员变量innerField System.out.println(innerField); // 输出父类类中的成员变量innerField super.getField(); } }; innerClass.getField(); } } class AnonymousOuterClassTest { public static void main(String[] args) { AnonymousOuterClass anonymousOuterClass = new AnonymousOuterClass(); anonymousOuterClass.anonymousInnerMethod(); } }
使用接口
public class AnonymousOuterClass { interface InnerInterface { void getField(); } public void anonymousInnerInterfaceMethod() { InnerInterface innerInterface = new InnerInterface() { private String innerField = "field in inner interface"; @Override public void getField() { // 输出匿名内部类中的成员变量innerField System.out.println(innerField); } }; innerInterface.getField(); } } class AnonymousOuterClassTest { public static void main(String[] args) { AnonymousOuterClass anonymousOuterClass = new AnonymousOuterClass(); anonymousOuterClass.anonymousInnerInterfaceMethod(); } }
七、lambda表达式内部类
使用lambda表达式内部类的理由很简单:当匿名内部类的父类或接口中只有一个方法时,其实现代码最少也得五六行,为了使代码简化,所以使用lambda表达式内部类。
- 编译后生成
外部类.class
。lambda表达式内部类不会生成单独的class
文件。 - 其余特性与匿名内部类相同。
除了实例化方式不同,lambda表达式内部类和匿名内部类其余使用限制完全一致。
public class LambdaOuterClass {
interface InnerInterface {
void sayHello();
}
public void sayHello() {
// lambda表达式内部类
InnerInterface innerInterface = () -> System.out.println("hello world");
innerInterface.sayHello();
}
}
class LambdaOuterClassTest {
public static void main(String[] args) {
LambdaOuterClass lambdaOuterClass = new LambdaOuterClass();
lambdaOuterClass.sayHello();
}
}
八、成员重名
无论是静态内部类还是非静态内部类,当内部类中与外部类具有相同名称的成员变量的情况下,当我们在内部类中使用该变量时,默认采用就近原则的方式访问该变量。如果需要访问外部类中的重名变量时,则需要通过OuterClass.this.field
访问,如下所示
public class OuterClass {
private String sameNameField = "the same name field in OuterClass";
class InnerClass {
private String sameNameField = "the same name field in InnerClass";
public void testSameNameField() {
// 访问内部类的重名变量
System.out.println(sameNameField);
// 访问外部类的重名变量
System.out.println(OuterClass.this.sameNameField);
}
}
}
九、序列化
java强烈建议不要序列化内部类,包括局部内部类和匿名类。当Java编译器编译某些结构时,比如内部类,它创建合成结构,这些是在源代码中没有相应构造的类、方法、字段和其他构造。合成结构使Java编译器能够在不改变JVM的情况下实现新的Java语言特性。然而,合成构造在不同的Java编译器实现中可能有所不同,这意味着在不同的实现中,类文件也可能不同。因此,如果我们序列化一个内部类,然后用不同的JRE实现反序列化它,可能会有兼容性问题。
十、如何选择内部类
非静态内部类
当需要访问外部类实例的非
public
修饰的成员变量和方法时。静态内部类
当不需要访问外部类实例的成员变量和方法时。
局部内部类
当我们需要创建一个类的多个实例,请访问其构造函数,或者引入一个新的命名类型(例如,因为您稍后需要调用其他方法)。
匿名内部类
当我们需要同时声明和实例化一个类,并且只需要使用一次局部内部类时。
lambda表达式内部类
纸上得来终觉浅,绝知此事要躬行。
————————我是万万岁,我们下期再见————————