引言

在java中,声明并使用一个内部类是相当简单的,就像使用一个普通的类一样,我们在工作中或多或少的能从内部类中获益,它能为我们隐藏很多代码逻辑, 能让代码变得简单优雅。
但是用法简单并不代表语义上也简单,不管是从实现上还是功能是,内部类是有很多值得我们研究的东西的。
这篇文章,我们先看普通的内部类即成员内部类和静态内部类(嵌套类)。

内部类的划分

从概念上来说,一个定义在另一个类或者方法里面的类,就可以叫做内部类。
如果将内部类进行划分,可以分为成员内部类、静态内部类、局部内部类和匿名内部类四种。下面看这四种内部类的用法:

成员内部类

成员内部类是最普通的内部类,它的定义位于另外一个类的内部。看下面这个例子:

  1. public class MemberOuter {
  2. class MemberInner{
  3. }
  4. }

注意内部类MemberInner没有static,这是很重要的一点。
声明很简单,但是作为最普通的内部类,成员内部类是有很多特性的,我们逐一来分析:

访问外部类的成员和方法

成员内部类可以没有限制的访问外部类的变量和方法,不论是私有的还是公有的:

  1. public class MemberOuter {
  2. public String aPublicString;
  3. protected String aProtectedString;
  4. String aDefaultString;
  5. private String aPrivateString;
  6. private static String aPrivateStaticString;
  7. private void aPrivateMethod(){
  8. System.out.println("aPrivateMethod");
  9. }
  10. class MemberInner{
  11. public void accessOuter(){
  12. System.out.println(aPublicString);
  13. System.out.println(aProtectedString);
  14. System.out.println(aDefaultString);
  15. System.out.println(aPrivateString);
  16. System.out.println(aPrivateStaticString);
  17. aPrivateMethod();
  18. }
  19. }
  20. }

我们在外部类中声明了各种访问标志的变量,在内部类中的accessOuter方法中,这些变量都能被访问,就向访问内部类自己的变量一样。

为什么能够访问

能访问静态变量我们可以理解,因为静态变量是所有外部类对象共享的,但是访问成员变量是怎样做到的?难道每个成员内部类对象都对应一个外部类的对象了?确实是这样的。
成员内部类是依附外部类而存在的,创建成员内部类的对象的前提是存在一个外部类的对象。我们可以看一下反编译后的文件,看看编译器是怎样将外部类和内部类对象关联在一起的:

  1. public class MemberOuter
  2. {
  3. public String aPublicString;
  4. protected String aProtectedString;
  5. String aDefaultString;
  6. private String aPrivateString;
  7. private static String aPrivateStaticString;
  8. private void aPrivateMethod()
  9. {
  10. System.out.println("aPrivateMethod");
  11. }
  12. class MemberInner
  13. {
  14. public void accessOuter()
  15. {
  16. System.out.println(aPublicString);
  17. System.out.println(aProtectedString);
  18. System.out.println(aDefaultString);
  19. System.out.println(aPrivateString);
  20. System.out.println(MemberOuter.aPrivateStaticString);
  21. aPrivateMethod();
  22. }
  23. final MemberOuter this$0;
  24. MemberInner()
  25. {
  26. this.this$0 = MemberOuter.this;
  27. super();
  28. }
  29. }
  30. }

注意内部类MemberInner的代码,编译器为我们增加了一个类型为MemberOuter的变量this$0,并且在构造方法中进行了赋值,这个就是创建内部类对象时的用到的外部类对象。有了这个变量,我们当然就可以任意访问外部类对象的方法和变量了。

通过外部类来创建内部类

那么问题又来了,我们要怎样编码来创建内部类对象呢?我们可以使用.new关键字,看下面的例子:

  1. public class MemberOuter {
  2. public static void main(String[] args) {
  3. MemberOuter mo = new MemberOuter();
  4. MemberInner memberInner = mo.new MemberInner();
  5. memberInner.accessOuter();
  6. }
  7. class MemberInner{
  8. public void accessOuter(){
  9. }
  10. }
  11. }

