前言:
当前各位老铁们对“java内部类使用场景”大概比较关注,各位老铁们都想要学习一些“java内部类使用场景”的相关知识。那么小编同时在网摘上网罗了一些关于“java内部类使用场景””的相关内容,希望咱们能喜欢,朋友们快快来学习一下吧!难度
初级
学习时间
30分钟
适合人群
零基础
开发语言
Java
开发环境JDK v11IntelliJ IDEA v2018.3提示
本教学属于系列教学,内容具有连贯性,本章使用到的内容之前教学中都有详细讲解。
1.内部类
前面学习过内部类,这里我们来温故一下。
演示:
请定义一个内部类。
代码:
没有学习过的同学或者是不太清楚内部类的同学也别急,可以点击下面内部类学习资料来进行学习。
附:内部类学习资料
“全栈2019”Java第六十七章:内部类、嵌套类详解
“全栈2019”Java第六十八章:外部类访问内部类成员详解
“全栈2019”Java第六十九章:内部类访问外部类成员详解
“全栈2019”Java第七十章:静态内部类详解
“全栈2019”Java第七十一章:外部类访问静态内部类成员详解
“全栈2019”Java第七十二章:静态内部类访问外部类成员
“全栈2019”Java第七十三章:外部类里多个静态非静态内部类详解
“全栈2019”Java第七十四章:内部类与静态内部类相互嵌套
“全栈2019”Java第七十五章:内部类持有外部类对象
“全栈2019”Java第七十六章:静态、非静态内部类访问权限
“全栈2019”Java第七十七章:抽象内部类与抽象静态内部类详解
“全栈2019”Java第七十八章:内部类可以继承其他类吗?
“全栈2019”Java第七十九章:类中可以嵌套接口吗?
“全栈2019”Java第八十章:外部类是否能实现本类中的接口?
“全栈2019”Java第八十一章:外部类能否访问嵌套接口里的成员?
“全栈2019”Java第八十二章:嵌套接口能否访问外部类中的成员?
“全栈2019”Java第八十三章:内部类与接口详解
“全栈2019”Java第八十四章:接口中嵌套接口详解
“全栈2019”Java第八十五章:实现接口中的嵌套接口
“全栈2019”Java第八十六章:嵌套接口可以多继承吗?
“全栈2019”Java第八十七章:类中嵌套接口的应用场景(拔高题)
“全栈2019”Java第八十八章:接口中嵌套接口的应用场景
“全栈2019”Java第八十九章:接口中能定义内部类吗?
“全栈2019”Java第九十章:内部类可以向上或向下转型吗?
“全栈2019”Java第九十一章:内部类具有多态特性吗?
“全栈2019”Java第九十二章:外部类与内部类成员覆盖详解
2.一个非常简单的应用场景
让我们通过一个非常简单的应用场景来了解为什么要使用内部类。
在我们日常编写代码的时候,都会写一个Main类,在Main类里面写一个main()方法来测试我们所写的功能。
例如:我们写了一个文字工具类,在这个类里面存放的都是关于如何处理文字的功能。
演示:
请定义一个文字工具类TextUtils,在TextUtils类中定义一个文字转大写的功能。
请在Main类的main()方法中测试TextUtils中的转大写功能。
请观察程序运行结果。
代码:
TextUtils类:
Main类:
结果:
从运行结果来看,符合预期,程序也没有任何错误。
过了一段时间,我又来了一个数学工具类,里面存放的都是跟数学有关的功能。
演示:
请定义一个数学工具类MathUtils,在其中定义一个加法功能。
请在Main类的main()方法中测试MathUtils中的加法功能。
请观察程序运行结果。
代码:
MathUtils类:
Main类:
结果:
从运行结果来看,程序没有问题。
上面我们一共写了三个类,一个TextUtils文字工具类,一个MathUtils数学工具类,还有一个测试类Main。
大家有没有发现,如果我想测试TextUtils类的转大写功能,我得来改Main类中的main()方法;如果我想测试MathUtils类的加法功能,我得来改Main类中的main()方法。假如还有其他很多工具类,我都想测一下它们里面功能,这意味着,我每测一次都得改一次Main类中的main()方法,麻烦的很。而且还不能保留我上次测试用例,很糟糕的体验,意味着每次你想测试功能的时候都得写一遍测试用例。
有没有改进的余地?
有。大家想想,既然每个类里面都可以拥有main()方法(没有规定只有Main类里面才能拥有main()方法),我们为什么不让每个工具类里面都拥有一个main()方法呢?测试直接在本来中进行。
好,接下来我们来试试在工具类中定义main()方法。
演示:
请将上述程序中Main类的main()方法移至TextUtils类中。
请在TextUtils类中执行main()方法。
请观察程序运行结果。
代码:
TextUtils类:
结果:
结果依然是正确的。说明将main()方法从Main类中移至TextUtils类是可以的。
好,请问main()方法就这样放在TextUtils类中妥不妥?
该做法实为不妥。大家想一想,我们TextUtils类是用来存放处理文字相关功能的,不包括测试方法。那就难了,也就是说,我们还得把main()方法重新写在Main类里面,现在我不想把main()方法写在Main类里面,只想把测试方法放在我功能方法放在一起,方便随时都可以测试。
针对上述问题,有没有解决的办法?
有。我们可以采取在工具类里面定义内部类的做法,该内部类是一个测试类,只用于测试。定义测试内部类的好处有很多,比如,不干扰正常类的使用;能够直接访问外部类中的成员(包括私有),这样就避免了很多麻烦。
接下来,我们就来改进TextUtils类。
演示:
请使用内部类的方式解决上述代码问题。
请观察程序运行结果。
代码:
TextUtils类:
结果:
从运行结果来看,程序没有问题,结果也符合预期。
我们简单的分析一下程序。首先来看测试内部类:
我们发现这个内部类它是一个静态内部类,为什么它会是一个静态内部类呢?
因为它里面的main()方法是静态,我们非静态的内部类是无法拥有静态方法的。
接着来看main()方法:
它就直接调用了外部类中的测试方法。甚至我们把这个TextUtils类名去掉也可以,直接写TextUtils类中的方法即可:
程序经过我们一番改进,现在可以说随时随地测试。测试类跟随着功能类,当然了,这里只是说用main()方法来测试程序功能时的做法,等后面我们需要单元测试等等一些高级测试后,那又是不一样的体验。
通过此例,我们能够感受到内部类带来的便利,这里小结一下:
内部类是一种逻辑分组仅在一个地方使用的类的方法:如果一个类只对另一个类有用,那么将它嵌入该类并将两者保持在一起是合乎逻辑的。 嵌套这样的“帮助类”使得它们的包更加简化。
测试类就是一个帮助类,它帮助了工具类更好的测试内部的功能。
3.迭代器设计模式
我们再来一个稍微复杂一点的例子,该例子很常见,也很实用。
友情提示:该案例从无到有一步一步讲解迭代器设计模式的形成,(本小节很长)希望大家能够有足够的耐性阅读,如果想直接看最终结果请拖至本节末尾。
演示:
请设计一个类,该类是一个专门存储数字的集合类。
请再设计一个类,该类是一个专门存储文字的集合类。
请观察程序代码。
代码:
NumberList类:
TextList类:
从程序代码来看呢,NumberList和TextList类里面各自都有一个数组,数组类型一个是int,存储的是数字;还有一个是String,存储的是文字。它们的初始大小都是10。
存储的容器定义完了,接下来该定义能够添加元素的方法了。
演示:
请将上例中的NumberList和TextList两个类各自新增一个添加元素的方法。
请观察程序代码。
代码:
NumberList类:
TextList类:
我们来解释一下这段代码是什么意思。拿NumberList类来说:
该实例变量position是用来记录当前数字数组已经添加到什么位置了。打个比方来说,初始position的值为0,这意味着第一次调用add()方法时:
条件满足,执行添加元素语句:
注意,此时position值为0,所以是给数组中的第一个位置上添加元素。
接着往下执行,累加position值:
这是因为数组中第一个位置已经添加过元素了,再添加的话就是第二个位置,当第二个位置添加元素时,再往下就是第三个元素,以此类推,直到把数组添加满为止。所以,position要不断累加,以让数组每一个位置都被添加上值。
下面,我们就来演示添加元素这个功能,在演示之前,我们还有一个TextList类没有说,其实这个类和NumberList类一摸一样,只是数组的类型不同,一个int,一个String,所以请大家谅解,这里不再赘述。如果还有不懂的同学,请在评论区留言,我会再详细讲解给你。
好了,我们来测试添加元素功能。
演示:
请保持上例已有代码不变。
请在Main类的main()方法中创建出NumberList和TextList两个集合类。
请使用两个集合类的添加元素方法。
请观察程序运行结果。
代码:
NumberList类:
TextList类:
Main类:
结果:
从运行结果来看,程序没有任何问题。可能有的同学有些疑惑,程序咋啥都没有输出呢?因为我们在程序中没有写任何的输出语句。该程序只要它不报错,就没有问题,为什么这么说呢?因为我在给NumberList添加元素的时候,是循环添加的,而循环次数是20次:
也就是说远远超过了NumberList类里面数组长度,所以,只要程序它不报错,就是正确的。
好,容器也定义了,元素也添加了,我想看看容器里到底存了什么元素,怎么看呢?数组嘛,当然是遍历啦。现在问题是数组定义在类里面,所以要么把数组暴露出来,在Main类里面遍历,要么在自己类里面遍历。选哪种?
第一种,暴露数组,自定义对元素的操作。
演示:
请在上例中NumberList类里添加一个获取数组的方法。
请在Main类main()方法中遍历NumberList中的数组。
请观察程序运行结果。
代码:
NumberList类:
Main类:
结果:
从运行结果来看,程序没有问题。
简单的来分析一个程序,我们给NumberList类中新增了一个可以获取内部数组的方法:
然后我们来看Main类中的main()方法:
获取数字数组,然后对其进行遍历,循环体中的内容由我们任意定义。执行结果:
这是第一种方式,下面我们再来看第二种方式。
第二种,不暴露数组,类中自行遍历,不能自定义对元素对操作。
演示:
请将NumberList获取数组方法替换为遍历数组方法。
请在Main类main()方法中调用遍历数组方法。
请观察程序运行结果。
代码:
NumberList类:
Main类:
结果:
从运行结果来看,程序没有问题。
我们简单的分析一下此程序,首先是NumberList的遍历数组方法:
一旦遍历方法写死,那就真的无法改变了,也就是说你只有遍历数字数组,那就是打印里面的数字,无法对数组中的元素进行操作,例如我想给遍历每一个元素时,打印该元素加100的结果,第一种方式很好就实现了:
而第二种方式又得去改NumberList的iteration()方法,很麻烦,而且我们对元素的操作千奇百怪,你是不可能在一个方法里面写完的,所以第二种方式不推荐大家使用。
好了,经过两个遍历定义在类里的数组的方式,发现第一种方式还是要好很多。
对了,我们除了NumberList需要遍历以外,TextList同样需要遍历,那么这两个类都需要将各自的数组提供出去。
演示:
请将NumberList类和TextList类都提供获取数组的方法。
请在Main类main()方法中遍历它们的元素。
请观察程序运行结果。
代码:
NumberList类:
TextList类:
Main类:
结果:
从运行结果来看,程序没有问题。至于大家看见结果中有7个null,那是String类型的默认值。因为我们只添加了三个文字元素,所以后面七个都是null。
刚刚说的不是重点,重点是我们遍历的操作有点重复:
该写一个公共方法,然后遍历的时候只需要调这个公共方法即可,而且方法的参数还得是Object类型的,因为设置为int类型,String不兼容;设置为String类型,int不兼容。该方法定义在Main类中:
看似很美好,我们来运行程序:
错误信息:
文字版:
/Users/admin/Workspace/Java/Hello/src/main/Main.java
Error:(22, 38) java: 不兼容的类型: int[]无法转换为java.lang.Object[]
完了,这下无法抽取公共遍历方法。
那有没有解决的办法呢?
有。大家想想,NumberList和TextList里面都有数组,而且都需要遍历数组,我们为什么不抽取它们公共部分呢?
有人估计想问公共部分是什么呢?
是遍历数组啊。让NumberList和TextList两个容器类之间产生一点联系,前面我们学过连接类与类用接口。问题是,接口里面定义一些什么东西呢?
首先,我们是一个一个取出来的,所以要有一个记录当前取到第几个的位置变量,还得有一个获取当前元素的方法,最后还得来一个询问还能继续往下取的方法。
根据描述,大概这样一个接口就定义好了。有人要问那个记录当前获取元素的位置变量怎么没有?因为接口中只能定义常量,所以不能定义在接口中,那么自然而然就得定义在实现类中。
好了,那就实现吧。
演示:
请定义一个接口Iterator,里面定义两个方法,一个是用于判断是否还有下一个元素,还有一个是获取下一个元素。
请NumberList类和TextList类实现Iterator接口,并在各自类中定义一个用于记录当前取到元素的位置。
请在Main类main()方法中使用Iterator方式遍历元素。
请观察程序运行结果。
代码:
Iterator接口:
NumberList类:
TextList类:
Main类:
结果:
从运行结果来看,程序没有问题。看似也很完美,简单的来分析一下程序,首先来看Iterator接口:
定义这两个方法,是因为它们是必须的。hasNext()方法是为了防止数组下标越界,next()方法是为了获取当前遍历的元素。
再来看看实现类,NumberList和TextList两个类,我们只看其中一个即可,因为它们只是类型不同,其他都一样:
除了实现接口中的两个抽象方法以外,我们还定义了一个用于记录当前遍历元素的位置变量,该变量很重要,它能让我们知道遍历到什么位置了,这样我们可以根据它来获取相对应的元素。
最后来看看Main类:
遍历方式改变了,使用while循环是因为类中判断条件很符合这种方式。我们通过next()方法获取当前元素。大家也看到了,这两处还能再抽取一个公共方法来:
运行程序,执行结果:
从运行结果来看,和之前的一样。
一切都看起来很美好,但是这里有一个小小的BUG,是什么呢?那就是NumberList和TextList迭代多次只有第一次迭代有效,后面迭代无效。来试试。
结果:
0-9数字只打印了一遍,按理说,应该打印两遍。
问题出在哪里呢?
问题就出在next变量在第一遍迭代完之后,没有被重置为0,它的值一直为9,导致第二遍开始遍历的时候,hasNext()方法一直返回false:
那有同学就说,我们在第二次遍历之前重置一下next不就好了吗,等等,这种操作什么时候做?在遍历前吗?你会永远都记得做这个操作吗?有可能一不留神就没有做这个操作,导致后面无法估量的损失,试想一下,你正在操作的是银行里的金额,稍不注意就会造成损失。所以这种在遍历前重置的操作不可取。
那怎么办?没有解决办法了吗?
不,还是有的,刚刚我们想到重置next变量,如果我们每一次遍历都是去获取一个迭代器对象,将next遍历封装到迭代器里面,那么每一次获取迭代器对象next都是初始值。
问题还没解决完,这个迭代器对象肯定是一个类,而且需要需要去实现Iterator接口。
那么这个类该定义在哪?外部类吗?不行,因为Iterator接口中的两个方法都用到了集合类中的数组,意味着迭代器类要能直接操作数组成员。而能直接操作类中成员的类只有内部类可以,所以迭代器是一个内部类。
演示:
请使用内部类迭代器Itr改写NumberList和TextList类。
请在Main类main()方法中使用新方式遍历元素。
请观察程序运行结果。
代码:
Iterator接口:
NumberList类:
TextList类:
Main类:
结果:
从运行结果来看,程序没有任何问题。结果也符合预期。之前说的两次迭代集合的问题已经得到解决。我们来分析一个该程序。
首先,我们得来看看新改写的NumberList类,TextList和NumberList是一摸一样的,就不再重复看,看其中一个即可:
内部类Itr里面有一个next变量,用于记录当前遍历元素的位置,每一次创建新的迭代器对象时,next变量都是新的,又从初始值开始。那我们就去看看创建迭代器对象的地方:
有三处,都是通过外部类对象来创建内部类对象的方式。第一次迭代结果:
第二次迭代结果:
第三次迭代结果:
三次迭代结果都是正确的。
大家有没有觉得我们创建迭代器的方式有点不妥,如果我们在外部类里面直接定义一个获取内部类对象的方法,岂不是更好?我们来优化NumberList类和TextList类:
NumberList类:
TextList类:
Main类:
结果:
从运行结果来看,程序没有任何问题。而且我们将内部类设为私有成员,提供了获取迭代器对象的方法。
到这里,迭代器设计模式案例讲解完毕!后面还会有设计模式专题,里面也会讲解到迭代器设计模式,敬请关注!
总结内部类是一种逻辑分组仅在一个地方使用的类的方法:如果一个类只对另一个类有用,那么将它嵌入该类并将两者保持在一起是合乎逻辑的。 嵌套这样的“帮助类”使得它们的包更加简化。内部类增强了封装:考虑两个顶级类A和B,其中B需要访问A的成员,否则这些成员将被声明为私有。 通过将类B隐藏在类A中,可以将A的成员声明为私有,并且B可以访问它们。 另外,B本身可以隐藏在外面。内部类可以带来更易读和可维护的代码:在顶级类中嵌套小类会使代码更接近于使用它的位置。
至此,Java中内部类应用场景相关内容讲解先告一段落,更多内容请持续关注。
答疑
如果大家有问题或想了解更多前沿技术,请在下方留言或评论,我会为大家解答。
上一章
“全栈2019”Java第九十二章:外部类与内部类成员覆盖详解
下一章
“全栈2019”Java第九十四章:局部内部类详解
学习小组
加入同步学习小组,共同交流与进步。
方式一:关注头条号Gorhaf,私信“Java学习小组”。方式二:关注公众号Gorhaf,回复“Java学习小组”。全栈工程师学习计划
关注我们,加入“全栈工程师学习计划”。
版权声明
原创不易,未经允许不得转载!
标签: #java内部类使用场景