当前位置:课程学习>>第四章 面向对象基础>>文本学习>>知识点六
一、内部类的概念
在Java对于类的定义除了象前面接触到的那样独立定义外,还可以嵌入到其他类中或方法甚至语句中,这种被嵌入的类称为内部类(Inner class)。这与C++里的嵌套类(Nested class)很类似,但两者有所区别。从形式看,内部类和普通类没什么特别的地方,通过深入的学习,就会发现内部类具有很多普通类所没有的优点,此外掌握和使用内部类可以优化程序结构。
【例4.31】内部类的定义和内部类对象的创建。
1 //InnerClass1.java
2 abstract class Test
3 {
4 abstract void prt();
5 }
6 public class InnerClass1
7 {
8 private int a=3;
9 private class InnerClass extends Test
10 {
11 void prt()
12 {
13 a++;
14 System.out.println("in the Innerclass,a="+a);
15 }
16 }
17 Test f1()
18 {
19 return new InnerClass();
20 }
21 public static void main(String[] args)
22 {
23 InnerClass1 ic=new InnerClass1();
24 Test t=ic.f1();
25 t.prt();
26 }
27
需要注意的是,编译该程序后,会生成三个字节码文件:Test.class、InnerClass1.class和InnerClass1$InnerClass.class。内部类字节码文件的名字是由外部类名加$符号再加内部类名构成的。
程序运行结果为:in the Innerclass,a=4
在例子中InnerClass类就是一个内部类(第9行~第16行),被定义为private。在后面的main方法里,直接通过外部类的方法f1()返回一个Test类的引用t,通过t直接调用了内部类InnerClass中的prt()方法。如果没有源码根本看不出使用了内部类。由此可见,内部类可以被外部类隐藏起来,从而实现封装性。
通过上面的例子,不能看出内部类的另外一个优点:在内部类中可以访问它所在的外部类的任何内容,包括私有成员(第8行、第13行)。在内部类中可以不需要创建外部类的对象就可以直接访问外部类的成员,不过如果内部类里的成员变量与外部类的成员变量同名,也即外部类的成员变量被内部类隐藏了,这样就需要使用this关键字就可以访问,用法为:外部类名.this.成员名。
在上面的例子中,如果InnerClass中也有一个成员为a,prt方法中也有变量a,如:
private int a=3;
private class InnerClass extends Test
{
private int a=10;
void prt()
{
int a=100;
System.out.println(“prt().a=”+a);
System.out.println(“InnerClass .a=”+this.a);
System.out.println("in the Innerclass,OuterClass.a="+InnerClass1.this.a);
}
}
用上面的代码替换掉例子中第8行到第16行的内容,保存后重新编译运行,结果为:
prt().a=100
InnerClass.a=10
in the InnerClass,OuterClass.a=3
同时,通过例子还可以看出如何创建内部类的对象。就是利用其外部类的方法创建并返回内部类的引用,此外还可以直接利用外部类的对象来创建内部类对象。其语法格式如下:
如果创建内部类对象的语句是在外部类的静态方法内,格式为:
内部类名 对象名=外部类对象名.new 内部类构造方法([实参列表])
如果创建内部类对象的语句在其他类中,格式为:
外部类命.内部类名 对象名=外部类对象名.new 内部类构造方法([实参列表])
【例4.32】内部类对象的创建一:在内部类的静态方法(如main方法)中创建对象。
1 //OuterClass2.java
2 public class OuterClass2
3 {
4 class InnerClass
5 {
6 int a;
7 void prt(int b)
8 {
9 System.out.println("In the inner class b="+b);
10 }
11 }
12 public static void main(String[] args)
13 {
14 OuterClass2 test=new OuterClass2();
15 InnerClass ic=test.new InnerClass();
16 OuterClass2.InnerClass ic2=test.new InnerClass();
17 ic.prt(1);
18 ic2.prt(2);
19 }
20 }
程序的运行结果为:
In the inner class b=1
In the inner class b=2
【例4.33】内部类对象的创建二:在其他类中创建对象。
1 //OuterClass3.java
2 class OuterClass
3 {
4 class InnerClass
5 {
6 int a;
7 void prt(int b)
8 {
9 System.out.println("In the inner class b="+b);
10 }
11 }
12 };
13 public class OuterClass3
14 {
15 public static void main(String[] args)
16 {
17 OuterClass test=new OuterClass();
18 //InnerClass ic=test.new InnerClass();
19 OuterClass.InnerClass ic2=test.new InnerClass();
20 ic2.prt(2);
21 }
22 };
这里需要注意的是如果去掉第18行的注释,在编译的时候会出现“找不到符号”的错误提示,是因为在其他类中必须在内部类名的前面加上外部类。程序的运行结果为:
In the inner class b=2
提示:创建非静态内部类对象时,一定要先创建起相应的外部类对象。
二、静态内部类
和普通的类一样,内部类也可以被static修饰,不过和非静态内部类相比,区别就在于静态内部类没有了指向外部的引用,因此静态内部类就不能直接引用外部类的实例成员和实例方法。在创建内部类对象时就可以不用先创建外部类对象,其格式为:
外部类名.内部类名 对象名=new 外部类名.内部类构造方法([ 实参列表])
除此之外,在任何非静态内部类中,都不能有静态数据,静态方法或者又一个静态内部类(内部类的嵌套可以不止一层)。不过静态内部类中却可以拥有这一切。
三、局部内部类(方法中的内部类)
Java内部类也可以是局部的,它可以定义在一个方法甚至一个代码块之内。对于方法体内的内部类,它可以访问外部类的成员和方法,但它不能访问该方法内的局部变量包括参数,除非局部变量为final类型的变量,即常量。
【例4.34】方法体内的内部类。
1 //OuterClass4.java
2 class SuperClass
3 {
4 String label;
5 String readLabel(){ return label; }
6 }
7 public class OuterClass4
8 {
9 public SuperClass f1(String s)
10 {
11 class SubClass extends SuperClass
12 {
13 private String label;
14 private SubClass(String str)
15 {
16 label = str;
17 }
18 String readLabel(){ return label; }
19 }
20 return new SubClass(s);
21 }
22 public static void main(String[] args)
23 {
24 OuterClass4 oc= new OuterClass4();
25 SuperClass sc = oc.f1("Test");
26 System.out.println(sc.readLabel());
27 }
28 }
程序运行结果为:Test
此外内部类甚至可以定义在语句中。
【例4.35】语句内的内部类。
1 //OuterClass5.java
2 public class OuterClass5
3 {
4 private String f1(boolean b)
5 {
6 if(b)
7 {
8 class InnerClass
9 {
10 private String id;
11 InnerClass(String s)
12 {
13 id = s;
14 }
15 String getId()
16 {
17 return id;
18 }
19 }
20 InnerClass ic= new InnerClass("inner class");
21 return ic.getId();
22 }
23 else
24 {
25 return "no find";
26 }
27 }
28 public static void main(String[] args)
29 {
30 OuterClass5 oc= new OuterClass5();
31 System.out.println(oc.f1(true));
32 }
33 }
程序运行结果为:inner class
在例子中,不能在if之外创建这个内部类的对象,因为这已经超出了它的作用域。不过在编译的时候,内部类TrackingSlip和其他类一样同时被编译,只不过它的作用域仅在if语句内,超出了这个范围就无效,除此之外它和其他内部类并没有区别。
四、匿名的内部类
在Java中还支持匿名的内部类,当只需要创建一个类的对象而且用不上它的名字时,使用内部类可以使代码看上去简洁清楚。它的语法规则是这样的:
new 接口名(){具体实现}; 或 new 父类名(){具体实现};
例如:
public class TestClass{
public Contents cont(){
return new Contents(){
private int i = 11;
public int value() {
return i;
}
};
}
}
这里方法cont()使用匿名内部类直接返回了一个实现了接口Contents的类的对象。
在Java的事件处理的匿名适配器中,匿名内部类被大量的使用。例如在想关闭窗口时加上这样一句代码:
frame.addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent e){
System.exit(0);
}
});
有一点需要注意的是,匿名内部类由于没有名字,所以它没有构造函数(但是如果这个匿名内部类继承了一个只含有带参数构造函数的父类,创建它的时候必须带上这些参数,并在实现的过程中使用super关键字调用相应的内容)。如果想要初始化它的成员变量,有下面三种方法:
(1)如果是在一个方法的匿名内部类,可以利用这个方法传进想要的参数,不过记住,这些参数必须被声明为final。
(2)将匿名内部类改造成有名字的局部内部类,这样它就可以拥有构造函数了。
(3)在这个匿名内部类中使用初始化代码块。