菜鸟学Java,现学现卖。
所谓内部类,就是一个类的定义放在了另一个类定义的内部,如:
内部类应该算得上是Java学习过程中的一个难点了。它之所以难,我觉着有两个方面:一是它的语法相比于Java其它部分要显得繁琐,有很多需要注意的细节; 二是它的应用场景,即为什么Java需要这么一个东西,它到底能够带来什么样的好处?如果不能回答这个问题,那么即便熟悉了它的相关语法,也很难在今后的实践中使用它。而在我个人的学习过程中,第二点更加长久的困扰了我。
这篇blog也主要是围绕着这两个难点展开的。
为了便于理解,我将所有内部类中和static有关的部分全部放在了整片文章的结尾。所以,一开始可以暂时将这些内容置之不理。最后,我会解释为什么要这么做。
先解释一些和语法相关的内容。
正如前面的代码所展现的那样,内部类是在某一个类的内部所定义的。这样的话,它至少有两点和普通的class不太一样:
1. 它体现了一种代码的隐藏机制和访问控制机制。在这一点上,它很像是C++的嵌套类的概念;
2. 它包含有一个外部类的this指针。这是理解内部类特性非常重要的一点。正是由于有了这个指针,内部类可以访问外部类的所有元素。
将这两点结合到一起,就是内部类的本质了。
针对1. 的补充:
如果内部类被声明为public
,那么外部类作用域之外的地方是可以使用这个类名的,但是使用的方法必须是:
OuterClass.InnerClass
这其实就有点命名空间的意思了。要用InnerClass,可以啊,但永远都得前面带着个前缀...
而想创建这样的内部类的实例,则需要使用一个外部类的实例。比如如果有一个OuterClass的实例,OuterObject:
OuterClass.InnerClass InnerObject = OuterObject.new InnerClass();
这样,就可以在外部类作用域以外的地方得到一个内部类的实例了。而之所以需要这样做的原因,就在于内部类的实例必须含有一个外部类的this指针,如果不是先有一个外部类实例,哪来的这个this指针呢?
如果内部类被声明为private
:
首先一点,在Java中,普通的类是不能被private修饰的。所以,只有内部类能够被private所修饰;
其次,如果被修饰成了private,那么内部类在外部类作用域之外的地方就不可见了。只有外部类能够使用内部类。这样,就实现了一种访问控制。
针对2. 的补充:
所谓可以访问外部类的所有元素,即包括了外部类的public/private的所有成员数据和方法。比如前面的代码,InnerClass是可以改变OuterClass的那个outerData的:
另一方面,反向的,外部类对于内部类的所有元素也都有访问权,包括内部类的私有成员和方法:
内部类的一种特殊的情况就是所谓的局部内部类,即在某个类的成员函数中定义内部类:
以前面介绍的内部类的两个特性来看待局部内部类:从访问控制上看,局部内部类不能够用public或者private来修饰,它只在这个成员方法内可见;从和外部类的联系上看,它和普通的内部类没有太大差别,都可以访问外部类的任意元素。唯一的区别在于,它还可以访问这个成员方法中的局部变量,只要这个局部变量被声明成final (这是个语法细节,我不展开说了)。
说了这么多语法了,可还是看不出内部类到底有什么用。所以接下来,先介绍一个内部类常用的一个场景,即内部类继承某个接口或者基类。而外部类的某个成员方法可以创建一个内部类的实例,然后将这个实例向上转型为它的接口/基类,例如:
在最后的main函数中,一个外部类的实例outerObj,通过调用它的一个成员函数.getBase(),最后我们获得了一个BaseIF这个接口的实例。但其实这个实例是一个内部类InnerClass,InnerClass在外部类之外是不可见的,但由于它是BaseIF的实现,所以我们仍然能够将它的实例作为返回值抛出来,然后向上转型成BaseIF来操作。
当然,现在这个例子,仍然看不懂内部类到底有什么大用处,别急,接着往下走。
正是由于内部类频繁的被用于这样的场景,所以又发明了一种针对这种场景的更简单的内部类,匿名内部类:
任何一个第一次看见这种代码的人一定会郁闷的,比如我。而任何一个试图去读懂这个代码的人一定会郁闷很久的,比如我。
但如果了解到之前的那个应用场景的话,那么这段代码就算是比较易懂了:
“return new BaseIF”这行代码,说明是要返回一个BaseIF的对象,而之后又出现了一对"{}",说明这对大括号里面就是一个匿名内部类,这个类实现了BaseIF(如果BaseIF不是接口而是一个基类,那么就不是实现而是继承)。大括号中的"public void func"是重新实现了BaseIF的成员函数;
而之所以称为匿名,是因为这个类确实没有名字,我们唯一能知道的就是它实现了BaseIF;
到此为止,大部分内部类的语法知识都说完了。但是,最重要的那个问题仍然没有被回答:为什么Java要费力的加入这么一个特性,它到底能够提供什么样的好处?
如果搜索网上的文章,那么你大概能找到这么一个答案:内部类能够帮助Java实现回调,进一步说,它适用于事件驱动的架构。但这么一个答案,仍然很难理解。
为了解释清楚这件事,先从一个最简单的事件驱动的程序开始:
一旦某个event被添加进了Controller中,Controller就会调用event的execute方法。
接下来,看看怎样能够去实例化一些event。一个直接的办法就是创建一些新的类,这些类是EventIF的实现,比如:
毫无疑问,这样做没有问题。我们可以创建一个Event1的实例,然后将它作为addEvent()的参数加入到controller中。
但是,这样做也意味着,任何一个类如果希望成为event能够被controller所调用,那么它必须是EventIF的实现。首先,这么做不一定合适。此外,如果Event1不是接口而是基类,那么对于某些已经继承了其它基类的派生类,它们就不可能再去继承EventIF(因为Java不支持多重继承),所以这些类就不可能作为Event被controller调用了。
为了解决这个问题,内部类显力的时候到了:
无论CommonClass是何种形式,继承了何种基类或者接口。我都可以通过一个内部类,使得它的一个成员函数能够返回一个EventIF的实现。在这段代码中,obj.getEvent()的结果就是内部类的一个实例,它也是EventIF的一个实现。这个内部类的实例相当于obj的替身被放入到Controller中。之所以可以被称为“替身”,是因为这个内部类的实例能够访问CommonClass的实例obj中的任何成员和方法。
用匿名内部类,看起来更简单:
我还可以做到更好,一个类实现两个不同的Event:
Light这个类,有两个成员函数,分别返回了不同的event。并且,这些event的execute()方法还改变了Light的私有数据。
所以,你可以说,有了内部类事件驱动模式就非常的容易实现,你也可以说,内部类最大的好处就是它能够达到和多重继承一样的效果。在我看来,这两者其实都对,相辅相成。
最后,再简单的说一说被static修饰了的内部类。
之所以放在最后,而且我也不愿意详细说,是因为被static修饰的内部类如同被阉割的动物,已经失去了它的活力。因为static内部类是没有包含外部类的this指针的,那么它也就不能够访问外部类的成员。所以,内部类带来的巨大好处和内部类的适用场景它都不具备。我更倾向于将static内部类单独的做为一种情况考虑,而不要将它和普通的内部类混为一谈。
分享到:
相关推荐
anonymousInner.java 匿名内部类 base.java 定义一个基类 BaseColors.java 一个简单的接口 basePoint.java 一个测试用的基类 Colorable.java 一个子接口 ColoredPoint.java 一个测试用子类 common.java 一个...
Java zip压缩包查看程序,应用弹出文件选择框,选择ZIP格式的压缩文件,可以像Winrar软件一样查看压缩文件内部的文件及文件夹,源码截图如上所示。 Java 数字签名、数字证书生成源码 2个目标文件 摘要:JAVA源码,...
实例068 匿名内部类的简单应用 104 实例069 静态内部类的简单应用 105 实例070 实例化Class类的几种方式 107 实例071 查看类的声明 108 实例072 查看类的成员 110 实例073 查看内部类信息 112 实例074 动态设置类的...
3.3.4 等号其实不简单 52 3.3.5 小心使用浮点数进行比较 53 3.3.6 boolean和char 55 3.3.7 不要使用还没有创建出来的变量 57 3.3.8 String——char串起的项链 58 3.3.9 转义符——看不见写得出 61 3.4 小结:...
Java zip压缩包查看程序,应用弹出文件选择框,选择ZIP格式的压缩文件,可以像Winrar软件一样查看压缩文件内部的文件及文件夹,源码截图如上所示。 Java 数字签名、数字证书生成源码 2个目标文件 摘要:JAVA源码...
Java zip压缩包查看程序,应用弹出文件选择框,选择ZIP格式的压缩文件,可以像Winrar软件一样查看压缩文件内部的文件及文件夹,源码截图如上所示。 Java 数字签名、数字证书生成源码 2个目标文件 摘要:JAVA源码...
3.3.4 等号其实不简单 52 3.3.5 小心使用浮点数进行比较 53 3.3.6 boolean和char 55 3.3.7 不要使用还没有创建出来的变量 57 3.3.8 String——char串起的项链 58 3.3.9 转义符——看不见写得出 61 3.4 小结:...
Java zip压缩包查看程序,应用弹出文件选择框,选择ZIP格式的压缩文件,可以像Winrar软件一样查看压缩文件内部的文件及文件夹,源码截图如上所示。 Java 数字签名、数字证书生成源码 2个目标文件 摘要:JAVA源码,...
Java zip压缩包查看程序,应用弹出文件选择框,选择ZIP格式的压缩文件,可以像Winrar软件一样查看压缩文件内部的文件及文件夹,源码截图如上所示。 Java 数字签名、数字证书生成源码 2个目标文件 摘要:JAVA源码,...
多线程 java 类查找工具 。搜索指定目录下的jar和zip,查找对应的class文件。注意不带包名哦。原理很简单就是查文件名。
010201_【第2章:简单Java程序】_简单Java程序笔记.pdf 010301_【第3章:Java基础程序设计】_Java数据类型笔记.pdf 010302_【第3章:Java基础程序设计】_运算符、表达式与语句笔记.pdf 010303_【第3章:Java基础程序...
Java zip压缩包查看程序,应用弹出文件选择框,选择ZIP格式的压缩文件,可以像Winrar软件一样查看压缩文件内部的文件及文件夹,源码截图如上所示。 Java 数字签名、数字证书生成源码 2个目标文件 摘要:JAVA源码...
学生提问:为什么静态内部类实例方法也不能访问外部类的实例属性呢? 207 学生提问:接口里是否能定义内部接口? 208 6.7.3 使用内部类 208 学生提问:既然内部类是外部类的成员,是否可以为外部类定义子类,在...
一个“.java”原文件中是否可以包括多个类(不是内部类)? 53.掌握内部类和接口的概念 54.StringTokenizer类的使用 55.数据结构,如何遍历List中的元素? 如果要按照键值保存或者访问数据,使用什么数据结构? ...
7.6.9 为什么要用内部类:控制框架 7.7 构建器和多形性 7.7.1 构建器的调用顺序 7.7.2 继承和finalize() 7.7.3 构建器内部的多形性方法的行为 7.8 通过继承进行设计 7.8.1 纯继承与扩展 7.8.2 下溯造型与运行期类型...
Static Nested Class是被声明为静态(static)的内部类,它可以不依赖于外部类实例被实例化。而通常的内部类需要在外部类实例化后才能实例化。 22、JSP中动态INCLUDE与静态INCLUDE的区别? 动态INCLUDE用jsp:...
-noinner -关掉对内部类的支持 (default: turn on) -nolvt - 忽略局部变量的表信息 -nonlb - 不要输出一个新行在打开一个括号之前 (default: do) -o - 无需确认直接覆盖输出 (default: no) -p - ...
生成HelloWorld.h文件(若提示找不到HelloWorld类文件,Java代码中不要写package) 有package时,在包所在目录下打开终端,输入命令javah -jni com.gjl.jnitest.HelloWorld (com.gjl.jnitest为包名) 生成 ...