Java内部类详解

一、什么是内部类

什么是内部类? 可以将一个类的定义放在另一个类的定义内部,这就是内部类。内部类是Java中一种非常有用的特性,因为它允许你把一一些逻辑相关的类组织在一起,并控制位于内部类的可视性。内部类看起来就像一种代码的隐藏机制,因为它将类置于其他类的内部。但是内部类的作用不仅这些,内部类与外部类之间还可以互相通信;而且内部类可以让你写出的代码优雅清晰。

内部类有两种情况:

  • 在类中定义一个类,这个内部类可以分为普通内部类静态内部类(嵌套类)
  • 在作用域中定义一个类,这个内部类可以分为局部内部类匿名内部类

二、普通内部类

创建普通内部类的方式就如创建一个类一样,直接把类的定义置于外部类的里面:

public class Outer {  //外部类

    private int mOuterVar;
    //外部类可以直接创建内部类
    private Inner mInner = new Inner();

    public class Inner { //内部类

        public void innerTest() {
            mOuterVar = 10;//等价于Outer.this.mOuterVar=10
            System.out.println(mOuterVar);
        }
    }

    public static void main(String[] args) {
        Outer outer = new Outer(); //首先创建外部类
        Outer.Inner inner = outer.new Inner();//用外部类对象创建内部类
        inner.innerTest();
        //Outer.Inner inner2 = new Outer.Inner(); 错误
    }
}

在上面的例子中我们创建了内部类的对象,并执行了其中的方法,从上面我们可以总结如下几点:

  • 如果我们想从外部类的非静态方法之外的任意位置创建某个内部类的对象,那么就必须通过OuterClassName.InnerClassName的方式定义这个内部类对象的类型,就如上面的Outer.Inner

  • 要想创建内部类对象,你不能按照你想象的方式,去引用外部类的名字Outer,而是必须使用外部类的对象来创建内部类的对象。也就说你不能直接用new Outer.Inner()的方式直接创建一个内部类对象,在拥有外部类对象之前是不可能创建内部类的对象的。但是,如果你创建的是嵌套类(静态类),是不需要先创建外部类的对象的,这点后面再说。

  • 内部类拥有外部类的所有元素的访问权限。就如上面所看到的,在内部类的方法中可以直接访问外部类中private修饰的mOuterVar变量。当生成一个内部类的对象时,此对象与制造它的外部对象直接就有了一种联系,所以它能访问其外部对象的所有成员,而不需要任何特殊条件。

  • 在内部类中如果需要访问外部类对象的引用,可以通过外部类的名字后面紧跟圆点和this。如Outer.this,这样就可以获取外部类Outer对象的引用了。

内部类的用途就是利用外围类对象的资源做事

内部类和外部类之间的相互访问

内部类对外部类的访问

我们首先看看类中内部类的两个特点:

1、在外部类的作用范围内可以任意创建内部类对象,即使内部类是私有的(私有内部类,由private修饰)。即内部类对包围它的外部类可见。

注意:只有内部类才可以用private 进行修饰,私有内部类除了外部类之外都不能进行访问,包括类型定义和创建。

//代码1:内部类对外部类可见  
class Outer{  
     //创建私有内部类对象  
     public Inner in=new Inner();  
     //私有内部类  
     private class Inner{  
          ...  
     }  
}  

2、在内部类中可以访问其外部类的所有域,即使是私有域。即外部类对内部类可见。

//代码2:外部类对内部类可见  
class Outer{  
       //外部类私有数据域  
       private int mOuterVar;  
       //内部类  
       class Inner{  
           public void innerTest(){  
                 //内部类访问外部私有数据域
                 mOuterVar = 10;
                 System.out.println(mOuterVar);   
           }   
       }  
}  

那么内部类为什么自动拥有对其外部类的所有成员的访问权限呢?其实,内部类是Java编译器一手操办的,虚拟机并不知道内部类与常规类有什么不同。编译器是如何瞒住虚拟机的呢?

