------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
一、内部类的概念
大部分时候,我们把类定义成一个独立的程序单元。在某些情况下,我们把一个类放在另一个类的内部定义,这个定义在其它类内部的类就被成为内部类,包含内部类的类也被称为外部类(或者宿主类)。内部类主要有如下作用:
》内部类提供了更好的封装,可以把内部类隐藏在外部类之内,不允许同一个包中的其它类访问该类。假设需要创建Dog
类,Dog类需要组合一个DogLeg对象,DogLeg类只有在Dog类里才有效,离开了Dog类之后没有任何意义。在这种情
况下,就可以把DogLeg定义成Dog的内部类,不允许其它类访问DogLeg。
》内部类成员可以直接访问外部类的私有数据,因为内部类被当成其外部类成员,同一个类的成员之间可以互相访问。但外部类
不能访问内部类的实现细节,例如内部类的成员变量。
》匿名内部类适合用于创建哪些仅需要一次使用的类。如命令模式,当需要传入一个Command对象时,重新专门定义
PrintCommand和AddCommand两个实现类可能没有太大的意义,因为这两个实现类可能仅需要使用一次。在这种情况下,使
用匿名内部类将更加方便。
二、非静态内部类
定义内部类非常简单,只要把一个类放在另一个类内部定义即可。此处的”内部类“包括类中的任何位置,甚至在方法中也可以定义内部类(方法里定义的内部类被称为局部内部类)。内部类的语法格式如下:
<span style="font-size:14px;">public class OuterClass{
//此处可以定义内部类
}</span>
大部分时候,内部类被作为成员内部类定义,而不是作为局部内部类。成员内部类是一种与Field、方法、构造器、初始化块相似的类成员;局部内部类和匿名内部类则不是类成员。
成员内部类分为两种:静态内部类和非静态内部类,使用static修饰的成员内部类是静态内部类,没有使用static修饰的成员内部类是非静态内部类。
因为内部类作为外部类的成员,所有可以使用任意访问控制符如private、protected和public等修饰。
下面程序在Dog类里定义了一个DogLeg非静态内部类,并在DogLeg类的实例方法中直接访问Dog的private访问权限的实例Field:
<span style="font-size:14px;">public class Dog
{
private double weight;
//外部类的两个重载的构造器
public Dog(){}
public Dog(double weight)
{
this.weight = weight;
}
//定义一个非静态内部类
private class DogLeg
{
//非静态内部类的两个Field
private double length;
private String color;
//非静态内部类的两个重载的构造器
public DogLeg(){}
public DogLeg(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); //①
}
}
public void test()
{
DogLeg cl = new DogLeg(1.12 , "黑白相间");
cl.info();
}
public static void main(String[] args)
{
Dog Dog = new Dog(378.9);
Dog.test();
}
}</span>
当在非静态内部类的方法内访问某个变量时,系统优先在该方法内查找是否存在该名字的局部变量,如果存在就使用该变量;如果不存在,则到该方法所在的内部类中查找是否存在该名字的成员变量,如果存在则使用该成员变量;如果不存在,则到内部类所在的外部类中查找是否存在该名字的成员变量,如果存在则使用该成员变量;如果不存在,系统将出现编译错误:提示找不到该变量。
因此,如果外部类成员变量、内部类成员变量与内部类里方法的局部变量同名,则可以通过使用this、外部类类名.this作为限定来区分。如下程序:
<span style="font-size:14px;">public class DiscernVariable
{
private String prop = "外部类的实例变量";
private class InClass
{
private String prop = "内部类的实例变量";
public void info()
{
String prop = "局部变量";
//通过 外部类类名.this.varName 访问外部类实例Field
System.out.println("外部类的Field值:"
+<strong> DiscernVariable.this.prop</strong>);
//通过 this.varName 访问内部类实例的Field
System.out.println("内部类的Field值:" + <strong>this.prop</strong>);
//直接访问局部变量
System.out.println("局部变量的值:" + <strong>prop</strong>);
}
}
public void test()
{
InClass in = new InClass();
in.info();
}
public static void main(String[] args)
{
new DiscernVariable().test();
}
}
</span>
非静态内部类的成员只有在非静态内部类范围内是可知的,并不能被外部类直接使用。如果外部类需要访问非静态内部类的成员,则必须显示创建非静态内部类对象来调用访问其实例成员。下面程序示范了这个规则:
<span style="font-size:14px;">public class Outer
{
private int outProp = 9;
class Inner
{
private int inProp = 5;
public void acessOuterProp()
{
//非静态内部类可以直接访问外部类的成员
System.out.println("外部类的outProp值:"
+ outProp);
}
}
public void accessInnerProp()
{
//外部类不能直接访问非静态内部类的实例Field,
//下面代码出现编译错误
//System.out.println("内部类的inProp值:" + inProp);
//如需访问内部类的实例Field,必须显式创建内部类对象
System.out.println("内部类的inProp值:"
+ new Inner().inProp);
}
public static void main(String[] args)
{
//执行下面代码,只创建了外部类对象,还未创建内部类对象
Outer out = new Outer(); //①
out.accessInnerProp();
}
}</span>
根据静态成员不能访问非静态成员的规则,外部类的静态方法、静态代码块不能方位非静态内部类,包括不能使用非静态内部类定义变量、创建实例等。总之,不允许在外部类的静态成员中直接使用非静态内部类。如下程序:
<span style="font-size:14px;">public class StaticTest
{
//定义一个非静态的内部类,是一个空类
private class In{}
//外部类的静态方法
public static void main(String[] args)
{
//下面代码引发编译异常,因为静态成员(main方法)
//无法访问非静态成员(In类)
new In();
}
}</span>
Java不允许在非静态内部类里定义静态成员。下面程序示范了非静态内部类里包含静态成员将引发编译错误。
<span style="font-size:14px;">public class InnerNoStatic
{
private class InnerClass
{
/*
下面三个静态声明都将引发如下编译错误:
非静态内部类不能有静态声明
*/
static
{
System.out.println("==========");
}
private static int inProp;
private static void test(){}
}
}</span>
非静态内部类里不能有静态方法、静态Field、静态初始化块,所有上面三个静态声明都会引发错误。
三、静态内部类
如果使用static来修饰一个内部类,则这个内部类就属于外部类本身,而不属于外部类的某个对象。因此使用static修饰的内部类被成为类内部类,有的地方也称为静态内部类。
静态内部类可以包含静态成员,也可以包含非静态成员。根据静态成员不能访问非静态成员的规则,静态内部类不能访问外部类的实例成员,只能访问外部类的类成员。即使是静态内部类的实例方法也不能访问外部类的实例成员,只能访问外部类的静态成员。下面程序演示了这条规则:
<span style="font-size:14px;">public class StaticInnerClassTest
{
private int prop1 = 5;
private static int prop2 = 9;
static class StaticInnerClass
{
//静态内部类里可以包含静态成员
private static int age;
public void accessOuterProp()
{
//下面代码出现错误:
//静态内部类无法访问外部类的实例成员
System.out.println(prop1);
//下面代码正常
System.out.println(prop2);
}
}
}</span>
静态内部类是外部类的一个静态成员,因此外部类的静态方法、静态初始化块中可以使用静态内部类来定义变量、创建对象等。
外部类依然不能直接访问静态内部类的成员,但可以使用静态内部类的类名作为调用者来访问静态内部类的类成员,也可以使用静态内部类对象作为调用者来访问静态内部类的实例成员。下面程序示范了这条规则:
<span style="font-size:14px;">public class AccessStaticInnerClass
{
static class StaticInnerClass
{
private static int prop1 = 5;
private int prop2 = 9;
}
public void accessInnerProp()
{
//System.out.println(prop1);
//上面代码出现错误,应改为如下形式:
//通过类名访问静态内部类的类成员
System.out.println(StaticInnerClass.prop1);
//System.out.println(prop2);
//上面代码出现错误,应改为如下形式:
//通过实例访问静态内部类的实例成员
System.out.println(new StaticInnerClass().prop2);
}
}</span>
除此之外,Java还允许在接口里定义内部类,接口里定义的内部类默认使用public static修饰,也就是说,接口内部类只能是静态内部类。
四、使用内部类
定义类的主要作用就是定义变量、创建实例和作为父类被继承。定义内部类的主要作用也如此,但使用内部类定义变量和创建实例则与外部类存在一些小小的差异。下面分三种情况讨论内部类的用法。
(1)在外部类内部使用内部类
从前面程序中可以看出,在外部类内部使用内部类时,与平常使用普通类有没太大区别。一样可以直接通过内部类类名来定义变量,通过new调用内部类构造器来创建实例。
唯一存在一个区别是:不用在外部类的静态成员(包括静态方法和静态初始化块)中使用非静态内部类,因为静态成员不能访问非静态成员。
在外部类内部定义内部类的子类与平常定义子类也没用太大的区别。
(2)在外部类以外使用非静态内部类
如果希望在外部类以外的地方访问内部类(包括静态和非静态),则内部类不能使用private访问控制符,private修饰的内部类只能在外部类内部使用。对应使用其他访问控制符修饰的内部类,则能在访问控制符对应的访问权限内使用。
在外部类以外的地方定义内部类(包括静态和非静态)变量的语法格式如下:
<span style="font-size:14px;">OuterClass.InnerClass varName</span>
因为非静态内部类的对象必须寄存在外部类的对象里,因此创建非静态内部类对象之前,必须先创建其外部类对象。在外部类以外的地方创建非静态内部类实例的语法如下:
<span style="font-size:14px;">OuterInstance.new InnerConstructor()</span>
如下程序示范了在外部类以外的地方创建非静态内部类的对象,并把它赋给非静态内部类类型的变量:
<span style="font-size:14px;">class Out
{
//定义一个内部类,不使用访问控制符,
//即只有同一个包中其他类可访问该内部类
class In
{
public In(String msg)
{
System.out.println(msg);
}
}
}
public class CreateInnerInstance
{
public static void main(String[] args)
{
Out.In in = new Out().new In("测试信息");
/*
上面代码可改为如下三行代码:
使用OutterClass.InnerClass的形式定义内部类变量
Out.In in;
创建外部类实例,非静态内部类实例将寄存在该实例中
Out out = new Out();
通过外部类实例和new来调用内部类构造器创建非静态内部类实例
in = out.new In("测试信息");
*/
}
}</span>
(3)在外部类以外的地方使用静态内部类
因为静态内部类是外部类类相关的,因此创建内部类对象时无须创建外部类对象,语法格式如下:
<span style="font-size:14px;">new OuterClass.InnerConstructor()</span>
下面程序示范了在外部类以外的地方创建静态内部类的实例:
<span style="font-size:14px;">class StaticOut
{
//定义一个静态内部类,不使用访问控制符,
//即同一个包中其他类可访问该内部类
static class StaticIn
{
public StaticIn()
{
System.out.println("静态内部类的构造器");
}
}
}
public class CreateStaticInnerInstance
{
public static void main(String[] args)
{
StaticOut.StaticIn in = new StaticOut.StaticIn();
/*
上面代码可改为如下两行代码:
使用OutterClass.InnerClass的形式定义内部类变量
StaticOut.StaticIn in;
通过new来调用内部类构造器创建静态内部类实例
in = new StaticOut.StaticIn();
*/
}
}</span>
如果把一个内部类放在方法里定义,则这个内部类就是一个局部内部类,局部内部类仅在该方法里有效。由于局部内部类不能在外部类的方法以外的地方使用,因此局部内部类也不能使用访问控制符和static修饰符。
如果需要用局部内部类定义变量、创建实例或者派生子类,那么都只能在局部内部类所在的方法内进行:
<span style="font-size:14px;">public class LocalInnerClass
{
public static void main(String[] args)
{
//定义局部内部类
class InnerBase
{
int a;
}
//定义局部内部类的子类
class InnerSub extends InnerBase
{
int b;
}
//创建局部内部类的对象
InnerSub is = new InnerSub();
is.a = 5;
is.b = 8;
System.out.println("InnerSub对象的a和b Field是:"
+ is.a + "," + is.b);
}
}</span>
匿名内部类适合创建那种只需要一次使用的类,例如命令模式所需要的Command对象。创建匿名内部类时会立刻创建一个该类的实例,这个类定义立即消失,匿名内部类不能重复使用。
定义匿名内部类的格式如下:
<span style="font-size:14px;">new 父类构造器(实参列表)| 实现接口()
{
//匿名内部类的类体部分
}</span>
匿名内部类必须继承一个父类,或实现一个接口,但最多只能继承一个父类,或实现一个接口。
关于匿名内部类还有如下两条规则:
》匿名内部类不能是抽象类。
》匿名内部类不能定义构造器,但匿名内部类可以定义实例初始化块。
如下程序示范了这个用法:
<span style="font-size:14px;">interface Product
{
public double getPrice();
public String getName();
}
public class AnonymousTest
{
public void test(Product p)
{
System.out.println("购买了一个" + p.getName()
+ ",花掉了" + p.getPrice());
}
public static void main(String[] args)
{
AnonymousTest ta = new AnonymousTest();
//调用test方法时,需要传入一个Product参数,
//此处传入其匿名实现类的实例
ta.test(new Product()
{
public double getPrice()
{
return 567.8;
}
public String getName()
{
return "AGP显卡";
}
});
}
}</span>