说明
在 Java 中,允许在一个类(或方法、语句块)的内部定义另一个类,称为内部类(Inner Class),有时也称为嵌套类(Nested Class)。包含内部类的类也被称为外部类(Outer Class),或者宿主类(Hast Class)。
内部类和外层封装它的类之间存在逻辑上的所属关系,一般只用在定义它的类或语句块之内,实现一些没有通用意义的功能逻辑,在外部引用它时必须给出完整的名称。
非静态内部类
定义内部类非常简单,只要把一个类放在另一个类内部定义即可。此处的“类内部”包含类中的任何位置,甚至在方法中也可以定义内部类(方法里定义的内部类被称为局部内部类),内部类定义语法格式如下:
public class outerClass{
//此处定义内部类
}
大部分时候,内部类都被作为成员内部类定义。成员内部类是一种与Field、方法、构造器和初始化块相似的类成员;局部内部类和匿名内部类则不是类成员。
成员内部类分为两种:静态内部类和非静态内部类,使用static修饰的成员内部类是静态内部类。
因为内部类作为其外部类的成员,所以可以使用任意访问控制修饰符如private、protected、public等修饰。
请看下面的例子:
public class Outer {
private int size;
public class Inner {
private int counter = 10;
public void doStuff() {
size++;
}
}
public static void main(String args[]) {
Outer outer = new Outer();
Inner inner = outer.new Inner();
inner.doStuff();
System.out.println(outer.size);
System.out.println(inner.counter);
// 编译错误,外部类不能访问内部类的变量
System.out.println(counter);
}
}
这段代码定义了一个外部类 Outer,它包含了一个内部类 Inner。
在非静态内部类里可以直接使用外部类的所有变量和方法,即使是 private 的。外部类要想访问内部类的成员变量和方法,则需要通过内部类的对象来获取。
将错误语句注释掉,编译,会生成两个 .class 文件:Outer.class 和 Outer$Inner.class。也就是说,内部类会被编译成独立的字节码文件。
注意:必须先有外部类的对象才能生成内部类的对象,因为内部类需要访问外部类中的成员变量,成员变量必须实例化才有意义。
如果外部类成员变量、内部类成员变量和局部变量同名,则可通过使用this、外部类名.this来区分。
看下面的例子:
class A{
String prop = "外部类的实例变量";
class B{
String prop = "内部类的实例变量";
void funB(){
String prop = "局部变量";
System.out.println(A.this.prop);
System.out.println(this.prop);
System.out.println(prop);
}
}
}
public class printObject {
public static void main(String[] args)
{
A a = new A();
A.B b = a.new B();
b.funB();
}
}
输出结果:
外部类的实例变量
内部类的实例变量
局部变量
Java不允许在外部类的静态成员中直接使用非静态内部类。如下程序所示:
public class StaticTest{
//定义一个非静态内部类,是一个空类
public class In(){}
//外部类的静态方法
public static void main(String[] args){
//下面代码引发编译异常,因为静态成员(main方法)无法访问非静态成员(In类)
new In();
}
}
Java不允许在非静态内部类中定义静态成员。如下程序所示:
public class InnerNoStatic
{
private class InnerClass
{
/*
下面三个静态声明都将引发如下编译错误:
非静态内部类不能有静态声明
*/
static
{
System.out.println("==========");
}
private static int inProp;
private static void test(){};
}
}
非静态内部类中不能声明任何 static 成员。因此上面三个静态声明都会出错。
静态内部类
用static关键字修饰的内部类称为静态内部类,有的地方也被称为类内部类。静态内部类属于外部类本身,而不属于外部类的某个对象。
静态内部类可以包含静态成员,也可以包含非静态成员。静态内部类不能访问外部类的实例成员,只能访问外部类的类成员。静态内部类的实例方法也不能访问外部类的实例成员,只能访问外部类的静态成员。看下面的程序:
public class TestStaticInnerClass
{
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(prop1);
//上面代码出现错误,应改为如下形式:通过类名访问静态内部类的类成员
System.out.println(StaticInnerClass.prop1);
//System.out.println(prop2);
//上面代码出现错误,应改为如下形式:通过实例访问静态内部类的实例成员
System.out.println(new StaticInnerClass().prop2);
}
}
Java也允许在接口中定义内部类,接口里定义的内部类默认使用public static修饰,也就是说接口内部类只能是静态内部类。
使用内部类
定义类的主要作用就是定义变量、创建实例和作为父类被继承。定义内部类的主要作用也如此,但使用内部类定义变量和创建实例则与外部类存在一些小小的差异。
- 使用内部类中定义的非静态变量和方法时,要先创建外部类的对象,再由“outObjectName.new”操作符创建内部类的对象,再调用内部类的方法,如下所示:
public class Demo{
public static void main(String[] args) {
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
inner.dostuff();
}
}
class Outer{
private int size;
class Inner{
public void dostuff() {
size++;
}
}
}
- static 内部类相当于其外部类的 static 成员,它的对象与外部类对象间不存在依赖关系,因此可直接创建。示例如下:
public class Demo{
public static void main(String[] args) {
Outer.Inner inner = new Outer.Inner();
inner.dostuff();
}
}
class Outer{
private static int size;
static class Inner {
public void dostuff() {
size++;
System.out.println("size=" + size);
}
}
}
输出结果:
size = 1;
- 由于内部类可以直接访问其外部类的成分,因此当内部类与其外部类中存在同名属性或方法时,也将导致命名冲突。所以在多层调用时要指明,如下所示:
public class Outer{
private int size;
public class Inner{
private int size;
public void dostuff(int size){
size++; // 局部变量 size;
this.size; // 内部类的 size
Outer.this.size++; // 外部类的 size
}
}
}
局部内部类
局部内部类(Local class)是定义在方法中的类。它们只在定义它们的方法中是可见的。由于局部内部类不能在外部类的方法以外的地方使用,因此局部内部类也不能使用访问控制符和static修饰符修饰。
局部类有几个重要特性:
仅在定义了它们的代码块中是可见的;
可以使用定义它们的代码块中的任何局部 final 变量;
可以使用定义它们的代码块中的任何局部 final 变量;
局部类不可以用 public、private、protected 修饰,只能使用缺省的;
局部类可以是 abstract 的。
看下面的代码:
public class Outer {
public static final int TOTAL_NUMBER = 5;
public int id = 123;
public void func() {
final int age = 15;
String str = "http://www.weixueyuan.net";
class Inner {
public void innerTest() {
System.out.println(TOTAL_NUMBER);
System.out.println(id);
// System.out.println(str);不合法,只能访问本地方法的final变量
System.out.println(age);
}
}
new Inner().innerTest();
}
public static void main(String[] args) {
Outer outer = new Outer();
outer.func();
}
}
输出结果:
5
123
15
匿名内部类
匿名内部类是局部内部类的一种特殊形式,也就是没有变量名指向这个类的实例,而且具体的类实现会写在这个内部类里面。
注意:匿名类必须继承一个父类或实现一个接口。
匿名内部类适合创建那种只需要使用一次的类,创建匿名内部类时会立即创建一个该类的实例,这个类定义立即消失,匿名内部类不能重复使用。
定义匿名内部类的语法格式如下:
new 父类构造器(实参列表)|(实现接口)(){
//匿名内部类的类体部分
}
从上面的定义看,匿名内部类必须继承一个父类或必须实现一个接口,但最多只能继承一个父类或实现一个接口。
关于匿名内部类还有两条规则:
匿名内部类不能是抽象类。因为系统在创建匿名内部类时,会立即创建匿名内部类的对象。
匿名内部类不能定义构造器,因为匿名内部类没有类名。但匿名内部类可以定义实例初始化块,通过实例初始化块来完成构造器需要完成的事情。
最常用的创建匿名内部类的方式是需要创建某个接口类型的对象,如下程序所示:
interface Product
{
public double getPrice();
public String getName();
}
public class printObject {
public void test(Product p)
{
System.out.println("购买了一个" + p.getName() + ",花掉了" + p.getPrice());
}
public static void main(String[] args)
{
printObject ta = new printObject();
//调用test方法时,需要传入一个Product参数,此处传入其匿名实现类的实例
ta.test(new Product()
{
public double getPrice()
{
return 567.8;
}
public String getName()
{
return "AGP显卡";
}
});
}
}
输出结果:
购买了一个AGP显卡,花掉了567.8
上面程序中定义了一个test方法,该方法需要一个Product对象作为参数,但Product只是一个接口,无法直接创建对象,因此此处考虑创建一个Product接口实现类的对象传入该方法——如果该接口实现类需要重复使用,则应该将该实现类定义成独立类;如果这个Product接口实现类只需一次使用,则可采用上面程序的方式,定义一个匿名类。
正如上面程序中看到的,定义匿名内部类无需class关键字,而是在定义匿名类时直接生成该匿名类的对象。下面代码部分就是匿名内部类的类体部分。
{
public double getPrice()
{
return 567.8;
}
public String getName()
{
return "AGP显卡";
}
}
由于匿名内部类不能是抽象类,所以匿名内部类必须实现它的抽象父类或者接口里包含的所有抽象方法。
对于上面的代码,我们可以用下面的代码具体实现如下:
interface Product
{
public double getPrice();
public String getName();
}
class ImPlementProduct implements Product{
public String getName(){
return "AGP显卡";
}
@Override
public double getPrice() {
// TODO Auto-generated method stub
return 567.8;
}
}
public class printObject {
public void test(Product p)
{
System.out.println("购买了一个" + p.getName() + ",花掉了" + p.getPrice());
}
public static void main(String[] args)
{
printObject ta = new printObject();
ImPlementProduct IP = new ImPlementProduct();
//调用test方法时,需要传入一个Product参数
ta.test(IP);
}
}
当通过实现接口来创建匿名内部类时,匿名内部类也不能显式创建构造器,因此匿名内部类只有一个隐式的无参数构造器,故new接口名后的括号里不能传入参数值。
匿名类继承一个父类的情况 :
abstract class Person {
public abstract void eat();
}
class Child extends Person {
public void eat() {
System.out.println("eat something");
}
}
public class Demo {
public static void main(String[] args) {
Person p = new Child();
p.eat();
}
}
输出结果:
eat something
可以看到,我们用Child继承了Person类,然后实现了Child的一个实例,将其向上转型为Person类的引用。但是,如果此处的Child类只使用一次,那么将其编写为独立的一个类岂不是很麻烦?
这个时候就引入了匿名内部类。使用匿名内部类实现:
abstract class Person {
public abstract void eat();
}
public class Demo {
public static void main(String[] args){
// 继承 Person 类
new Person() {
public void eat() {
System.out.println("eat something");
}
}.eat();
}
}
可以看到,匿名类继承了 Person 类并在大括号中实现了抽象类的方法。
匿名内部类访问外部局部变量
如果匿名内部类需要访问外部类的局部变量,则需要使用final修饰符来修饰外部类的局部变量,否则会出错:
interface A
{
void test();
}
public class TestA
{
public static void main(String[] args)
{
int age = 0;
A a = new A()
{
public void test()
{
//下面语句将提示错误:匿名内部类内访问局部变量必须使用final修饰
System.out.println(age);
}
};
}
}