您的位置:首页 > 编程语言 > Java开发

Java魔法堂:内部类详解

2015-02-03 15:02 387 查看
一、前言                              

对于内部类平时编码时使用的场景不多,比较常用的地方应该就是绑定事件处理程序的时候了(从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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: