Java魔法堂:内部类详解
2015-02-03 15:02
387 查看
一、前言
对于内部类平时编码时使用的场景不多,比较常用的地方应该就是绑定事件处理程序的时候了(从C#、JS转向Java阵营的孩子总不不习惯用匿名内部类来做事件订阅:()。本文将结合Bytecode对四种内部类作介绍,当作一次梳理以便日后查阅。
首先要明确的是内部类是编译器提供的特性,编译器会将含内部类的java文件编译成外部类和内部类的N个文件(N>=2) ,然后JVM就按普通类的方式运行。就如下面的源码会被编译为Outer.class和和Outer$Inner.class文件。
三、成员内部类
定义在一个类的内部。相对外部类仅有默认和public两种访问修饰符而言,成员内部类可有默认、private、proteced和public四种访问修饰符,效果与成员字段和方法的一样。
示例:
并生成MemberCls.class和MemberCls$Inner.class两个类文件。
LocalCls$1Inner.class
上述两个类文件与成员内部类的几乎一模一样,那么就是说内部类作用范围的限制其实是编译器的限制,而不是JVM的限制了。
注意:
1. 不能有public、protected、private和static作修饰;
2. 局部内部类中仅能访问方法或作用域内的常量,若访问的是变量则编译时会出错。
Q:为什么不能访问局部变量呢?
A:假设可以访问局部变量,那么要考虑的是如何引用到局部变量。
首先局部变量是存放在JVM栈帧中的局部变量表中,并且当方法执行完栈帧也随之弹出,也就是说局部变量所占的内存空间是短暂的(不稳定)。
假如局部变量A是基本类型的话,那么数据直接就存放在局部变量表中相应的Slots中,方法执行完就没了。那局部内部类中所访问的局部变量A到底是什么就无从得知了! 假如局部变量A是String类型或其他类类型,那么局部内部类中访问的局部变量A时就有两种方式了,第一种是访问String常量池中该字符串的地址,第二种是指向局部变量A的地址,然后通过变量A去访问String常量池中该字符串。
但上述这些均是在运行时才能决定,而编译时是无法正确地被描述出来。并且由于内部类将被编译成独立的类文件,访问其他类方法的局部变量的操作无法在类文件中描述。而常量则可以在内部类文件的常量池部分中被正确地描述,而JVM中处理时也十分简单高效。类文件的常量池条目将合并到运行时常量池中,因此外部和内部量访问的是同一个常量。
下面的Bytecodes表示内部类中直接将常量池中的常量压栈后作为返回值返回。
五、匿名内部类
匿名内部类其实是局部内部类的特殊形式。一般用来绑定事件监听处理程序上。Android示例:
上述代码生成了一个继承OnClickListener类的匿名内部类,然后实例化匿名类的一个实例,然后以该实例作为参数调用setOnClickListener方法。
并生成一个Outer.class和Outer$1.class类文件。
注意事项与局部内部一样。
六、静态内部类
静态内部类定义在类下,只不过多了个关键字static。静态内部类只能访问外部类的静态字段和静态方法。
而实例化静态内部类时只需 new 外部类.静态内部类() 。
七、总结
尊重原创,转载请注明来自:/article/4741142.html ^_^肥仔John
八、参考
http://www.cnblogs.com/dolphin0520/p/3811445.html
对于内部类平时编码时使用的场景不多,比较常用的地方应该就是绑定事件处理程序的时候了(从C#、JS转向Java阵营的孩子总不不习惯用匿名内部类来做事件订阅:()。本文将结合Bytecode对四种内部类作介绍,当作一次梳理以便日后查阅。
首先要明确的是内部类是编译器提供的特性,编译器会将含内部类的java文件编译成外部类和内部类的N个文件(N>=2) ,然后JVM就按普通类的方式运行。就如下面的源码会被编译为Outer.class和和Outer$Inner.class文件。
class Outer{ class Inner{} }
三、成员内部类
定义在一个类的内部。相对外部类仅有默认和public两种访问修饰符而言,成员内部类可有默认、private、proteced和public四种访问修饰符,效果与成员字段和方法的一样。
示例:
import java.io.*; // Main.java文件 class Main{ public static void main(String[] args) throws IOException{ MemberCls outer = new MemberCls(); Inner inner1 = outer.new Inner(); Inner inner2 = outer.getInner(); System.out.println(inner1.getVal()); System.out.println(inner2.getVal()); inner1.setVal(2); System.out.println(inner1.getVal()); System.out.println(inner2.getVal()); inner2.setVal(3); System.out.println(inner1.getVal()); System.out.println(inner2.getVal()); System.in.read(); } } // MemberCls.java文件 class MemberCls{ private int val = 1; class Inner{ void setVal(int val){ MemberCls.this.val = val; } int getVal(){ return val; } } Inner getInner(){ return new Inner(); } // 运行结果 // 1 // 1 // 2 // 2 // 3 // 3
并生成MemberCls.class和MemberCls$Inner.class两个类文件。
Classfile /F:/skyDrive/repos/self/jottings/java/sample/02/LocalCls$1Inner.class Last modified 2015-2-3; size 651 bytes MD5 checksum a4bf7c12f15f22b2ebb3f79438a555ab Compiled from "LocalCls.java" class LocalCls$1Inner SourceFile: "LocalCls.java" EnclosingMethod: #23.#24 // LocalCls.print InnerClasses: #31= #6; //Inner=class LocalCls$1Inner minor version: 0 major version: 51 flags: ACC_SUPER Constant pool: #1 = Fieldref #6.#25 // LocalCls$1Inner.this$0:LLocalCls; #2 = Methodref #7.#26 // java/lang/Object."<init>":()V #3 = Methodref #23.#27 // LocalCls.access$000:(LLocalCls;)I #4 = Methodref #23.#28 // LocalCls.access$002:(LLocalCls;I)I #5 = String #29 // fsjohnhuang #6 = Class #30 // LocalCls$1Inner #7 = Class #33 // java/lang/Object #8 = Utf8 this$0 #9 = Utf8 LLocalCls; #10 = Utf8 <init> #11 = Utf8 (LLocalCls;)V #12 = Utf8 Code #13 = Utf8 LineNumberTable #14 = Utf8 getVal #15 = Utf8 ()I #16 = Utf8 setVal #17 = Utf8 (I)V #18 = Utf8 getName #19 = Utf8 ()Ljava/lang/String; #20 = Utf8 SourceFile #21 = Utf8 LocalCls.java #22 = Utf8 EnclosingMethod #23 = Class #34 // LocalCls #24 = NameAndType #35:#36 // print:()V #25 = NameAndType #8:#9 // this$0:LLocalCls; #26 = NameAndType #10:#36 // "<init>":()V #27 = NameAndType #37:#38 // access$000:(LLocalCls;)I #28 = NameAndType #39:#40 // access$002:(LLocalCls;I)I #29 = Utf8 fsjohnhuang #30 = Utf8 LocalCls$1Inner #31 = Utf8 Inner #32 = Utf8 InnerClasses #33 = Utf8 java/lang/Object #34 = Utf8 LocalCls #35 = Utf8 print #36 = Utf8 ()V #37 = Utf8 access$000 #38 = Utf8 (LLocalCls;)I #39 = Utf8 access$002 #40 = Utf8 (LLocalCls;I)I { final LocalCls this$0; flags: ACC_FINAL, ACC_SYNTHETIC LocalCls$1Inner(LocalCls); flags: Code: stack=2, locals=2, args_size=2 0: aload_0 1: aload_1 2: putfield #1 // Field this$0:LLocalCls; 5: aload_0 6: invokespecial #2 // Method java/lang/Object."<init>":()V 9: return LineNumberTable: line 15: 0 int getVal(); flags: Code: stack=1, locals=1, args_size=1 0: aload_0 1: getfield #1 // Field this$0:LLocalCls; 4: invokestatic #3 // Method LocalCls.access$000:(LLocalCls;)I 7: ireturn LineNumberTable: line 17: 0 void setVal(int); flags: Code: stack=2, locals=2, args_size=2 0: aload_0 1: getfield #1 // Field this$0:LLocalCls; 4: iload_1 5: invokestatic #4 // Method LocalCls.access$002:(LLocalCls;I)I 8: pop 9: return LineNumberTable: line 20: 0 line 21: 9 java.lang.String getName(); flags: Code: stack=1, locals=1, args_size=1 0: ldc #5 // String fsjohnhuang 2: areturn LineNumberTable: line 23: 0 }
LocalCls$1Inner.class
上述两个类文件与成员内部类的几乎一模一样,那么就是说内部类作用范围的限制其实是编译器的限制,而不是JVM的限制了。
注意:
1. 不能有public、protected、private和static作修饰;
2. 局部内部类中仅能访问方法或作用域内的常量,若访问的是变量则编译时会出错。
Q:为什么不能访问局部变量呢?
A:假设可以访问局部变量,那么要考虑的是如何引用到局部变量。
首先局部变量是存放在JVM栈帧中的局部变量表中,并且当方法执行完栈帧也随之弹出,也就是说局部变量所占的内存空间是短暂的(不稳定)。
假如局部变量A是基本类型的话,那么数据直接就存放在局部变量表中相应的Slots中,方法执行完就没了。那局部内部类中所访问的局部变量A到底是什么就无从得知了! 假如局部变量A是String类型或其他类类型,那么局部内部类中访问的局部变量A时就有两种方式了,第一种是访问String常量池中该字符串的地址,第二种是指向局部变量A的地址,然后通过变量A去访问String常量池中该字符串。
但上述这些均是在运行时才能决定,而编译时是无法正确地被描述出来。并且由于内部类将被编译成独立的类文件,访问其他类方法的局部变量的操作无法在类文件中描述。而常量则可以在内部类文件的常量池部分中被正确地描述,而JVM中处理时也十分简单高效。类文件的常量池条目将合并到运行时常量池中,因此外部和内部量访问的是同一个常量。
下面的Bytecodes表示内部类中直接将常量池中的常量压栈后作为返回值返回。
java.lang.String getName(); flags: Code: stack=1, locals=1, args_size=1 0: ldc #5 // String fsjohnhuang 2: areturn LineNumberTable: line 23:
五、匿名内部类
匿名内部类其实是局部内部类的特殊形式。一般用来绑定事件监听处理程序上。Android示例:
class Outer{ public void subs(){ scan_bt.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub } }); } }
上述代码生成了一个继承OnClickListener类的匿名内部类,然后实例化匿名类的一个实例,然后以该实例作为参数调用setOnClickListener方法。
并生成一个Outer.class和Outer$1.class类文件。
注意事项与局部内部一样。
六、静态内部类
静态内部类定义在类下,只不过多了个关键字static。静态内部类只能访问外部类的静态字段和静态方法。
而实例化静态内部类时只需 new 外部类.静态内部类() 。
七、总结
尊重原创,转载请注明来自:/article/4741142.html ^_^肥仔John
八、参考
http://www.cnblogs.com/dolphin0520/p/3811445.html
相关文章推荐
- java 内部类详解
- java 内部类详解
- java 内部类详解
- Java 之 -------------- 内部类 详解
- java提高篇(八)_详解内部类 转自 http://cmsblogs.com
- JAVA_Object和内部类详解
- Java魔法堂:找外援的利器——Runtime.exec详解
- java基础 内部类详解
- java基础 内部类详解
- Java魔法堂:注解用法详解——@Override
- java 内部类详解(转)
- java提高篇(八)----详解内部类
- Java学习系列(五)Java面向对象之抽象类、接口、内部类、枚举类详解
- 详解java匿名内部类
- java提高篇(九)-----详解匿名内部类
- 【java】内部类详解
- Java学习系列(五)Java面向对象之抽象类、接口、内部类、枚举类详解
- java提高篇(七)-----详解内部类
- [JAVA基础]内部类详解
- Java魔法堂:JUnit4使用详解