内部类

我们在前面的学习过程中,在一个文件中定义两个类,并且其中任何一个类都不在另一个类的内部。如果在类中再定义一个类,则将在类中再定义的那个类称为内部类。成员内部类和匿名类是最常见的内部类,本节将对这两种内部类进行讲解。

成员内部类

成员内部类简介

在一个类中使用内部类,可以在内部类中直接存取其所在类的私有成员变量。成员内部类的语法如下:

class OuterClass {          // 外部类
    class InnerClass {      // 内部类

    }
}

在成员内部类中可以随意使用外部类的成员方法及成员变量,尽管这些类成员被修饰为 private。图8.3充分说明了内部类的使用,尽管成员变量 i 以及成员方法 g() 都在外部类中被修饰为 private,但在成员内部类中可以直接使用。

image 2024 03 01 11 38 31 494
Figure 1. 图8.3 内部类可以使用外部类的成员

内部类的实例一定要被绑定在外部类的实例上,如果从外部类中初始化一个内部类对象,那么内部类对象就会被绑定在外部类对象上。内部类初始化方式与其他类的初始化方式相同,都是使用 new 关键字。下面来看一个实例。

【例8.3】使用成员内部类模拟发动机点火(实例位置:资源包\TM\sl\8\3)

首先创建 Car 类,Car 类中有私有属性 brand 和 start() 方法,然后在 Car 类的内部创建 Engine 类,Engine 类中有私有属性 model 和 ignite() 方法,最后输出 “启动大众朗行,发动机EA211点火”。

public class Car {							 // 创建汽车类
	private String brand; 					 // 汽车品牌
	public Car(String brand) {				 // 汽车类的构造方法,参数为汽车品牌
		this.brand = brand; 					 // 给汽车品牌赋值
	}
	class Engine {							 // 发动机类(内部类)
		String model; 						 // 发动机型号
		public Engine(String model) {			 // 发动机类的构造方法,参数为发动机型号
			this.model = model; 				 // 给发动机型号赋值
		}
		public void ignite() {				 // (发动机)点火方法
			System.out.println("发动机" + this.model + "点火");
		}
	}
	public void start() {						 // 启动(汽车)方法
		System.out.println("启动" + this.brand);
	}
	public static void main(String[] args) {
		Car car = new Car("大众朗行");			 // 创建汽车类对象,并为汽车品牌赋值
		car.start();							 // 汽车类对象调用启动(汽车)方法
		// 创建发动机类(内部类)对象,并为发动机型号赋值
		Car.Engine engine = car.new Engine("EA211");
		engine.ignite();						 // 发动机类对象调用(发动机)点火方法
	}
}

运行结果如下:

     启动大众朗行
     发动机EA211点火

成员内部类不止可以在外部类中使用,在其他类中也可以使用。在其他类中创建内部类对象的语法非常特殊,语法如下:

外部类 outer = new 外部类();
外部类.内部类 inner = outer.new 内部类();
  1. 如果在外部类和非静态方法之外实例化内部类对象,需要使用 "外部类.内部类" 的形式指定该对象的类型。

  2. 内部类对象会依赖于外部类对象,除非已经存在一个外部类对象,否则类中不会出现内部类对象。

使用this关键字获取内部类与外部类的引用

如果在外部类中定义的成员变量与内部类的成员变量名称相同,可以使用 this 关键字。

【例8.4】在内部类中调用外部类对象(实例位置:资源包\TM\sl\8\4)

在项目中创建 TheSameName 类,在类中定义成员变量 x,再定义一个内部类 Inner,在内部类中也创建x变量,并在内部类的 doit() 方法中定义一个局部变量 x。

public class TheSameName {
	private int x = 7; // 外部类的x

	private class Inner {
		private int x = 9;// 内部类的x

		public void doit() {
			int x = 11; // 局部变量x
			this.x++; // 调用内部类的x
			TheSameName.this.x++; // 调用外部类的x
		}
	}
}

在类中,如果遇到内部类与外部类的成员变量重名的情况,则可以使用 this 关键字进行处理。例如,在内部类中使用 this.x 语句可以调用内部类的成员变量 x,而使用 TheSameName.this.x 语句可以调用外部类的成员变量 x,即使用外部类名称后跟一个点操作符和 this 关键字便可获取外部类的一个引用。

匿名内部类

匿名类是只在创建对象时才会编写类体的一种写法。匿名类的特点是 “现用现写”,其语法如下:

new 父类/父接口() {
  子类实现的内容
};

最后一个大括号之后有分号。

【例8.5】使用匿名内部类创建一个抽象狗类的对象(实例位置:资源包\TM\sl\8\5)

创建一个抽象的狗类,类中有一个颜色属性和两个抽象方法,在测试类的主方法中创建抽象类对象,并用匿名内部类实现该对象的抽象方法。

abstract class Dog {
	String Color;

	public abstract void move();

	public abstract void call();
}

public class Demo {
	public static void main(String args[]) {
		Dog maomao = new Dog() {
			public void move() {
				System.out.println("四腿狂奔");
			}

			public void call() {
				System.out.println("嗷呜~");
			}
		};
		maomao.Color = "灰色";
		maomao.move();
		maomao.call();
	}
}

运行结果如下:

     四腿狂奔
     嗷呜~

从这个结果中可以看出,本来无法创建对象的抽象类竟然也可以出现在 new 关键字的右侧。为何叫匿名内部类?就是因为实例中 Dog 抽象类的实现类没有名称。创建出的对象 maomao 既不是金毛犬,也不是哈士奇犬,在程序中 maomao 只能被解读为 “一只具体的无名之狗”。使用匿名类时应该遵循以下原则:

  • 匿名类不能写构造方法。

  • 匿名类不能定义静态的成员。

  • 如果匿名类创建的对象没有赋值给任何引用变量,会导致该对象用完一次就会被 Java 虚拟机销毁。

匿名内部类编译以后,会产生以 “外部类名$序号” 为名称的 .class 文件,序号以 1~n 排列,分别代表 1~n 个匿名内部类。

编程训练(答案位置:资源包\TM\sl\8\编程训练)

【训练3】让心脏成为内部类 创建一个人类,人类中包含一个内部类—心脏类。当人类执行走路方法时,心脏方法也会同时执行。

【训练4】猫吃鱼,狗吃肉 参照下面的代码,创建Animal类的匿名子类对象,重写eat()方法,执行该方法后会在控制台上输出“猫吃鱼,狗吃肉”字样。

class Animal {
    void eat() {

    }
}