内部类成员可以直接访问外部类的私有数据,外部类不能访问内部类的实现细节,例如内部类的成员变量。内部类比外部类可以多使用三个修饰符:private、protected、static。内部类的上一级单元是外部类,它就具有4个作用域:同一个类、同一个包、父子类和任何位置。

非静态内部类

没有使用static修饰额成员内部类是非静态内部类。在非静态内部类对象里,保持了一个它所寄生的外部类对象的引用(当调用非静态内部类的实例方法时,必须有一个非静态内部类实例,非静态内部类实例必须寄生在外部类实例里)。

public class DiscernVariable
{
private String prop = "外部类的实例变量";
private class InClass
{
private String prop = "内部类的实例变量";
public void info()
{
String prop = "局部变量";
//通过外部类类名.this.varName访问外部类实例变量
System.out.println("外部类的实例变量值:"+DiscernVariable.this.prop);
//通过this.varName访问内部类实例变量
System.out.println("内部类的实例变量值:"+this.prop);
//直接访问局部变量
System.out.println("局部变量值:"+prop);
}
}
public void test()
{
InClass in = new InClass();
in.info();
}
public static void main(String[] args)
{
new DiscernVariable().test();
}
}

非静态内部类的成员只在非静态内部类范围是可知的,并不能被外部类直接使用。如果外部类需要访问非静态内部类的成员,则必须显示创建非静态内部类对象来调用访问其实例成员。–> 非静态内部类对象必须寄生在外部类对象里,而外部类对象则不必一定有非静态内部类对象寄生其中。简单地说,如果存在一个非静态内部类对象,则一定存在一个被它寄生的外部类对象。但外部类对象存在时,外部类对象里不一定寄生了非静态内部类对象。因此外部类对象访问非静态内部类成员时,可能非静态普通内部类对象根本不存在。而非静态内部类对象访问外部类成员时,外部类对象一定存在。

不允许外部类的静态成员中直接使用非静态内部类。非静态内部类不能拥有静态成员

public class StaticTest
{
private class In{}
public static void main(String[] args)
{
new In(); // 引发编译异常,无法访问非静态成员In类
}
}

在外部类以外使用非静态内部类:内部类不能使用private访问控制权限,private修饰的内部类只能在外部类内部使用。对于使用其他访问控制符修饰的内部类,则能在访问控制符对应的访问权限内使用。
在外部类以外定义内部类变量的语法:​​​OuterClass.InnerClass varName​​​。
非静态内部类的对象必须寄生在外部类对象里,创建非静态内部类对象前,必须创建外部类对象。在外部类外创建非静态内部类实例语法:​​​OuterInstance.new InnerConstructor()​​​。
举例:

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("测试信息");
// 等同于下面的
Out.In in;
Out out = new Out();
in = out.new in("测试信息");
}
}
}

创建非静态内部类的子类,必须保证让子类构造器可以调用非静态内部类的构造器,调用非静态内部类的构造器时,必须存在一个外部类对象。

public class SubClass extends Out.In
{
public SubClass(Out out)
{
//通过传入的Out对象显式调用In的构造器
out.super("hello");
}
}

非静态内部类In类的构造器必须使用外部类对象来调用,代码中super代表调用In类的构造器,而out代表外部类对象。SubClass是非静态内部类In类的子类,非静态内部类In对象里有一个对Out对象的引用,其子类也应该持有对Out对象的引用。当创建SubClass对象时传给该构造器的Out对象,就是SubClass对象里Out对象引用所指向的对象。

静态内部类

static修饰的内部类属于外部类本身不属于外部类的对象。静态内部类可以包含静态成员,也可以包含非静态成员。根据静态成员不能访问非静态成员的规则,静态内部类不能访问外部类的实例成员,只能访问外部类的类成员。即使是静态内部类的实例方法也不能访问外部类的实例成员,只能访问外部类的静态成员。
静态内部类对象不是寄生在外部类的实例中,而是寄生在外部类的类本身中。当静态内部类对象存在时,并不存在一个被它寄生的外部类对象,静态内部类对象只持有外部类的类引用,没有持有外部类对象的引用。如果允许静态内部类的实例方法访问外部类的实例成员,但找不到被寄生的外部类对象,这将引起错误。

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);
}
}
}

静态内部类是外部类的一个静态成员,因此外部类的所有方法、所有初始化块中可以使用静态内部类来定义变量、创建对象等。外部类依然不能直接访问静态内部类的成员,但可以使用静态内部类的类名作为调用者来访问静态内部类的类成员,也可以使用静态内部类对象作为调用者来访问静态内部类的实例成员。

public class AccessStaticInnerClass
{
static class StaticInnerClass
{
private static int prop1 = 5;
private int prop2 = 9;
}
public void accessInnerProp()
{
//通过类名访问静态内部类的类成员
System.out.println(StaticInnerClass.prop1);
//通过实例访问静态内部类的实例成员
System.out.println(new StaticInnerClass().prop2);
}
}

在外部类以外定义静态内部类变量的语法:​​OuterClass.InnerClass varName​​​。
创建静态内部类对象时无须创建外部类对象,在外部类外创建静态内部类实例的语法:​​​new OuterClass.InnerConstructor()​

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();
// 等同下面的代码
StaticOut.StaticIn in;
in = new StaticOut.StaticIn();
}
}

创建静态内部类的子类:​​public class StaticSubClass extends StaticOut.StaticIn {}​

局部内部类

如果把一个内部类放在方法里定义,则这个内部类就是一个局部内部类,局部内部类仅在该方法里有效。局部内部类不能使用访问控制符和static修饰符。使用局部类定义变量、创建实例或派生子类,都只能在局部内部类所在的方法内进行。

匿名内部类

匿名内部类适合创建只需要一次使用的类,创建匿名内部类时会立即创建一个该类的实例,这个类定义立即消失,匿名内部类不能重复使用。匿名内部类必须继承一个父类,或实现一个接口。匿名类不能是抽象类,无法创建该匿名内部类对象。匿名内部类不能定义构造器,匿名内部类没有类名,无法定义构造器。但是匿名内部类可以定义初始化块,可以通过实例初始化块来完成构造器需要完成的事情。

new 实现接口() | 父类构造器(实参列表)
{
}

举例

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();
ta.test(new Product()
{
public double getPrice() { return 567.8; }
public String getName() { return "AGP显卡"; }
});
}
}

当通过实现接口来创建匿名内部类时,匿名内部类不能显示创建构造器,因为匿名内部类只有一个隐式的无参数构造器,故new接口名后的括号里不能传入参数值。

abstract class Device
{
private String name;
public abstract double getPrice();
public Device() {}
public Device(String name) { this.name = name; }
}
public class AnonymousTest
{
public void test(Device p){
System.out.println("购买一个"+p.getName()+",花掉"+p.getPrice());
}
public static void main(String[] args)
{
AnonymousTest ta = new AnonymousTest();
//调用有参数的构造器创建Device匿名实现类的对象
ta.test(new Device("电子示波器")
{
public double getPrice() { return 567.8; }
});
//调用无参数的构造器创建Device匿名实现类的对象
Device d = new Device()
{
//初始化块
{ System.out.println("匿名内部类的初始化块"); }
//实现抽象方法
public double getPrice() { return 567.8; }
//重写父类的实例方法
public String getName() { return "显卡"; }
});
ta.test(d);
}
}

通过继承父类来创建匿名内部类,匿名内部类将拥有和父类相似的构造器,即拥有相同的形参列表。

在Java 8之前,Java要求被局部内部类、匿名内部类访问的局部变量必须使用final修饰,从Java 8开始这个限制被取消。如果局部变量被匿名内部类访问,那么该局部变量相当于自动使用final修饰。