1. 创建内部类

内部类的创建就是把类的定义置于外围类的里面:

  1. package innerClass;
  2. // 外部类 OuterClass
  3. public class OuterClass {
  4. // 内部类 InnerClass
  5. class InnerClass {
  6. private String msg;
  7. InnerClass(String msg) {
  8. this.msg = msg;
  9. }
  10. String readMsg() {
  11. return msg;
  12. }
  13. }
  14. public void setInner(String msg) {
  15. InnerClass innerClass = new InnerClass(msg);
  16. System.out.println(innerClass.readMsg());
  17. }
  18. public static void main(String[] args) {
  19. OuterClass outerClass = new OuterClass();
  20. outerClass.setInner("inner");
  21. }
  22. }
  1. 通常情况下,外部类会通过一个方法来获取内部类的引用:
public class OuterClass {
    class InnerClass {
        private String msg;

        InnerClass(String msg) {
            this.msg = msg;
        }

        String readMsg() {
            return msg;
        }
    }

    // 获取内部类的引用
    public InnerClass getInnerClass(String msg) {
        return new InnerClass(msg);
    }

    public static void main(String[] args) { 
        InnerClass inner = outerClass.getInnerClass("inner");
        System.out.println(inner.readMsg());
    }
}

如果想从外部类的非静态方法之外的任意位置创建某个内部类的对象,那么必须像在 test() 方法中具体地指明这个对象的类型:**OuterClassName.InnerClassName**。( 在外部类的静态方法中可以直接指明类型 **InnerClassName**)

public class TestInner {
    @Test
    public void test() {
        OuterClass outerClass = new OuterClass();

        // OuterClassName.InnerClassName
        OuterClass.InnerClass innerClass = outerClass.getInnerClass("test");
        System.out.println(innerClass.readMsg());
    }
}

当生成一个内部类时,它能访问其外围类的所有成员:

interface Selector {
    boolean end();
    Object current();
    void next();
}

public class Sequence {
    private Object[] items;
    private int next = 0;

    public Sequence(int size) {
        items = new Object[size];
    }

    public void add(Object x) {
        if(next < items.length)
            items[next++] = x;
    }

    private class SequenceSelector implements Selector {
        private int i = 0;

        @Override
        public boolean end() { 
            return i == items.length; 
        }

        @Override
        public Object current() { 
            return items[i]; 
        }

        @Override
        public void next() { 
            if(i < items.length) i++; 
        }
    }

    public Selector selector() {
        return new SequenceSelector();
    }

    public static void main(String[] args) {
        Sequence sequence = new Sequence(10);
        for(int i = 0; i < 10; i++) {
            sequence.add(Integer.toString(i));
        }

        Selector selector = sequence.selector();

        while(!selector.end()) {
            System.out.print(selector.current() + " ");
            selector.next();
        }
    }
}

**Sequence** 类只是一个固定大小的 **Object** 的数组,以类的形式包装了起来。可以调用 add() 在序列末尾增加新的对象,要获取 **Sequence** 中的每一个对象,可以使用 **Selector** 接口。这是“迭代器”设计模式的一个例子。**Selector** 允许检查序列是否到末尾了(end()),访问当前对象(current()),以及移到序列中的下一个对象(next())。因为 Selector 是一个接口,所以别的类可以按它们自己的方式来实现这个接口,并且其他方法能以此接口为参数,来生成更加通用的代码。
这里,**SequenceSelector** 是提供 **Selector** 功能的 private 类。可以看到,在 main() 中创建了一个 **Sequence**,并向其中添加了一些 **String** 对象。然后通过调用 selector() 获取一个 **Selector**,并用它在 **Sequence** 中移动和选择每一个元素。 注意方法 end()current()next() 都用到了 items,这是一个引用,是外围类中的一个 private 字段。
内部类自动拥有对其外围类所有成员的访问权,当某个外部类的对象创建了一个内部类对象时,此内部类对象会获取一个指向外部类对象的引用,在访问此外部类的成员时,通过该引用来获取外部类的成员,编译器会处理所有的细节。
可以看到:内部类的对象只能在与其外围类的对象相关联的情况下才能被创建(就像你应该看到的,内部类是非 static 类时)。构建内部类对象时,需要一个指向其外围类对象的引用,如果编译器访问不到这个引用就会报错。

2. .this 和 .new

如果需要生成对外部类对象的引用,可以使用 **OuterClassName.this**。这样产生的引用自动地具有正确的类型,这一点在编译期就被知晓并受到检查,因此没有任何运行时开销。下面的示例展示了如何使用 .this

package innerClass;

public class OuterClass {   
    public void printThis() {
        System.out.println("外部类");
    }

    class InnerClass {
        // 获取外部类
        OuterClass getOuterClass() {
            return OuterClass.this;
        }
    }

    public InnerClass getInnerClass(String msg) {
        return new InnerClass(msg);
    }

    public static void main(String[] args) {
        OuterClass outerClass = new OuterClass();

        InnerClass inner = outerClass.getInnerClass();

        OuterClass outerClass1 = inner.getOuterClass();

        outerClass1.printThis();
    }
}

有时可能想要告知某些其他对象,去创建其某个内部类的对象,此时必须在 new 表达式中提供对其他外部类对象的引用,使用 .new 语法,就像下面这样:

public class DotNew {
    public class Inner {}

    public static void main(String[] args) {
        DotNew dn = new DotNew();
        DotNew.Inner dni = dn.new Inner();
    }
}

要想直接创建内部类的对象,不能去引用外部类的名字 **DotNew**,而是必须使用外部类的对象来创建该内部类对象。在拥有外部类对象之前是不可能创建内部类对象的。这是因为内部类对象会隐秘地连接到建它的外部类对象上。但是,如果创建的是嵌套类(静态内部类),那么它就不需要对外部类对象的引用。

3. 向上转型

当将内部类向上转型为其基类,尤其是转型为一个接口的时候,内部类就有了用武之地。这是因为此内部类-某个接口的实现-能够完全不可见,并且不可用。所得到的只是指向基类或接口的引用,所以能够很方便地隐藏实现细节。

public interface Destination {
    String readLabel();
}
public interface Contents {
    int value();
}

class Parcel {
    private class PContents implements Contents {
        private int i = 11;
        @Override
        public int value() { return i; }
    }
    protected final class PDestination implements Destination {
        private String label;
        private PDestination(String whereTo) {
            label = whereTo;
        }
        @Override
        public String readLabel() { return label; }
    }
    public Destination destination(String s) {
        return new PDestination(s);
    }
    public Contents contents() {
        return new PContents();
    }
}
public class TestParcel {
    public static void main(String[] args) {
        Parcel p = new Parcel();
        Contents c = p.contents();
        Destination d = p.destination("Tasmania");
        // 非法,不允许通过构造函数获取 Content
        //- Parcel.PContents pc = p.new PContents();
    }
}

**Parcel** 中,内部类 **PContents**private,所以除了 **Parcel**,没有人能访问它。普通(非内部)类的访问权限不能被设为 private 或者 protected;他们只能设置为 publicpackage 访问权限。
**PDestination**protected,所以只有 **Parcel** 及其子类、还有与 **Parcel** 同一个包中的类能访问 **PDestination**,这意味着,如果客户端程序员想了解或访问这些成员,那是要受到限制的。private 内部类给类的设计者提供了一种途径,通过这种方式可以完全阻止任何依赖于类型的编码,并且完全隐藏了实现的细节。

4. 方法内部类和作用域

内部类的语法覆盖了大量其他的更加难以理解的技术。例如,可以在一个方法里面或者在任意的作用域内定义内部类。这么做有两个理由:

  1. 如前所示,实现了某类型的接口,于是可以创建并返回对其的引用。
  2. 要解决一个复杂的问题,想创建一个类来辅助解决,但是又不希望这个类是公共可用的。\ ```java package innerClass;

public class OuterClass2 { public Inner getInnerClass() { final class InnerClass2 implements Inner { @Override public void pintMsg() { System.out.println(“方法内部类”); } }

    return new InnerClass2();
}

public static void main(String[] args) {
    OuterClass2 outerClass2 = new OuterClass2();
    Inner inner = outerClass2.getInnerClass();
    inner.pintMsg();
}

}

interface Inner { void pintMsg(); }

`**InnerClass2**` 类是 `getInnerClass()` 方法内的一个内部类,所以 `**OuterClass2**` 类是无法访问到。在 return 语句中的向上转型返回的是 `**Inner**` 的引用, `getInnerClass()` 中定义了内部类 `**InnerClass2**` ,可以在方法外部调用。
<a name="WuKJK"></a>
# 5. 匿名内部类
创建一个继承自 `**Inner**` 的匿名类的对象,把该匿名类的引用自动向上转型为 `**Inner**` 并返回。
```java
public class OuterClass2 {
    public Inner getInnerClass() {
        return new Inner() {
            @Override
            public void pintMsg() {
                System.out.println("匿名内部类");
            }
        };
    }

