Java内部类深入解析
一、介绍
在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表达式内部类
到此这篇关于Java内部类深入解析的文章就介绍到这了,更多相关Java内部类内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
SpringBoot整合RabbitMQ实战教程附死信交换机
这篇文章主要介绍了SpringBoot整合RabbitMQ实战附加死信交换机,本文通过示例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下2022-06-06
最新评论