Java基础:内部类

来源:互联网 发布:linux redmine 启动 编辑:程序博客网 时间:2024/06/17 21:19

前言

在开发过程中不想创建类文件或者想把逻辑相关的类放在一起,就在类中创建类,这就是本节要讨论——内部类。

正题

可以将一个类的定义放在另一个类的定义内部,这就是内部类。

为什么要使用内部类?

1)内部类看起来像是一种代码隐藏机制,将类置于其他类的内部,但是你将会了解到,内部类远远不止于此,它了解外围类并能与之通信。而且你用内部类写出的代码更加优雅而清晰,尽管并不总是这样。

2)内部类是一种非常有用的特性,因为它允许你把一些逻辑相关的类组织在一起,并控制位于内部的类的可见性。

3)内部类拥有外围类的所有元素的访问权

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

当某个外围类的对象创建了一个内部类对象时,此内部类对象必定会秘密的捕获一个指向那个外围类对象的引用,此后在你访问外围类成员时,就是用那个引用来选择外围类的成员。必须要用外部类的对象来创建内部类对象。

使用.this创建对象

如果需要生成对外部类对象的引用,可以使用外部类的名字后面紧跟圆点和this。这样产生的引用自动的具有正确的类型,这一点在编译器就被知晓并受到检查,因此没有任何运行时开销。

public class DotThis {    void f(){        System.out.println("DotThis.f()");    }    public class Inner {        public DotThis outer()        {            return DotThis.this;        }    }    public Inner inner()    {        return new Inner();    }    public static void main(String args[]) {        DotThis dotThis = new DotThis();        DotThis.Inner inner = dotThis.inner();        inner.outer().f();    }}
对应的字节码:
Constant pool:   #1 = Fieldref           #3.#15         // back/mdj/com/test/DotThis$Inner.this$0:Lback/mdj/com/test/DotThis;   #2 = Methodref          #4.#16         // java/lang/Object."<init>":()V   #3 = Class              #18            // back/mdj/com/test/DotThis$Inner   #4 = Class              #21            // java/lang/Object   #5 = Utf8               this$0   #6 = Utf8               Lback/mdj/com/test/DotThis;   #7 = Utf8               <init>   #8 = Utf8               (Lback/mdj/com/test/DotThis;)V   #9 = Utf8               Code  #10 = Utf8               LineNumberTable  #11 = Utf8               outer  #12 = Utf8               ()Lback/mdj/com/test/DotThis;  #13 = Utf8               SourceFile  #14 = Utf8               DotThis.java  #15 = NameAndType        #5:#6          // this$0:Lback/mdj/com/test/DotThis;  #16 = NameAndType        #7:#22         // "<init>":()V  #17 = Class              #23            // back/mdj/com/test/DotThis  #18 = Utf8               back/mdj/com/test/DotThis$Inner  #19 = Utf8               Inner  #20 = Utf8               InnerClasses  #21 = Utf8               java/lang/Object  #22 = Utf8               ()V  #23 = Utf8               back/mdj/com/test/DotThis{  final back.mdj.com.test.DotThis this$0;    descriptor: Lback/mdj/com/test/DotThis;    flags: ACC_FINAL, ACC_SYNTHETIC  public back.mdj.com.test.DotThis$Inner(back.mdj.com.test.DotThis);    descriptor: (Lback/mdj/com/test/DotThis;)V    flags: ACC_PUBLIC    Code:      stack=2, locals=2, args_size=2         0: aload_0         1: aload_1         2: putfield      #1                  // Field this$0:Lback/mdj/com/test/DotThis;         5: aload_0         6: invokespecial #2                  // Method java/lang/Object."<init>":()V         9: return      LineNumberTable:        line 12: 0  public back.mdj.com.test.DotThis outer();    descriptor: ()Lback/mdj/com/test/DotThis;    flags: ACC_PUBLIC    Code:      stack=1, locals=1, args_size=1         0: aload_0         1: getfield      #1                  // Field this$0:Lback/mdj/com/test/DotThis;         4: areturn      LineNumberTable:        line 15: 0}
outer()方法返回的是内部类指向外围类对象的引用。

使用.new创建对象

