内部类即定义在某个类中的类,它表示的是类之间的关系,与对象无关。即即使在某个类中定义了内部类,当实例化该外部类对象之后,并不代表该外部对象就包含有定义的内部类对象。内部类一种编译器现象,即java虚拟机并不知道所谓的内部类这个概念,这是后话。
java中存在着四种内部类,分别是常规内部类,局部内部类,匿名内部类和静态内部类。
常规内部类
常规内部类就是定义于某个类代码段当中的类,可以有访问修饰符public,private等,注意只有内部类可以是private,如下面代码段:
package ck.innerclass;
public class Father {
/**
* @param args
*/
private int num;
private int aaa;
public int bbb;
private int ccc;
public Father(int inputnum1,int inputnum2)
{
num=inputnum1;
aaa=inputnum2;
}
public class PublicInner
{
public void functest()
{
if(1==num)//can access the private
System.out.print("I am public inner class\n");
else if(2==aaa)
System.out.print("hahah\n");
else if(3==bbb)
System.out.print("bbbb\n");
}
}
private class PrivateInner
{
public void functest()
{
if(1==num)//can access the private
System.out.print("I am private inner class\n");
}
}
public void TestInnerclass()
{
PublicInner pubobj=new PublicInner();//correct
PrivateInner obj=new PrivateInner();//correct
}
}
class Test
{
public void func()
{
Father myfFather=new Father(20,30);
Father.PublicInner pubf=myfFather.new PublicInner();//correct
Father.PublicInner pubf2=new Father(30,40).new PublicInner();//correct
// Father.PrivateInner...//error
}
}
上述代码中定义了一个Father类,在其中定义了两个内部类,分别是public和private的访问权限,区别很显然,若是public内部类,则任何Father对象都可以在外部构造该内部类对象,若是private内部类,则只能在外部类内部才可以。显然这符合java访问修饰符的基本规则。并且注意到,在内部类中可以直接去访问两个私有域num和aaa。
内部类之所以能够访问外部类的私有域,是因为内部类对象中总有一个隐式引用指向创建他的外部类对象。前面说过java内部类是一种编译器现象,与虚拟机无关。编译器在处理的时候会把内部类翻译成用$分隔外部类与内部类名的常规的.class文件,编译后会生成Father.class,Father$PrivateInner.class ,Father$PublicInner.class 三个常规类文件。既然是常规类文件,那么虚拟机是如何实现访问私有域成员呢?利用反射机制查看两个内部类的class文件,
joker@Joker-PC:~/workspace/TestOracle/bin/ck/innerclass$ javap -private Father\$PublicInner.class
Compiled from "Father.java"
public class ck.innerclass.Father$PublicInner {
final ck.innerclass.Father this$0;
public ck.innerclass.Father$PublicInner(ck.innerclass.Father);
public void functest();
}
可以发现编译器为Father$PublicInner.class生成了一个附加的实例域final ck.innerclass.Father this$0,并且在该类的构造函数中传入了外部类ck.innerclass.Father参数。类似实现大概就是下面这种:
public ck.innerclass.Father$PublicInner(ck.innerclass.Father myfather)
{
this$0=myfather;
}
之后在内部类函数functest中访问外部类私有域的num时候,利用this$0来访问:
if(1==this$0.num)
System.out.print("I am public inner class\n");
else if(2==this$0.aaa)
System.out.print("hahah\n");
else if(3==bbb)//public可以直接访问
System.out.print("bbbb\n");
一切好像已经明朗,但是,上述代码显然会出错,因为num是私有域,无法在外部去访问,一定还有别的原因。继续利用反射机制来查看外部类Father:
joker@Joker-PC:~/workspace/TestOracle/bin/ck/innerclass$ javap -private Father.class
Compiled from "Father.java"
public class ck.innerclass.Father {
private int num;
private int aaa;
public ck.innerclass.Father(int, int);
public void TestInnerclass();
static int access$0(ck.innerclass.Father);
static int access$1(ck.innerclass.Father);
}
可以发现,编译器为外部类生成了两个static int access 0和staticintaccess 1的静态方法,这两个静态方法的返回值作为参数传递给 外部类中需要被访问的private域,可以发现Father中定义了三个private域num,aaa,ccc但是并没有为ccc生成一个静态方法。即内部类访问外部类的num和aaa两个私有域的实现应当是下面:
if(1==access$0(myfather))
System.out.print("I am public inner class\n");
else if(2==access$0(myfather))
System.out.print("hahah\n");
else if(3==bbb)//public可以直接访问
System.out.print("bbbb\n");
即只要有内部类试图访问私有域,就会在外部类中为该私有域生成一个静态的方法。
局部内部类
局部内部类,就是定义在函数体内部的类,局部内部类不能用访问修饰符进行声明,因为它的作用域已经被限定于这个局部的代码块当中,局部类能够与外界完全隐藏。
package ck.innerclass;
public class LocalClass {
public void func(int a,final boolean b,final int c,final int d)
{
class testlocal
{
public void test()
{
if(b)
System.out.print("hahhaah\n");
else if(1==c)
System.out.print("ggggg\n");
}
}
testlocal cl=new testlocal();
cl.test();
}
}
在局部内部类中只能访问函数的final局部变量,即在上述代码中,在testlocal类中访问变量a就会报错,这是因为 当我们在外部调用func函数时,首先会实例化testlocal对象,然后调用其test函数,这时候func函数退出,那么局部变量会被回收,为了保证test函数的正常运行,需要对在局部类中对使用到的局部变量进行备份,
对编译后的局部类的class文件进行反射查看:
joker@Joker-PC:~/workspace/TestOracle/bin/ck/innerclass$ javap -private LocalClass\$1testlocal.class
Compiled from "LocalClass.java"
class ck.innerclass.LocalClass$1testlocal {
final ck.innerclass.LocalClass this$0;
private final boolean val$b;
private final int val$c;
ck.innerclass.LocalClass$1testlocal(ck.innerclass.LocalClass, boolean, int);
public void test();
}
可以发现在局部类的构造函数中传入了三个参数,分别是外部类参数,一个boolean参数和一个int参数,并且编译器还为局部类省城了两个private final的域,显然,编译器为局部类需要访问的局部变量生成了一个副本。之所以需要是final修饰,是因为要保证局部变量与在局部类中建立的副本保持一致。
匿名内部类
如果我们需要创建一个类的对象,但是只需要使用一次不再重复使用,就可以为其创建一个匿名内部类,匿名内部类的语法如下:
new SuperClass(construction parameters)
{
methods...
datas...
}
对于匿名内部类需要注意,因为构造函数要与类名相同,匿名类没有名字所以不能有显式的构造函数。
package ck.Anonymous;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Date;
import javax.swing.JOptionPane;
import javax.swing.Timer;
public class TestAnonymous {
public static void main(String[] args) {
// TODO Auto-generated method stub
ActionListener listen=new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.print("Now time is "+new Date());
}
};
Testcls cls=new Testcls();
cls.testfunc(listen);
JOptionPane.showMessageDialog(null,"quit?");
System.exit(0);
}
}
class Testcls
{
public void testfunc(ActionListener act)
{
Timer clockTimer=new Timer(1000, act);
clockTimer.start();
}
}
如上述代码,在类Testcls中有个testfunc函数,该函数需要传入一个ActionListenner对象,但是ActionListenner是接口,不能实例化对象,上述代码传入了一个匿名内部类对象,如果不使用匿名内部类,常规做法如下:
//首先定义一个类实现接口:
class ImpleAnnoy implements ActionListener
{
public void actionPerformed(ActionEvent e) {
System.out.print("Now time is "+new Date());
}
}
//实例化对象
public static void main(String[] args)
{
// TODO Auto-generated method stub
ImpleAnnoy impcls=new ImpleAnnoy();
ActionListener listen=impcls;
Testcls cls=new Testcls();
cls.testfunc(listen);
JOptionPane.showMessageDialog(null,"quit?");
System.exit(0);
};
上述是使用接口来创建匿名类,还可以通过继承某个超类来实现匿名类,如下:
public class TestAnonymous {
public static void main(String[] args) {
// TODO Auto-generated method stub
ActionListener listen=new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.print("Now time is "+new Date());
}
};
Testcls cls=new Testcls();
cls.testfunc(listen);
JOptionPane.showMessageDialog(null,"quit?");
System.exit(0);
}
}
public class TestAnonymous {
public static void main(String[] args) {
// TODO Auto-generated method stub
SuperClass PengK=new SuperClass(1, 2)
{
public void sonfunc()
{
System.out.print("I am Pengk\n");
}
private int age;
};
}
}
上述代码中 通过扩展SuperClass生成匿名内部类,并对其进行了扩展。如果不使用匿名内部类,通常的做法是:
//继承超类
class InheritClass extends SuperClass
{
public InheritClass(int x,int y)
{
super(x, y);
}
public void sonfunc()
{
System.out.print("I am Pengk\n");
}
private int age;
}
//引用
public class TestAnonymous {
public static void main(String[] args) {
// TODO Auto-generated method stub
InheritClass PengK=new InheritClass(1, 2);
SuperClass CK=PengK;
}
匿名类可以使我们简写大量代码~
静态内部类
静态内部类只是为了把该类隐藏在另一个类的内部,即无隐式引用。与C++的嵌套类类似,静态内部类对外部类并没有特殊的访问权限。
如下:
package ck.innerclass;
public class StaticInner {
private int a;
public static class Innercls
{
public void func()
{
// if(1==a)//error 没有特殊访问权限
System.out.print("I am static\n");
}
}
}
对其编译后的内部类class文件利用反射进行查看,
joker@Joker-PC:~/workspace/TestOracle/bin/ck/innerclass$ javap -private StaticInner\$Innercls.class
Compiled from "StaticInner.java"
public class ck.innerclass.StaticInner$Innercls {
public ck.innerclass.StaticInner$Innercls();
public void func();
}
可以发现编译器并没有为其像之前一样生成隐士引用this$0。
这是在学习core java时对于内部类的一些总结。