内部类是什么?
内部类是定义在另一个类中的类,使用场景:
- 内部类方法可以访问该类定义所在的作用域中的数据
- 内部类可以对同一个包中的其他类隐藏起来
- 使用匿名内部类可以减少代码的书写
访问外部数据
内部类Coat可访问外部类Person的age域,或通过Person.this显示调用
class Person {
private int age;
public class Coat {
void printAge() {
System.out.println(age);
System.out.println(Person.this.age);
}
}
}
为什么?因为内部类存在外部类的隐式引用,其在内部类的构造方法中设置,利用反射还原内部类,可以看到有个指向Person的this$0和对其赋值的构造方法
public class com.example.demo0.Person$Coat {
final com.example.demo0.Person this$0;
public com.example.demo0.Person$Coat(com.example.demo0.Person);
public void printAge();
}
那内部类是如何调用外部类私有变量age的呢?利用反射还原外部类,可以看到多出一个access$000()静态方法,它将返回接收参数所对应的对象中的域
class com.example.demo0.Person {
private int age;
com.example.demo0.Person();
static int access$000(com.example.demo0.Person);
}
即System.out.println(age)会转为System.out.println(Person.access$000(this$0))
创建外部类和内部类
外部类创建和普通类一样,而内部类则需要通过outerObject.new InnerClass()创建
Person person = new Person();
Person.Coat coat = person.new Coat();
局部内部类
局部内部类将类定义在方法里,不能用访问修饰符修饰,其作用域在{}块中,除方法体外无任何地方可以访问
class Person {
public void setCoat(int coat) {
class Coat {
public void printCoat() {
System.out.println(coat);
}
}
}
}
局部内部类可以访问final类型的局部变量(coat),在java 8之前必须显示声明为final,利用反射还原局部内部类,可以看到局部内部类会创建val$coat域用于存储方法的参数
public class com.example.demo0.Person$Coat {
final int val$coat;
final com.example.demo0.Person this$0;
public com.example.demo0.Person$Coat(com.example.demo0.Person,int);
public void printCoat();
}
如果在要局部内部类中修改变量,可使用长度为1的数组间接修改
class Person {
public static void change(int coat) {
int[] num = new int[1];
num[0] = 1;
class Coat {
public void printNum() {
num[0] += 1;
}
}
}
}
匿名内部类
对于继承类或实现接口的内部类,且该内部类只创建一次对象,可不用对其命名,转为匿名内部类
class ActionInterface {
public ActionInterface(String para) {
System.out.println(para);
}
void action() {
}
}
class Person {
public void printAction() {
class ActionImpl extends ActionInterface {
public ActionImpl(String para) {
super(para);
}
public void action() {
System.out.println("action");
}
}
ActionImpl actionImpl = new ActionImpl();
actionImpl.action();
}
}
将上述代码转为下面的匿名内部类
- 因为构造方法同类名,匿名内部类没有类名,故也不能有构造方法,会将构造方法的参数para传给父类构造方法
- 对匿名内部类调用getClass()会得到class PackageName.OuterClassName$1(如下为class com.example.demo0.Person$1),再调用getEnclosingClass()可获取其外部类对象
class Person {
public void printAction() {
ActionInterface actionImpl = new ActionInterface("para") {
@Override
public void action() {
System.out.println("action");
System.out.println(this.getClass());
System.out.println(this.getClass().getEnclosingClass());
}
};
actionImpl.action();
}
}
利用上面的第二点性质,可实现在静态方法中获取类对象,因为静态方法无this指针,故可创建内部类再间接获取其外部类对象
class Person {
public static void printClass() {
//System.out.println(this.getClass());
System.out.println(new Person(){}.getClass().getEnclosingClass());
}
}
利用匿名内部类,我们可以实现双括号初始化,如下先创建ArrayList匿名内部类,再使用构造代码块初始化
ArrayList<String> name = new ArrayList<>();
name.add("tom");
name.add("john");
new ArrayList<String>() {{
add("tom");
add("john");
}};
静态内部类
上面说到在内部类中存在对外部类的引用OuterClass this$0,如果不需要此引用,可将内部类置为静态内部类
class Person {
static class Coat {
}
}
利用反射还原类,只有默认生成的无参构造方法,静态内部类没有对外部类的this$0引用
static class com.example.demo0.Person$Coat {
com.example.demo0.Person$Coat();
}
Tips:接口中的内部类自动声明为static和public