有时需要告知某些其他对象,去创建其某个内部类的对象。要实现此目的,必须在new表达式中提供对其他外部类对象的引用,这是需要使用.new语法。

public class OutClass {    void f()    {        System.out.println("OutClass.f()");    }    public class Inner {        public OutClass outer()        {            return OutClass.this;        }    }    public static void main(String[] args) {        OutClass outClass = new OutClass();        outClass.new Inner();    }}

.new表达式对应的字节码:

  public static void main(java.lang.String[]);    descriptor: ([Ljava/lang/String;)V    flags: ACC_PUBLIC, ACC_STATIC    Code:      stack=4, locals=2, args_size=1         0: new           #5                  // class back/mdj/com/test/OutClass         3: dup         4: invokespecial #6                  // Method "<init>":()V         7: astore_1         8: new           #7                  // class back/mdj/com/test/OutClass$Inner        11: dup        12: aload_1        13: dup        14: invokevirtual #8                  // Method java/lang/Object.getClass:()Ljava/lang/Class;        17: pop        18: invokespecial #9                  // Method back/mdj/com/test/OutClass$Inner."<init>":(Lback/mdj/com/test/OutClass;)V        21: pop        22: return}

对应的字节码:

Constant pool:   #1 = Fieldref           #3.#15         // back/mdj/com/test/OutClass$Inner.this$0:Lback/mdj/com/test/OutClass;   #2 = Methodref          #4.#16         // java/lang/Object."<init>":()V   #3 = Class              #18            // back/mdj/com/test/OutClass$Inner   #4 = Class              #21            // java/lang/Object   #5 = Utf8               this$0   #6 = Utf8               Lback/mdj/com/test/OutClass;   #7 = Utf8               <init>   #8 = Utf8               (Lback/mdj/com/test/OutClass;)V   #9 = Utf8               Code  #10 = Utf8               LineNumberTable  #11 = Utf8               outer  #12 = Utf8               ()Lback/mdj/com/test/OutClass;  #13 = Utf8               SourceFile  #14 = Utf8               OutClass.java  #15 = NameAndType        #5:#6          // this$0:Lback/mdj/com/test/OutClass;  #16 = NameAndType        #7:#22         // "<init>":()V  #17 = Class              #23            // back/mdj/com/test/OutClass  #18 = Utf8               back/mdj/com/test/OutClass$Inner  #19 = Utf8               Inner  #20 = Utf8               InnerClasses  #21 = Utf8               java/lang/Object  #22 = Utf8               ()V  #23 = Utf8               back/mdj/com/test/OutClass{  final back.mdj.com.test.OutClass this$0;    descriptor: Lback/mdj/com/test/OutClass;    flags: ACC_FINAL, ACC_SYNTHETIC  public back.mdj.com.test.OutClass$Inner(back.mdj.com.test.OutClass);    descriptor: (Lback/mdj/com/test/OutClass;)V    flags: ACC_PUBLIC    Code:      stack=2, locals=2, args_size=2         0: aload_0         1: aload_1         2: putfield      #1                  // Field this$0:Lback/mdj/com/test/OutClass;         5: aload_0         6: invokespecial #2                  // Method java/lang/Object."<init>":()V         9: return      LineNumberTable:        line 13: 0  public back.mdj.com.test.OutClass outer();    descriptor: ()Lback/mdj/com/test/OutClass;    flags: ACC_PUBLIC    Code:      stack=1, locals=1, args_size=1         0: aload_0         1: getfield      #1                  // Field this$0:Lback/mdj/com/test/OutClass;         4: areturn      LineNumberTable:        line 16: 0}

字段二级制数据:10 10 00 05 00 06

从输出的字节码可以看出:内部类维护一个final型的外部类对象,而且内部类的构造器都会自动加上指向其外部类对象的一个引用参数,导致内部类维护外部类的一个引用。

在拥有外部类对象之前是不可能创建内部类对象的,这是因为内部类对象会暗暗的连接到创建它的外部类对象上。

一般来说,内部类继承自某各类或实现某个接口,内部类的代码操作创建它的外围类的对象。所以可以认为内部类提供了某种进入其外围类的窗口。

每个内部类都能独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。

多重继承

如果没有内部类提供的可以继承多个具体的或抽象的类的能力,一些设计编程问题就很难解决。从这个角度看,内部类使得多重继承的解决方案变得完整。接口实现了部分问题,而内部类有效的实现了“多重继承”,也就是说,内部类允许继承多个非接口类型。

public class D {}public abstract class E {}public class F extends E{    public D makeD()    {        return new D();    }}public class G {    static void takesD(D d){}    static void takesE(E e){}    public static void main(String args[]) {        F f = new F();        takesD(f.makeD());        takesE(f);    }}

java语言单继承特性,以上场景通过内部类来实现多重继承。

在方法和作用域内的内部类

上面只是简单的在类中创建内部类,然而内部类的语法覆盖了大量其他难以理解的技术,例如,可以在一个方法或者任意作用域内定义内部类,这么做有两大理由:

1)实现了某类型的接口,于是可以创建并返回对其的引用。