在main方法中,我们先创建了一个外部类的对象,然后通过.new innerClass();这种方式创建了一个内部类对象。这就是使用外部类对象创建内部类对象的一种方式。这样,我们反编译后的代码中内部类的this$0变量就指向我们创建的MemberOuter对象了。
当然,我们也可以在外部类中定义额外的方法来创建内部类对象,看下面的例子:

  1. public class MemberOuter {
  2. public MemberInner inner(){
  3. return new MemberInner();
  4. }
  5. public static void main(String[] args) {
  6. MemberOuter memberOuter = new MemberOuter();
  7. MemberInner inner = memberOuter.inner();
  8. }
  9. class MemberInner{
  10. public void accessOuter(){
  11. }
  12. }
  13. }

在外部类中,我们声明了一个inner方法,直接返回一个内部类的对象,然后再main方法中直接调用。这样,内部类的this$0也会是我们创建的MemberOuter的对象。注意,在外部类中,我们只能通过非静态方法来创建内部类的对象,因为只有非静态方法才能拿到到外部类对象的引用,所以类似下面的方法是不能编译通过的:
inner.png
错误提示也很明显,在static方法中不能访问到this。

在成员内部类中使用外部类对象的引用

知道了怎样通过外部类来创建成员内部类对象,我们来看一下怎样在内部类中访问与之关联的外部类对象的引用。
很简单,因为编译器已经为我们添加了this&0变量来指向与之关联的外部类的对象,我们肯定是能在内部类中拿到这个引用的,通过 .this关键字,我们就可以达到目的:

  1. public class MemberOuter {
  2. public MemberInner inner(){
  3. return new MemberInner();
  4. }
  5. public static void main(String[] args) {
  6. MemberOuter memberOuter = new MemberOuter();
  7. MemberInner inner = memberOuter.inner();
  8. inner.accessOuter();
  9. }
  10. class MemberInner{
  11. public void accessOuter(){
  12. System.out.println(MemberOuter.this);
  13. }
  14. }
  15. }

内部类的accessOuter方法中,我们使用MemberOuter.this就能访问与之关联的外部类了。为什么不是直接用this$0呢?可能是因为这个变量名是编译器自动为我们声明的或者Outer.this更加好理解吧。
所以我们知道了,对于成员内部类来说,能够随意访问外部类的变量和方法,是因为编译器为我们构造内部类时加入了外部类对象的引用,然后对创建方式进行了限制,只能通过外部类的对象创建,例如.new关键字和外部类的非静态方法,这样实现了成员内部类的特性,成员内部类就像外部类的一个成员一样。

禁用static声明

在成员内部类中,是不能有静态字段和静态方法声明的:
static.png
要想在内部类中声明静态字段和静态方法,可以使用静态内部类

对应关系

一个内部类对象肯定有一个外部类与之对应,但是一个外部类对象不一定有对应的内部类对象,不创建就行了,对吧。一个外部类对象也可能对应多个内部类对象,这样这些内部类对象的this$0就是同一个:

  1. public class MemberOuter {
  2. public MemberInner inner(){
  3. return new MemberInner();
  4. }
  5. public static void main(String[] args) {
  6. MemberOuter memberOuter = new MemberOuter();
  7. MemberInner inner1 = memberOuter.inner();
  8. MemberInner inner2 = memberOuter.inner();
  9. MemberInner inner3 = memberOuter.inner();
  10. inner1.accessOuter();;
  11. inner2.accessOuter();
  12. inner3.accessOuter();
  13. }
  14. class MemberInner{
  15. public void accessOuter(){
  16. System.out.println(MemberOuter.this);
  17. }
  18. }
  19. }

inner1、inner2、inner3三个内部类的外部类对象是一个,所以三次调用accessOuter方法输出的是一样的。

成员内部类的继承

成员内部类的继承语法有点特殊。上面我们已经看到,编译器在成员内部类中默认加上了到外部类对象的引用,我们继承成员内部类也得需要保证这一点。《java编程思想》中给出的示例如下:

  1. public class WithInner {
  2. public class Inner{
  3. }
  4. }
  5. public class InheritInner extends WithInner.Inner {
  6. public InheritInner(WithInner withInner) {
  7. withInner.super();
  8. }
  9. }

