[TOC]
*****
# 17.1 内部类概述
Java语言中允许在一个类(或方法、代码块)的内部定义另一个类,后者称为“内部类”,封装它的类称为“外部类”。内部类与外部类之间存在逻辑上的隶属关系,内部类一般只用在封装它的外部类或代码块中使用。
## 17.1.1 内部类的作用
内部类的作用如下:
1. 封装。将不想公开的实现细节封装到一个内部类中,内部类可以声明为私有的,只能在所在外部类中访问。
2. 提供命名空间。静态内部类和外部类能够提供有别于包的命名空间。
3. 便于访问外部类成员。内部类能够很方便访问所在外部类的成员,包括私有成员也能访问。
## 17.1.2 内部类的分类
内部类的分类如图17-1所示,按照内部类在定义的时候是否给它一个类名,可以分为:有名内部类和匿名内部类。有名内部类又按照作用域不同可以分为:局部内部类和成员内部类,成员内部类又分为:实例内部类和静态内部类。
![](https://box.kancloud.cn/6700bb0988a36f93e8cc4f2df9be78b3_809x628.jpg)
# 17.2 成员内部类
成员内部类类似于外部类的成员变量,在外边类的内部,且方法体和代码块之外定义的内部类。
## 17.2.1 实例内部类
实例内部类与实例成员类似,可以声明为公有级别、私有级别、默认级别或保护级别,即4种访问级别都可以,而外部类只能声明为公有或默认级别。
**实例内部类示例代码如下:**
```
//Outer.java文件
package no_17;
//外部类
public class Outer {
// 外部类成员变量
private int x = 10;
// 外部类方法
private void print() {
System.out.println("调用外部方法...");
}
// 测试调用内部类
public void test() {
Inner inner = new Inner();
inner.display();
}
// 内部类
class Inner {
// 内部类成员变量
private int x = 5;
// 内部类方法
void display() {
// 访问外部类的成员变量x
System.out.println("外部类成员变量 x = " + Outer.this.x);
// 访问内部类的成员变量x
System.out.println("内部类成员变量 x = " + this.x);
System.out.println("内部类成员变量 x = " + x);
// 调用外部类的成员方法
Outer.this.print();
print(); //8
}
}
}
```
如果内部类和外部类它们的成员命名没有冲突情况下,在引用外部类成员时可以不用加“外部类名.this”,如代码第8行的print()方法只有外部类中定义,所以可以省略Outer.this。
**测试内部HelloWorld代码如下:**
```
//Outer.java文件
package no_17;
public class HelloWorld {
public static void main(String[] args) {
// 通过外部类访问内部类
Outer outer = new Outer();
outer.test();
System.out.println("-------直接访问内部类------");
// 直接访问内部类
Outer.Inner inner = outer.new Inner(); //2
inner.display(); //3
}
}
```
通常情况下,使用实例成员内部类不是给外部类之外调用使用的,而是给外部类自己使用的。但是一定要在外部类的之外访问内部类,Java语言也是支持的,见代码第②行内部类的类型表示“外部类.内部类”,实例化过程是先实例化外部类,再实例化内部类,outer对象是外部类实例,outer.new Inner()表达式实例化内部类对象。 另外,HelloWorld与内部类Inner在同一个包中,内部类Inner和它的方法display()访问级别都是默认的,它们对于在同一包中HelloWorld是可见的。
> **提示** 内部类编译成功后生成的字节码文件是“外部类$内部类.class”。
## 17.2.2 静态内部类
静态内部类与静态成员类似,在声明的时候使用关键字static修饰。静态内部类只能访问外部类静态成员,所以静态内部类使用的场景不多。但可以提供有别于包的命名空间。
**示例代码如下:**
```
//Outer.java文件
package no_17;
//外部类
public class View {
// 外部类实例变量
private int x = 20;
// 外部类静态变量
private static int staticX = 10;
// 静态内部类
static class Button {
// 内部类方法
void onClick() {
//访问外部类的静态成员
System.out.println(staticX);
//不能访问外部类的非静态成员
// System.out.println(x); //编译错误
}
}
}
```
上述代码第③行定义了静态内部类Button,在静态内部类中可以访问外部类的静态成员,见代码第⑤行。但是不能访问非静态成员,见代码第⑥行试图访问外部类的x实例变量,会发生编译错**误。
测试内部HelloWorld代码如下:**
```
//HelloWorld.java文件
package com.a51work6;
public class HelloWorld {
public static void main(String[] args) {
// 直接访问内部类
View.Button button = new View.Button();
button.onClick();
}
}
```
从代码View.Button button = new View.Button()可见,在声明静态内部时采用“内部类.静态内部类”形式,实例化也是如此形式。
> **提示** 如果不看代码或文档,View.Button形式看起来像是View包中的Button类,事实上它是View类中静态内部类Button。View.Button形式客观上能够提供有别于包的命名空间,View相关的类集中管理起来,View.Button可以防止命名冲突。
# 17.3 局部内部类
局部内部类就是在方法体或代码块中定义的内部类,局部内部类的作用域仅限于方法体或代码块中。
局部内部类访问级别只能是默认的,不能使用public、private和protected修饰。局部内部类也不能是静态,即不能使用static修饰。局部内部类可以访问外部类所有成员。
**示例代码如下:**
```
~~~
//Outer.java文件
package com.a51work6;
//外部类
public class Outer {
// 外部类成员变量
private int value = 10;
// 外部类方法
public void add(final int x, int y) { //1
//局部变量
int z = 100;
// 定义内部类
class Inner { //2
// 内部类方法
void display() {
int sum = x + z + value; //3
System.out.println("sum = " + sum);
}
}
// Inner inner = new Inner();
// inner.display();
//声明匿名对象
new Inner().display(); //4
}
}
~~~
```
上述代码在add方法中定义了局部内部类,见代码第②行,在内部类中代码第③行访问了外部类成员变量value、方法参数x和方法局部变量z,其中方法参数应该声明为final的,见代码第①行x参数是final的。
> **提示** 代码第④行new Inner().display()是实例化Inner对象后马上调用它的方法,没有为Inner对象分配一个引用变量名,这种写法称为“匿名对象”。匿名对象适合只运行一次情况下。匿名对象写法使代码变得简洁,但是给初学者阅读代码带来了难度。
**测试内部HelloWorld代码如下:**
```
//HelloWorld.java文件
package com.a51work6;
public class HelloWorld {
public static void main(String[] args) {
Outer outer = new Outer();
outer.add(100, 300);
}
}
~~~
```
# 17.4 匿名内部类
匿名内部类是没有名字的内部类,本质上是没有名的局部内部类,具有局部内部类所有特征。例如:可以访问外部类所有成员。如果匿名内部类在方法中定义,它所访问的参数需要声明为final的。
**下面通过示例介绍一下匿名内部类。有如下一个View类:**
~~~
//View.java文件
package com.a51work6;
//外部类
public class View {
public void handler(OnClickListener listener) { ①
listener.onClick();
}
}
~~~
代码第①行中handler方法需要一个实现OnClickListener接口的参数。OnClickListener接口代码如下:
~~~
//OnClickListener.java文件
package com.a51work6;
public interface OnClickListener {
void onClick();
}
~~~
接口中只有一个onClick()方法。使用匿名内部类的示例代码如下:
~~~
//HelloWorld.java文件
package com.a51work6;
public class HelloWorld {
public static void main(String[] args) {
View v = new View(); //1
// 方法参数是匿名内部类
v.handler(new OnClickListener() { //2
@Override
public void onClick() {
System.out.println("实现接口的匿名内部类...");
}
});
//继承类的匿名内部类
Figure f = new Figure() { //3
@Override
public void onDraw() {
System.out.println("继承类的匿名内部类...");
}
};
//具体类作为内部类
Person person = new Person("Tony", 18) { //4
@Override
public String toString() {
return "匿名内部类.实现 "
+ " Person[name=" + name
+ ", age=" + age + "]";
}
};
//打印过程自动调用person的 toString()方法
System.out.println(person);
}
}
~~~
在HelloWorld的main方法中,代码第①行是实例化View对象,代码第②行是调用它的handler方法,该方法需要一个实现OnClickListener接口的参数,new OnClickListener() {… }表达式是实际参数,它就是匿名内部类。表达式中OnClickListener是要实现的接口或要继承的类,new是为匿名内部类创建对象,()是调用构造方法,{… }是类体部分。
代码第③行是在赋值时候使用匿名内部类,其中Figure是14.1.2节使用过的抽象类。匿名内部类实现了它的抽象方法onDraw()。
代码第④行也是在赋值时候使用匿名内部类,其中Person是13.4.2节使用过的具体类,匿名内部类覆盖了toString()方法。它有个两个参数的构造方法,匿名内部类使用了这个构造方法。
匿名内部类通常用来实现接口或抽象类的,很少覆盖具体类。