Java 每日一刊(第15期):内部类

news2024/9/23 9:20:53

在这里插入图片描述

文章目录

    • 前言
    • 内部类
      • 成员内部类(Member Inner Class)
      • 静态内部类(Static Nested Class)
      • 局部内部类(Local Inner Class)
      • 匿名内部类(Anonymous Inner Class)
    • 内部类的详细对比
    • 内部类字节码文件
      • 文件命名规则
      • 字节码结构上的区别
    • 内部类的典型使用场景
      • GUI 事件监听器
      • 数据封装与实现隐藏
      • 构建复杂对象(Builder 模式)
      • 访问外部类的私有成员
      • 封装特定行为的组合逻辑
    • 本期小知识

前言

这里是分享 Java 相关内容的专刊,每日一更。

本期将为大家带来以下内容:

  1. 内部类
  2. 内部类的详细对比
  3. 内部类字节码文件
  4. 内部类的典型使用场景

内部类

Java 内部类是一种将类嵌套在另一个类内部的编程结构。内部类可以访问外部类的成员(包括私有成员),从而形成了类与类之间更紧密的关联。Java 内部类有四种主要类型:

  1. 成员内部类(Member Inner Class)
  2. 静态内部类(Static Nested Class)
  3. 局部内部类(Local Inner Class)
  4. 匿名内部类(Anonymous Inner Class)

成员内部类(Member Inner Class)

成员内部类是指在另一个类中定义的类,并且不使用 static 修饰。它作为外部类的一个成员,可以和外部类的成员变量、方法共存。

特点:

  • 成员内部类拥有外部类的所有非静态成员的访问权,包括 private 成员。
  • 成员内部类的实例与外部类的实例相关联,只有在 外部类的实例 存在的情况下才能创建成员内部类的实例。

使用场景: 适用于某些内部逻辑只和外部类强相关的情况。通过成员内部类,内部的逻辑更加封闭且紧密绑定外部类。

语法:

class Outer {
    private String name = "Outer";

    class Inner {
        void display() {
            System.out.println("Outer name: " + name);
        }
    }
}

实例化: 成员内部类需要通过外部类的对象进行实例化:

Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
inner.display();

静态内部类(Static Nested Class)

静态内部类是用 static 关键字修饰的内部类。由于它是静态的,它独立于外部类的实例存在。

特点:

  • 静态内部类不能访问外部类的非静态成员,只能访问外部类的静态成员。
  • 它的实例不依赖外部类的实例,可以直接创建。

使用场景: 当内部类逻辑与外部类没有强依赖关系时,可以使用静态内部类。典型场景包括实现工具类、嵌套数据结构等。

语法:

class Outer {
    private static String name = "Outer";

    static class StaticInner {
        void display() {
            System.out.println("Outer name: " + name);
        }
    }
}

实例化: 静态内部类可以直接通过类名实例化:

Outer.StaticInner inner = new Outer.StaticInner();
inner.display();

局部内部类(Local Inner Class)

局部内部类是定义在方法或代码块中的类,它的作用域仅限于方法或代码块内。

特点:

  • 局部内部类可以访问方法中的局部变量,前提是这些变量必须被声明为 final 或“事实上是 final”。
  • 它的生命周期与方法的执行周期一致,方法执行完毕后,局部内部类将不再可用。

使用场景: 局部内部类适用于仅在特定方法中使用,逻辑较为局限的场景,如处理复杂算法的临时类。

语法:

class Outer {
    void method() {
        final String greeting = "Hello";
        class LocalInner {
            void printGreeting() {
                System.out.println(greeting);
            }
        }
        LocalInner localInner = new LocalInner();
        localInner.printGreeting();
    }
}

注意: 由于局部内部类的作用范围仅限于定义它的代码块,因此它 只能 在方法或代码块内部使用。

匿名内部类(Anonymous Inner Class)

匿名内部类是一种特殊的内部类,它没有名字,通常用于简化代码,尤其是在需要临时实现一个接口或者继承一个类时。匿名内部类直接创建类的实例,并实现或继承该类。

特点:

  • 匿名内部类是一次性的,不能重复使用。
  • 可以继承一个类或实现一个接口。
  • 它通常用于简化代码,在特定的上下文中快速定义并使用一个类。

