`
lovecontry
  • 浏览: 1036835 次
文章分类
社区版块
存档分类
最新评论

不简单的JAVA内部类

 
阅读更多

菜鸟学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内部类单独的做为一种情况考虑,而不要将它和普通的内部类混为一谈。

分享到:
评论

相关推荐

    Java开发技术大全(500个源代码).

    anonymousInner.java 匿名内部类 base.java 定义一个基类 BaseColors.java 一个简单的接口 basePoint.java 一个测试用的基类 Colorable.java 一个子接口 ColoredPoint.java 一个测试用子类 common.java 一个...

    java源码包---java 源码 大量 实例

     Java zip压缩包查看程序,应用弹出文件选择框,选择ZIP格式的压缩文件,可以像Winrar软件一样查看压缩文件内部的文件及文件夹,源码截图如上所示。 Java 数字签名、数字证书生成源码 2个目标文件 摘要:JAVA源码,...

    Java经典编程源码基础例程300.zip

    实例068 匿名内部类的简单应用 104 实例069 静态内部类的简单应用 105 实例070 实例化Class类的几种方式 107 实例071 查看类的声明 108 实例072 查看类的成员 110 实例073 查看内部类信息 112 实例074 动态设置类的...

    JAVA入门1.2.3:一个老鸟的JAVA学习心得 PART1(共3个)

    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源码包4

     Java zip压缩包查看程序,应用弹出文件选择框,选择ZIP格式的压缩文件,可以像Winrar软件一样查看压缩文件内部的文件及文件夹,源码截图如上所示。 Java 数字签名、数字证书生成源码 2个目标文件 摘要:JAVA源码...

    java源码包3

     Java zip压缩包查看程序,应用弹出文件选择框,选择ZIP格式的压缩文件,可以像Winrar软件一样查看压缩文件内部的文件及文件夹,源码截图如上所示。 Java 数字签名、数字证书生成源码 2个目标文件 摘要:JAVA源码...

    Java入门1·2·3:一个老鸟的Java学习心得.PART3(共3个)

    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上百实例源码以及开源项目

     Java zip压缩包查看程序,应用弹出文件选择框,选择ZIP格式的压缩文件,可以像Winrar软件一样查看压缩文件内部的文件及文件夹,源码截图如上所示。 Java 数字签名、数字证书生成源码 2个目标文件 摘要:JAVA源码,...

    JAVA上百实例源码以及开源项目源代码

     Java zip压缩包查看程序,应用弹出文件选择框,选择ZIP格式的压缩文件,可以像Winrar软件一样查看压缩文件内部的文件及文件夹,源码截图如上所示。 Java 数字签名、数字证书生成源码 2个目标文件 摘要:JAVA源码,...

    java解压jar类查找工具

    多线程 java 类查找工具 。搜索指定目录下的jar和zip,查找对应的class文件。注意不带包名哦。原理很简单就是查文件名。

    Java开发详解.zip

    010201_【第2章:简单Java程序】_简单Java程序笔记.pdf 010301_【第3章:Java基础程序设计】_Java数据类型笔记.pdf 010302_【第3章:Java基础程序设计】_运算符、表达式与语句笔记.pdf 010303_【第3章:Java基础程序...

    java源码包2

     Java zip压缩包查看程序,应用弹出文件选择框,选择ZIP格式的压缩文件,可以像Winrar软件一样查看压缩文件内部的文件及文件夹,源码截图如上所示。 Java 数字签名、数字证书生成源码 2个目标文件 摘要:JAVA源码...

    疯狂JAVA讲义

    学生提问:为什么静态内部类实例方法也不能访问外部类的实例属性呢? 207 学生提问:接口里是否能定义内部接口? 208 6.7.3 使用内部类 208 学生提问:既然内部类是外部类的成员,是否可以为外部类定义子类,在...

    JAVA面试题最全集

    一个“.java”原文件中是否可以包括多个类(不是内部类)? 53.掌握内部类和接口的概念 54.StringTokenizer类的使用 55.数据结构,如何遍历List中的元素? 如果要按照键值保存或者访问数据,使用什么数据结构? ...

    java 编程入门思考

    7.6.9 为什么要用内部类:控制框架 7.7 构建器和多形性 7.7.1 构建器的调用顺序 7.7.2 继承和finalize() 7.7.3 构建器内部的多形性方法的行为 7.8 通过继承进行设计 7.8.1 纯继承与扩展 7.8.2 下溯造型与运行期类型...

    java 面试题 总结

    Static Nested Class是被声明为静态(static)的内部类,它可以不依赖于外部类实例被实例化。而通常的内部类需要在外部类实例化后才能实例化。 22、JSP中动态INCLUDE与静态INCLUDE的区别? 动态INCLUDE用jsp:...

    java反编译工具 jad

     -noinner -关掉对内部类的支持 (default: turn on)  -nolvt - 忽略局部变量的表信息  -nonlb - 不要输出一个新行在打开一个括号之前 (default: do)  -o - 无需确认直接覆盖输出 (default: no)  -p - ...

    jni简单使用

    生成HelloWorld.h文件(若提示找不到HelloWorld类文件,Java代码中不要写package) 有package时,在包所在目录下打开终端,输入命令javah -jni com.gjl.jnitest.HelloWorld (com.gjl.jnitest为包名) 生成 ...

Global site tag (gtag.js) - Google Analytics