在InheritInner构造方法中,有一个到外部类WithInner的对象,并且必须调用使用Outer.super()这种特殊的语法才能保证编译成功。
成员内部类的继承并不常见,我们只需要记住这种特殊的语法即可。

静态内部类(嵌套类)

静态内部类也被称为嵌套类。 它与上面说的成员内部类有很大不同,但是理解了成员内部类的特性,再学习静态内部类就简单很多了。

定义嵌套类

静态内部类在定义时需要加上static关键字。下面就是一个简单的静态内部类:

  1. public class MemberOuter {
  2. static class MemberInner{
  3. public void accessOuter(){
  4. }
  5. }
  6. }

我们对比成员内部类来说明静态内部类的特点,静态内部类与成员内部类的区别主要在以下三个方面:
(1)要创建静态内部类的对象,并不需要外部类的对象。
(2)不能从静态内部类的对象中访问非静态的外部类对象。
(3)成员内部类不能声明静态字段和静态方法,也不能包含静态内部类,而静态内部类可以包含所有这些。
下面我们具体来看一下这三点:

嵌套类对象的创建

对成员内部类,编译器自动生成了this$0这个到外部类的引用来建立和外部类对象的依赖关系,保证了成员内部类对象的创建必须通过外部类对象这一机制,使得成员内部类能够访问外部类对象的任何字段和方法。但是编译器不会对嵌套类做这样的处理,我们不需要外部类的对象就能创建嵌套类的对象。
看下面的例子:

  1. public class MemberOuter {
  2. public static MemberInner inner(){
  3. return new MemberInner();
  4. }
  5. public static void main(String[] args) {
  6. new MemberInner();
  7. }
  8. static class MemberInner{
  9. public void accessOuter(){
  10. }
  11. }
  12. }

反编译后的文件:

  1. public class MemberOuter
  2. {
  3. static class MemberInner
  4. {
  5. public void accessOuter()
  6. {
  7. }
  8. MemberInner()
  9. {
  10. }
  11. }
  12. public MemberOuter()
  13. {
  14. }
  15. public static MemberInner inner()
  16. {
  17. return new MemberInner();
  18. }
  19. public static void main(String args[])
  20. {
  21. new MemberInner();
  22. }
  23. }

编译器没有自动加上到外部对象的引用。
我们在静态的inner方法和main方法中都直接使用new关键字创建了嵌套类的对象,并且没有创建外部类的对象。这与成员内部类不同,成员内部类只能通过外部类的对象创建,所以成员内部类的.new关键字对嵌套类也是无法编译成功的:
static.png
实际上,嵌套类的创建就跟普通的java类创建一样,只要使用new关键字就行。

嵌套类访问外部类的成员

正是由于嵌套类的创建不依赖与外部类对象,所以嵌套类就不能访问外部类的非静态字段和方法了,这个很好理解。
inner.png

嵌套类可以声明静态字段和方法

成员内部类不能声明静态字段和方法,但是嵌套类可以。

  1. public class MemberOuter {
  2. public static void main(String[] args) {
  3. }
  4. static class MemberInner{
  5. private String name;
  6. private static String aStaticString;
  7. private static String getTheStaticString(){
  8. return aStaticString;
  9. }
  10. public void accessOuter(){
  11. System.out.println(aStaticString);
  12. }
  13. }
  14. }

小结

成员内部类作为最普通的内部类,更像是外部类的一个成员,它能够自由地访问外部类的任意字段和方法,每一个成员内部类的对象都有一个对应的外部类对象,为了实现这个目标,编译器为成员内部类的代码自动加上了到外部类对象的引用,我们在成员内部类中可以通过这个引用访问到对应的外部类,同时编译器对成员内部类的创建方式做了限制,只能通过外部类对象来创建,另外,成员内部类不能拥有静态字段和静态方法。
静态内部类(嵌套类)更像是一个普通的类,不需要与外部类对象一一对应,可以随意创建,也可以拥有自己的静态字段和静态方法,但是当静态内部类访问外部类时,就不能那么随意了,它只能访问外部类的静态字段和方法。
成员内部类和静态内部类都是定义在外部类里面的类,下一篇文章,我们来学习另外两种内部类—局部内部类和匿名内部类,这两种是在方法或者作用域内定义的类。