Java基础-内部类
内部类
- 引言
- 内部类的共性
- 成员内部类
- 静态内部类
- 非静态内部类
- 局部内部类
- 匿名内部类
- 内部类的使用场景和好处
引言
Java不仅可以定义变量和方法,还可以定义类.
内部类允许你把一些逻辑相关的类组织在一起,并可以控制内部中类的可见性.
这么看来,内部类就像是代码一种隐藏机制:将类放在其他类的内部,从而隐藏名字和组织代码的模式.
根据定义方式的不同,分为四种类型: 静态内部类, 成员内部类,局部内部类,匿名内部类
内部类的共性
- 依然是一个独立的类,在编辑之后内部类会被编辑成独立的.class文件,但是前面会添加外部类的类名和$符号
- 声明为静态的,就不能随便访问外部类的成员变量了,此时内部类只能访问外部类的静态成员变量或方法
- 外部类不能直接访问内部类的成员,但可以通过内部类对象来访问
内部类是外部类的一个成员,因此内部类可以自由访问外部类的成员变量,无论是否为private.
因为某个外围类对象创建内部类对象时,此内部类会捕获一个隐式引用,它引用了实例化内部对象的外围类对象,通过这个指针,可以访问外围类对象的全部状态.
实现原理
反编译内部类字节码,分析主要通过以下几步做到的:
- 编译器为内部类添加一个成员变量,它的类型和外部类的类型相同,这个成员变量就是指向外部类对象的引用
- 编译器为内部类的构造方法添加一个参数,参数类型是外部类类型,在构造方法内部使用这个参数为1中添加的成员变量赋值
- 在调用内部类的构造函数初始化内部类对象时,会默认传入外部类的引用
下面我们举个例子来看一下:
public class Outter {private Inner inner = null;public Outter() {}public Inner getInnerInstance() {if(inner == null)inner = new Inner();return inner;}protected class Inner {public Inner() {}}
}

