大部分时候,我们把类定义成一个独立的程序单元。在某些情况下,我们把一个类在另一个类的内部定义,这个定义在其他类内部的类就被称为内部类(有的地方也叫嵌套类),包含内部类的类也被称为外部类(有的地方也叫宿主类)。Java从JDK1.1开始引入内部类,内部类主要有如下作用:
a、内部类提供了更好的封装,可以把内部类隐藏在外部类之内,不允许同一个包中的其他类访问该类。假设需
要创建Cow类,Cow类需要组合一个CowLeg对象,CowLeg类只有在Cow类里才有效,离开了Cow类之后
没有任何意义。在这种情况下,就可以把CowLeg定义成Cow的内部类,不允许其他类访问CowLeg。
b、内部类成员可以直接访问外部类的私有数据,因为内部类被当成其外部类成员,同一个类的成员之间可以互
相访问。但外部类不能访问内部类的实现细节,例如内部类的成员变量。
c、匿名内部类适合用于创建那些仅需要一次使用的类。对于前面介绍的命令模式,当需要传入一个COmmand
对象时,重新
专门
定义PrintCommand和AddCommand两个实现类可能没有太大的意义,因为这两个实现
类可能仅需要使用一次,在这种情况下,使用匿名内部类将更方便。
非静态内部类:
定义内部类非常简单,只要把一个类放在另一个类内部定义即可。此处的“类内部”包括类中的任何位置,甚至在方法中也可以定义内部类(方法里定义的内部类被称为局部内部类)。内部类定义语法格式如下:
public class OuterClass
{
//此处可以定义内部类
}
大部分时候,
内部类都被作为成员内部类定义,而不是作为局部内部类。成员内部类是一种与Field、方法、构造器和初始化块相似的类成员;局部内部类和匿名内部类则不是类成员。
成员内部类分为两种:
静态内部类和非静态内部类,使用static修饰的成员内部类是静态内部类,反则是非静态内部类。
因为内部类作为其外部类的成员,所以可以使用任意访问控制符如private、protected和public等修饰符。
注意:
外部类的上一级程序单元是包,所以它只有2个作用域:同一个包内和任意位置。因此只需2种访问权限:包访问权限和公开访问权限,正好对应省略访问控制符和public访问控制符。省略访问控制符是包访问权限,即同一包中的其他类可以访问省略访问控制符的成员。因此,如果一个外部类不使用任何访问控制符修饰,则只能被同一个包中其他类访问。而内部类的上一级程序单元是外部类,它就具有4个作用域:同一个类、同一个包、父子类和任何位置,因此可以使用4种访问控制权限。
下面程序在Cow类里定义了一个CowLeg非静态内部类,并在CowLeg类的实例方法中直接访问Cow的private访问权限的实例Field:
程序清单:
package cow;
public class Cow
{
private double weight;
public Cow() {}
public Cow(double weight)
{
this.weight = weight;
}
//定义一个非静态内部类
private class CowLeg
{
//非静态内部类的两个Field
private double length;
private String color;
public CowLeg() {}
public CowLeg(double length,String color)
{
this.length = length;
this.color = color;
}
public void setLength(double length)
{
this.length = length;
}
public double getLength()
{
return this.length;
}
public void setColor(String color)
{
this.color = color;
}
public String getColor()
{
return this.color;
}
//非静态内部类的实例方法
public void info()
{
System.out.println("当前牛腿颜色是:" + color + ",高" +
length);
//直接访问外部类的private修饰的Field
System.out.println("本牛腿所在奶牛重:" + weight); //1
}
}
public void test()
{
CowLeg cl = new CowLeg();
cl.info();
}
public static void main(String[] args)
{
Cow cow = new Cow(399);
cow.test();
}
}
运行结果: 当前牛腿颜色是:null,高0.0
本牛腿所在奶牛重:399.0
程序分析:
编译上面程序,看到在文件所在路径生成了两个class文件,一个是Cow.class,另一个是Cow$CowLeg.class,前者是外部类Cow的class文件,后者是内部类CowLeg的class文件,即
成员内部类(包括静态内部类、非静态内部类)的class文件总是这种形式:QuterClass$InnerClass.class。
在非静态内部类里可以直接访问外部类的private成员,上面程序中1处代码,就是在
CowLeg类的方法内直接访问其外部类的private实例变量。这是因为:
在非静态内部类对象里,保存了一个它寄存的外部类对象的引用(当调用非静态内部类的实例方法时,必须有一个非静态内部类实例,而非静态内部类实例必须寄存在外部类实例里)。下图显示了程序运行时的内存示意图:
当在非静态内部类的方法内访问某个变量时,系统优先在该方法内查找是否存在该名字的局部变量,如果存在就使用该变量;如果不存在,则到该方法所在的内部类中查找是否存在该名字的成员变量,如果存在则使用该成员变量;如果不存在,则到该内部类所在的外部类中查找是否存在该名字的成员变量,如果存在则使用该成员变量;如果依然不存在,系统将出现编译错误:提示找不到该变量。
因此,如果外部类成员变量、内部类成员变量与内部类里方法的局部变量同名,则可通过使用this、外部类类名.this作为限定类区分。如下程序所示:
package discern;
public class DiscernVar
{
private String prop = "外部类的实例变量";
private class InClass
{
private String prop = "内部类的实例变量";
public void info()
{
String prop = "局部变量";
//通过外部类类名.this.varName 访问外部类实例Field
System.out.println("外部类的FIeld值:" +
DiscernVar.this.prop);
//通过this.varName访问内部类实例的Field
System.out.println("内部类的Field值:" + this.prop);
//直接访问局部变量
System.out.println("局部变量的值:" + prop);
}
}
public void test()
{
InClass inClass = new InClass();
inClass.info();
}
public static void main(String[] args)
{
new DiscernVar().test();
}
}
运行结果:外部类的FIeld值:外部类的实例变量
内部类的Field值:内部类的实例变量
局部变量的值:局部变量
非静态内部类的成员可以访问外部类的private成员,但反过来就不成立了。非静态内部类的成员只在非静态内部类范围内是可知的,并不能被外部类直接使用。如果外部类需要访问非静态内部类的成员,则必须显式创建非静态内部类对象来调用访问其实例成员。
下面程序示范了这个规则:
程序清单:
package outer;
public class Outer
{
private int outProp = 9;
class Inner
{
private int inProp = 5;
public void acessOuterProp()
{
//非静态内部类可以直接访问外部类的成员
System.out.println("外部类的outProp值:" +
outProp);
}
}
public void acessInnerProp()
{
//外部类不能直接访问非静态内部类的实例Field
//下面代码编译出错
//System.out.println("内部类的inProp值:" + inProp);
//如需访问内部类的实例Field,则必须显式创建内部类对象
System.out.println("内部类的inProp值:" + new
Inner().inProp);
//通过内部类访问外部类的成员
Inner inner = new Inner();
inner.acessOuterProp();
}
public static void main(String[] args)
{
Outer outer = new Outer();
outer.acessInnerProp();
}
}
运行结果:内部类的inProp值:5
外部类的outProp值:9
非静态内部类对象和外部类对象的关系:
非静态内部类对象必须寄存在外部类对象里,而外部类对象则不必一定有非静态内部类对象寄存其中。简单地说,如果存在一个非静态内部类对象,则一定存在一个被它寄存的外部类对象。但外部类对象存在时,外部类对象里不一定寄存了非静态内部类对象。因此外部类对象访问非静态内部类成员时,可能非静态普通内部类对象根本不存在!而非静态内部类对象访问外部类成员时,外部类对象一定存在。
根据静态成员不能访问非静态成员的规则,外部类的静态方法、静态代码块不能访问非静态内部类,包括不能使用非静态内部类定义变量、创建实例等。总之,不允许在外部类的静态成员中直接使用非静态内部类。如下程序所示:
程序清单:
package staticdemo;
public class StaticDemo
{
//定义一个非静态内部类,是个空类
private class In{}
//外部类的静态方法
public static void main(String[] args)
{
//下面代码引起编译异常,因为静态成员(main方法)
//无法访问非静态成员(In类)
new In();
}
}
Java不允许在非静态内部类里定义静态成员。下面程序示范了非静态内部类里包含静态成员将引发编译错误:
程序清单:
public class InnerNoStatic
{
private class InnerClass
{
/*
下面三个静态声明都将引发编译错误:
非静态内部类不能有静态声明
*/
static
{
System.out.println("======");
}
private static int inProp;
private static void test(){}
}
}
程序分析:
非静态内部类不能有静态方法、静态FIeld、静态初始化块,所以上面三个静态声明将引发错误。
注意:
非静态内部类里不可以有静态初始化块,但可以包含普通初始化块。非静态内部类普通初始化块的作用与外部类初始化块的作用完全相同。
静态内部类:
如果使用static来修饰一个内部类,则这个内部类就属于外部类本身,而不属于外部类的某个对象。因此使用static修饰的内部类被称为类内部类,有的地方也称为静态内部类。
注意:
static关键字的作用是把类的成员变成类相关,而不是实例相关,即static修饰的成员属于整个类,而不属于单个对象。外部类的上一级程序单元是包,所以不可使用static修饰;而内部类的上一级程序单元是外部类,使用static修饰可以将内部类变成外部类相关,而不是外部类实例相关。因此static关键字不可修饰外部类,但可修饰内部类。
静态内部类可以包含静态成员,也可以包含非静态成员。根据静态成员不能访问非静态成员的规则,静态内部类不能访问外部类的实例成员,只能访问外部类的类成员。即使是静态内部类的实例方法也不能访问外部类的实例成员,只能访问外部类的静态成员。下面程序演示了这条规则:
程序清单:
package staticdemo;
public class StaticInnerClass
{
private int prop1 = 5;
private static int prop2 = 9;
static class StaticInClass
{
//静态内部类里可以包含静态成员
private static int age;
public void accessOuterProp()
{
//下面代码出现错误
//静态内部类无法访问外部类的实例成员
System.out.println(prop1);
//下面代码正常
System.out.println(prop2);
}
}
}
为什么静态内部类的实例方法也不能访问外部类的实例属性呢?
因为静态内部类是外部类的类相关,而不是外部类的对象相关的。也就是说,静态内部类对象不是寄存在外部类对象里的,而是寄存在外部类的类本身中。当静态内部类对象存在时,并不存在一个被它寄存的外部类对象,静态内部类对象里只有外部类的类引用,没有持有外部类对象的引用。如果允许静态内部类的实例方法访问外部类的实例成员,但找不到被寄存的外部类对象,这将引起错误。