内部类:将一个类定义置入另一个类定义中。
Java
顶级类类名必须与文件名相同(大小写也必须一样),并且顶级类只能使用 public
或者不用访问控制符(default
,包内可见)。在顶级类中新定义的类,都是内部类。Java
内部类分四种:成员内部类、局部内部类、匿名内部类和静态内部类。
成员内部类
定义
定义在一个类的内部,但是没有 static
关键字修饰,像成员变量一样定义的类,即为成员内部类。
作用域
成员内部类作用域是整个外部类,类似成员变量。
访问范围
- 成员内部类能够访问外部类的所有属性及方法(包含外部类的
private
成员) - 外部类对内部类实例化对象后(不能直接使用),通过该对象能够访问成员内部类的所有成员和方法(包含成员内部类的
private
成员)
实例化
成员内部类的实例化,必须先实例化外部类。通过外部类的对象,new 一个成员内部类对象。
1 | OuterClass outerClass = new OuterClass(); |
示例
1 | // 1. 成员内部类 |
局部内部类
定义
局部内部类指的是定义在一个方法或代码块中的类。因为在方法或代码块内部,所以和局部变量一样,不能有任何访问控制修饰符(public
也不行)来修饰局部内部类。
作用域
局部内部类的作用域为方法或代码块内部,类似方法内的局部变量。
访问范围
- 局部内部类在方法或代码块外是不可见的。所以只能在当前方法或代码块中对局部内部类实例化对象,并可以访问局部内部类所有的成员和方法(
private
类型也可以) - 局部内部类可以访问外部类所有成员和方法
- 局部内部类访问方法或代码块内的成员变量时,只能访问
final
类型变量(语法检查,jdk 1.8
及以上不必指定final
)
在局部内部类内部,可以使用访问控制符修饰变量或方法,但是局部内部类整个作用域都在方法或代码块内,而这个范围是可以访问局部内部类所有成员和方法的,所以使用访问控制符没有任何意义。
示例
1 | public class OuterMethodLocalClass { |
匿名内部类
定义
没有类名的,特殊的局部内部类,隐式的继承一个父类或者是实现某个接口。所谓匿名内部类就是在 new
一个对象时,改变类的方法。Java
抽象类和接口(特殊的抽象类)是不能实例化的,但是抽象类或接口对象是可以指向实现类对象实例,最常见的方式就是使用匿名内部类充当这个实现类。
作用域
匿名内部类的作用域,它是一次性使用,用完即走。
访问范围
因匿名内部类为特殊的局部内部类,所以局部内部类的所有限制都对其生效。
特点
- 注意事项:匿名内部类不能有构造方法
- 实例化对象的格式
1
2
3new 类名或接口名(){
重写方法;
}; //注意分号
匿名内部类,new
出来一个对象,该对象可以直接调用匿名内部类的方法。
作用
- 重写或实现方法
当仅仅需要重写类方法,或者实现抽象类/接口的方法时,使用匿名内部类使代码变得很简洁。 - 重新定义新方法,调用外部类的
protected
方法
示例
1 | public class OuterAnonymousClass { |
静态内部类
定义
必须以 static
关键字标注类名,只能修饰成员内部类。
作用域
静态内部类作用域为全局,并不依赖外部类实例,可以直接访问和使用。
特点
- 嵌套类
静态内部类在oracle
中被定义为static nested classes
,即嵌套类;而其他为内部类inner classes
,是平行类。 - 创建对象
创建嵌套类对象,并不需要外部类实例化对象,静态类直接使用可以将其看成静态成员。
访问范围
- 只能访问外部类中的静态的成员变量或者是静态的方法
- 外部类访问静态内部类成员变量不受限制
示例
1 | public class OuterStaticNestedClass { |
内部类持有外部类的引用
反编译内部类
成员内部类
1
2
3
4
5
6
7
8
9
10
11
12
13public class OuterClass$InnerClass
{
...
// 持有外部类引用
public OuterClass$InnerClass(OuterClass this$0) {}
public void show()
{
System.out.println("InnerClass show: " +
OuterClass.access$100(this.this$0));
OuterClass.access$200(this.this$0);
}
}局部内部类
1
2
3
4
5
6
7
8
9
10
11
12
13
14class OuterMethodLocalClass$1MethodLocalInnerClass
{
...
// 持有外部类引用
public OuterMethodLocalClass$1MethodLocalInnerClass(
OuterMethodLocalClass this$0) {}
public void show()
{
System.out.println("MethodLocalInnerClass: "
+ OuterMethodLocalClass.access$000(this.this$0));
System.out.println("MethodLocalInnerClass: finalStringShow");
}
}匿名内部类
1
2
3
4
5
6
7
8
9
10
11
12
13class OuterAnonymousClass$1
implements Runnable
{
...
// 持有外部类引用
OuterAnonymousClass$1(OuterAnonymousClass this$0) {}
public void run()
{
System.out.println("OuterAnonymousClass::AnonymousInnerClass "
+ OuterAnonymousClass.access$000(this.this$0));
}
}静态内部类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23public class OuterStaticNestedClass$StaticNestedClass
{
private String staticNestedClassPrivateStr
= "staticNestedClassPrivateStr";
public String staticNestedClassPublicStr
= "staticNestedClassPublicStr";
public static String staticNestedClassPublicStaticStr
= "staticNestedClassPublicStaticStr";
public void showNormalMethod()
{
System.out.println("OuterStaticNestedClass::StaticNestedClass
::showNormalMethod " + OuterStaticNestedClass.access$000());
}
public static void showStaticMethod()
{
System.out.println("OuterStaticNestedClass::StaticNestedClass
::showStaticMethod " + OuterStaticNestedClass.access$000());
System.out.println("OuterStaticNestedClass::StaticNestedClass
::showStaticMethod " + staticNestedClassPublicStaticStr);
}
}
小结
通过反编译内部类对应的 class
文件,可以看出内部类在构造方法中,传入了外部类的引用 this$0
,内部类在访问外部类的成员或方法时,都需要传递该参数 this.this$0
,只有静态内部类例外。这也可看出为什么静态内部类在实例化时不需要外部类实例化,而其他内部类在实例化时必须先实例化外部类了。
换句话说:静态内部类不持有外部类对象的引用,而其他内部类都会持有。
内部类虽然和外部类写在同一个文件中,但是编译完成后会生成各自的 class
文件,编译过程中:
- 编译器自动为非静态内部类添加一个成员变量,这个成员变量的类型和外部类的类型相同,这个成员变量就是指向外部类对象的引用
- 编译器自动为非静态内部类的构造方法添加一个参数,参数的类型是外部类的类型,这个参数为内部类中添加的成员变量赋值
- 在调用非静态内部类的构造函数初始化内部类对象时,会默认传入外部类的引用
总结
- 作用域
四种内部类作用域不同,根据作用域范围大小:静态内部类 > 成员内部类 > 局部内部类 > 匿名内部类。通常我们可以根据需求(作用域)来选择使用哪种内部类。 - 访问范围
在外部类整个类内部这个范围内,是不受访问控制符限制的,即private
变量外部类和内部类是可以相互直接访问到的。所以内部类中通常会省略访问控制符。 - 生成的
class
文件
内部类是一个编译时的概念,一旦编译成功,就会成为完全不同的两个类。每个类编译后都会生成一个.class
文件,内部类生成的对应文件格式为:外部类$内部类.class
,例如:OuterClass$Inner.class
。 static
关键字static
关键字只能出现在静态内部类,不能出现在其他内部类中,除非是常量static final
。非静态内部类需要引用外部类的对象,如果有static
成员,意味着外部类不需要创建实例对象,所以这是不允许的。- 引用外部类的成员
格式:OuterClass.this.***
,***
表示成员变量,或者直接使用***
。
内部类的作用
- 内部类能很好的聚合代码
- 内部类可以很好的实现隐藏,通过访问控制符控制是否能被外部类访问
- 内部类可以隐藏接口的实现细节,而只需对外提供这个接口就行
- 实现多重继承,使用多个内部类分别继承,达到多重继承的目的
静态内部类的设计意图
首先它是内部类,所以拥有内部类的一些便捷性,外部类可以访问静态内部类的所有成员变量和方法。但是因为静态属性,静态内部类不持有外部类引用,和外部类平级。由静态内部类的角度看外部类,可以认为是独立的两个类。静态内部类适合于当做内部类使用但是又不依赖外部类。
闭包和局部内部类
闭包:是指可以包含自由(未绑定到特定对象)变量的代码块;这些自由变量不是在这个代码块内或者任何全局上下文中定义的,而是在定义代码块的环境中定义(局部变量)。闭包是动态语言中的概念,并不适合 Java
这种静态语言。
闭包允许将一些行为封装,将它像一个对象一样传来递去,而且它依然能够访问到原来第一次声明时的上下文。闭包允许我们创建函数指针,并把它们作为参数传递(在 JAVA
中,闭包是通过“接口+内部类”实现)。
参考上面局部内部类中的示例:
1 | // OuterMethodLocalClass.java |
其中:内部类 MethodLocalInnerClass
使用了局部变量 finalStringShow
,这个就是闭包的例子。
同时根据注释也可以看出,这个局部变量必须是 final
的,否则会编译不过(Java 8
支持这个语法糖可以编过,8 以下都无法编译)。也就是说,Java
中实现闭包的概念需要使用 final
来修饰自由变量。