在一个文件中定义两个类,但其中任何一个类都不在另一个类的内部,而如果在类中再定义一个类,则将在类中再定义的那个类称为内部类。内部类可分为成员内部类、局部内部类以及匿名内部类等……
一.成员内部类
1.成员内部类介绍
(1) 在一个类中使用内部类,可以在内部类中直接存取其所在类的私有成员变量。成员内部类语法如下:
public class OuterClass{ //外部类
private class InnerClass{ //内部类
........ //程序代码
}
}
在内部类中可以随意使用外部类的成员方法以及成员变量,尽管这些类成员被修饰为private。例如:
public class OuterClass { //外部类
private int i=0; //修饰符为private的成员变量
private void go() { //修饰符为private的成员方法
System.out.println("Hello World!!");
}
class InnerClass{ //内部类
void f() {
go(); //外部类的类成员可以在内部类中随意使用
i++;
}
}
}
(2)内部类的实例一定要绑定在外部类的实例上,如果从外部类中初始化一个内部类对象,那么内部类对象就会绑定在外部类对象上。内部类初始化方式与其他类初始化方式相同,都是使用new关键字。例如:
public class OuterClass {
innerClass in=new innerClass(); //在外部类实例化内部类对象引用
public void ouf() {
in.inf(); //在外部类方法中调用内部类方法
}
class innerClass{
innerClass() { //内部类构造方法
}
public void inf() { //内部类成员方法
}
int y=0; //定义内部类成员变量
}
public innerClass doit() { //外部类方法,返回值为内部类引用
//y=4; //报错,外部类不可以直接访问内部类成员变量
in.y=4;
return new innerClass();
}
public static void main(String[] args) {
OuterClass out=new OuterClass();
//内部类的对象实例化操作必须在外部类或外部类的非静态方法中实现
OuterClass.innerClass in=out.doit();
OuterClass.innerClass in2=out.new innerClass();
}
}
外部类创建内部类实例与其他类创建对象引用时相同。内部类可以访问它的外部类成员,但内部类的成员只有在内部类的范围之内是可知的,不能被外部类使用。如在本例中,对内部类的成员变量y再次赋值时将会出错,但是可以使用内部类对象引用调用成员变量y。
(3)内部类对象与外部类对象关系紧密,内外可以交互使用彼此类中定义的变量。如图:
如果在外部类和非静态方法之外实例化内部类对象,需要使用外部类。内部类的形式指定该对象的类型。例如在上个例子中,在主方法中实例化一个内部类对象:
public static void main(String[] args) {
OuterClass out=new OuterClass();
OuterClass.innerClass in=out.doit();
OuterClass.innerClass in2=out.new innerClass(); //实例化内部类对象
}
主方法中如果不使用doit()方法返回内部类对象引用,可以直接使用内部类实例化内部类对象,但由于是在主方法中实例化内部类对象,必须在new操作符之前提供一个外部类的引用。
注意:内部类对象会依赖于外部类对象,除非已经存在一个外部类对象,否则类中不会出现内部类对象。
2.内部类向上转型为接口
如果将一个权限修饰符为private的内部类向上转型为其父类对象,或者直接向上转型为一个接口,在程序中就可以完全隐藏内部类的具体实现过程。
问:在一般的类中是不能多次实现接口中的同一方法,如何解决??
答:可以在外部提供一个接口,在接口中声明一个方法。如果在实现该接口的内部类中实现该接口的方法,就可以定义多个内部类以不同的方式实现接口中的同一个方法。
interface OutInterface{ //定义一个接口
public void f();
}
public class InterfaceInner {
public static void main(String[] args) {
OutClass2 out=new OutClass2(); //实例化一个外部类对象
OutInterface outinter=out.doit(); //调用doit()方法,返回一个OutInterface接口
outinter.f(); //调用f()方法
}
}
class OutClass2{
//定义一个内部类实现OutInterface接口
private class InnerClass implements OutInterface{
InnerClass(String s) { //内部类构造方法
System.out.println(s);
}
@Override
public void f() { //实现接口中的f()方法
System.out.println("访问内部类中的f()方法");
}
}
//定义一个方法,返回值类型为OutInterface接口
public OutInterface doit() {
return new InnerClass("访问内部类构造方法");
}
}
输出结果:
访问内部类构造方法
访问内部类中的f()方法
上面例子可以看出,OuterClass2类中定义了一个修饰权限为private的内部类,这个内部类实现了OutInterface接口,然后修改doit()方法,使该方法返回一个OutInterface接口。由于内部类InnerClass修饰权限为private,所以除了OuterClass2类可以访问该内部类之外,其他类都不能访问,而可以访问doit()方法,该方法返回一个外部接口类型,这个接口可以作为外部使用的接口。接口中包含一个f()方法,在继承此接口的内部类中实现了该方法,如果某个类继承了外部类,由于内部的权限不可以向下转型为内部类InnerClass,同时也不能访问f()方法,但是却可以访问接口中的f()方法。如,InterfaceInner类中最后一条语句,接口引用调用f()方法,从执行结果来看,这条语句执行的是内部类中的f()方法,很好的对继承该类的子类隐藏了实现细节,仅留下一个接口和一个外部类,同时也可以调用f()方法。
注意:非内部类不能被声明为private或protected访问类型。
3.使用 this关键字 获取内部类与外部类的引用
(1)如果在外部类中定义的成员变量与内部类的成员变量名称相同,可以使用this关键字进行处理。例如:
public class TheSameName {
private int x; //定义一个外部类变量x
private class Inner{
private int x=9; //定义一个内部类变量x并赋值
public void doit(int x) {
x++; //调用的是形参x
this.x++; //调用的是内部类的变量x
TheSameName.this.x++; //调用的是外部类的变量x
}
}
}
(2)内存中变量的布局如图:
在内存中所有对象均放置在堆中,方法以及方法中的形参或局部变量放置在栈中。如图,栈中的doit()方法指向内部类的对象,而内部类的对象与外部类的对象是相互依赖的,Outer.this对象指向外部类对象。