一、使用内部类访问对象的内部状态
代码:
public class TestInnerClass {
public static void main(String[] args) {
Car car= new Car("Mike");
car.drive();
//切换自动驾驶模式
car.drive2();
}
}
class Car{
private String driver;
public Car(String driver){
this.driver = driver;
}
public void drive(){
Engine e = new Engine();
e.move();
}
public void drive2(){
AutoDrivingSystem as = new AutoDrivingSystem();
as.autoMove();
}
private void turnLeft(){
System.out.println(driver + " is turning left!");
}
public class Engine{
public void move(){
//内部类可以直接调用外部为的属性和方法
System.out.println(driver + " is driving!");
turnLeft();
}
}
private class AutoDrivingSystem{
public void autoMove(){
System.out.println("The car automatically moving!");
}
}
}
运行结果:
内部类可以访问创建它的外围类对象的数据域,也可以访问它的外围对象的数据域;
内部类有一个指向创建了它的外部类对象的隐式引用,这个引用在内部类的定义中是不可见的,可以理解成编译器修改了内部类的构造器,添加了一个外围类的引用参数;
如果外部类有些成员设置成私有的,就可以使用内部类;内部类可以访问外部类的私有成员;而且只有内部类可以设置成私有的,常规类只有包可见性和公有可见性,私有内部类只有外围类可以访问;
二、内部类的特殊语法规则
表示外围类的正规语法:OuterClass.this(外围类的作用范围)
更加明确地编写内部类的构造器:outerObject.new InnerClass(construction parameters);在外围类的作用范围内outerObject可以省略。
在外围类的作用域外这样引用内部类:
OuterClass.InnerClass
三、内部类是否有用、必要和安全
内部类是一种编译器现象,与虚拟机无关。因为编译器会把内部类翻译成用$(美元符号)分隔外部类名与内部类名的常规类文件,而虚拟机对此一无所知。
编译器为了引用外围类,生成了一个附加的实例域,自己编写的代码不可以引用它。
例子:
this$0就是编译器自动生成的外围类的对象引用
如果自己编写一个独立的常规类代替内部类,那么就无法访问外围类的私有域了;
内部类是怎样获得额外的访问权:
编译器会根据内部类调用了外围类的属性生成对应属性的静态方法,这个静态方法将返回作为参数传递给它的对象对应的属性。内部类调用外围类属性时相当于调用对应的静态方法。
编译器生成的静态方法有安全风险,因为生成的静态方法有包可见性,同一个包里的其他类也可以调用它,黑客可以使用十六进制编辑器创建一个用虚拟机指令调用那个方法的类文件。
四、局部内部类
如果一个类只在一个方法中使用,那么可以在那个方法中定义那个内部类,这就是局部内部类。
局部类不能用public或private访问说明符进行声明,它的作用域被限定在生命这个局部类的块中。
局部类可以对外部世界完全隐藏起来,除了所在的方法,没有任何方法知道这个类。
五、由外部方法访问final变量
与其他内部类相比,局部类还可以访问声明为final的局部变量;
public class TestPartClass {
public static void main(String[] args) {
A a = new A();
//加载类A、B、C,根据方法funcA有final修饰的形参,
// 编译器添加隐藏的属性final int val$a,用来保存final修饰的形参的备份,
// 调用构造器创建一个类A的对象
a.funcA(110);
//从类A的方法对照表里找到匹配的方法funcA(final int a)并执行
//创建类B对象,
//过程中复制形参的值到隐藏属性,
//以类B对象为参数创建一个类C对象,并赋值给类A的属性c,
//对像a的方法funcA执行完后局部变量a被销毁,副本则被保存下来
a.op();
//从类A的方法对照表里找到匹配的方法op(),
//从类的方法对照表里找到匹配的方法run(),
//打印之前保存的局部变量副本
}
}
interface X{
void run();
}
class A{
private C c;
public void funcA(final int a){
class B implements X{
public void run(){
System.out.println(a);
}
}
B b = new B();
c = new C(b);
}
public void op(){
c.func();
}
}
class C{
private X x;
C (X x){
this.x = x;
}
public void func(){
x.run();
}
}
结果:
final修饰的变量只能够赋值一次,这样保证了局部变量与副本的一致性。
使用一个长度为1的数组来作为final 局部变量更加灵活
例子:
六、匿名内部类
匿名内部类:只创建一次的局部类可以不命名,这就是匿名内部类。
语法格式:
SurperType是一个接口,内部类就要实现这个类,不能有构造参数,例子:
是一个类,内部类就要拓展这个类
七、静态内部类
静态内部类:声明为static的内部类;
静态内部类隐藏在另一个类的内部,不能引用外围类对象。
例子: