当前位置: 首页 > article >正文

java内部类详解

文章目录

  • 一、介绍
  • 二、为什么要使用内部类
  • 三、非静态内部类
  • 四、静态内部类
  • 五、局部内部类
  • 六、匿名内部类
  • 七、lambda表达式内部类
  • 八、成员重名
  • 九、序列化
  • 十、如何选择内部类

一、介绍

在java中,我们被允许在编写一个类(外部类OuterClass)时,在其内部再嵌套一个类(嵌套类NestedClass),java将嵌套类分为两种:非静态内部类(简称内部类)静态内部类,如下所示

public class OuterClass {

    class InnerClass {

    }

    static class StaticInnerClass {

    }
}

嵌套类作为外部类的一个成员,无论其是否为静态内部类,我们都可以对其添加任何的访问修饰符(publicprotectedprivatedefault),如下所示

public class OuterClass {

    public class InnerClass {

    }

    protected static class StaticInnerClass {

    }
}

非静态内部类对其外部类中的所有成员变量和方法都具有访问权,即便它被private修饰也可以。但是静态内部类对外部类中的成员变量和方法没有访问权。


二、为什么要使用内部类

内部类为我们提供了以下便利:

  • 它对只在一个地方使用的类进行逻辑分组

    如果一个类A.java只对另一个类B.java有用,那么将它嵌入到那个类中并把两个类放在一起岂不合理?即把A.java作为内部类,而将B.java作为外部类。

  • 它增加了封装性

    考虑两个类A.javaB.java,其中B.java需要访问A.java的被声明为private的成员变量或方法。通过将类B.java隐藏在类A.java中,A.java的成员变量或方法可以被声明为privateB.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表达式内部类



纸上得来终觉浅,绝知此事要躬行。

————————我是万万岁,我们下期再见————————


http://www.kler.cn/a/159964.html

相关文章:

  • 蓝队知识浅谈(上)
  • 【2024软考架构案例题】你知道 Es 的几种分词器吗?Standard、Simple、WhiteSpace、Keyword 四种分词器你知道吗?
  • 比ChatGPT更酷的AI工具
  • 【mysql】使用宝塔面板在云服务器上安装MySQL数据库并实现远程连接
  • 性能测试|JMeter接口与性能测试项目
  • WPF学习之路,控件的只读、是否可以、是否可见属性控制
  • matlab实践(十):贝塞尔曲线
  • Linux搭建FTP并安装xrdp,实现Windows系统下利用FileZilla传输文件和远程桌面连接
  • Seo优化是什么,怎么进行seo优化
  • 服务器数据恢复—服务器重装系统导致逻辑卷发生改变的数据恢复案例
  • uni-app详解、开发步骤、案例代码
  • 使用Vue写一个日期选择器
  • 使用K-means把人群分类
  • MySql概述及其性能说明
  • 【PUSDN】centos查看日志文件内容,包含某个关键字的前后5行日志内容,centos查看日志的几种方法
  • 9个典型的交通行业AI应用
  • Java面试题(每天10题)-------连载(43)
  • kubeadm快速搭建k8s高可用集群
  • 目标检测常用评价指标
  • MATLAB Simulink +STM32硬件在环 (HIL)实现例程测试
  • 前后端数据传输格式(上)
  • 「音视频处理」音频编码AAC详解,低码率提高音质?
  • 【Python】Python读Excel文件生成xml文件
  • 智能优化算法应用:基于梯度算法无线传感器网络(WSN)覆盖优化 - 附代码
  • Spring boot -- 学习HttpMessageConverter
  • 【LeetCode 0170】【哈希】两数之和(3) 数据结构设计