java基础(十三)-----详解内部类——Java高级开发必须懂的
java基础(十三)-----详解内部类——Java高级开发必须懂的
目录
- 为什么要使用内部类
- 内部类基础
- 静态内部类
- 成员内部类
- 成员内部类的对象创建
- 继承成员内部类
- 局部内部类
- 推荐博客
- 匿名内部类
正文
可以将一个类的定义放在另一个类的定义内部,这就是内部类。
为什么要使用内部类
为什么要使用内部类?在《Think in java》中有这样一句话:使用内部类最吸引人的原因是:每个内部类都能独立地继承一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。
在我们程序设计中有时候会存在一些使用接口很难解决的问题,这个时候我们可以利用内部类提供的、可以继承多个具体的或者抽象的类的能力来解决这些程序设计问题。可以这样说,接口只是解决了部分问题,而内部类使得多重继承的解决方案变得更加完整。
public interface Father {}public interface Mother {}public class Son implements Father, Mother {}public class Daughter implements Father{class Mother_ implements Mother{} }
其实对于这个实例我们确实是看不出来使用内部类存在何种优点,但是如果Father、Mother不是接口,而是抽象类或者具体类呢?这个时候我们就只能使用内部类才能实现多重继承了。
其实使用内部类最大的优点就在于它能够非常好的解决多重继承的问题,但是如果我们不需要解决多重继承问题,那么我们自然可以使用其他的编码方式,但是使用内部类还能够为我们带来如下特性(摘自《Think in java》):
1、内部类可以用多个实例,每个实例都有自己的状态信息,并且与其他外围对象的信息相互独立。
2、在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或者继承同一个类。
3、创建内部类对象的时刻并不依赖于外围类对象的创建。
4、内部类并没有令人迷惑的“is-a”关系,他就是一个独立的实体。
5、内部类提供了更好的封装,除了该外围类,其他类都不能访问。
内部类基础
在这个部分主要介绍内部类如何使用外部类的属性和方法,以及使用.this与.new。
当我们在创建一个内部类的时候,它无形中就与外围类有了一种联系,依赖于这种联系,它可以无限制地访问外围类的元素。
public class OuterClass {private String name ;private int age;/**省略getter和setter方法**/public class InnerClass{public InnerClass(){name = "chenhao";age = 23;}public void display(){System.out.println("name:" + getName() +" ;age:" + getAge());}}public static void main(String[] args) {OuterClass outerClass = new OuterClass();OuterClass.InnerClass innerClass = outerClass.new InnerClass();innerClass.display();} } -------------- Output: name:chenhao ;age:23
在这个应用程序中,我们可以看到内部了InnerClass可以对外围类OuterClass的属性进行无缝的访问,尽管它是private修饰的。这是因为当我们在创建某个外围类的内部类对象时,此时内部类对象必定会捕获一个指向那个外围类对象的引用,只要我们在访问外围类的成员时,就会用这个引用来选择外围类的成员。
其实在这个应用程序中我们还看到了如何来引用内部类:引用内部类我们需要指明这个对象的类型:OuterClasName.InnerClassName。同时如果我们需要创建某个内部类对象,必须要利用外部类的对象通过.new来创建内部类:OuterClass.InnerClass innerClass = outerClass.new InnerClass();。
同时如果我们需要生成对外部类对象的引用,可以使用OuterClassName.this,这样就能够产生一个正确引用外部类的引用了。当然这点实在编译期就知晓了,没有任何运行时的成本。
public class OuterClass {public void display(){System.out.println("OuterClass...");}public class InnerClass{public OuterClass getOuterClass(){return OuterClass.this;}}public static void main(String[] args) {OuterClass outerClass = new OuterClass();OuterClass.InnerClass innerClass = outerClass.new InnerClass();innerClass.getOuterClass().display();} } ------------- Output: OuterClass...
到这里了我们需要明确一点,内部类是个编译时的概念,一旦编译成功后,它就与外围类属于两个完全不同的类(当然他们之间还是有联系的)。对于一个名为OuterClass的外围类和一个名为InnerClass的内部类,在编译成功后,会出现这样两个class文件:OuterClass.class和OuterClass$InnerClass.class。
在Java中内部类主要分为静态内部类、成员内部类、局部内部类、匿名内部类。
静态内部类
静态内部类: 一般也称”静态嵌套类“,在类中用static
声明的内部类。
因为是static,所以不依赖于外围类对象实例而独立存在,静态内部类的可以访问外围类中的所有静态成员,包括private
的静态成员,但是它不能使用任何外围类的非static成员变量和方法。
同时静态内部类可以说是所有内部类中独立性最高的内部类,其创建对象、继承(实现接口)、扩展子类等使用方式与外围类并没有多大的区别。
public class OuterClass {//外围类public int aa; //实例成员private static float f = 1.5f;//private的静态成员static void println() {System.out.println("这是静态方法");}protected static class StaticInnerClass{//protected的静态内部类float a;public StaticInnerClass() {a = f;// 外围类的private静态变量println();//外围类的静态方法 }} } class OtherClass{public static void main(String[] args) {//创建静态内部类的对象OuterClass.StaticInnerClass staticInnerClass = new OuterClass.StaticInnerClass(); } }
成员内部类
成员内部类: 定义在类的内部,而且与成员方法、成员变量同级,即也是外围类的成员之一,因此 成员内部类 与 外围类 是紧密关联的。
注意:
这种紧密关联指的是,成员内部类的对象的创建必须依赖于外围类的对象(即没有外围类对象,就不可能创建成员内部类)。因此,成员内部类有以下3个特点:
成员内部类可以访问外围类的所有成员,包括私有成员;
成员内部类是不可以声明静态成员(包括静态变量、静态方法、静态成员类、嵌套接口),但有个例外---可以声明
static final
的变量, 这是因为编译器对final
类型的特殊处理,是直接将值写入字节码;成员内部类对象都隐式地保存了一个引用,指向创建它的外部类对象;或者说,成员内部类的入口是由外围类的对象保持着(静态内部类的入口,则直接由外围类保持着)
成员内部类中的 this,new关键字:
获取外部类对象:
OuterClass.this
明确指定使用外部类的成员(当内部类与外部类的名字冲突时):
OuterClass.this
.成员名创建内部类对象的new:
外围类对象.new
//先创建外围类对象 OuterClass outer=new OuterClass(); //创建成员内部类对象 OuterClass.InnerClass inner=outer.new InnerClass();
成员内部类的对象创建
我们知道,成员内部类就像外围类的实例成员一样,一定要存在对象才能访问,即成员内部类必须绑定一个外围类的对象。上面已经介绍了成员内部类的创建格式了,我们直接看一个例子
public class OuterClass {//外围类public int aa; //实例成员private static float f = 1.5f;//private的静态成员public void initInnerClass() {System.out.println("内部类的初始化方法");}public void createInnerClass() {////外围类的成员方法中创建成员内部类对象InnerClass innerClass = new InnerClass();}class InnerClass{//成员内部类private double aa; //与围类的变量aa的名字重复public InnerClass(){this.aa = OuterClass.this.aa + f;//明确指定两个aa的所属 initInnerClass();}} } //其他类 class OtherClass{public static void main(String[] args) {//其他类中创建成员内部类OuterClass oc = new OuterClass();//外部类对象//创建内部类对象OuterClass.InnerClass innerClass = oc.new InnerClass();} }
补充几点:
成员内部类可以继续包含成员内部类,而且不管一个内部类被嵌套了多少层,它都能透明地访问它的所有外部类所有成员;
成员内部可以继续嵌套多层的成员内部类,但无法嵌套静态内部类;静态内部类则都可以继续嵌套这两种内部类。
class InnerClass{//成员内部类private double aa; //与围类的变量aa的名字重复public InnerClass(){this.aa = OuterClass.this.aa + f;//明确指定两个aa的所属 initInnerClass();}public class InnerInnerCalss2{//成员内部类中的成员内部类protected double aa = OuterClass.this.aa;//最外层的外围类的成员变量}//InnerInnerCalss2 }//InnerClass
继承成员内部类
在内部类的访问权限允许的情况下,成员内部类也是可以被继承的。由于成员内部类的对象依赖于外围类的对象,或者说,成员内部类的构造器入口由外围类的对象把持着。因此,继承了成员内部类的子类必须要与一个外围类对象关联起来。同时,子类的构造器是必须要调用父类的构造器方法,所以也只能通过父类的外围类对象来调用父类构造器。
下面的例子也是基于上面的例子的,只贴出多出的部分代码。
class ChildClass extends OuterClass.InnerClass{//成员内部类的子类的构造器的格式public ChildClass(OuterClass outerClass) {outerClass.super();//通过外围类的对象调用父类的构造方法 } }
局部内部类
局部内部类: 就是在方法、构造器、初始化块中声明的类,在结构上类似于一个局部变量。因此局部内部类是不能使用访问修饰符。
局部内部类的两个访问限制:
对于局部变量,局部内部类只能访问
final
的局部变量。不过,后期JDK(忘了是JDK几了)局部变量可不用final修饰,也可以被局部内部类访问,但你必须时刻记住此局部变量已经是final了,不能再改变。对于类的全局成员,局部内部类定义在实例环境中(构造器、对象成员方法、实例初始化块),则可以访问外围类的所有成员;但如果内部类定义在静态环境中(静态初始化块、静态方法),则只能访问外围类的静态成员。
public class OuterClass {private int a = 21;static {//静态域中的局部内部类class LocalClass1{// int z = a; //错误,在静态的作用域中无法访问对象成员 }}{//实例初始化块中的局部内部类class localClass2{ }}public OuterClass(){int x = 2;final int y = 3;// x = 3;//若放开此行注释,编译无法通过,因为局部变量x已经是final类型//构造器中的局部内部类class localClass3{int z = y; //可以访问final的局部变量int b = a;//可以访问类的所有成员//访问没有用final修饰的局部变量int c = x;}}public void createRunnable() {final int x = 4;//方法中的局部内部类class LocalClass4 implements Runnable {// @Overridepublic void run() {System.out.println("局部final变量:"+x);System.out.println("对象成员变量:"+a);}}} }
推荐博客
程序员写代码之外,如何再赚一份工资?
匿名内部类
匿名内部类: 与局部内部类很相似,只不过匿名内部类是一个没有给定名字的内部类,在创建这个匿名内部类后,便会立即用来创建并返回此内部类的一个对象引用。
作用:匿名内部类用于隐式继承某个类(重写里面的方法或实现抽象方法)或者实现某个接口。
匿名内部类的访问限制: 与局部内部类一样,请参考局部内部类;
匿名内部类的优缺点:
优点: 编码方便快捷;
缺点:
只能继承一个类或实现一个接口,不能再继承其他类或其他接口。
只能用于创建一次对象实例;
下面的例子是我们创建线程时经常用到的匿名内部类的方式来快速地创建一个对象的例子:
class MyOuterClass {private int x = 5;void createThread() {final int a = 10;int b = 189;// 匿名内部类继承Thread类,并重写Run方法Thread thread = new Thread("thread-1") {int c = x; //访问成员变量int d = a; //final的局部变量int e = b; //访问没有用final修饰的局部变量 @Overridepublic void run() {System.out.println("这是线程thread-1");}};// 匿名内部类实现Runnable接口Runnable r = new Runnable() {@Overridepublic void run() {System.out.println("线程运行中");}};} }
相关文章:

C++中函数指针的使用
A function pointer is a variable that stores the address of a function that can later be called through that function pointer. This is useful because functions encapsulate behavior.函数指针是一个指向函数的指针,函数指针表示一个函数的入口地址。指针是变量&…

只做好CTR预估远不够,淘宝融合CTR、GMV、收入等多目标有绝招
作者 | 吴海波转载自知乎用户吴海波【导读】一直以来,电商场景就存在 ctr、cvr、gmv、成交 uv 等多个目标,都是核心指标。理想情况下,提升 ctr 就能提升 gmv,但本文作者认为,在一定程度上, ctr 和 gmv 并不…

Android监听HOME按键
2019独角兽企业重金招聘Python工程师标准>>> <!-- lang: java --> class HomeKeyEventBroadCastReceiver extends BroadcastReceiver {static final String SYSTEM_REASON "reason";static final String SYSTEM_HOME_KEY "homekey";// …

OpenCV代码提取:merge/split函数的实现
对OpenCV中的merge/split函数进行了实现,经测试,与OpenCV3.1结果完全一致。merge实现代码merge.hpp:// fbc_cv is free software and uses the same licence as OpenCV // Email: fengbingchun163.com#ifndef FBC_CV_MERGE_HPP_ #define FBC_…

DeepMind提图像生成的递归神经网络DRAW,158行Python代码复现
作者 | Samuel Noriega译者 | Freesia编辑 | 夕颜出品 | AI科技大本营(ID: rgznai100)【导读】最近,谷歌 DeepMInd 发表论文( DRAW: A Recurrent Neural Network For Image Generation),提出了一个用于图像生成的递归神…

其他进制的数字
JS中如果需要表示16进制的数字,则需要以0X开头 0X10 八进制数字以0开头 070 070有些浏览器会以8进制解析,但是有些则用10进制解析,10进制为70,8进制为56 所以parseint() 第二个参数可以设定进制,比如 parseint(“070”,10)代表以10进制解析070 2进制以0b开头,但是不是所有浏览…

java中的移位运算符
移位运算符是在数字的二进制形式上进行平移。主要有左移(<<)、带符号右移(>>)以及无符号右移(>>>)。左移运算符(<<)的运算规则为:按二进制形…

C++11中nullptr的使用
在C语言中,NULL实际上是一个void* 的指针,然后把void* 指针赋值给其它类型的指针的时候,会隐式转换成相应的类型。而如果用一个C编译器来编译的时候是要出错的,因为C是强类型的,void* 是不能隐式转换成其它指针类型的。…

埃森哲、亚马逊和万事达卡抱团推出的区块链项目有何神通?
据外媒报道,今日埃森哲宣布了一项新的区块链项目,该项目为基于区块链的循环供应链,将与万事达卡和亚马逊共同合作。据官方介绍,这个基于区块链的循环供应链能够让客户识别供应链上的小规模供应商和种植者,例如…

小团队如何玩转物联网开发?
近几年来,物联网发展迅速:据中商产业研究院《2016——2021年中国物联网产业市场研究报告》显示,预计到2020年,中国物联网的整体规模将达2.2万亿元,产业规模比互联网大30倍。与之相反的是,物联网开发者在开发…

Build Boost C++ libraries for x32/x64 VC++ compilers on Windows
2019独角兽企业重金招聘Python工程师标准>>> Boost is a set of libraries for the C programming language that provide support for tasks and structures such as linear algebra, pseudorandom number generation, multithreading, image processing, regular …

C++11中auto的使用
在C语言中,就有了auto关键字,它被当作是一个变量的存储类型修饰符,表示自动变量(局部变量)。它不能被单独使用,否则编译器会给出警告。在C11标准中,添加了新的类型推导特性。在C 11中,使用auto定义的变量不…
攻和防谁更厉害?AI技术在恶意软件检测中的应用和对抗
AI技术的发展为网络安全带来新机遇的同时,黑客也在逐渐利用AI漏洞建立对抗样本以躲避攻击,双方在各自领域的更多尝试也将是AI技术发展的一场新博弈。那么,在应用中,如何利用AI检测技术与恶意软件展开对抗? 腾讯安全技术…

一文看懂机器学习中的常用损失函数
作者丨stephenDC编辑丨zandy来源 | 大数据与人工智能(ID: ai-big-data)导语:损失函数虽然简单,却相当基础,可以看做是机器学习的一个组件。机器学习的其他组件,还包括激活函数、优化器、模型等。本文针对机…

Using Apache2 with JBoss AS7 on Ubuntu
大体思路同《Using Apache Web Server with Jboss AS 7》一致,但在Ubuntu上的操作与之前有些区别。 这里仍然演示mod_proxy的配置。 首先加载相应的模块。Ubuntu中加载模块和卸载模块均可以通过命令操作,与其对应的命令分别是a2enmod和a2dismod。 启用…
OpenCV代码提取:rotate函数的实现
OpenCV中并没有直接提供实现rotate的函数,这里通过getRotationMatrix2D和warpAffine函数实现rotate,并增加了一个crop参数,用来判断是否进行crop。目前支持uchar和float两种类型,经测试,与OpenCV3.1结果完全一致。公式…
在 Node.js 中用子进程操作标准输入/输出
翻译:疯狂的技术宅原文:http://2ality.com/2018/05/chi... 本文首发微信公众号:jingchengyideng欢迎关注,每天都给你推送新鲜的前端技术文章 在本中,我们在 Node.js 中把 shell 命令作为子进程运行。然后异步读取这些进…

再见,Python 2.x
整理 | 屠敏来源 | CSDN(ID:CSDNnews)在技术的长河中,软件、工具、系统等版本的迭代本是常事,但由于使用习惯、版本的兼容性、易用性等因素,很多用户及开发者在使用或做开发的过程中,并不愿意及…

Android UI系列-----CheckBox和RadioButton(1)
主要记录一下CheckBox多选框和RadioGroup、RadioButton单选框的设置以及注册监听器 1.CheckBox 布局文件: <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"xmlns:tools"http://schemas.android.com/tools"android…

C++中struct的使用
C语言继承了C语言的struct,并且加以扩充。在C语言中struct是只能定义数据成员,而不能定义成员函数的。而在C中,struct类似于class,在其中既可以定义数据成员,又可以定义成员函数。结构类型是用户定义的复合类型&#x…

填报表中也可以添加 html 事件
在实际的项目开发中,填报表的应用十分广泛。 多数情况下,填报表会作为整个项目的一部分配合需求灵活使用,但有时也会受大项目环境的影响,产生一些特别的要求。比如,通常报表单元格的数据类型大多是文本,有时…

60+业内技术专家,9大核心技术专题,AI ProCon倒计时一周!
2018 年,由 CSDN 举办的第一届 AI 开发者大会喊出“只讲技术,拒绝空谈”,两天会议时间,国内外几十家顶尖科技企业讲述了其主流技术及其应用案例,真正引领国内开发者紧跟技术浪潮。一年过去,在你还未有所觉察…

密码学研究-数字签名
引入:提到签名,大家都不陌生,大家知道,重大的文件一般都要领导签名,来确保这个文件的真实有效。而一些比较重要的合同,比如买房的购房合同,都要盖“骑缝章”,这个骑缝章,…
C++11中shared_ptr的使用
在C中,动态内存的管理是通过一对运算符来完成的:new,在动态内存中为对象分配空间并返回一个指向该对象的指针,可以选择对对象进行初始化;delete,接受一个动态对象的指针,销毁该对象,…

colly源码学习
colly源码学习 colly是一个golang写的网络爬虫。它使用起来非常顺手。看了一下它的源码,质量也是非常好的。本文就阅读一下它的源码。 使用示例 func main() {c : colly.NewCollector()// Find and visit all linksc.OnHTML("a[href]", func(e *colly.HTM…

可惜了,你们只看到“双马会”大型尬聊
作者 | 夕颜出品 | AI科技大本营(ID:rgznai100)导读:2019 年 8 月 29 日,世界人工智能大会(WAIC)在上海正式拉开帷幕。开幕式上,最让人瞩目的莫过于阿里巴巴前 CEO 马云与特斯拉 CEO Elon Musk …

Java 过滤特殊字符的 正则表达式
Java正则表达式学习: 因为正则表达式是一个很庞杂的体系,此例仅举些入门的概念,更多的请参阅相关书籍及自行摸索。 \\ 反斜杠 \t 间隔 (\u0009) \n 换行 (\u000A) \r 回车 (\u000D) \d 数字 等价于[0-9] \D 非数字 等价于[^0-9] \s 空…
C++11中unique_ptr的使用
在C中,动态内存的管理是通过一对运算符来完成的:new,在动态内存中为对象分配空间并返回一个指向该对象的指针,可以选择对对象进行初始化;delete,接受一个动态对象的指针,销毁该对象,…

从这篇YouTube论文,剖析强化学习在工业级场景推荐系统中的应用
作者 | 吴海波转载自知乎用户吴海波【导读】本文作者根据两篇工业界背景的论文解答了 RL 在推荐场景需要解决的问题与困难,以及入门需要学习得相关知识点。2 个月前,业界开始流传 youtube 成功将 RL 应用在了推荐场景,并且演讲者在视频中说是…

java中两个Integer类型的值相比较的问题
转载自: https://www.cnblogs.com/xh0102/p/5280032.html 两个Integer类型整数进行比较时,一定要先用intValue()方法将其转换为int数之后再进行比较,因为直接使用比较两个Integer会出现问题。 总结: 当给Integer直接赋值时&#x…