使用场景: 常见于回调机制、事件监听器等场景,或者需要快速实现某个类或接口的地方。

语法:

interface Greeting {
    void sayHello();
}

public class Test {
    public static void main(String[] args) {

        // 在这里,匿名类实现了 Greeting 接口,并在创建时就直接定义了它的行为。
        Greeting greeting = new Greeting() {
            @Override
            public void sayHello() {
                System.out.println("Hello, World!");
            }
        };
        greeting.sayHello();
    }
}

此处有点难以理解,我们进行进一步解释:

new Greeting() 表示创建对象,可是 Greeting 是接口,所以单独使用 new Greeting() 是错误的 。我们仔细观察上面的代码,发现在 new Greeting() 的后面紧跟着一个 {}。这个紧跟着的 {} 表示的是一个匿名类(没有名字的类)。为什么呢?

class Hello {}

上面是一个标准类的定义,有 class 关键字和类名,而前面的匿名类没有使用 class 关键字和类名,所以被叫做匿名类。

注意:只有在 创建对象时 紧跟着的 {} 才被称作匿名类,其他地方的 {} 并不是。

内部类的详细对比

特性成员内部类(Member Inner Class)静态内部类(Static Nested Class)局部内部类(Local Inner Class)匿名内部类(Anonymous Inner Class)
类名有类名有类名有类名无类名
访问外部类成员可以访问外部类的所有成员只能访问外部类的静态成员可以访问外部类的所有成员可以访问外部类的所有成员
实例化方式需要通过外部类实例不需要外部类实例在方法中实例化创建时直接实例化
使用场景用于强关联类用于工具类或静态数据结构局限于局部方法快速实现接口或继承类
生命周期与外部类的实例相同与外部类无关与方法的生命周期一致生命周期短,随用随销毁

内部类字节码文件

Java 编译器在编译内部类时会生成与普通类不同的字节码文件。具体区别如下:

文件命名规则

普通类:外部类和普通类的 .class 文件直接使用类名命名,如 Outer.class

内部类:编译器为内部类生成的 .class 文件使用 外部类名$内部类名.class 的格式。例如:

  • 对于成员内部类 Outer.Inner,生成的字节码文件是 Outer$Inner.class
  • 对于匿名内部类,Java 编译器会生成类似 Outer$1.class 这样的文件,其中的数字表示匿名类出现的顺序。
  • 对于局部内部类,它的 .class 文件命名也是基于外部类的名称,但同样会根据顺序编号来标识不同的局部类。

字节码结构上的区别

内部类访问外部类的成员:当内部类访问外部类的成员(尤其是私有成员)时,Java 编译器在生成字节码时会自动为内部类生成一个指向外部类的隐式引用。因此,内部类的构造函数中会隐含一个指向外部类的引用,这样才能在运行时通过这个引用访问外部类的成员。

例如,成员内部类 Inner 会持有对外部类 Outer 的引用(Outer.this),这是字节码文件中的一个额外字段。

  • 静态内部类与普通类没有区别:静态内部类由于没有外部类的隐式引用,因此它的 .class 文件与普通类的结构更为相似。
  • 匿名内部类:匿名内部类的 .class 文件中不会有类的名称,它直接在生成的字节码中定义类的行为。此外,由于匿名内部类通常用来实现接口或继承类,它会包含接口或父类的方法实现。

内部类的典型使用场景

GUI 事件监听器

在 Java 的 GUI 编程中(如 SwingAWT),事件监听器通常用匿名内部类来实现。事件监听器需要实现一个单一的方法,如按钮点击事件。

button.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("Button clicked!");
    }
});

使用匿名内部类可以避免定义一个新的类,并且保持代码的简洁和紧凑。

数据封装与实现隐藏

内部类经常用于隐藏一些不需要对外公开的实现细节。例如,在一个复杂的数据结构如 HashMap 中,内部使用了很多私有类(如 Entry),这些类只为外部类服务,并不需要单独暴露给外部用户。

class HashMap {
    private static class Entry {
        final int key;
        String value;
        Entry next;

        Entry(int key, String value) {
            this.key = key;
            this.value = value;
        }
    }
}

通过将 Entry 作为静态内部类,HashMap 可以封装其内部实现,保证用户无法直接操作这些内部类的结构。

构建复杂对象(Builder 模式)

静态内部类常用于构建者模式(Builder Pattern),通过静态内部类实现链式调用以构建复杂对象。这种模式提高了代码的可读性和灵活性。

class Product {
    private String name;
    private int price;

    private Product(Builder builder) {
        this.name = builder.name;
        this.price = builder.price;
    }

    public static class Builder {
        private String name;
        private int price;

        public Builder setName(String name) {
            this.name = name;
            return this;
        }

        public Builder setPrice(int price) {
            this.price = price;
            return this;
        }

        public Product build() {
            return new Product(this);
        }
    }
}

访问外部类的私有成员

当内部类需要访问外部类的私有成员时,使用成员内部类是最直接的方式。由于内部类持有对外部类实例的隐式引用,它可以直接操作外部类的成员,无需通过 getter 或 setter 方法。

class Outer {
    private int data = 10;

    class Inner {
        void display() {
            // 直接访问外部类的私有成员
            System.out.println("Data from outer: " + data); 
        }
    }
}

封装特定行为的组合逻辑

在面向对象设计中,有时内部类用于封装某些行为,它们仅仅是外部类逻辑的一部分。这种模式可以帮助保持外部类的清晰,而将复杂的逻辑隔离到内部类中。

class TaskManager {
    class Task {
        String name;

        Task(String name) {
            this.name = name;
        }

        void execute() {
            System.out.println("Executing task: " + name);
        }
    }

    void runTask(String name) {
        Task task = new Task(name);
        task.execute();
    }
}

本期小知识

由于 成员内部类 会持有外部类的引用,如果内部类对象的生命周期比外部类对象长,可能会导致 内存泄漏

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2157173.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

浅谈Spring Cloud:Nacos的配置

Nacos,一个更易于构建云原生应用的动态服务发现,配置管理和服务管理平台。所以Nacos是⼀个注册中心组件,但它又不仅仅是注册中心组件。 目录 安装 注册 负载均衡 环境隔离 配置管理 搭建集群 安装 在官网下载好安装包解压后&#xf…

深度学习01-概述

深度学习是机器学习的一个子集。机器学习是实现人工智能的一种途径,而深度学习则是通过多层神经网络模拟人类大脑的方式进行学习和知识提取。 深度学习的关键特点: 1. 自动提取特征:与传统的机器学习方法不同,深度学习不需要手动…

手机在网状态查询接口如何用Java进行调用?