反编译Outter$Inner.class文件得到下面信息:
E:\Workspace\Test\bin\com\cxh\test2>javap -v Outter$Inner
Compiled from "Outter.java"
public class com.cxh.test2.Outter$Inner extends java.lang.ObjectSourceFile: "Outter.java"InnerClass:#24= #1 of #22; //Inner=class com/cxh/test2/Outter$Inner of class com/cxh/tes
t2/Outterminor version: 0major version: 50Constant pool:
const #1 = class #2; // com/cxh/test2/Outter$Inner
const #2 = Asciz com/cxh/test2/Outter$Inner;
const #3 = class #4; // java/lang/Object
const #4 = Asciz java/lang/Object;
const #5 = Asciz this$0;
const #6 = Asciz Lcom/cxh/test2/Outter;;
const #7 = Asciz <init>;
const #8 = Asciz (Lcom/cxh/test2/Outter;)V;
const #9 = Asciz Code;
const #10 = Field #1.#11; // com/cxh/test2/Outter$Inner.this$0:Lcom/cxh/t
est2/Outter;
const #11 = NameAndType #5:#6;// this$0:Lcom/cxh/test2/Outter;
const #12 = Method #3.#13; // java/lang/Object."<init>":()V
const #13 = NameAndType #7:#14;// "<init>":()V
const #14 = Asciz ()V;
const #15 = Asciz LineNumberTable;
const #16 = Asciz LocalVariableTable;
const #17 = Asciz this;
const #18 = Asciz Lcom/cxh/test2/Outter$Inner;;
const #19 = Asciz SourceFile;
const #20 = Asciz Outter.java;
const #21 = Asciz InnerClasses;
const #22 = class #23; // com/cxh/test2/Outter
const #23 = Asciz com/cxh/test2/Outter;
const #24 = Asciz Inner;{
/看中间的代码
final com.cxh.test2.Outter this$0; //注意这一行代码!!!
/看中间的代码
public com.cxh.test2.Outter$Inner(com.cxh.test2.Outter);Code:Stack=2, Locals=2, Args_size=20: aload_01: aload_12: putfield #10; //Field this$0:Lcom/cxh/test2/Outter;5: aload_06: invokespecial #12; //Method java/lang/Object."<init>":()V9: returnLineNumberTable:line 16: 0line 18: 9LocalVariableTable:Start Length Slot Name Signature0 10 0 this Lcom/cxh/test2/Outter$Inner;
}
final com.cxh.test2.Outter this$0;
这是一个指向外部类对象的指针.
也就是说,编译器会默认成员内部类添加了一个指向外部类对象的引用
那这个引用如何赋初值呢?
下面接着看一下内部类的构造器
public com.cxh.test2.Outter$Inner(com.cxh.test2.Outter);
虽然我们定义的内部类的构造器说无参构造器,但是编译器还是默认添加一个参数,该参数类型为指向外部类对象的一个引用.
所以成员内部类中的Outter this&0指针便指向类外部类对象,因此可以在成员内部类中随意访问外部类的成员.
从这里也间接说明了成员内部类是依赖外部类的,如果没有创建外部类的对象,则无法对Outter this&0引用进行初始化赋值,也就无法创建成员内部类的对象了.
成员内部类
成员内部类像是外部类的一个成员,它定义在另一个类的内部
成员内部类分为两种:
- 静态成员内部类: 使用static修饰类
- 非静态成员内部类: 不实用static修饰类,在没说明是静态成员内部类时,默认成员内部类指的是非静态成员内部类
静态内部类
定义在类内部的静态类,就是静态内部类.
静态内部类不需要依赖外部类,这点和静态成员属性类似,并且它不能使用外部类的非Static成员变量和方法.
因为没有外部类对象的情况下,我们可以创建出静态内部类对象,这时候如果允许访问外部类的非Static成员就会产生矛盾,因为外部类的非Static成员必须依附于具体的对象.
public class Out {private static int a;private int b;public static class Inner {public void print() {System.out.println(a);}}
}
访问作用域: 可以访问外部类所有的静态变量和方法,即使是private也一样可以访问
与类不同点: 和一般类一致,可以定义静态变量,方法,构造方法等
使用的方法: 外部类.静态内部类。如:Out.Inner inner = new Out.Inner();inner.print();
非静态内部类
最普通的内部类,定义位于另一类的内部,如下面形式
class Circle {double radius = 0;public Circle(double radius) {this.radius = radius;}class Draw { //内部类public void drawSahpe() {System.out.println("drawshape");}}
}
类Draw像是Circle的一个成员,Circle称为外部类.
成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)
但是外部类访问内部类成员,首先要创建一个成员内部类的对象,再通过指向这个对象的引用来访问
- 内部类访问外部类
class Circle {private double radius = 0;public static int count =1;public Circle(double radius) {this.radius = radius;}class Draw { //内部类public void drawSahpe() {System.out.println(radius); //外部类的private成员System.out.println(count); //外部类的静态成员}}
}
- 外部类访问内部类
class Circle {private double radius = 0;public Circle(double radius) {this.radius = radius;getDrawInstance().drawSahpe(); //必须先创建成员内部类的对象,再进行访问}private Draw getDrawInstance() {return new Draw();}class Draw { //内部类public void drawSahpe() {System.out.println(radius); //外部类的private成员}}
}
注意:
成员内部类和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员.
如果要访问外部类的同名成员,需要以下面的形式进行访问:
外部类.this.成员变量
外部类.this.成员方法
局部内部类
定义在一个方法或者一个作用域里面的内,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内.
class People{public People() {}
}class Man{public Man(){}public People getWoman(){class Woman extends People{ //局部内部类int age =0;}return new Woman();}
}
注意: 局部内部类就像是方法里面的一个局部变量一样,是不能有public,protected,private以及static修饰符的.
匿名内部类
这个应该是我们编写代码时用的最多的,在编写事件监听的代码时使用匿名内部类不但方便,而且使代码更容易维护.
public class AnonymousInnerClassExample { public static void main(String[] args) { // 创建一个订单处理系统实例 OrderProcessingSystem system = new OrderProcessingSystem(); // 创建一个订单实例 Order order = new Order("123", 100.0); // 使用匿名内部类实现一个打印订单详情的处理器 system.process(order, new OrderProcessor() { @Override public void processOrder(Order order) { System.out.println("Processing order: " + order); } }); // 使用另一个匿名内部类实现一个打折处理订单的逻辑 system.process(order, new OrderProcessor() { @Override public void processOrder(Order order) { double discount = 0.1; // 假设打10%的折扣 double discountedAmount = order.getAmount() * (1 - discount); System.out.println("Processing discounted order: " + order + " with discounted amount: " + discountedAmount); } }); }
}
使用匿名内部类的好处显而易见
- 比较简洁,只需要实现一个接口的简单任务,使用它可以避免创建额外命名类
- 可以访问其外部类所有成员(包括私有成员),在某些场景下非常灵活
内部类的使用场景和好处
为什么Java需要内部类呢?总结有下面四点:
- 每个内部类都能独立的继承一个接口的实现,无论外部类是否已经继承了某个(接口)实现,对于内部类都没有影响.
解释一下:
Java本身不支持类的多继承,但可以通过内部类来实现接口的多继承效果.我们可以在一个外部类中定义多个内部类,每个内部类都可以实现不同的接口,从而达到类类似多继承的效果.
举个简单的代码例子来说明这个概念:
// 定义一个接口A
interface InterfaceA { void methodA();
} // 定义另一个接口B
interface InterfaceB { void methodB();
} // 外部类
class OuterClass { // 外部类可以继承自一个类,这里我们假设它继承自Object(实际上所有类都隐式继承自Object) // 内部类1,实现接口A private class InnerClassA implements InterfaceA { @Override public void methodA() { System.out.println("Implementing methodA from InterfaceA in InnerClassA"); } } // 内部类2,实现接口B private class InnerClassB implements InterfaceB { @Override public void methodB() { System.out.println("Implementing methodB from InterfaceB in InnerClassB"); } } // 可以提供获取内部类实例的方法 public InterfaceA getInnerClassA() { return new InnerClassA(); } public InterfaceB getInnerClassB() { return new InnerClassB(); }
} // 使用示例
public class Main { public static void main(String[] args) { OuterClass outer = new OuterClass(); // 获取并调用内部类A的方法 InterfaceA innerA = outer.getInnerClassA(); innerA.methodA(); // 获取并调用内部类B的方法 InterfaceB innerB = outer.getInnerClassB(); innerB.methodB(); }
}
- 方便将存在一定逻辑关系的类组织在一起,又可以对外界隐藏
- 方便编写事件驱动程序
- 方便编写线程代码
相关文章:
Java基础-内部类
内部类 引言内部类的共性成员内部类静态内部类非静态内部类 局部内部类匿名内部类内部类的使用场景和好处 引言 Java不仅可以定义变量和方法,还可以定义类. 内部类允许你把一些逻辑相关的类组织在一起,并可以控制内部中类的可见性. 这么看来,内部类就像是代码一种隐藏机制:将类…...
设计模式-行为型模式-职责链模式
在软件系统运行时,对象并不是孤立存在的,它们可以通过相互通信协作完成某些功能,一个对象在运行时也将影响到其他对象的运行。行为型模式(Behavioral Pattern)关注系统中对象之间的交互,研究系统在运行时对…...
代码随想录算法训练营第四十天|LeetCode343 整数拆分、LeetCode96 不同的二叉搜索树
343.整数拆分 思路:确定dp数组以及下标的含义 dp[i]代表 i可以被拆分后的最大乘积。确定递推公式,假如拆成连个数,dp[i] j*(i-j),拆成两个数以上,dp[i]j*dp[i-j],j的范围为1到i-1.dp[i]找到所有情况的最大值。初始化…...
接口自动化测试用例如何设计
说到自动化测试,或者说接口自动化测试,多数人的第一反应是该用什么工具,比如:Python Requests、Java HttpClient、Apifox、MeterSphere、自研的自动化平台等。大家似乎更关注的是哪个工具更优秀,甚至出现“ 做平台的 &…...
弱电综合布线:连接现代生活的纽带
在当今信息化快速发展的时代,弱电网络布线作为信息传输的重要基础设施,其作用日益凸显。它不仅保障了数据的高效流通,还确保了通信的稳定性。从商业大厦到教育机构,从政府机关到医院急救中心,再到我们居住的社区&#…...
Java零基础 - 数组的定义和声明
哈喽,各位小伙伴们,你们好呀,我是喵手。 今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流,互相学习,一个人虽可以走的更快,但一群人可以走的更远。 我是一名后…...
CSS补充(下),弹性布局(上)
高级选择器 1.兄弟选择器 2.同时满足 div.bg{background-color: red;}p.bg{background-color: green;}spam.bg{background-color: blue;}注:选择器中间没有空格,有明确标识的选择器写在后面 3.各种伪类的应用 3.1作为第几个子元素 选择器:nth-child…...
图数据库 之 Neo4j - 应用场景4 - 反洗钱(9)
原理 Neo4j图数据库可以用于构建和分析数据之间的关系。它使用节点和关系来表示数据,并提供实时查询能力。通过使用Neo4j,可以将大量的交易数据导入图数据库,并通过查询和分析图结构来发现洗钱行为中的模式和关联。 案例分析 假设有一家转账服务公司,有以下交易数据,每个…...
uboot分区介绍
RK平台的U-Boot支持两种分区表 RK paramter格式(旧)和 标准GPT格式(新),当机器上同时存在 两种分区表时,优先使用GPT分区表。无论是 GPT 还是 RK parameter,烧写用的分区表文件都叫parameter.t…...
快速收集诊断信息,敏捷诊断工具obdiag应用实践——《OceanBase诊断系列》之三
1. 前言 作为OceanBase的敏捷诊断工具,obdiag具有以下特点: 部署便捷:提供rpm包和OBD上部署的模式,都能够一键部署安装。用户可以选择将其部署到集群中任意一台能连接到各个节点的设备上,而不仅限于OBServer节点。即…...
C++错误总结(1)
1.定义函数类型时,如果没有返回值,用void void swap(int &x, int &y){ int tem x; x y; y tem; } 2.输入时,不加换行符 cin >> a >> b >> c >> endl ;(红色标记的是错误的部分) 3.【逆序出入…...
std::shared_from_this注意事项:exception bad_weak_ptr
1.不可以在构造函数中调用shared_from_this() 因为它的实现是: _LIBCPP_INLINE_VISIBILITYshared_ptr<_Tp> shared_from_this(){return shared_ptr<_Tp>(__weak_this_);}也就是它依赖的__weak_this_此时还未创建完成。 2.一定要public继承 class MyTy…...
【工具】Raycast – Mac提效工具
引入 以前看到同事们锁屏的时候,不知按了什么键,直接调出这个框,然后输入lock屏幕就锁了。 跟我习惯的按Mac开机键不大一样。个人觉得还是蛮炫酷的~ 调研 但是由于之前比较繁忙,这件事其实都忘的差不多了࿰…...
蓝桥杯集训·每日一题2024 (二分,双指针)
前言: 开学了,平时学习的压力也逐渐大起来了,不过还算可以接受,等到后面阶段考的时候就不一样了,我目前为了转专业退选了很多课,这些课我都需要花时间来刷绩点,不然保研就没有竞争力了。我自己会…...
在Linux(Ubuntu)中使用终端编译 vscode安装
文章目录 📚在Linux(Ubuntu)中使用终端编译🐇.cpp程序编译🐇.py程序编译🐇查看Python、C编程环境 📚vscode安装 📚在Linux(Ubuntu)中使用终端编译 虚拟机安装…...
官网正在被哪些产品蚕食,定制网站又被哪些建站产品挤占。
2023-12-09 16:22贝格前端工场 官网建设是一个被大多数人看衰的市场,本文来理性分析下,谁在蚕食这个市场,谁又在挤占这个产品生存空间,欢迎大家评论,探讨。 网站正在被以下产品形式取代: 1. 移动应用&…...
BUUCTF---[MRCTF2020]你传你呢1
1.题目描述 2.打开题目链接 3.上传shell.jpg文件,显示连接成功,但是用蚁剑连接却连接不上。shell文件内容为 <script languagephp>eval($_REQUEST[cmd]);</script>4.用bp抓包,修改属性 5.需要上传一个.htaccess的文件来把jpg后缀…...
vite+vue3门户网站菜单栏动态路由控制
门户网站用户端需要分板块展示,板块内容由管理端配置,包括板块名称,访问路径,路由组件,展示顺序,是否展示。如下图所示: 用户访问门户网站时,展示菜单跳转通过板块配置,动…...
【C语言】linux内核packet_setsockopt
一、中文注释 // 发送数据包函数。它尝试通过特定的网络设备队列直接传输一个skb(socket缓冲区)。 static int packet_direct_xmit(struct sk_buff *skb) {return dev_direct_xmit(skb, packet_pick_tx_queue(skb)); // 调用dev_direct_xmit函数&#x…...
LeetCode的使用方法
LeetCode的使用方法 一、LeetCode是什么?1.LeetCode简介2.LeetCode官网 二、LeetCode的使用方法1.注册账号2.力扣社区力扣编辑器 2.1 讨论发起讨论参与讨论关注讨论 2.2 文章撰写文章关注文章 3.力扣面试官版测评面试招聘竞赛 4.力扣学习LeetBook 书架我的阅读猜您喜…...
治疗方案优化系统
1. 系统概述 1.1 是什么 治疗方案优化系统(Treatment Plan Optimization System, TPOS)是 CANS 架构中负责多目标治疗方案生成与优化的决策智能体系统。它基于诊断结果、患者个体化生理模型、药物规划方案和患者偏好,在多个候选治疗方案中进行…...
Parsec VDD:Windows虚拟显示器终极解决方案,免费扩展你的数字工作空间
Parsec VDD:Windows虚拟显示器终极解决方案,免费扩展你的数字工作空间 【免费下载链接】parsec-vdd ✨ Perfect virtual display for game streaming 项目地址: https://gitcode.com/gh_mirrors/pa/parsec-vdd 在当今多任务处理日益普及的数字时代…...
用CH9329做个扫码枪?手把手教你串口转USB HID的完整开发流程(附代码)
用CH9329打造低成本扫码枪:从硬件连接到键码映射的全流程解析 在零售仓储、图书馆管理等场景中,扫码枪作为高效的数据录入工具早已普及,但商用设备动辄上千元的售价让个人开发者和小型项目望而却步。其实借助CH9329这款国产串口转USB HID芯片…...
用动态规划思路,一步一步实现响应式数据(从本质到落地)
用动态规划思路,一步一步实现响应式数据(从本质到落地) 文章目录用动态规划思路,一步一步实现响应式数据(从本质到落地)一、第一步:用动态规划逆向思维,拆解响应式的“最终目标”1. …...
别再死记硬背!用Three.js十分钟搞懂透视投影与正交投影的区别
十分钟用Three.js实战理解透视与正交投影的本质区别 记得第一次接触3D开发时,我被各种投影矩阵公式折磨得头晕眼花。直到某天在Three.js里拖动相机参数滑块,看着实时变化的3D场景突然开窍——原来理解投影方式最有效的方式不是背诵公式,而是亲…...
基于KITTI数据集:从LIO-SAM算法适配到EVO精度评估全流程解析
1. KITTI数据集准备与格式转换 第一次接触KITTI数据集时,我被它庞大的数据量和复杂的目录结构搞得一头雾水。经过多次实践,我总结出一套最高效的处理流程。KITTI作为自动驾驶领域最权威的公开数据集,包含城市、乡村和高速公路等多种场景的传感…...
HCPL-553K,密封、晶体管输出光耦合器
简介今天我要向大家介绍的是 Broadcom 的光耦合器——HCPL-553K。这是一款双通道、密封晶体管输出光耦合器,适用于模拟和数字应用。它内部每个通道均包含一个GaAsP发光二极管,并光学耦合至集成光子探测器,通过分离的光电二极管和输出晶体管集…...
口碑好的不锈钢彩涂板企业
朋友,最近是不是在头疼选不锈钢彩涂板的事儿?是不是感觉市场上牌子五花八门,价格从几十到几百一平都有,销售说得天花乱坠,自己却越看越懵圈?别急,今天咱不聊虚的,就跟你像朋友一样唠…...
深入QN8027寄存器:从芯片手册到C代码,一次搞懂FM发射配置(避坑指南)
深入QN8027寄存器:从芯片手册到C代码,一次搞懂FM发射配置(避坑指南) 在嵌入式FM发射器开发中,QN8027因其高集成度和低功耗特性成为热门选择。但真正让工程师头疼的,往往是芯片手册中晦涩的寄存器描述与实际…...
Phi-3.5-mini-instruct系统提示词设计:专家/教师/程序员角色设定
Phi-3.5-mini-instruct系统提示词设计:专家/教师/程序员角色设定 1. 模型概述 Phi-3.5-mini-instruct是微软推出的轻量级指令微调大语言模型,采用Transformer解码器架构,支持128K超长上下文窗口。该模型针对多语言对话、代码生成和逻辑推理…...
