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 书架我的阅读猜您喜…...

Vue事件处理:.passive修饰符与应用场景
.passive修饰符 passive这个修饰符会执行默认方法。你们可能会问,明明默认执行为什么会设置这样一个修饰符。这就要说一下这个修饰符的本意了。 浏览器只有等内核线程执行到事件监听器对应的JavaScript代码时,才能知道内部是否会调用preventDefa…...

智慧城市中的数字孪生:构建城市管理的未来框架
目录 一、引言 二、数字孪生技术概述 三、数字孪生技术在智慧城市中的应用 1、实时监测与预警 2、模拟与优化 3、智能化决策 4、协同与共享 四、数字孪生技术构建城市管理的未来框架的价值 1、提高管理效率 2、优化资源配置 3、提升公共服务水平 4、增强应对突发事…...

强引用、软引用、弱引用、幻象引用 —— Java的四种引用类型解析
强引用、软引用、弱引用、幻象引用 —— Java的四种引用类型解析 在Java中,对象的生命周期并不总是由我们直接控制。除了我们常见的强引用外,Java还提供了软引用、弱引用和幻象引用这三种引用类型,它们对对象生命周期的影响各不相同。理解这…...

基于禁忌搜索算法(TS)的TSP(Python实现)
本篇文章是博主在最化优学习、人工智能等领域学习时,用于个人学习、研究或者欣赏使用,并基于博主对相关等领域的一些理解而记录的学习摘录和笔记,若有不当和侵权之处,指出后将会立即改正,还望谅解。文章分类在最优化算…...

Linux shell 网络掩码地址转CIDR
例子: ./1.sh 255.255.255.0 ./1.sh 255.255.255.128 ./1.sh 255.255.0.0 源实现: #!/bin/bashnetmask_to_cidr() {local IFSlocal -a octetslocal i0local cidr0IFS. read -r -a octets <<< "$1"for octet in "${octets[]}…...

C#,煎饼排序问题(Pancake Sorting Problem)算法与源代码
1 煎饼排序问题 给定一个未排序的数组,任务是对给定数组进行排序。您只能在阵列上执行以下操作。 翻转(arr,i):将数组从0反转为i 示例: 输入:arr[]{23、10、20、11、12、6、7} 输出:…...

13.西瓜书——半监督学习
1.概述 (1) 纯半监督学习 (Pure Semi-Supervised Learning) 纯半监督学习是一种典型的半监督学习方法,它的主要特点是同时利用有标签数据和无标签数据进行模型训练。目标是通过整合这两种类型的数据来提高模型的泛化性能。在这个过程中&#…...

C++进阶之路---继承(二)
顾得泉:个人主页 个人专栏:《Linux操作系统》 《C从入门到精通》 《LeedCode刷题》 键盘敲烂,年薪百万! 一、继承与友元 友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员。 class Student; class Per…...

C及C++每日练习(3)
选择题: 1.以下程序的输出结果是() #include <stdio.h> main() { char a[10] {1, 2, 3, 4, 5, 6, 7, 8, 9, 0}, *p; int i; i 8; p a i; printf("%s\n", p - 3); } A.6 B. 6789 C. 6 D.789 对于本题࿰…...

黑马点评-附近商户实现
GEO数据结构 Redis在3.2版本中加入了对GEO的支持,允许存储地理坐标信息,根据经纬度来检索数据。 GEO本质上是基于sortedSet实现的,在Sorted Set中,每个成员都是与一个分数(score)相关联的,这个分数用于对成员进行排序…...