内部类学习笔记

内部类讲解

内部类有两种情况:

  • 在类中定义一个类(私有内部类,静态内部类)
  • 在方法中定义一个类(局部内部类,匿名内部类)

私有内部类

1.在外部类的作用范围内可以任意创建内部类对象,即使内部类是私有的(私有内部类)。即内部类对包围它的外部类可见。

2.在内部类中可以访问其外部类的所有域,即使是私有域。即外部类对内部类可见。

问题来了:上面两个特点到底如何办到的呢?内部类的”内部”到底发生了什么?

其实,内部类是Java编译器一手操办的。虚拟机并不知道内部类与常规类有什么不同。对内部类进行编译后发现有两个class文件:Outer.class和Outer@Inner.class.这说明内部类Inner仍然被编译成一个独立的类(Outer@Inner.class),而不是Outer类的某一个域。虚拟机运行的时候,也是把Inner作为一个常规类来处理的。

那么,为什么外部类可以创建内部类的对象?

首先编译器将外、内部类编译后放在同一个包中。在内部类中附加一个包可见构造器。这样,虚拟机运行Outer类中Inner in=new Inner(); 实际上调用的是包可见构造: new Outer@Inner(this,null)。因此即使是private内部类,也会通过隐含的包可见构造器成功的获得私有内部类的构造权限。再者,Outer@Inner类中有一个指向外部类Outer的引用this@0,那么通过这个引用就可以方便的得到外部类对象中可见成员。

但是Outer类中的private成员是如何访问到的呢?这就要看看下面Outer.class文件中的秘密了。那么,为什么内部类可以引用外部类的私有域?

原因的关键就在编译器在外围类中添加了静态方法access@0。它将返回值作为参数传递给他的对象域data。

这样内部类Inner中的打印语句: System.out.println(data);

实际上运行的时候调用的是:System.out.println(this@0.access@0(Outer));

总结一下编译器对类中内部类做的手脚:

  • 在内部类中偷偷摸摸的创建了包可见构造器,从而使外部类获得了创建权限。
  • 在外部类中偷偷摸摸的创建了访问私有变量的静态方法,从而 使 内部类获得了访问权限。

这样,类中定义的内部类无论私有,公有,静态都可以被包围它的外部类所访问。

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class OuterClass {
private String name ;
private int age;
/**省略getter和setter方法**/
public class InnerClass{
public InnerClass(){
name = "chenssy";
age = 23;
}
public void display(){
System.out.println("name:" + getName() +" ;age:" + getAge());
}
public OuterClass getOuterClass(){
return OuterClass.this;
}
}
public static void main(String[] args) {
OuterClass outerClass = new OuterClass();
OuterClass.InnerClass innerClass = outerClass.new InnerClass();
innerClass.display();
}
}

静态内部类

静态内部类和私有内部类最大的区别在于,静态内部类中无法引用到其外围类的非静态成员.也就是说静态内部类只能访问其外围类的静态成员,除此之外与非静态内部类没有任何区别。

局部内部类

局部内部类是在方法中定义的内部类,有两个特点:

  • 方法中的内部类没有修饰符号,就是说方法中的内部类对包围它的方法之外的任何东西都不可见。
  • 方法内部类只能够访问该方法中的局部变量,所以也叫局部内部类。而且这些局部变量一定要是final修饰的常量。

代码示例:

1
2
3
4
5
6
7
8
9
class Outter{
public void outMethod(){
final int beep=0;
class Inner{
//使用beep
}
Inner in=new Inner();
}
}

我们可以这样解释Inner类中的这个备份常量域,首先当JVM运行到需要创建Inner对象之后,Outter类已经全部运行完毕,这是垃圾回收机制很有可能释放掉局部变量beep。那么Inner类到哪去找beep变量呢?

编译器又出来帮我们解决了这个问题,他在Inner类中创建了一个beep的备份 ,也就是说即使Ouuter中的beep被回收了,Inner中还有一个备份存在,自然就不怕找不到了。

但是问题又来了。如果Outter中的beep不停的在变化那。那岂不是也要让备份的beep变量无时无刻的变化。为了保持局部变量与局部内部类中备份域保持一致。 编译器不得不规定死这些局部域必须是常量,一旦赋值不能再发生变化了。

所以为什么局部内部类应用外部方法的域必须是常量域的原因所在了。

总结内部类的特点

(1) 在方法间定义的非静态内部类:

● 外围类和内部类可互相访问自己的私有成员。

● 内部类中不能定义静态成员变量。

(2) 在方法间定义的静态内部类:

● 只能访问外部类的静态成员。

(3) 在方法中定义的局部内部类:

● 该内部类没有任何的访问控制权限

● 外围类看不见方法中的局部内部类的,但是局部内部类可以访问外围类的任何成员。

● 方法体中可以访问局部内部类,但是访问语句必须在定义局部内部类之后。

● 局部内部类只能访问方法体中的常量,即用final修饰的成员。

(4) 在方法中定义的匿名内部类:

● 没有构造器,取而代之的是将构造器参数传递给超类构造器。

补充

匿名内部类的作用

除了匿名只使用一次之外,当你想使用一个类的protected方法时,但是又不和这个类在同一个包下,你是没办法调用的。这时候匿名类就派上用场了,你可以声明一个匿名类继承该类,并定义一个方法,在这个方法内使用super调用你想调用的那个方法(其实你也可以写个类继承这个类,就能调用父类的protected方法了,但是匿名类更简洁,因为你只想调用这个方法而已)

代码示例:

1
2
3
4
5
6
public class TestClass{
protected void test{
System.out.println("test");
}
}

这个类有一个protected方法test,如果你在其他包下想调用这个protected方法是不行的,可以发现没有提示test方法.这时候你可以使用匿名类继承这个类,定义一个方法callParentTest(),在这个方法体内调用super.test(),最后调用这个callParentTest()即可。

1
2
3
4
5
6
7
8
9
public static void main(String args[]){
new TestClass(){
void callParentTest(){
super.test();
}
}.callParentTest();
}
0%