java中嵌套类型(nested type),也就是所谓的内部类(inner class),主要有四种:
- 静态内部类型,在类内部定义为static成员类型,如
static class,静态内部类需要注意以下几点:- 不管有没有static关键字,interface/annotation/enum都是静态类型;
- 静态内部类不能定义在任何非静态内部类中;
- 静态内部类的程序可以访问它所在的外部类的所有静态属性和方法,包括私有的静态属性和方法;
- 静态内部类其实就是顶层类,它不与任何对象关联;
- 静态内部类可以被声明为private的,但是interface里定义的方法仍然只能是public的。
- 非静态内部类,只能是class,注意:
- 一个非静态内部类的实例总是与外部类的实例
outerClassInstance相关联; - 非静态内部类的程序可以访问它所在的外部类的所有属性和方法,包括私有的方法;
- 不可以包含任何static属性,方法;
- java为非静态内部类设计了一些特殊语法,使用
OuterClassName.this形式引用外部类的实例对象,其相关用法举例如下:- OuterClassName.this.fieldName
- OuterClassName.this.anyMethod()
- OuterClassName.this.new InnerClassName()
- outerClassInstance.new InnerClassName()
- 内部类与外部类属性名相同时,访问外部类的实例属性,只能以
OuterClassName.this.fieldName方式访问; - 使用
OuterClassName.super形式来访问外部类的超类。
- 一个非静态内部类的实例总是与外部类的实例
- 局部类(local class),是定义在java代码块里的类,特点:
- interface/annotation/enum不能定义为局部类;
- 局部类可以访问代码块内可见范围内的所有final局部变量,java 8中变量可以不显式声明为final的,但实际是不能修改变量的引用;
- 局部类不能声明为private/protected/public/static,这些修饰符针对类成员,不能声明局部变量和局部类。
- 匿名类(annonymous class),是没有名称的局部类。
- 匿名类没有名称,所以在匿名类内是不能为它定义构造函数的,这是匿名类最大的限制;
- 可以使用初始化块来代替构造函数;
- 匿名类语法:
- new class-name ( [ argument-list ] ) { class-body }
- new interface-name () { class-body }
上面提到java为非静态内部类设计了一些特殊语法,应该尽量少用。
jvm中非静态内部类的实现说明
package com.example.test.javap;public class OuterClass { class InnerClass { private int var = 3; private int test() { return var; } } public static void main(String[] args) { OuterClass example = new OuterClass(); InnerClass innerClass = example.new InnerClass(); System.out.println(innerClass.test()); }} |
非静态内部类实际上只是java在语法上表现出来的代码组织形式而已,jvm中并不存在内部类,编译器会为内部类编译成一个独立的顶层类文件,并且为了实现java语法上的语义,编译器会在生成class文件时合成(ACC_SYNTHETIC)某些特殊的方法。
外部类反编译
先看一下外部类的字节码反编译后的内容:
javap -private -v com.example.test.javap.OuterClass |
Last modified Aug 7, 2017; size 937 bytes MD5 checksum 1c58b97ef43cffed94844c5317b405c0 Compiled from "OuterClass.java"public class com.example.test.javap.OuterClass minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPERConstant pool: #1 = Methodref #10.#29 // java/lang/Object."<init>":()V #2 = Class #30 // com/example/test/javap/OuterClass #3 = Methodref #2.#29 // com/example/test/javap/OuterClass."<init>":()V #4 = Class #31 // com/example/test/javap/OuterClass$InnerClass #5 = Methodref #10.#32 // java/lang/Object.getClass:()Ljava/lang/Class; #6 = Methodref #4.#33 // com/example/test/javap/OuterClass$InnerClass."<init>":(Lcom/example/test/javap/OuterClass;)V #7 = Fieldref #34.#35 // java/lang/System.out:Ljava/io/PrintStream; #8 = Methodref #4.#36 // com/example/test/javap/OuterClass$InnerClass.access$000:(Lcom/example/test/javap/OuterClass$InnerClass;)I #9 = Methodref #37.#38 // java/io/PrintStream.println:(I)V #10 = Class #39 // java/lang/Object #11 = Utf8 InnerClass #12... #48 = Utf8 java/io/PrintStream #49 = Utf8 println #50 = Utf8 (I)V{ public com.example.test.javap.OuterClass(); 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 3: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/example/test/javap/OuterClass; public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=4, locals=3, args_size=1 0: new #2 // class com/example/test/javap/OuterClass 3: dup 4: invokespecial #3 // Method "<init>":()V 7: astore_1 8: new #4 // class com/example/test/javap/OuterClass$InnerClass 11: dup 12: aload_1 13: dup 14: invokevirtual #5 // Method java/lang/Object.getClass:()Ljava/lang/Class; 17: pop 18: invokespecial #6 // Method com/example/test/javap/OuterClass$InnerClass."<init>":(Lcom/example/test/javap/OuterClass;)V 21: astore_2 22: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream; 25: aload_2 26: invokestatic #8 // Method com/example/test/javap/OuterClass$InnerClass.access$000:(Lcom/example/test/javap/OuterClass$InnerClass;)I 29: invokevirtual #9 // Method java/io/PrintStream.println:(I)V 32: return LineNumberTable: line 15: 0 line 16: 8 line 17: 22 line 18: 32 LocalVariableTable: Start Length Slot Name Signature 0 33 0 args [Ljava/lang/String; 8 25 1 example Lcom/example/test/javap/OuterClass; 22 11 2 innerClass Lcom/example/test/javap/OuterClass$InnerClass;}SourceFile: "OuterClass.java"InnerClasses: #11= #4 of #2; //InnerClass=class com/example/test/javap/OuterClass$InnerClass of class com/example/test/javap/OuterClass |
上面反编译的代码中需要注意这个方法调用:
invokestatic #8 // Method com/example/test/javap/OuterClass$InnerClass.access$000:(Lcom/example/test/javap/OuterClass$InnerClass;)I |
外部类会调用内部类的一个静态方法,方法为access$000(OuterClass$InnerClass),这是编译器为了使外部类实例可以访问内部类的私有方法而添加的桥接方法。
内部类反编译
下面看一下内部类的字节码反编译后的内容:
javap -private -v OuterClass\$InnerClass |
Classfile /Users/david/github.com/learning-programming/java/target/classes/com/example/test/javap/OuterClass$InnerClass.class Last modified Aug 7, 2017; size 779 bytes MD5 checksum f46d7e051891da67a5a7152ae11635bf Compiled from "OuterClass.java"class com.example.test.javap.OuterClass$InnerClass minor version: 0 major version: 52 flags: ACC_SUPERConstant pool: #1 = Methodref #5.#27 // com/example/test/javap/OuterClass$InnerClass.test:()I #2 = Fieldref #5.#28 // com/example/test/javap/OuterClass$InnerClass.this$0:Lcom/example/test/javap/OuterClass; #3 = Methodref #6.#29 // java/lang/Object."<init>":()V #4 = Fieldref #5.#30 // com/example/test/javap/OuterClass$InnerClass.var:I #5 = Class #32 // com/example/test/javap/OuterClass$InnerClass #6 = Class #33 // java/lang/Object #7 = Utf8 var #8 = Utf8 I #9 = Utf8 this$0 #10... #33 = Utf8 java/lang/Object #34 = Utf8 ()V #35 = Utf8 com/example/test/javap/OuterClass{ private int var; descriptor: I flags: ACC_PRIVATE # 这个final属性是编译器合成的SYNTHETIC属性,用来指向外部类实例 final com.example.test.javap.OuterClass this$0; descriptor: Lcom/example/test/javap/OuterClass; flags: ACC_FINAL, ACC_SYNTHETIC # 编译器生成的构造方法,通过这个构造方法,将外部类的实例对象传进来,并赋值给this$0属性 com.example.test.javap.OuterClass$InnerClass(com.example.test.javap.OuterClass); descriptor: (Lcom/example/test/javap/OuterClass;)V flags: Code: stack=2, locals=2, args_size=2 0: aload_0 1: aload_1 2: putfield #2 // Field this$0:Lcom/example/test/javap/OuterClass; 5: aload_0 6: invokespecial #3 // Method java/lang/Object."<init>":()V 9: aload_0 10: iconst_3 11: putfield #4 // Field var:I 14: return LineNumberTable: line 6: 0 line 7: 9 LocalVariableTable: Start Length Slot Name Signature 0 15 0 this Lcom/example/test/javap/OuterClass$InnerClass; 0 15 1 this$0 Lcom/example/test/javap/OuterClass; private int test(); descriptor: ()I flags: ACC_PRIVATE Code: stack=1, locals=1, args_size=1 0: aload_0 1: getfield #4 // Field var:I 4: ireturn LineNumberTable: line 10: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/example/test/javap/OuterClass$InnerClass; # 这个也是编译器合成的包级别的方法,用来委托请求到私有的test方法上,没有这个桥接的方法,是没办法访问对象的私有方法的 # 对应可以看外部类的字节码,外部类实例实际上调用的就是这个静态方法 static int access$000(com.example.test.javap.OuterClass$InnerClass); descriptor: (Lcom/example/test/javap/OuterClass$InnerClass;)I flags: ACC_STATIC, ACC_SYNTHETIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method test:()I 4: ireturn LineNumberTable: line 6: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 x0 Lcom/example/test/javap/OuterClass$InnerClass;}SourceFile: "OuterClass.java"InnerClasses: #17= #5 of #31; //InnerClass=class com/example/test/javap/OuterClass$InnerClass of class com/example/test/javap/OuterClass |
上面字节码反编译之后内容中,最重要的几行前面加了中文注释,将上面的代码翻译成java代码,大致内容如下:
package com.example.test.javap;class OuterClass$InnerClass { private int var; // 这个final属性是编译器合成的SYNTHETIC属性,用来指向外部类实例 final OuterClass this$0; // 编译器生成的构造方法,通过这个构造方法,将外部类的实例对象传进来,并赋值给this$0属性 OuterClass$InnerClass(OuterClass this$0) { this.this$0 = this$0; this.var = 3; } private int test() { return this.var; } // 这个也是编译器合成的包级别的方法,用来委托请求到私有的test方法上,没有这个桥接的方法,是没办法访问对象的私有方法的 // 对应可以看外部类的字节码,外部类实例实际上调用的就是这个静态方法 static int access$000(com.example.test.javap.OuterClass$InnerClass innerClassInstance) { return innerClassInstance.test(); }} |
上面这个例子可以看到,非静态内部类最后也是编译成为顶层类。java的顶层类实际上只能是public或者是包级别可见的,所以protected和public的内部类都会被编译成public的顶级类,而包级别和private class则会编译成为包级别可见的顶级类。
java反射查看内部类
利用java反射来查看一下内部类的这几个方法和属性:
package com.example.test.javap;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;public class ReflectInnerClass { public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InvocationTargetException, InstantiationException { Class<?> innerClass = Class.forName("com.example.test.javap.OuterClass$InnerClass"); Constructor<?>[] declaredConstructors = innerClass.getDeclaredConstructors(); for (Constructor<?> declaredConstructor : declaredConstructors) { System.out.printf("%14s\t%s\n", "constructor : ", declaredConstructor); } Method[] declaredMethods = innerClass.getDeclaredMethods(); for (Method declaredMethod : declaredMethods) { System.out.printf("%14s\t%s\n", "method : ", declaredMethod); } Field[] declaredFields = innerClass.getDeclaredFields(); for (Field declaredField : declaredFields) { System.out.printf("%14s\t%s\n", "field : ", declaredField); } System.out.println("\n"); System.out.println("get OuterClass$InnerClass instance using reflect:"); OuterClass outerClassInstance = new OuterClass(); Constructor<?> syntheticConstructor = declaredConstructors[0]; Object innerClassInstance = syntheticConstructor.newInstance(outerClassInstance); System.out.println("innerClassInstance class is : " + innerClassInstance.getClass()); System.out.println("\n"); System.out.println("get field OuterClass$InnerClass.this$0 of OuterClass$InnerClass:"); Field field_1 = declaredFields[1]; Object this_0 = field_1.get(innerClassInstance); System.out.println("this$0 is instance of OuterClass : " + (this_0 == outerClassInstance)); System.out.println("\n"); System.out.println("run method static int com.example.test.javap.OuterClass$InnerClass.access$000 :"); Method access_000 = declaredMethods[1]; // 这里必须设置为true,不然反射抛出访问异常如下,虽然这个合成方法在字节码里看到是默认的包访问级别,但是反射不能执行: // java.lang.IllegalAccessException: // Class com.example.test.javap.ReflectInnerClass can not access a member of class // com.example.test.javap.OuterClass$InnerClass with modifiers "private" access_000.setAccessible(true); Object invoked = access_000.invoke(innerClassInstance); System.out.println(invoked); }} |
运行输出内容如下:
constructor : protected com.example.test.javap.OuterClass$InnerClass(com.example.test.javap.OuterClass)
method : static int com.example.test.javap.OuterClass$InnerClass.access$000(com.example.test.javap.OuterClass$InnerClass)
method : private int com.example.test.javap.OuterClass$InnerClass.test()
field : private int com.example.test.javap.OuterClass$InnerClass.var
field : final com.example.test.javap.OuterClass com.example.test.javap.OuterClass$InnerClass.this$0get OuterClass$InnerClass instance using reflect:
innerClassInstance class is : class com.example.test.javap.OuterClass$InnerClassget field OuterClass$InnerClass.this$0 of OuterClass$InnerClass:
this$0 is instance of OuterClass : truerun method static int com.example.test.javap.OuterClass$InnerClass.access$000 :
3
通过java反射也可以看到内部类中的属性this$0,合成的桥接方法access$0000,还有带参数的构造函数InnerClass(OuterClass this$0)。