    public static void main(String[] args) {
        OuterClass2 outerClass2 = new OuterClass2();
        Inner inner = outerClass2.getInnerClass();
        inner.pintMsg();
    }
}

可以向匿名内部类传参:

public class Parcel {
    public Wrapping wrapping(int x) {
        // Base constructor call:
        return new Wrapping(x) { // 1
            @Override
            public int value() {
                return super.value() * 47;
            }
        }; // 2
    }
    public static void main(String[] args) {
        Parcel p = new Parcel();
        Wrapping w = p.wrapping(10);
    }
}

public class Wrapping {
    private int i;
    public Wrapping(int x) { i = x; }
    public int value() { return i; }
}
  1. 将合适的参数传递给基类的构造器。
  2. 在匿名内部类末尾的分号,并不是用来标记此内部类结束的。实际上,它标记的是 return 表达式的结束,只不过这个表达式正巧包含了匿名内部类。

在匿名类中定义字段时,还能够对其执行初始化操作,如果定义一个匿名内部类,并且希望它使用一个在其外部定义的对象,编译器会要求其参数引用是 final 的(也就是说,它在初始化后不会改变,所以可以被当作 final),在 destination() 的参数中看到。这里省略掉 final

public class Parcel {
    // 匿名内部类如果想要使用方法传进的参数,则该参数必须为 final
    public Destination destination(final String dest) {
        return new Destination() {
            private String label = dest;
            @Override
            public String readLabel() { return label; }
        };
    }

    public static void main(String[] args) {
        Parcel p = new Parcel();
        Destination d = p.destination("Tasmania");
    }
}

6. 嵌套类

如果不需要内部类对象与其外围类对象之间有联系,可以将内部类声明为 static,通常称为嵌套类,嵌套类意味着:

  1. 要创建嵌套类的对象,并不需要其外围类的对象。
  2. 不能从嵌套类的对象中访问非静态的外围类对象。
  3. 普通的内部类不能有 static 数据和 static 字段,也不能包含嵌套类,但嵌套类可以包含这些。

    public class Parcel11 {
     private static class ParcelContents implements Contents {
         private int i = 11;
         @Override
         public int value() { return i; }
     }
    
     protected static final class ParcelDestination
             implements Destination {
         private String label;
         private ParcelDestination(String whereTo) {
             label = whereTo;
         }
         @Override
         public String readLabel() { return label; }
         // Nested classes can contain other static elements:
         public static void f() {}
         static int x = 10;
         static class AnotherLevel {
             public static void f() {}
             static int x = 10;
         }
     }
    
     public static Destination destination(String s) {
         return new ParcelDestination(s);
     }
    
     public static Contents contents() {
         return new ParcelContents();
     }
    
     public static void main(String[] args) {
         Contents c = contents();
         Destination d = destination("Tasmania");
     }
    }
    

    如果想要创建某些公共代码,使得它们可以被某个接口的所有不同实现所共用,则可以在该接口宏编写一个嵌套类,嵌套类可以作为接口的一部分,放到接口中的任何类都自动地是 publicstatic 的。因为类是 static 的,只是将嵌套类置于接口的命名空间内,还可以在内部类中实现其外围接口,就像下面这样:

    public interface ClassInInterface {
     void howdy();
     class Test implements ClassInInterface {
         @Override
         public void howdy() {
             System.out.println("Howdy!");
         }
         public static void main(String[] args) {
             new Test().howdy();
         }
     }
    }
    

    7. 使用内部类的原因

    使用内部类最吸引人的原因是:每个内部类都能独立地实现一个接口或继承一个类,无论外部类是否已经继承了某个(接口的)实现,对于内部类都没有影响。
    使用内部类,还可以获得其他一些特性:

  4. 内部类可以有多个实例,每个实例都有自己的状态信息,并且与其外部类对象的信息相互独立。

  5. 在单个外部类中,可以让多个内部类以不同的方式实现同一个接口,或继承同一个类。
  6. 创建内部类对象的时刻并不依赖于外部类对象的创建
  7. 内部类没有 “is-a”关系,它就是一个独立的实体。