Chinaunix首页 | 论坛 | 博客
  • 博客访问: 69231
  • 博文数量: 43
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 420
  • 用 户 组: 普通用户
  • 注册时间: 2014-06-27 15:04
个人简介

记录,分享

文章分类

全部博文(43)

文章存档

2017年(24)

2015年(1)

2014年(18)

我的朋友

分类: Java

2014-06-27 16:28:24

一、引言
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文件,这些类文件的命名规则如下:
  • 成员内部类:外部类的名字$内部类的名字
  • 局部内部类:外部类的名字$数字$内部类的名字
  • 匿名类:外部类的名字$数字

阅读(666) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~