2)你需要解决一个复杂的问题,想创建一个类来辅助你的解决方案,但是又不希望这个类是公共的。

public interface Destination {    String readLabel();}public class Parcel1 {    public Destination destination(String s)    {        class PDestination implements  Destination        {            private String label;            private PDestination(String s)            {                label = s;            }            @Override            public String readLabel() {                return label;            }        }        return new PDestination(s);    }    public static void main(String args[]) {        Parcel1 parcel1 = new Parcel1();        parcel1.destination("child");    }}

PDestination类时destination()方法的一部分,而不是Parcel1的一部分。所以在destination()方法之外不能访问PDestination。

虽然PDestination构造方法是private,但是Java虚拟机会自动转为包访问权限:

  com.inner.Parcel1$1PDestination(com.inner.Parcel1, java.lang.String);    flags:    Code:      stack=2, locals=3, args_size=3         0: aload_0         1: aload_1         2: putfield      #1                  // Field this$0:Lcom/inner/Parcel1;         5: aload_0         6: invokespecial #2                  // Method java/lang/Object."<init>":()V         9: aload_0        10: aload_2        11: putfield      #3                  // Field label:Ljava/lang/String;        14: return

内部类覆盖

public class Egg {    private York y;    protected class York {        public York() {            System.out.println("Egg.York");        }    }    public Egg() {        System.out.println("Egg");        y = new York();    }}public class BigEgg extends Egg {    public class York {        public York() {            System.out.println("Egg.York");        }    }    public static void main(String args[]) {        BigEgg bigEgg = new BigEgg();    }}
运行结果:
EggEgg.York

从结果可以看出:当继承某个外围类的时候,内部类并没有发生什么特别神奇的变化。这两个内部类是完全独立地两个实体,各自在自己的命名空间内。

继承内部类

内部类的构造器必须连接到指向其外围类对象的引用上,所以在继承内部类的时候,事情会变得有点复杂。问题在于那个指向外围类对象的“秘密的”引用必须被初始化,而在导出类不再存在可连接的默认对象,要解决问题,必须使用特殊的语法来明确说清它们之间的关联。

public class D {    public class A{}}public class C extends D.A{    C(D d) {        d.super();    }}
可以看到C只继承自内部类,而不是外围类。但是当要生成一个构造器时,默认的构造器并不算好,而且不能只是传递一个指向外围类对象的引用,此外必须在构造器内使用如下语法:

enclosingClassReference.super();

接口内部类

正常情况下,不能在接口内放置任何代码,但嵌套类可以作为接口的一部分。你放到接口中的任何类都自动的是public和static。因为类是static的,只是将嵌套类置于接口的命名空间内,这并不违反接口的规则。你甚至可以在内部类中实现其外围接口。

public interface AInterface {    void A();    class B implements AInterface {        @Override        public void A() {            System.out.println("A()");        }        public static void main(String args[]) {            new B().A();        }    }}

对应的字节码:

Constant pool:   #1 = Methodref          #8.#19         // java/lang/Object."<init>":()V   #2 = Fieldref           #20.#21        // java/lang/System.out:Ljava/io/PrintStream;   #3 = String             #22            // A()   #4 = Methodref          #23.#24        // java/io/PrintStream.println:(Ljava/lang/String;)V   #5 = Class              #25            // back/mdj/com/test/AInterface$B   #6 = Methodref          #5.#19         // back/mdj/com/test/AInterface$B."<init>":()V   #7 = Methodref          #5.#28         // back/mdj/com/test/AInterface$B.A:()V   #8 = Class              #29            // java/lang/Object   #9 = Class              #30            // back/mdj/com/test/AInterface  #10 = Utf8               <init>  #11 = Utf8               ()V  #12 = Utf8               Code  #13 = Utf8               LineNumberTable  #14 = Utf8               A  #15 = Utf8               main  #16 = Utf8               ([Ljava/lang/String;)V  #17 = Utf8               SourceFile  #18 = Utf8               AInterface.java  #19 = NameAndType        #10:#11        // "<init>":()V  #20 = Class              #31            // java/lang/System  #21 = NameAndType        #32:#33        // out:Ljava/io/PrintStream;  #22 = Utf8               A()  #23 = Class              #34            // java/io/PrintStream  #24 = NameAndType        #35:#36        // println:(Ljava/lang/String;)V  #25 = Utf8               back/mdj/com/test/AInterface$B  #26 = Utf8               B  #27 = Utf8               InnerClasses  #28 = NameAndType        #14:#11        // A:()V  #29 = Utf8               java/lang/Object  #30 = Utf8               back/mdj/com/test/AInterface  #31 = Utf8               java/lang/System  #32 = Utf8               out  #33 = Utf8               Ljava/io/PrintStream;  #34 = Utf8               java/io/PrintStream  #35 = Utf8               println  #36 = Utf8               (Ljava/lang/String;)V{  public back.mdj.com.test.AInterface$B();    descriptor: ()V    flags: ACC_PUBLIC    Code:      stack=1, locals=1, args_size=1         0: aload_0         1: invokespecial #1                  // Method java/lang/Object."<init>":()V         4: return      LineNumberTable:        line 10: 0  public void A();    descriptor: ()V    flags: ACC_PUBLIC    Code:      stack=2, locals=1, args_size=1         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;         3: ldc           #3                  // String A()         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V         8: return      LineNumberTable:        line 14: 0        line 15: 8  public static void main(java.lang.String[]);    descriptor: ([Ljava/lang/String;)V    flags: ACC_PUBLIC, ACC_STATIC    Code:      stack=2, locals=1, args_size=1         0: new           #5                  // class back/mdj/com/test/AInterface$B         3: dup         4: invokespecial #6                  // Method "<init>":()V         7: invokevirtual #7                  // Method A:()V        10: return      LineNumberTable:        line 18: 0        line 19: 10}
其中InnerClasses属性值:

00 1B 00 00 00 0A 00 01 00 05 00 09 00 1A 00 09

从InnerClasses属性的访问标志位:00 09可知该内部类是public和static的。

匿名内部类

public interface Contents {    int value();}public class Parcel {    public Contents contents()    {        return new Contents() {            private int i = 8;            @Override            public int value() {                return i;            }        };    }    public static void main(String args[]) {        Parcel parcel = new Parcel();        parcel.contents();    }}

使用javac命令编译Parcel文件,会自动生成Parcel$1.class文件,对应函数contents()创建的匿名内部类。

对应的字节码:

