内部类 类型、功能概述
一、概述
- 将一个类定义在另一个类 / 方法里面,这样的类称为内部类。
- 内部类可以分为四种:成员内部类、局部内部类、匿名内部类、静态内部类。
二、分类
1、成员内部类
- 成员内部类的定义方式,类似于类的成员变量,通常的定义方式如下:
public class OuterClass {
public class InnerClass{
//成员内部类
}
}
① 成员内部类无条件访问外部类的属性和方法
- 成员内部类访问外部类的属性、方法时,由于成员内部类在形式上处于外部类以内,所以我们无需手动创建外部类对象,可直接访问。
public class OuterClass {
private int o = 5; //定义外部类成员变量(属性)
private void move(){} //定义外部类方法move
public class InnerClass{ //成员内部类
int b = o; //将外部类成员变量(属性)的值赋予内部类的成员变量(属性)
//定义内部类自己的方法 moveFast
public void moveFast() {
System.out.println(o); //直接打印外部类成员变量(属性)的值
move(); //直接调用外部类的 move方法
}
}
}
② 外部类访问成员内部类属性和方法
- 而外部类需访问内部类属性、方法时,则需创建内部类对象,方可访问。
public class OuterClass {
InnerClass innerClass = new InnerClass();//创建内部类对象
//定义外部类的方法innerLove
public void innerLove(){
innerClass.outerLove(); //
System.out.println(innerClass.b);
}
public class InnerClass{ //成员内部类
int b = 3; //定义成员内部类的成员变量
//定义内部类自己的方法 outerLove
public void outerLove() {}
}
}
③ 外部类属性或方法隐藏
- 如果成员内部类的属性或者方法与外部类的同名,将导致外部类的这些属性与方法在内部类被隐藏。
- 可按照该格式调用(外部类.this.属性 / 方法):
package com.outerinnerdemo;
public class OuterClass { //外部类
int b = 4; //定义外部类的成员变量
//定义外部类的方法 love
public void love(){System.out.println("外部类");}
public class InnerClass{ //成员内部类
int b = 3; //定义成员内部类的成员变量
//定义内部类自己的方法 love,方法重名(注意观察是否注释该方法,运行结果的差别)
public void love() {
System.out.println("内部类");
}
//协助方法ll,用以区分
public void ll(){
OuterClass.this.love(); //调用外部类方法(只要有同名方法存在,就必须通过这种方式 --> 调用外部类的对应方法)
love(); //调用内部类方法
}
}
}
package com.outerinnerdemo;
public class Demo {
OuterClass outerClass = new OuterClass();
OuterClass.InnerClass innerClass = outerClass.new InnerClass();
public static void main(String[] args) {
Demo demo = new Demo();
demo.loveInner();
}
public void loveInner(){
innerClass.ll();//调用协同方法
}
}
运行结果如下:
④ 创建成员内部类对象
public class OuterClass {
//创建内部类对象
InnerClass innerClass = new InnerClass();
public class InnerClass{
//成员内部类
}
}
- 成员内部类可被 权限(public、protected、default、private)修饰符修饰,这意味着它可以被外部的其他类访问。
- 不仅可以在外部类中创建对应内部类对象,也可以在外部类外创建内部类对象,一般的访问方式如下:
- 我们注意到,由于InnerClass不是静态内部类,说明其依托于自身外部类,必须先创建外部类对象,再通过这个外部类对象创建内部类对象。
2、局部内部类
- 位于方法体中的类,我们成为局部内部类,局部内部类不可被 权限(public、protected、private)修饰符修饰,这意味着它不可与外界交互,完全依附于方法体。
- 即 局部内部类的访问权限仅限于方法或作用域内。
- 局部内部类不可被static修饰。
public class OuterClass {
public void move(){
class myclass{
//局部内部类
}
}
}
- 我们可以明显看到,其与成员内部类的差异:
3、匿名内部类
- 创建一个匿名类,实现一个特定的接口(继承某个类),完成某种特定的功能。
- 由于匿名内部类没有特定名称,所以匿名内部类不可被外部访问。
- 一般作为参数使用,可以使原先类在不实现该接口的情况下,完成接口提供的某种方法。
我们来看下面的例子,定义了一个接口Car。
分析代码知道静态方法driveCar需要一个Car对象,我们通过实现接口创建一个匿名类对象传递过去。
public class carTest {
public static void main(String[] args) {
//调用 driveCar 方法
driveCar(new Car(){ //继承Car接口的匿名内部类
//重写实现run方法
@Override
public void run() {
System.out.println("汽车跑起来!");
}
});
}
//定义方法
public static void driveCar(Car car) {
car.run();
}
}
//定义接口Car
interface Car {
//定义抽象方法run
void run();
}
注意事项:匿名内部类没有构造方法。也是唯一没有构造方法的内部类。匿名内部类和局部内部类只能访问外部类的final变量。
① 匿名内部类访问外部类的属性和方法
- 当匿名内部类需要访问外部类的属性和方法,但名称冲突时,可以以
外部类名.this.方法名 / 属性名
的形式对其进行访问。
匿名内部类大多数作为参数传入,方便了我们的使用,但也可以以其他形式出现,例如:
public class Demo { //测试类
public static void main(String[] args) {
A a = new A() { //实例化一个实现A接口的 匿名内部类对象,并创建接口引用指向子类对象
@Override
public void run() { //实现接口A规定的 run 方法
System.out.println("running......");
}
};
a.run(); //调用这个内部类对象的方法
}
}
//定义接口A
interface A {
//定义抽象方法run
void run();
}
② lambda表达式
- 匿名内部类看起来十分的不美观,尤其是当接口中方法较少时,我们很容易直到这些方法的参数。
- 为简化匿名内部类,我们可以使用 lambda表达式 对其进行简化,简化上一小节的代码,转换为如下形式:
public class Demo { //测试类
public static void main(String[] args) {
//实例化一个匿名的、实现A接口的内部类对象,并创建接口引用指向子类对象
A a1 = () -> {System.out.println("running......");};
a1.run(); //调用这个内部类对象的方法
//当实现的方法体中仅有一句语句时,可省略花括号,直接写出语句
A a2 = () -> System.out.println("running......");
a2.run(); //调用这个内部类对象的方法
}
}
//定义接口A
@FunctionalInterface
interface A {
//定义抽象方法run
void run();
}
4、静态内部类(静态嵌套类)
- 静态内部类的核心关键字是static,关于它的介绍,可以参考博主的上一篇文章(static关键字介绍)
Nested classes are divided into two categories: static and non-static. Nested classes that are declared static are called static nested classes. Non-static nested classes are called inner classes.
(——转自Oracle官方)
根据官方说法可以看到,我们俗称的 “静态内部类” 的官方名称为 静态嵌套类。
-
字面上也可以看出来,静态内部类可以方便我们更好的使用形式上存在于外部类(对应“嵌套类”的概念,我们可以称之为“外壳类”)的静态变量,其本身与其他static修饰的静态域一样,不依附于外部类。
-
用static修饰的内部类,称为“静态内部类”。
① 静态内部类无条件访问外部类的的静态变量、方法
- 静态内部类可直接使用外部类的静态变量、方法,无论相隔多少层,可直接使用名称调用、使用。
- 将外部类的变量赋予内部类成员时,其自身必须为static修饰的静态变量。
② 在静态内部类使用main方法
- 只有静态内部类可以调用main方法,而普通的内部类则不可以。
③ 外部类访问静态内部类属性和方法
- 外部类访问静态内部类属性,内部类的属性必须为静态属性。
④ 创建静态内部类对象
- 创建静态内部类对象与使用静态变量、静态方法相类似,不同于成员内部类,我们无需提前创建一个外部类对象,直接创建内部类对象即可:
三、分析
- 为了进一步揭示内部类与外部类的关系,我们将逐一对各种内部类进行编译与反编译:
1、成员内部类
public class OuterClass {
int b = 4;
public void love(){
System.out.println("OuterClass ");
}
public class InnerClass{
int b = 3;
public void love() {
System.out.println("InnerClass");
}
public void ll(){
OuterClass.this.love();
love();
}
}
}
使用 javac 编译:
javac OuterClass.java
编译后得到的结果为:
此时我们注意到,经过编译后,内部类与外部类分离,并且内部类的命名为外部类名$内部类名.class
。
使用 javap 对内部类 class文件 进行反编译:
javap OuterClass$InnerClass.class
- 从反编译结果看,第一行说明了该类的外部类为OuterClass。
- 非静态内部类有一个 所在外部类 类型的属性 this$0 ,还有一个参数为外部类类型的构造方法。
- this$0 是内部类所自动保留的一个指向所在外部类的引用,数字代表当前类外部类的层数。
//OuterClass.java
public class OuterClass {//this$0
public class FirstInnerClass {//this$1
public class SecondInnerClass {//this$2
public class ThirdInnerClass {
}
}
}
}
编译后得到如下结果:
我们对第三层内部类 class 文件进行反编译:
这说明了,内部类确实是逐层向外指向,即自动保留的一个指向所在外部类的引用,而数字代表当前类所在外部类的层数。
2、局部内部类
为探寻 局部内部类,我们定义如下的demo类:
public class Demo{
public void run(){
class A{}
}
public void fly(){
class B{}
}
}
使用 javac 编译:
- 它以数字编号区分 不同方法 同名的类,从而生成不同的class文件。
为进一步探究,我们更改 fly 方法中 A类的名称,再次编译:
public class Demo{
public void run(){
class A{}
}
public void fly(){
class B{}
}
}
- 这验证了我们上面的结论,体现了编号的作用。
同样我们对其中一个内部类 class 文件反编译,得到这样的结果:
- 与成员内部类相类似,唯一的不同是,它的构造方法是没有访问权限修饰符的(default),所以它的访问权限仅限于方法或作用域内。
3、匿名内部类
为探寻 匿名内部类,我们定义car接口 与 carTest类:
public interface Car {
void run();
}
public class CarTest {
public static void main(String[] args) {
driveCar(new Car(){
@Override
public void run() {
System.out.println("running......");
}
});
}
public static void driveCar(Car car) {
car.run();
}
}
使用 javac 编译:
- 我们看到了与局部内部类类似的命名方法,由于它是匿名的,所以它只拥有编号。
使用 javap 对内部类 class 文件反编译:
- 反编译的结果证实了它是一个实现接口的匿名类,并且拥有一个没有参数的默认构造方法。
- 与成员内部类、局部内部类不同的是它并没有 形如 this$x 的final 引用,这也说明了其 “匿名性质”,即外部类无法直接访问到它。
4、静态内部类
- 同样,我们定义这样的类:
public class Demo {
public static class StaticDemo{
int a = 0;
}
}
使用 javac 编译:
使用 javap 对内部类 class 文件反编译:
- 可以看到,静态内部类中仅有一个无参的默认构造方法以及其拥有的属性,并没有保留指向外部类的引用。
四、总结
内部类的优点:
- 完善了Java多继承机制,由于每一个内部类都可以独立的实现接口、继承类。
- 无论外部类 是否 继承某个类 或 实现某个接口,对内部类没有影响。
- 方便写事件驱动程序。