一、什么是手机在网状态查询接口? 手机在网状态查询接口,又叫运营商在网状态查询,手机号在网状态查询,传入手机号码,查询该手机号的在网状态,返回内容有正常使用、停机、在网但不可用、不在网(…

【网络】高级IO——epoll版本TCP服务器初阶

目录 前言 一,epoll的三个系统调用接口 1.1.epoll_create函数 1.1.1.epoll_create函数干了什么 1.2. epoll_ctl函数 1.2.1.epoll_ctl函数函数干了什么 1.3.epoll_wait函数 1.3.1.epoll_wait到底干了什么 1.4.epoll的工作过程中内核在干什么 二,…

【Elasticsearch系列廿】Logstash 学习

💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

PostgreSQL 的log_hostname 参数测试

PostgreSQL 的log_hostname 参数测试 log_hostname 是 PostgreSQL 配置文件 (postgresql.conf) 中的一个参数,用于控制是否在日志条目中记录客户端主机名。默认情况下,PostgreSQL 只记录客户端的IP地址,而 log_hostname 参数允许数据库管理员…

【最基础最直观的排序 —— 冒泡排序算法】

最基础最直观的排序 —— 冒泡排序算法 冒泡排序(Bubble Sort)是一种计算机科学领域的较简单的排序算法,属于交换排序。其基本思想是在待排序的一组数中,将相邻的两个数进行比较,若前面的数比后面的数大就交换两数&am…

农产品商城系统小程序的设计

管理员账户功能包括:系统首页,个人中心,用户管理,产品分类管理,热销农产品管理,订单管理,系统管理 微信端账号功能包括:系统首页,热销弄产品,网站公告&#…

基于c++实现的简易shell

代码逻辑 核心思想 解析命令行,拆解命令及其选项创建子进程,在子进程中执行命令如果是前台执行命令,则父进程就阻塞等待子进程中命令执行结束后回收子进程的资源如果是后台执行命令,则父进程不进行阻塞等待,可继续向下…

STM32 软件触发ADC采集

0.91寸OLED屏幕大小的音频频谱,炫酷! STM32另一个很少人知道的的功能——时钟监测 晶振与软件的关系(深度理解) STM32单片机一种另类的IO初始化方法 ADC是一个十分重要的功能,几乎任何一款单片机都会包含这个功能&a…

记一次MySQL索引不当引发死锁问题

一、前言 在并发量很低的情况下,mysql 的响应时延一切正常,一旦并发量上去了,mysql就会出现死锁的情况,你有没有遇到过?到底是是什么原因导致的呢,让我们一起看看真实的案例。 二、遇到的问题 先介绍一下…

LabVIEW提高开发效率技巧----利用第三方库和工具

LabVIEW开发不仅依赖于自身强大的图形化编程能力,还得益于其庞大的用户社区和丰富的第三方库。这些工具和库能够帮助开发者快速解决问题,提升开发效率,避免从头开始编写代码。 1. LabVIEW工具网络(NI Tools Network) …

MateBook 16s 2023在Deepin下开启性能模式,调节风扇转速到最大,全网首发!

方法 在Deepin下按住Fnp快捷键,开启性能模式。 验证 首先去debian下载acpi-call-dkms https://packages.debian.org/sid/all/acpi-call-dkms/download 然后使用root用户执行: apt install --simulate ./acpi-call-dkms_1.2.2-2.1_all.deb apt inst…

LeetCode 面试经典150题 191.位1的个数

Java中的算术右移和逻辑右移的区别 题目:编写一个函数,获取一个正整数的二进制形式并返回其二进制表达式中设置位的个数(也被称为汉明重量)。 设置位的个数即二进制中1的个数。 思路:方法一:因为正数的原…

基于阿里云免费部署Qwen1-8B-chat模型并进行lora参数微调从0到1上手操作

文章目录 一、申请资源二、创建实例三、克隆微调数据四、部署Qwen1-8B-chat模型1、环境配置2、模型下载3、本地模型部署 五、模型微调1、拉取Qwen仓库源码2、微调配置3、合并微调参数4、本地部署微调模型 一、申请资源 阿里云账号申请PAI资源详细教程我已于部署ChatGLM3时写过…

Golang | Leetcode Golang题解之第430题扁平化多级双向链表

题目: 题解: func dfs(node *Node) (last *Node) {cur : nodefor cur ! nil {next : cur.Next// 如果有子节点,那么首先处理子节点if cur.Child ! nil {childLast : dfs(cur.Child)next cur.Next// 将 node 与 child 相连cur.Next cur.Chi…

遗传算法与深度学习实战(14)——进化策略详解与实现

遗传算法与深度学习实战(14)——进化策略详解与实现 0. 前言1. 进化策略1.1 进化策略原理1.2 将进化策略应用于函数逼近 2. 实现进化策略小结系列链接 0. 前言 进化策略 (Evolutionary Strategies, ES) 是进化计算和遗传方法的扩展,增加了控…

【Python学习手册(第四版)】学习笔记24-高级模块话题

个人总结难免疏漏,请多包涵。更多内容请查看原文。本文以及学习笔记系列仅用于个人学习、研究交流。 本来计划中秋发布几篇文章,结果阳了,发烧、头疼、咽疼,修养了近一周,还没好完。希望大家都能有个好身体&#xff0…

proteus仿真软件简体中文版网盘资源下载(附教程)

对于电子通信专业的小伙伴来说,今天文章的标题应该不会陌生。Proteus是一款具有广泛应用的仿真软件,它的功能非常强大,适用于所有单片机的仿真工作,能够从原理图、调试、到与电路的协同仿真一条龙全部搞定,受到所有用户…

自己开发了一个电脑上滚动背单词的软件

在这个快节奏的时代,我们每天都在忙碌中度过,手机虽然方便,但往往难以找到一整块时间来专心背单词。然而,你是否意识到,每天坐在电脑前的时间远比使用手机的时间要长?现在我们来介绍一个新型的学习软件灵思…