Java的内部类真的那么难以理解?
01 前言
昨天晚上,我把车停好以后就回家了。回家后才发现手机落在车里面了,但外面太冷,冷到骨头都能感受到寒意——实在是不想返回一趟去取了(小区的安保还不错,不用担心被砸车玻璃),于是打定主意过几个小时的“世外桃源”生活——别人找不到我,我也找不到别人,这种与世隔绝的状态非常适合读书写作。
把厚厚的《Java编程思想》摆在桌子上,正襟危坐,认认真真地读起了第十章——内部类。尽管我已经非常耐心和用心了,但内部类的这一章非常的枯燥,并且难以理解,我整个人几乎处于崩溃的边缘。
很早之前,有想要转行学习Java的朋友咨询我,有哪方面的书可以推荐,我郑重其事地介绍了《Java编程思想》,并且一再叮嘱他这是一本Java入门级的经典书,必须耐着性子读完它。现在想想,自己当时的推荐真是轻率!
我这样说,并不是为了否认《Java编程思想》这本书的价值,因为站在书本的角度,它可能会感慨说:这王二的学习能力有问题啊,读我竟然这么困难!
不是有那样一句话嘛:“如果你手里有一把锤子,所有东西看上去都像钉子。”我认为“内部类”这一章很难懂,其根本的原因在于我对“内部类”没有很好的理解。想要继续扎实Java的基础知识,唯一要做的就是——想尽一切办法搞懂“内部类”,并梳理成文。
02 内部类的定义
顾名思义,内部类就是放在另外一个类的内部定义的类。非常重要的一点是,内部类能够访问外部类的所有成员,包括private
修饰的。
来看程序清单1-1:
public class Wanger {
private int age;
public Wanger(int age) {
this.age = age;
}
class Thought {
public void know() {
System.out.println("沉默王二的年龄" + age);
}
}
public Thought getThought() {
return new Thought();
}
public static void main(String[] args) {
Wanger wanger = new Wanger(29);
Wanger.Thought thought = wanger.getThought();
thought.know(); // 输出:沉默王二的年龄29
// 使用.new的形式创建内部类对象
Wanger.Thought thought1 = wanger.new Thought();
thought1.know();
}
}
程序清单1-1要表达什么意思呢?
答案是:我,沉默王二,已经29岁了,89年出生(有人说89年出生明明是30岁)。上了年纪了,总想装点嫩,理解一下。我读书不多,但特别爱思考,于是我就给自己创建了一个会思考的内部类Thought。
从程序清单1-1可以看得出,尽管Thought是内部类,但可以访问外部类Wanger的私有成员变量age。
如果想创建内部类的对象,需要先指明对象引用的类型,格式为OuterClassName.InnerClassName,就像main()方法中的Wanger.Thought
那样。
紧接着,就要来创建内部类对象了,有两种形式。第一种形式是先在外部类中定义一个方法Thought getThought()
,返回使用new
关键字创建的内部类对象,然后使用外部类对象调用该方法wanger.getThought()
;第二种形式是直接通过外部类对象.new
创建wanger.new Thought()
。
03 匿名内部类
以我的编程经验来看,匿名内部类使用最频繁的场合就是在创建线程的时候。
来看程序清单2-1:
public class Demo {
public void test(String title) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
// title = "我不要吃鸡";
// 改变时会提示错误
// 在封闭范围中定义的局部变量必须是final的。
System.out.println(title);
}
});
thread.start();
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
Demo demo = new Demo();
demo.test("我要吃鸡" + i);
}
}
}
在程序清单2-1中,test()方法内部有一个线程对象thread,是通过new Thread()创建的。new Thread()
可以接收一个实现了Runnable接口类型的对象,这个对象要怎么创建呢?可以通过匿名内部类的形式来创建——new Runnable() {public void run(){......}}
——这段简短的代码等同于:
// 实现Runnable接口
class MyRunnable implements Runnable {
@Override
public void run() {
}
}
// 向上转型
Runnable myRunnable = new MyRunnable();
匿名内部类的好处就在于不仅节省了定义实现类的过程,还能够自动向上转型。
在程序清单2-1中,test()方法还有一个参数title,JDK1.8之前,编译器要求它必须是final类型的。但JDK1.8之后,如果我们在匿名内部类中需要访问局部变量,那么这个局部变量不再需要用final
关键字修饰了。
但如果想要在匿名内部类中改变局部变量的值,编译器就会提醒你不能这样做,它会提示:“在封闭范围中定义的局部变量必须是final的。”
04 为什么需要内部类
Java的内部类让我很容易的想起来JavaScript的闭包,闭包就是定义在一个函数内部的函数——这听起来和Java的内部类定义一样一样的。本质上,闭包是将函数内部与函数外部连接起来的桥梁。内部类一样,它是将内部类与外部类连接起来的桥梁。
来看看什么是闭包吧:
function wanger() {
var age = 30;
function know() {
console.log(age);
}
}
wanger();
// 控制台输出30
除此之外,内部类最引人注意的原因是:
内部类可以独立地继承一个抽象类或者实现一个接口,无论外部类是否也这样做了,对内部类都没有影响。
总而言之,内部类让类的内容丰富多彩,就好像一个人的心中还可以住着另外一个人。
Java的内部类真的那么难以理解?的更多相关文章
- 夯实Java基础系列18:深入理解Java内部类及其实现原理
本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点下 ...
- java中内部类使用小结
内部类是指在一个外部类中再定义一个类,类名不需要和文件名相同 内部类可以是静态的,类的修饰符可以是private,default,protect,public修饰 ,而外部类只能是public 和 d ...
- JAVA基础——内部类详解
JAVA内部类详解 在我的另一篇java三大特性的封装中讲到java内部类的简单概要,这里将详细深入了解java内部类的使用和应用. 我们知道内部类可分为以下几种: 成员内部类 静态内部类 方法内部类 ...
- java中内部类的积累
放在一个类的内部的类我们就叫内部类. 二. 作用 1.内部类可以很好的实现隐藏 一般的非内部类,是不允许有 private 与protected权限的,但内部类可以 2.内部类拥有外围类的所有元素的访 ...
- JAVA动态代理的全面深层理解
Java 动态代理机制的出现,使得 Java 开发人员不用手工编写代理类,只要简单地指定一组接口及委托类对象,便能动态地获得代理类.代理类会负责将所有的方法调用分派到委托对象上反射执行,在分派执行的过 ...
- JAVA运行内部类的main方法
运行内部类的main方法 定义两个线程: 一个线程的名字"thread1",线程功能输出1~10的阶乘. 另一个线程的名字"thread2",线程功能输出线程的 ...
- 【原创】这道Java基础题真的有坑!我也没想到还有续集。
前情回顾 自从我上次发了<这道Java基础题真的有坑!我求求你,认真思考后再回答.>这篇文章后.我通过这样的一个行文结构: 解析了小马哥出的这道题,让大家明白了这题的坑在哪里,这题背后隐藏 ...
- 小白学Java:内部类
目录 小白学Java:内部类 内部类的分类 成员内部类 局部内部类 静态内部类 匿名内部类 内部类的继承 内部类有啥用 小白学Java:内部类 内部类是封装的一种形式,是定义在类或接口中的类. 内部类 ...
- Java的内部类
Java的内部类 首先我们来了解一下什么是内部类? 内部类是指在一个外部类的内部再定义一个类.内部类可以是静态static的,也可用public,default,protected和private修饰 ...
随机推荐
- URL包里的URL.getpath()对路径中空格识别为%20的处理办法
方法(1),使用repaceAll("%20",' ')替换后,只能解决空格问题.但是路径中包含%和中文就不行了. 方法(2),使用URLDecoder.decode(str,&q ...
- log4j警告:WARN Please initialize the log4j system properly 的解决方法
出现这个问题的原因则是因为没有为log4j建立配置文件导致的.所以解决问题的方法很简单,只要在 src文件目录下建立配置文件即可: 右键点击src -> New -> File 文件 ...
- 手把手教你从ESXI部署到vSphere web Client管理控制
作为实验环境,一台物理机即可 既然是实验环境,那么首先把这个物理机装成ESXI6.5的宿主机并配置网络系统 第二步骤就是在ESXI上面导入OVF文件,注册一台虚机,作为数据管理中心 第三步骤就是基于这 ...
- 课下必做MyCP
一.Linux的CP命令 Linux 的cp命令 功能: 复制文件或目录 说明: cp指令用于复制文件或目录,如同时指定两个以上的文件或目录,且最后的目的地是一个已经存在的目录,则它会把前面指定的所有 ...
- 2018-2019-2 20165220 《网络对抗技术》Exp1 PC平台逆向破解
实验目的 本次实践的对象是一个名为pwn1的linux可执行文件. 该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串. 该程序同时包含另一个代码片段,getShe ...
- SOAPA来临,SIEM时代终结?
安全信息和事件管理(SIEM)产品及服务负责从大量企业安全控件.主机操作系统.企业应用和企业使用的其他软件中收集安全日志数据,并进行分析和报告.有些SIEM还可以试图阻止它们检测到正在进行的攻击,这可 ...
- Android Training
Building Apps with Content Sharing Simple Data --> Intent && ActionProvider 介绍如何让应用程序共享简单 ...
- (lua) 基于cocos 的插入光效
具体的表现是:移动滚动容器到头部, 新增icon淡入并掉入格子,显示格子中的图标,icon放大并淡出 function UISkyResource:playActivedEffect(id) then ...
- JS for循环 if判断、white循环。小练习二
假设一个简单的ATM机的取款过程是这样的:首先提示用户输入密码(password),最多只能输入三次,超过3次则提示用户“密码错误,请取卡”结束交易.如果用户密码正确,再提示用户输入取款金额(amou ...
- C# 异步编程之 Task 的使用
(说明:随笔内容为学习task的笔记,资料来源:https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task?redi ...