Constant pool:   #1 = Fieldref           #4.#22         // com/inner/Parcel$1.this$0:Lcom/inner/Parcel;   #2 = Methodref          #5.#23         // java/lang/Object."<init>":()V   #3 = Fieldref           #4.#24         // com/inner/Parcel$1.i:I   #4 = Class              #25            // com/inner/Parcel$1   #5 = Class              #27            // java/lang/Object   #6 = Class              #28            // com/inner/Contents   #7 = Utf8               i   #8 = Utf8               I   #9 = Utf8               this$0  #10 = Utf8               Lcom/inner/Parcel;  #11 = Utf8               <init>  #12 = Utf8               (Lcom/inner/Parcel;)V  #13 = Utf8               Code  #14 = Utf8               LineNumberTable  #15 = Utf8               value  #16 = Utf8               ()I  #17 = Utf8               SourceFile  #18 = Utf8               Parcel.java  #19 = Utf8               EnclosingMethod  #20 = Class              #29            // com/inner/Parcel  #21 = NameAndType        #30:#31        // contents:()Lcom/inner/Contents;  #22 = NameAndType        #9:#10         // this$0:Lcom/inner/Parcel;  #23 = NameAndType        #11:#32        // "<init>":()V  #24 = NameAndType        #7:#8          // i:I  #25 = Utf8               com/inner/Parcel$1  #26 = Utf8               InnerClasses  #27 = Utf8               java/lang/Object  #28 = Utf8               com/inner/Contents  #29 = Utf8               com/inner/Parcel  #30 = Utf8               contents  #31 = Utf8               ()Lcom/inner/Contents;  #32 = Utf8               ()V{  final com.inner.Parcel this$0;    descriptor: Lcom/inner/Parcel;    flags: ACC_FINAL, ACC_SYNTHETIC  com.inner.Parcel$1(com.inner.Parcel);    descriptor: (Lcom/inner/Parcel;)V    flags:    Code:      stack=2, locals=2, args_size=2         0: aload_0         1: aload_1         2: putfield      #1                  // Field this$0:Lcom/inner/Parcel;         5: aload_0         6: invokespecial #2                  // Method java/lang/Object."<init>":()V         9: aload_0        10: bipush        8        12: putfield      #3                  // Field i:I        15: return      LineNumberTable:        line 6: 0        line 7: 9  public int value();    descriptor: ()I    flags: ACC_PUBLIC    Code:      stack=1, locals=1, args_size=1         0: aload_0         1: getfield      #3                  // Field i:I         4: ireturn      LineNumberTable:        line 10: 0}SourceFile: "Parcel.java"EnclosingMethod: #20.#21                // com.inner.Parcel.contentsInnerClasses:     #4; //class com/inner/Parcel$1

contents方法将返回值的生成与表示这个这个返回值的类的定义结合在一起,另外这个类时匿名的,它没有名字。更糟糕的是,看起来似乎是你正要创建一个Contents对象。这种奇怪的语法指的是:“创建一个继承自Contents的匿名类的对象。”通过new表达式返回的引用被自动向上转型为对Contents的引用。

在匿名内部类末尾的分号,并不是用来标记此内部类结束的。实际上它标记的是表达式的结束,只不过这个表达式正巧包含了匿名内部类罢了。因此这与别的地方使用的分号是一致的。

下面看看内部类的一些特性:

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

2)在单个外围类中,可以让多个内部类以不同的方式实现同一个接口或继承同一个类。

3)创建内部类对象的时刻不依赖于外围类对象的创建。

4)内部类并没有令人迷惑的“is—a”关系,它就是一个独立地实体。

嵌套类

如果不需要内部类对象与其外围类对象之间有联系,那么可以将内部类声明为static,这通常称为嵌套类,想要理解static应用于内部类时的含义,就必须记住普通的内部类对象隐藏的保存了一个引用,指向创建它的外围类对象上。然而,当内部类时static时,就不是这样了。

public class OuterClass {public static class Inner {}public static void main(String[] args) {Inner nInner = new Inner();}}
但是如果你创建的是嵌套类(静态内部类),那么它就不需要外部类对象的引用。

嵌套类意味着:

1)要创建嵌套类对象并不需要创建外围类对象。

2)不能从嵌套类对象中访问非静态的外围类对象

嵌套类与普通内部类还有一个区别:普通内部类的字段与方法,只能放在类的外部层次上,所以普通的内部类不能有static数据和static字段,也不能包含嵌套类(内部类对象依附于外围类,内部维护一个指向外围类对象的引用,而static数据和字段只属于类不属于对象,所以不能再内部类中包含static数据)。

就像你再本节前面看到的那样,在一个普通的(非static)内部类中,通过一个特殊的this引用可以链接到其外围类对象。嵌套类就没有这个特殊的this引用,这使得它类似于一个static方法。

当继承了某个外围类的时候,内部类并没有发生什么特别的神奇变化,这两个内部类是完全独立地个体,各自在自己的命名空间内。

原创粉丝点击