一、内部类的基本语法
1、顶层类只能处于public和默认访问级别,而成员内部类可以处于public、protected、默认和private四种访问级别。
2、实例内部类有以下特点:
a) 在创建实例内部类的实例时,外部类的实例必须已经存在。
b) 实例内部类的实例自动持有外部类的实例的引用。在内部类中,可以直接访问外部类的所有成员,包括成员变量和成员方法。并且在多重嵌套中,内部类可以访问所有外部类的成员。
c) 外部类实例与内部类实例之间是一对多的关系,一个内部类实例只会引用一个外部类实例,而一个外部类实例对应零个或多个内部类实例。在外部类中不能直接访问内部类实例,必须通过内部类的实例去访问。
d) 在实例内部类中不能定义静态成员,而只能定义实例成员
e) 如果实例内部类B与外部类A包含同名的成员(比如成员变量v),那么在类B中,this.v表示类B的成员,A.this.v表示类A的成员
3、静态内部类有以下特点:
a) 静态内部类的实例不会自动持有外部类的特定实例的引用,在创建内部类的实例时,不必创建外部类的实例
b) 静态内部类可以直接访问外部类的静态成员,如果访问外部类的实例成员,就必须通过外部类的实例去访问
c) 在静态内部类中可以定义静态成员和实例成员
d) 客户类可以通过完整的类名直接访问静态内部类的静态成员
4、局部内部类是一个在方法中定义的内部类,它的可见范围是当前方法,和局部变量一样,局部内部类不能用访问控制修饰符及static修改符来修饰。局部内部类具有以下特点:
a) 局部内部类只能在当前方法中使用。
b) 局部内部类和实例内部类一样,不能包含静态成员。
c) 在局部内部类中定义的内部类也不能被public、protected和private这些访问控制修饰符修饰。
d) 局部内部类和实例内部类一样,可以访问外部类的所有成员,此外,局部内部类还可以访问所在方法中的final类型的参数和变量。不可以访问没有被final修饰的局部变量。
二、内部类的继承
在一个类A继承某个外部类B的内部类C时,要求类A的构造方法必须通过参数传递一个类B的实例的引用,然后在构造方法中调用super语句来建立类A实例与类B实例的关联关系。示例代码如下:
class B{
public B(){}
class C{
public C(){}
}
}
public class A extends B.C{
public A(B b){
b.super();
}
public static void main(String[] args){
B b = new B();
A a = new A(b);
}
}
三、匿名类
public class A {
A(int v){
System.out.println("another constructor");
}
A(){
System.out.println("default constructor");
}
void method(){
System.out.println("from A");
}
public static void main(String[] args){
A a = new A(){ //匿名类
void method(){
System.out.println("from anonymous");
}
};
a.method();
}
}
//以上”new A(){…}”定义了一个继承类A的匿名类,大括号内是类A的类体,”new A(){…}”返回
//匿名类的一个实例的引用。
//打印结果为:
default constructor
from anonymous
匿名类本身没有构造方法,但是会调用父类的构造方法。示例代码如下:匿名类有以下特点:
public static void main(){
int v = 1;
A a = new A(v){ //匿名类
void method(){
System.out.println("from anonymous");
}
};
a.method();
}
//以下代码的打印结果为:
another constructor
from anonymous
在以上
“new A(v){…}”
中,如果参数
v
是局部变量,并且在匿名类的类体中会使用它,那么
v
必须是
final
类型,否则会导致编译错误。public static void main(){
int v = 1; //编译错误,v必须定义为final类型
A a = new A(v){ //匿名类
void method(){
System.out.println("from anonymous"+v); //使用局部变量v
}
};
a.method();
}
匿名类尽管没有构造方法,但是可以在匿名类中提供一段实例初始化代码,Java虚拟机会在调用了父类的构造方法后,执行这段代码。示例如下:
public static void main(){
int v = 1;
A a = new A(v){ //匿名类
{ //实例初始化代码---开始
System.out.println("initialize instance");
} //实例初始化代码---结束
void method(){
System.out.println("from anonymous");
}
};
a.method();
}
//打印结果如下:
another constructor
initialize instance
from anonymous
除了可以在外部类的方法内定义匿名类以外,还可以在声明一个成员变量时定义一个匿名类。示例代码如下:类
A
有一个实例变量
a
,它引用一个继承类
A
的匿名类的实例。注意:匿名类的实例只能有一种初始化方式。
abstract class A{
A a = new A(){
void method(){
System.out.println("inner");
}
};
abstract void method();
}
匿名类和局部内部类一样,可以访问外部类的所有成员,如果匿名类位于一个方法中,还能访问所在方法的
final
类型的变量和参数。匿名类除了可以继承类以外,还可以实现接口,示例代码如下:
public class Sample {
public static void main(){
Thread t = new Thread(new Runnable(){
public void run(){
System.out.println("hi");
}
});
t.start();
}
}
局部内部类的名字在方法外是不可见的,因此与匿名类一样,能够起到封装类型名字的作用,局部内部类与匿名类有以下区别:
匿名类和程序代码比较简短。
一个局部内部类可以有多个重载构造方法,并且客户类可以多次创建局部内部类的实例。而匿名类没有重载构造方法,并且只能创建一次实例。
四、内部接口及接口中的内部类
在一个类中也可以定义内部接口。
在接口中可以定义静态内部类,此时静态内部类位于接口的命名空间中。
五、内部类的类文件
对于每个内部类来说,Java编译器会生成独立的.class文件。这此类文件的命名规则如下:
成员内部类:外部类的名字$内部类的名字
局部内部类:外部类的名字$数字$内部类的名字
匿名类:外部类的名字$数字
六、内部类的特殊用法
从一个内部类的实例拿到所在外部类的实例可以使用this$0默认字段,this$0就是内部类自动保留的一个指向所在外部类的引用。
package com.dailey.example.java.innerclass;
import java.lang.reflect.Field;
public class Outer {//this$0
public class FirstInner {//this$1
public class SecondInner {//this$2
public class ThirdInner {
}
}
}
public static void main(String[] args) throws Exception,
NoSuchFieldException {
Outer test = new Outer();
FirstInner first = test.new FirstInner();
FirstInner.SecondInner second = first.new SecondInner();
FirstInner.SecondInner.ThirdInner third=second.new ThirdInner();
//Outer
Field outerfield = first.getClass().getDeclaredField("this$0");
outerfield.setAccessible(true);
Object object = outerfield.get(first);
System.out.println(object instanceof Outer);
//FirstInner
Field firstInnerfied = second.getClass().getDeclaredField("this$1");
firstInnerfied.setAccessible(true);
object = firstInnerfied.get(second);
System.out.println(object instanceof FirstInner);
//SecondInner
Field secondInnerfield = third.getClass().getDeclaredField("this$2");
secondInnerfield.setAccessible(true);
object = secondInnerfield.get(third);
System.out.println(object instanceof FirstInner.SecondInner);
}
}
用
Java 反编译工具生成以上内部类代码可以看出:每个内部类都有一个指向所在最近的外部类的一个引用
,只不过该引用不需要手动添加,编译器会负责public class Outer$FirstInner {// FirstInner内部类有一个带外部类参数的构造子
public Outer$FirstInner(Outer paramOuter) {
}
// ....
}
public class Outer$FirstInner$SecondInner {
public Outer$FirstInner$SecondInner(Outer.FirstInner paramFirstInner) {
}
// ....
}
public class Outer$FirstInner$SecondInner$ThirdInner {
public Outer$FirstInner$SecondInner$ThirdInner(
Outer.FirstInner.SecondInner paramSecondInner) {
}
}