在一般情况,类都是独立定义的。但在某些情况下,我们把一个类定义到另一个类的内部,定义到内部的这个类就叫内部类。包含内部类的类被称为外部类(也称为宿主类)。内部类可以提供更好的封装。内部类成员可以直接访问外部类的私有数据。匿名的内部类适合那些仅需要一次使用的类。

1、非静态内部类

定义内部类很简单,只要把一个类放在另一个类内部定义即可。

public class OuterClass
{
        //此处可以定义内部类
}

注意:同一个java源文件定义了多个类,它们之间是相互独立的,那种情况不是内部类。

内部类做为其外部类的成员,因此可以使用任意访问控制符如private、protected和public修饰。
外部类的上一级程序单元是包,所以它只有2个作用域:同一个包和任何位置。因此只需两种访问权限:包访问权限和公开访问权限。正好对应省略访问控制符和public访问控制符。省略访问控制符是包访问权限,即同一包中的其他类可访问省略访问控制符的成员。因此,如果一一个外部类不使用任何访问控制符修饰,则只能被同一一个包中其他类访问。而内部类的_上一级程序单元是外部类,它就具有四个作用域:同一个类、同一个包、父子类和任何位置,因此可以使用四种访问控制权限。

下面代码定义了一个Man类,Man类中定义了一个内部类Speaker。外部类中的Say方法会创建内部类对象,并且调用内部类对象的Say方法。如果外部类属性、内部类属性与内部类里方法的局部变量同名,则可通过使用this、外部类类名.this作为限定来区分。具本看一下如下代码,

public class Man
{
 private int age;
 private String name="man";
 public Man(){ }
 public Man(int age)
{
      this.age=age;
}
//定义一个非静态内部
private class Speaker
{
    private String content;
    private String name="speaker man";
    public Speaker(){ }
    public Speaker(String content)
    {
           this.content=content;
    }
    public void setContent(String content)
    {
           this.content=content;
    }
    public void Say()
    {
          //直接访问外部类的属性age
          System.out.println("My age is "+age);
          System.out.println("My content is " + content);
          //通过 外部类类名.this.varName访问外部类实例属性
          System.out.println("外部类的属性值:" + Man.this.name);
          //通过this.varName访问内部类实例的属性
          System.out.println("内部类的属性值:" + this.name);
          //直接访问局部变量
          System.out.println("局部变量的属性值:" + name);
    }
}
public void Say()
  {
      Speaker speaker = new Speaker();
      speaker.Say();
  }
}

非静态内部类的成员可以访问外部类的private成员,但反过来就不成立了。非静态内部类的成员只在非静态内部类范围内是可知的,并不能被外部类直接使用。如果外部类需要访问非静态内部类成员,则必须显式创建非静态内部类对象来调用访问其实例成员。

注意:非静态内部类里不能有静态方法、静态属性、静态初始化块。
静态内部类里不可以有静态初始化块,但可以包含普通初始化块。非静态内部类普通初始化块的作用与顶层类初始化块的作用完全相同。

2、静态内部类

使用static来修饰一个内部类,则这个内部类变成是外部类类相关的,属于整个外部类,而不是单独属于外部类的某个对象。因此使用static修饰的内部类被称为类内部类,也被称为静态内部类。

public class Man
{
   private int prop1 = 3;
   private static int prop2 = 9;
   static class StaticInnerClass
   {
        private static int age;
        public void accessOuterProp()
        {
           //下面代码出现错误,访问不到
           System.out.println(prop1);
           //可以正常访问到
           System.out.println(prop2);
        }
   }
}

上面代码可以看出,静态内部类的实例方法也不能访问外部类的实例成员,只能访问外部类的静态成员。

public class AccessStaticInnerClass
{
        static class StaticInnerClass
        {
                private static int prop1 = 3;
                private int prop2 =8;
        }
        public void accessInnerProp()
        {
             //System.out.println(prop1)
             //上面代码出现错误,就改为如下形式: 通过类名访问静态内部类的类成员
             System.out.println(StaticInnerClass.prop1)
             //System.out.println(prop2)
             //上面代码出现错误,就改为如下形式:通过实例访问静态内部类的实例成员
             System.out.println(new StaticInnerClass().prop2);
        }
}

从上面可以看出,外部类依然不能直接访问静态内部类的成员,但可以通过静态内部类的类名来访问静态内部类的类成员。调用静态内部类的实例成员还是要new一个静态内部类的对象。

除此之外,Java还允许在接口里定义内部类,接口里定义的内部类默认使用public static修饰,也就是说接口中内部类只能是静态内部类。接口内部类要指定访问控制符,则只能指定public访问控制符。如果省略访问修饰符,接口内部类默认是public访问控制权限。

3、局部内部类

如果把一个内部类放在方法里定义,则这个内部类就是一个局部内部类,局部内部类仅在该方法里有效。因些,局部内部类不能在外部类以外的地方使用,那么局部内部类也无需使用访问控制符和static修饰衔修饰。
注意:对于局部成员而言,不管是局部变量,还是局部内部类,它们的上一级程度单元是方法,而不是类,使用static修饰它们没有意义。因此,所有局部成员都不能使用static修饰。另外,局部成员的作用域是所在方法,其他程序单元永远也不可能访问另一个方法中的局部成员。所以所有局部成员都不能使用访问控制符修饰。

4、匿名内部类

匿名内部类适合创建那种只需要一次使用的类,创建匿名内部类时会立即创建一个该类的实例,这个类的定义立即消失,匿名内部类不能重复使用。

匿名内部类的定义格式如下:

new  父类构造器(实参列表) | 实现接口()
{
  //匿名内部类的实现
}

从上面定义可以看出,匿名内部类必须继承一个父类,或实现一个接口,但最多只能继承一个父类,或实现一个接口。

  • 匿名内部类不能是抽象类
  • 匿名内部类不能定义构造器,要通过实例初始化块代替构造器。
  • 最常用就是通过匿名内部类的方式创建某个接口类型的对象,代码如下:
interface Product
{
public double getPrice();
public String getName();
}
public class TestAnonymous
{
public static void show(Product p)
{
System.out.println( "name is "+ p.getName() + " price is "+ p.getPrice());
}
public static void main(String[] args)
{
TestAnonymous.show(new Product(){
public double getPrice()
{
return 44.8;
}
public String getName()
{
return "book";
}
});
}
}

创建匿名内部类时,必须实现接口或抽象父类里的所有抽象方法。也可以重写父类中的普通方法。
如果匿名内部类需要访问外部类的局部变量,则必须使用final修饰符来修饰外部类的局部变量。

5、闭包和回调

闭包(Closure)是一种能被调用的对象,它保存了创建它的作用域的信息。Java并不能像Js那样显式地支持闭包,但对于非静态内部类而言,它不仅记录了其外部的详细信息,还保留了一个创建非静态内部类对象的引用,并且可惜直接调用外部类的private成员,因此可以把非静态内部类当成面向对象领域的闭包。仿闭包非静态类可以很方便地实现回调。回调就是某个方法获得了内部类对象引用,就可以在合适的时候反过来调用外部类实例的方法。

如果基类的方法和实现的接口方法同名,子类实现了接口,就不能保留两个方法了。这时可以考虑使用仿闭包来实现。