一、引言
1. 使用内部类的原因
-
内部类方法可以访问该类定义所在作用域的变量,包括私有变量。
-
内部类可以对同一个包中的其他类隐藏自身,达到封装效果。
-
当想要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较方便
2. 内部类的划分
->成员内部类:a.实例内部类 b.静态内部类
->局部内部类
二、内部类的语法
与普通类访问级别只有public和默认两种不同,成员内部类的访问级别有四种:public , protected , private 和默认。
class Outer{
private Inner inner = new Inner();
public class Inner{
public int add(int a,int b){
return a+b;
}
}
}
在Out类中,可以直接使用Inner类。而在Outer外部使用Inner,则必须使用Inner的完整类名 Outer.Inner,如
Outer.Inner inner = new Outer().new Inner(); Map.Entry就是典型的内部类。
可以将Inner声明为private类型,以拒绝从外部访问该类。
1. 实例内部类
实例内部类是成员内部类的一种,不用static修饰。它具有以下特点:
(1)在外部类之外创建实例内部类的实例时,外部类的实例必须已经存在。例如
Outer.Inner inner = new Outer().new Inner();
它等价于 Outer outer = new Outer(); Inner inner = outer.new Inner();
而 Outer.Inner inner = new Outer.Inner()将导致编译错误。
(2)在内部类中,可以直接访问外部类的所有成员,包括成员变量和成员方法。因为当内部类的实例存在时,外部类的实例必定也存在,而内部类会自动持有外部类实例的引用。
(3)一个内部类只会引用一个外部类实例。而一个外部类可以对应多个内部类实例。在外部类中不能直接访问内部类的成员,必须通过内部类的实例去访问,因为外部类不会持有内部类实例的引用。
(4)在实例内部类中不能定义静态成员,而只能定义实例成员。
(5)如果实例内部类B与外部类A包含同名的成员v,那么在类B中,this.v表示类B的成员,A.this.v表示类A的成员。如下
public class A{
int v = 1;
class B{
int v = 2;
public void test(){
System.out.println("this.v : "+this.v);
System.out.println("A.this.v : "+A.this.v);
}
}
}
执行结果:
this.v : 2
A.this.v : 1
2. 静态内部类
静态内部类是成员内部类的一种,使用static修饰,它具有如下特点
(1)静态内部类的实例不会自动持有外部类的实例的引用,在客户类中创建内部类的实例时,不必创建外部类的实例。
(2)静态内部类可以直接访问外部类的静态成员。而如果要访问外部类的成员,则必须通过外部类的实例访问。访问不受访问级别的限制。
class A{
privte int a1;
private static int a2;
public static class B{
System.out.println(a2); //合法
System.out.println(a1); //编译错误
System.out.println(new A().a1); //合法,可以通过外部类实例访问外部类的私有成员变量
}
}
(3)在静态内部类中可以定义静态成员和实例成员。
(4)客户类可以通过完整的类名直接访问静态内部类的静态成员。
3. 局部内部类
局部内部类是在一个方法中定义的内部类,它的可见范围是当前方法。和局部变量一样,局部内部类不能用访问控制符修饰及static修饰符来修饰。它具有以下特点:
(1)局部内部类只能在当前方法中使用。
(2)局部内部类和实例内部类一样,不能包含静态成员。
(3)在局部内部类中定义的内部类不能使用访问控制修饰符修饰。
(4)局部内部类和实例内部类一样,可以访问外部类的所有成员。但是,局部内部类不能访问所在方法的可变参数和变量,只能访问方法中final类型的参数和变量。
三、内部类的继承
如下,某个外部类Sample继承了外部类Outer的内部类Inner。
class Outer{
private int outerV;
public Outer(int para){
this.outerV = para;
}
class Inner{
public Inner(){};
public void print(){ System.out.println("a:"+a);
}
}
public class Sample extends Outer.Inner{
//public Sample(){}; 编译错误
public Sample(Outer o){ o.super();}
public static void main(String[] args){
Outer outer1 = new Outer(1);
Outer outer2 = new Outer(2);
Outer.Inner in = Outer1.new Inner();
in.print(); //打印a=1
Sample s1 = new Sample(outer1); //s1与outer1相关联
s1.print(); //打印a=1
Sample s2 = new Sample(outer2); //s2与outer2相关联
s2.print(); //打印a=2
}
}
在直接构造实例内部类Inner的时候,虚拟机会自动使Inner实例引用它的所属外部类Outer的实例。
但当直接构造Inner子类Sample的时候,虚拟机就无法决定让Sample实例引用哪个Outer实例。因此,直接使用Sample s = new Smaple() 会导致编译错误。Sample类的构造方法必须有一个Outer引用类型的参数o,然后在构造方法中调用Sample父类Outer.Inner的构造方法并用o作为参数。public Sample(Outer o){ super(o);}
四、子类与父类的内部类重名
内部类不存在覆盖的概念。当子类与父类拥有重名的内部类时,两个内部类是在不同命名空间中,不会发生冲突。
五、匿名类
如下代码是典型的匿名类的使用
Comparator comparator = new Comparator(){
public int compare(StorageResource o1 , StorageResource o2){
long f1=0;
long f2=0;
try {
f1 = o1.getFreeCapacity(null);
f2 = o2.getFreeCapacity(null);
} catch (NotSupportedRequestException e) {
//do nothing here
}
return (f1
}
};
匿名类的形式一般是
InterfaceOrClassName instance = new InterfaceOrClassName (...){ public void method(...);...}
它等价于:
public class A{
class InterfaceOrClassName {
public void method(...){
...
}
}
public void test(){
InterfaceOrClassName instance = new InterfaceOrClassName();
instance.method();
}
}
匿名类具有如下特点:
(1)匿名了本身没有构造方法,但是它会调用父类的构造方法。如果传给匿名类的构造参数是局部变量,则它必须是final类型的,否则编译出错。
(2)匿名类尽管没有构造方法,但是可以为它提供一个初始化代码块,虚拟机会在在父类构造方法结束后调用这个代码块。
(3)匿名类的实例除了可以声明为外部类方法的局部变量外,也可以声明为外部类的成员变量。
(4)匿名类除了可以继承类外,还可以实现接口。一般情况下,new关键字后面无法直接使用接口名,但使用匿名类时可以,注意这种形式仍然是创建实现接口的类的实例,而不是创建接口实例,接口无法实例化。
(5)匿名类与局部内部类一样,可以访问外部类的所有成员,如果匿名类位于一个方法中,还可以访问方法所有final类型的变量与参数。
匿名类与局部内部类的区别:
一个局部内部类可以有多个重载构造方法,并且外部类可以多次创建局部内部类的实例。而匿名类没有重载构造方法,并且只能创建一次实例。
六、内部接口及接口中的内部类
在一个类中可以定义内部接口。
在一个接口中也可以定义静态内部类,此时静态内部类位于接口的命名空间中。
七、内部类的用途
1. 封装类型
2. 直接访问外部类的成员。假设有两个类A和类B,类B的reset方法负责将A的数据重置,则如果将A和B都定义为外部类,则破坏了封装,A与B硬耦合。应该将B作为A的内部类使用。
3. 回调(CallBack)
回调实质上是指一个类尽管实际上实现了某个功能,但是没有直接提供相应的接口,客户类可以通过这个类的内部类实现的接口来获得这种功能。这个内部类本身没有提供真正的实现,仅仅调用外部类的实现。可见,回调充分发挥了内部类可以访问外部类成员这一优势。
看下面这个例子
public interface MusicPlayer{
public void play(File mp3);
}
class VideoPlayer{
public void play(File mp4){...}
}
现在需要一个多媒体播放器,它既能播放mp3又能播放mp4。如果将它实现MusicPlayer接口并继承VideoPlayer,这并不能满足要求,因为这意味着这个类实现了MusicPlayer的play方法并覆盖了父类VideoPlayer的方法,结果此类只有播放mp3的功能。
解决方法是使用内部类实现回调
public MultiMediaPlayer extends VideoPlayer{
public void play(File mp4){...}
private void playMp3(File mp3){...};
private class Enclosure implements MusicPlayer {
public void play(File mp3){
playMp3(mp3);
}
}
public MusicPlayer getCallBackReference(){
return new Enclosure();
}
}
下面是通过MultiMediaPlayer播放mp3的代码
public Test {
public static void main(String[] args){
MultiMediaPlayer player = new MultiMediaPlayer();
MusicPlayer musicPlayer = player.getCallBackReference();
musicPlayer.play(new File("D:\\hi.mp3"));
}
}
八、内部类的类文件
对于每个内部类来说,编译器会为其生成独立的.class文件,这些类文件的命名规则如下:
-
成员内部类:外部类的名字$内部类的名字
-
局部内部类:外部类的名字$数字$内部类的名字
-
匿名类:外部类的名字$数字