对内部类进行编译后发现有两个class文件:Outer.classOuter$Inner.class 。这说明内部类Inner仍然被编译成一个独立的类Outer$Inner.class,而不是Outer类的某一个域。 虚拟机运行的时候,也是把Inner作为一种常规类来处理的。而且这两个class文件是放在同一个包下的。

但问题来了,即然是两个常规类,为什么他们之间可以互相访问私有域呢(最开始提到的两个内部类特点)?这就要问问编译器到底把这两个类编译成什么东西了。

我们利用反射机制来探查了一下内部类编译后的情况。

(1)、编译代码1生成Outer$Inner.class 文件后使用 ReflectUtil.reflect("Outer$Inner") 对内部类Inner进行反射(ReflectUtil是一个反射Class对象的工具类)。运行结果 发现了三个隐含的成分:

class Outer$Inner
{
   private Outer$Inner(Outer); //带有外部类对象类型参数的私有构造方法
   Outer$Inner(Outer,Outer$1); //带有外部类对象类型参数的包构造方法

   public void innerTest();

   final Outer this$0;  //存储外部类对象实例的变量
}

通过上面的反射后的结果就可以大概知道,为什么外部类可以创建内部类的对象?并且内部类能够访问外部类的成员对象?

首先编译器将外、内部类编译后放在同一个包中。在内部类中添加了一个包可见的构造方法。这样在外部类中当然可以直接创建内部类的对象。因此即使是private内部类,也会通过隐含的包可见构造方法成功的获得私有内部类的构造权限。

Outer$Inner类中还有一个私有的构造方法,包可见构造方法会调用私有的构造方法,在私有的构造方法中会把外部类Outer的引用直接赋给this$0,那么内部类就可以利用this$0这个变量方便的访问到外部类对象中可见成员了,这也是为什么创建内部类对象之前必须创建外部类对象的原因了。

但是外部类中的private成员是如何访问到的呢?这就要看看下面Outer.class文件中的秘密了。

(2)、编译代码2生成Outer.class文件,然后使用 ReflectUtil.reflect("Outer")对外部类Outer进行反射。运行结果 发现两个隐含成分如下:

class Outer
{
   public Outer();
   public static void main([Ljava.lang.String;);
   static int access$000(Outer);  //新增的方法
   static int access$002(Outer,int);//新增的方法

   private int mOuterVar;
}

通过上面反编译的结果,就知道了为什么内部类可以访问外部类的私有域了?

原因的关键就在域编译器在外部类添加了两个静态方法access$000access$002access$000用于读取mOuterVar变量的值,access$002用于写入mOuterVar变量的值。

access$000的内部实现是这样的,用于返回mOuterVar变量的值。因为一个类的内部拥有对其所有成员的访问权限,所以这里可以通过outer.mOuterVar进行访问。

static int access$000(Outer outer){
return outer.mOuterVar;
}

而当我们执行System.out.println(mOuterVar)时,实际运行的时候调用的是这样的:

System.out.println(Outer.access$000(this$0)


access$000的内部实现是这样的,用于对mOuterVar变量进行赋值:

static int access$002(Outer outer,int i){
   outer.mOuterVar=i;
}

而当我们执行mOuterVar = 10时,实际运行的时候调用的是这样的:

Outer.access$002(this$0,10)

我们用下面的一张图对比编译前和编译后,编译器对内部类做的处理:

这里写图片描述

这图就可以很好的解释,为什么外部类中可以创建私有的内部类?内部类为什么可以访问外部类的所有成员?以及为什么创建内部类对象之前必须要创建外部类对象的原因了。

这时候有人就会发出了一个疑问?如果我内部类声明了带有参数的构造方法,那么不就和编译器添加的构造方法冲突了,编译器到底要调用哪个构造方法?如果调用我们的构造方法外部类对象又如何传入?

 private class Inner { //内部类       
      Inner(int a,int b){
            ...      
        }
}

其实啊,编译器会聪明的在自定义的构造方法中再添加一个外部类类型的参数,这样就可以把外部类对象的引用给传递进来了:

class Outer$Inner
{
    Outer$Inner(Outer,int,int);  //在自定义造方法的基础上添了加外部类类型的参数
    ....
}

外部类对内部类的访问

既然内部类能直接访问外部类的成员并且没有任何限制。那么反过来,外部类可以直接访问内部类的所有成员吗?很明显,答案是否定的,如果我们没有创建内部类对象,肯定是无法访问其成员变量的。但是如果我们创建了内部类对象,利用这几对象,外部类拥有对内部类中所有元素的访问权限的,包括private修饰

public class Outer {  //外部类

    private void outerTest() {
        Inner inner = new Inner();
        inner.mInnerVar = 10;
        System.out.println(inner.mInnerVar);
    }

    private class Inner { //内部类
        private int mInnerVar;
    }

    public static void main(String[] args) {
        Outer outer=new Outer();
        outer.outerTest();

    }
}

那么,在外部类中为什么获取了内部类对象就可以访问其所有的元素呢?其实这也是Java编译器一手操办的。废话不多说,我们直接反射Outer.classOuter$Inner.class这两个类:

(1)、Outer.class 反射内容如下:

class Outer
{
   public Outer();

   public static void main([Ljava.lang.String;);
   private void outerTest();
}

我们发现内外部类中什么方法都没有进行添加。

(2)、Outer$Inner.class反射内容如下,添加了四个隐含的成分:

class Outer$Inner
{
   private Outer$Inner(Outer);
   Outer$Inner(Outer,Outer$1);  

   static int access$100(Outer$Inner); //新增的方法
   static int access$102(Outer$Inner,int);//新增的方法

   private int mInnerVar;
   final Outer this$0;
}

除了两个构造方法用于传入外部类对象以外,内部类中还添加了两个静态方法,用于对mInnerVar变量进行读取和写入。这样大家瞬间就明白了,通过这两个静态方法对mInnerVar变量的访问,外部类才可以访问内部类中的所有元素。这简直和内部类直接访问外部类方式如出一辙。

分析过程和上面的方式如出一辙,给个图大家就明白了:
这里写图片描述

private构造方法的访问

如果一个类有且仅有一个private修饰的构造方法,那么在该类以外的地方都不能对其进行访问。那么这种情况对内部类是否成立呢?在外部类中可以创建仅有一个私有构造方法的内部类对象吗?

通过上面的分析,我们知道编译器会为我们的内部类添加一个包构造方法,这就为我们外部类创建内部类提供了一个契机,外部类可以通过这包构造方法来创建内部类对象。而这个包构造方法只服务于外部类,在外部类以外的地方你是无法创建仅仅带有私有构造方法的内部类对象的。

外部类中可以创建仅有一个私有构造方法的内部类对象,那么反过来内部类中可以创建仅有一个私有构造方法的外部类对象吗?答案也是肯定的,当内部类企图创建外部类时,编译器会为外部类添加一个包构造方法Outer(Outer$1),这样内部类就可以通过这个包构造方法来创建外部类对象。当然,这个包构造方法也是服务于内部类,外部类以外的的地方是无法创建仅仅带有私有构造方法的外部类对象的。

总结

那么我们总结一下编译器对内部类做的手脚把:

  1. 内部类拥有对外部类所有元素直接访问的权限,同理外部类也拥有内部类所有元素访问的权限(前提是必须获取到内部类对象)。因为编译器偷偷的往外部类或内部类中创建了可以访问私有变量的静态方法。

  2. 外部类中可以创建私有内部类以及声明了私有构造方法的内部类,同理内部类中可以创建声明了私有构造方法的外部类。因为编译器偷偷的往内部类或外部类中创建了包可见构造方法,从而使外部类或内部类获得了访问权限。

  3. Java虚拟机其实并不知道内部类这个概念,编译器会把内部类抽离成一个带有$名称的普通类,并对其内部类和外部类,添加相应的构造方法、静态方法、成员变量,而这些元素让外部类和内部类建立起了联系。

普通内部类中的static

在内部类中不能声明任何静态字段、静态方法、静态内部类,但是允许存在静态常量(只包括基本类型和String)。

public class Outer {  
    private int mOuterVar;

    public class Inner {
        // public static int a=4; error

        // public static void  test(){} error

        //public final static Integer i=new Integer(4); error

        public final static int b=4; //ok

        public  void innerTest(){}
    }
}

我们知道内部类的对象脱离了其外部类的对象就不会存在。静态变量的作用就是让该类的所有对象共享一个状态。这个类的所有对象都可以获取和修改这个状态。如果仅仅是这个目的,就可以推出这个状态也是所有外部对象所共享的状态,因此这个定义就可以提升至外围类中定义,没有必要在内部类中定义,因此在Java中不允许在内部类中声明 静态变量,但是可以允许其继承父类的静态变量,因为父类可能有很多子类,这些子类不一定是用作内部类。

class Father{
    public static int sNum=4;
}

public class Outer {
    public class Inner extends Father{
        public  void innerTest(){
        }
    }
    public static void main(String[] args) {
        System.out.println(Outer.Inner.sNum);//可以对父类静态变量进行访问
    }
}

但为什么允许静态常量的存在呢?因为基本类型和String类型的静态常量,在编译阶段就会声明在常量池中,在类加载之前就已经分配内存。非静态内部类不能脱离外部类这个上下文实例化,但是常量池使得final变量脱离了类实例化这个条件,编译期间变可确定。

三、静态内部类(嵌套类)

如果想直接创建内部类,不需要内部类对象与其外部类对象之间有联系,那么可以将内部类声明为static。被static修饰的内部类被称为静态内部类,也可称为嵌套类。普通的内部类对象隐式地保存了一个引用,指向创建它的外部类对象。然而当内部类是static的时候,就不是这样了。嵌套类意味着:

  1. 要创建嵌套类的对象,并不需要其外部的对象。可以直接创建
  2. 不能从嵌套类的对象中访问非静态的外部类对象。
  3. 嵌套类中可以包含static字段和static方法,甚至可以包含嵌套类和普通内部类
public class Outer { //外部类

    private int mVar;

    public void outerTest() {
    }

    public static void outerTest2() {
    }

    public static class Inner {//嵌套类

        public static int i = 4;
        public int j = 5;

        public void innerTest() {
            //Outer.this.mVar=4; error
            //outerTest(); error
            outerTest2();//ok
        }

        public class Inner2{}//内部类
        public static class Inner3{}//嵌套类
    }
    public static void main(String[] args) {
        Outer.Inner inner = new Outer.Inner();//直接创建
        inner.innerTest();
    }
}

main方法中,我们可以直接OuterClassName.InnerClassName的方式直接创建嵌套类,而不必先创建外部类。如上面所看到的,在嵌套类中可以定义static字段和static方法,甚至定义嵌套类和普通内部类,这些都是普通内部类无法实现的(普通内部类中允许定义自己的普通内部类)。

虽然嵌套类允许static元素的存在,但是它无法像普通内部类一样,通过一个特殊的this引用链接到其外部类对象。因此嵌套类无法直接访问外围类的非静态成员,静态内部类只能访问其外围类的静态成员。也就是说嵌套类和其外围类之间是不存在任何联系关系的,嵌套类就像一个独立的类,不需要依赖外围类而生存。

嵌套类和普通内部类一样编译后会生存两个class文件:Outer.classOuter$Inner.class。因为Inner类中还定义了两个内部类,所有还会生成Outer$Inner$Inner2.classOuter$Inner$Inner3.class两个class文件。只要有几个内部类就会生成几个class文件。我们通过ReflectUtil.reflect("Outer$Inner")反射Outer$Inner类:

class Outer$Inner
{
   public Outer$Inner();
   public void innerTest();
   public int j;
}

与上面普通内部类反射后的比较发现,少了一个指向外围类对象的引用final Outer this$0,也就是说静态内部类无法得到其外围类对象的引用,那么自然也就无法访问外围类的非静态成员了。这也就更加证实了嵌套类和其外围类之间是不存在任何联系关系的。

接口中的嵌套类

在接口中允许定义嵌套类。你放到接口中的任何类都会自动地被定义为static和public

public interface ITest {

   class  Inner{} //自动地被定义为static和public
   static class  Inner2{} //自动地被定义为static和public

}

如上面的定义的Inner类会被自动地定义为staticpublic。你可以通过new ITest.Inner()的方式直接创建Inner对象。因为类是static的,只是将嵌套类置于接口的命名空间内,这并不违反接口的规则。你甚至可以在内部类中实现外部接口,就像下面这样:

public interface ITest {

    void say();  //接口所有成员自动被设置为public的

     class  Inner implements ITest {//嵌套类实现外部接口

         @Override
         public void say() {
             System.out.println("Hello");
         }

         public static void main(String [] args){
             new Inner().say();
         }
     }
}

如果你想要创建某些公共代码,使得它们可以被某个接口的所有不同实现所公用,那么使用接口内部的嵌套类会是一个不错的选择。

如果习惯在每一个类都写一个main方法,用来测试这个类。那么这样做有一个缺点,编译过的类会带有这些额外的测试代码。如果你不想携带这些测试代码,那就可以使用嵌套类来放置测试代码:

public class MyApple {

    public void f(){
        System.out.println("f()");
    }

    public static class Tester{
        public static void main(String[] args){
            MyApple myApple=new MyApple();
            myApple.f();
        }
    }
}

上面的代码编译后,会生成了一个独立的类MyApple$Tester。可以使用这个类来做测试,但是不必在发布的产品中包含它,在将产品打包前可以简单地删除MyApple$Tester.class

四、匿名内部类

匿名内部类隐式地继承了一个父类或者实现了一个接口,适合只使用一次的类。

interface IContents {
    void say();
}

public class Test {

    public static void main(String[] args) {

        IContents iContents = new IContents() {//匿名内部类

            @Override
            public void say() {
                System.out.println("Hello");
            }
        };
        iContents.say();
    }
}

匿名内类中不能定义构造器方法(因为它根本没有名字),在上面中例子中,使用了默认的构造方法来生成IContents

如果继承的基类需要有一个参数的构造方法,该怎么办?我们只需简单地传递合适的参数给基类的构造方法即可:

abstract class Father {
    Father(int i){
        System.out.println("i = "+i);
    }
}

public class Test {
    public static void main(String[] args) {

       new Father(7){  //匿名内部类
       };
    }
}

既然在匿名内部类中不能有命名的构造方法,如果想做一些类似构造器的初始化行为,该怎么办呢?我们可以通过实例初始化,就能够达到为匿名内部类创建一个构造器的效果,就像这样:

abstract class Father {}

public class Test {

    public void go(final int i) {

        new Father() {  //匿名内部类
            private int coast;

            {    //实例初始化
                if (i > 5) {
                    coast = 100;
                }
                System.out.println(coast);
            }

            public void ha() {}
        };
    }

    public static void main(String[] args) {
        Test test = new Test();
        test.go(7);
    }
}

就如上面的所看到的,实例初始化的实际效果就是构造器。

如果在方法中定义了一个匿名内部类,并且在该内部类中使用了其方法中的局部变量,那么该局部变量要用final进行修饰,否则会得到一个编译时的错误:

abstract class Father {
    Father(int i){
        System.out.println("i = "+i);
    }
}

public class Test {

    public static void main(String[] args) {

      final int a=4; //必须得final修饰

       new Father(7){  //匿名内部类
            public void ha(){
                System.out.println(a);//使用局部变量
            }
       };
    }
}

为什么局部变量需要final修饰呢?

因为局部变量和匿名内部类的生命周期不同。匿名内部类是创建后是存储在堆中的,而方法中的局部变量是存储在Java栈中,当方法执行完毕后,就进行退栈,同时局部变量也会消失。那么此时匿名内部类还有可能在堆中存储着,那么匿名内部类要到哪里去找这个局部变量呢?

为了解决这个问题编译器为自动地帮我们在匿名内部类中创建了一个局部变量的备份,也就是说即使方法执结束,匿名内部类中还有一个备份,自然就不怕找不到了。

但是问题又来了。如果局部变量中的a不停的在变化。那么岂不是也要让备份的a变量无时无刻的变化。为了保持局部变量与匿名内部类中备份域保持一致。编译器不得不规定死这些局部域必须是常量,一旦赋值不能再发生变化了。所以为什么匿名内部类应用外部方法的域必须是常量域的原因所在了。

特别注意:在Java8中已经去掉要对final的修饰限制,但其实只要在匿名内部类使用了,该变量还是会自动变为final类型(只能使用,不能赋值)。可以参考http://docs.oracle.com/javase/tutorial/java/javaOO/localclasses.html#accessing-members-of-an-enclosing-class

总结下匿名内部类的几个特点:

  1. 匿名内部类没有名字,所以不能定义任何的构造方法。
  2. 匿名内部即可以扩展类,也可以实现接口,但是不能两者兼备。而且如果是实现接口,也只能实现一个接口。
  3. 匿名内部类中访问方法中的局部变量时,这些局部变量一定要用final修饰(Java8除外)。

大部分匿名内部类用于接口回调。一般来说,匿名内部类用于继承其他类或是实现接口,并不需要增加额外的方法,只是对继承方法的实现或是重写。

五、局部内部类

在方法的作用域内创建的一个完整的类,我们称之为局部内部类

interface Counter {
    int next();
}

public class Test {

    Counter getCounter(final String name) {

        class LocalCounter implements Counter { //创建局部内部类

            private int count = 0;

            public LocalCounter() {//局部内部类允许自定义构造方法
                System.out.println("LocalCounter()");
            }

            @Override
            public int next() {
                System.out.println(name);//局部内部类中使用局部变量。
                return count++;
            }
        }

        return new LocalCounter();
    }

    public static void main(String[] args) {
        Test test = new Test();
        Counter counter = test.getCounter("Local inner");
        counter.next();
    }
}

局部内部类也有几个特点:
1. 局部内部类的名字在方法外是不可见的。局部内部类的创建不能有访问修饰符,因为它不是外围类的一部分。但允许自定义构造方法的存在
2. 与匿名内部类一样,局部内部类只能够访问该方法中的局部变量。而且这些局部变量一定要是final修饰的常量(java8除外)。
3. 外围类看不见方法中的局部内部类的,但是局部内部类可以访问外围类的任何成员
4. 方法体中可以访问局部内部类,但是访问语句必须在定义局部内部类之后。也就是说new LocalCounter()必须放在定义LocalCounter类之后。

局部内部类一旦脱离了方法,那么它的名字 就变为不可见了。那么局部内部类相对匿名内部类相比有什么优势呢? 如果我们需要一个已命名的构造器,或需要重载构造器,我们就可以使用局部内部类。而匿名内部类只能用于实例初始化。

局部内部类相对于匿名内部类还有一个优势,匿名内部类只有一个对象,而局部内部类可以有多个。

六、继承内部类

继承一个嵌套类,跟继承一个普通类,并没有什么区别。因为嵌套类也是一个独立的类。但是,如果我们继承的是普通的内部类,那么情况就变得复杂了。因为普通内部类的必须持有外部类对象的引用,而要继承的类中不可能有默认的外部类对象的存在。要解决这个问题,必须使用特殊的语法来明确说清它们之间的关系:

class Outer {
    class Inner {
    }
}

public class Test extends Outer.Inner {//继承内部类

    //  Test(){};无法编译通过

    Test(Outer outer) {
        outer.super();//使用特殊语法
    }

    public static void main(String[] args) {
        Outer outer = new Outer();
        Test test = new Test(outer);
    }
}

可以看到,Test继承了Outer的内部类Inner。但是Test必须存在一个传递外部类的构造器。并在构造器中使用了特殊语法:外部类对象引用.super()。 只有在构造器中提供了必要的引用,程序才允许编译通过。

七、内部类可以被覆盖吗

存在一个类A,创建了一个内部类a,然后让类B继承类A并重新定义此内部类 a。那么此内部类a可以被覆盖吗?我们见如下的例子:

class Outer {

    private Inner inner;

    public Outer(){
        System.out.println("Outer");
        inner=new Inner();
    }

    public class Inner {
         public Inner(){
             System.out.println("Outer$Inner");
         }
    }
}

public class Test extends Outer{

    public class Inner{
        public Inner(){
            System.out.println("Test$Inner");
        }
    }

    public static void main(String[] args) {
        Test test=new Test();
    }
}
/*  输出:
Outer
Outer$Inner
*/

这个例子说明,当继承了某个外部类的时候,内部类并没有发生什么特别神奇的变化。这两个内部类是完全独立的两个实体,各自在自己的命名空间内。

八、为什么需要内部类

那么Sun公司为什么会如此费心地增加内部类这项基本的语言特性呢?

  1. 内部类可以很好的实现隐藏。一般的非内部类,是不允许有privateprotected权限的,但内部类可以。
  2. 内部类拥有外围类的所有元素的访问权限。
  3. 可以间接的实现多重继承(主要)。

实现隐藏

平时我们对类的访问权限,都是通过类前面的访问修饰符来限制的,一般的非内部类,是不允许有privateprotected权限的,但内部类可以,所以我们能通过内部类来隐藏我们的信息。可以看下面的例子:

 interface A
{
    void f();
}

class Outer {

    private class Inner implements A{

        @Override
        public void f() {
            System.out.println("Hello");
        }
    }

    public A getA()
    {
        return new Inner();
    }
}

public class Test {

    public static void main(String[] args) {
        Outer outer=new Outer();
        //A a1= outer.new Inner(); 访问受限
        A a=outer.getA();
        a.f();
    }
}

通过getA方法返回一个接口A的实例,但是我们从外部无法知道这个实例是怎么实现的。而且由于Innersprivate修饰的,所以在Outer类以外的地方是无法访问Inner这个类的,所以内部类具有很好的隐藏特性。

间接实现多重继承

内部类的存在使得Java的多重继承机制更加完善。这也是内部类存在的最大理由之一。我们知道Java中只能继承一个类,但是可以继承多个接口。但是继承接口有时候有很多不方便的地方,比如我们实现一个接口就必须实现它里面的所有方法。但是内部类的存在可以使我们的内间接的继承多个具体类或抽象类:

class A{}

class B{}

abstract class C{}

class Outer extends A{

    class Inner  extends B{
    }

    class Inner2  extends C{
    }
}

正如上面所看到的,如果继承的是抽象类或具体类,并且需要继承多个,那么只能使用内部类才可以间接实现多重继承的机制。

总结

如果没有内部类提供的,可以继承多个具体的或抽象的类的能力,一些设计与编程问题就很难解决。内部类使得多重继承的问题方案变得完整。接口解决了部分多重继承的问题,而内部类有效地实现了“多重继承”。

参考:
Bruce Eckel-《Thinking in Java》

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值