定义:
大部分时候,类是一个独立的程序单元,但是在一些情况下,会把一个类放进另外一个类内部进行定义,这个定义在其他类中的类就叫做内部类。
作用:
- 内部类提供了更好的封装
- 内部类成员可以直接访问外部类的私有数据,外部类却不能够访问内部类的实现细节。
- 改善Java类单继承的缺陷。
- 匿名内部类可以用来创建那些只用一次的类对象
下面分别对这四个作用进行说明:
1.内部类提供了更好的封装
public class Cow {
private class CowLeg{
private void print() {
System.out.println("这是一个cowLeg");
}
}
public static void main(String[] args) {
CowLeg cowLeg = new Cow().new CowLeg();
cowLeg.print();
}
}
如上述代码所示,CowLeg类只能在Cow外部类中被调用,在包中的其他类是不能够访问到的,这就提供了更好的封装。
2. 内部类成员可以直接访问外部类的私有数据,外部类却不能够访问内部类的实现细节
public class Cow {
private int cowLegCount = 4;
private class CowLeg{
private int a = 5;
private void print() {
System.out.println("这是一个cowLeg");
System.out.println("它有" + cowLegCount + "条腿");
}
}
public static void main(String[] args) {
CowLeg cowLeg = new Cow().new CowLeg();
cowLeg.print();
System.out.println(cowLeg.a);
}
}
CowLeg的对象能够访问外部类中的私有成员变量,但是外部类不能够直接访问内部类的私有成员变量,必须通过内部类的对象来进行调用。
3. 改善Java类单继承的缺陷。
因为Java是只能实现单继承的,在有内部类出现之前,一般使用的是多接口实现。但是接口要求实现其内部所有的抽象方法,不是特别灵活,而且接口中不存在成员变量。
class A{
public void testA() {
System.out.println("这是A类中方法");
}
public void doSomething() {
//do something
}
}
class B{
public void testB() {
System.out.println("这是类B中的方法");
}
}
public class test {
private class subA extends A{
private void testSubA() {
super.testA();
}
}
private class subB extends B{
private void testSubB() {
super.testB();
}
}
public static void main(String[] args) {
new test().new subA().testSubA();
new test().new subB().testSubB();
}
}
如代码所示,通过让内部类继承需要的类,就可以间接达到多继承的效果。而且只需要关注需要使用的方法,如果是使用接口,那就必须考虑实现A类中的doSomething方法了。
内部类的分类
如下图所示
内部类 (1).png
成员内部类编译出的class文件的命名是OuterClass&InnerClass.class的方式。
1. 非静态内部类
非静态内部类就相当于是类中的一个普通的成员变量。但是这个成员变量有很多特殊之处。
-
非静态内部类能够访问外部类的私有数据,但是外部类中不能够直接访问内部类中的数据,需要使用内部类的对象进行访问。原因是内部类实际上相当于一个独立的类,仅仅靠对外部类对象的引用与外部类联系一起。
引用关系.png - 静态内部类方法内访问变量的时候,查找顺序如下图所示。
查找顺序.png
如果外部类,内部类,内部类方法中存在同名变量,则可以通过this.变量名获取内部类中变量,通过外部类名.this.变量名来获取外部类变量。
public class test{
private int a = 4;
private class sub{
private int a = 44;
private void print() {
int a = 444;
System.out.println("内部类方法中a为:" +a);
System.out.println("内部类中a为:" +this.a);
System.out.println("外部类中方法为: " + test.this.a);
}
}
public static void main(String[] args) {
new test().new sub().print();
}
}
非静态内部类对象与外部类对象的联系:
非静态内部类对象必须寄生在外部类对象中,有外部类对象,不一定有内部类对象,有内部类对象,一定有外部类对象。外部类访问内部类的数据必须通过内部类对象才能够获取。此外,非静态内部类中不允许有静态的方法、成员变量、初始化块等。因为内部类实际上是通过外部类对象联系起来的。
2. 静态内部类
- 静态内部类相当于外部类的静态成员,它属于外部类本身,而不是其对象。
- 静态内部类作为一个完整的类可以拥有非静态成员。但是其不能够访问外部类的非静态成员,因为静态内部类是随着类加载初始化的,而此时外部类对象还没有出现,所以就不能够访问外部类的非静态成员。
- 外部类不能是直接访问静态内部类成员,但是可以使用静态内部类的类名来作为调用者访问静态内部类的类成员
3. 局部内部类
在方法中定义的内部类叫做局部内部类。
局部内部类只存活在方法生存的周期内,因此使用局部内部类定义变量,创建实例,派生子类的时候都只能在方法内部进行。
- 局部内类不允许使用访问权限修饰符 public private protected 均不允许, 因为其只能在方法内部使用,所以访问权限修饰符就没有意义了。
- 局部内部类对外完全隐藏,除了创建这个类的方法可以访问它其他的地方是不允许访问的。
- 局部内部类与成员内部类不同之处是他可以引用成员变量,但该成员必须声明为 final,并内部不允许修改该变量的值。(这句话并不准确,因为如果不是基本数据类型的时候,只是不允许修改引用指向的对象,而对象本身是可以被就修改的)
4.匿名内部类
- 匿名内部类是没有访问修饰符的。
- 匿名内部类必须继承一个抽象类或者实现一个接口
- 匿名内部类中不能存在任何静态成员或方法
- 匿名内部类是没有构造方法的,因为它没有类名。
- 与局部内部类相同,匿名内部类也可以引用局部变量。此变量也必须声明为 final
- 匿名内部类不能是抽象类,因为系统在创建匿名内部类的时候会立刻创建其对象。
- 匿名内部类不能够定义构造器,因为其没有类名。但是其可以定义初始化块,从而完成构造器需要做的事情。
匿名内部类的作用就是产生一个具有方法的对象,所以不需要修饰符。而引用局部变量必须使用final修饰则是因为匿名内部类是在堆内存中的,而局部变量是在方法栈中,当这个方法结束了,局部变量就会消失,而堆内存中的匿名内部类对象还引用着局部变量,这该怎么处理呢?答案就是在匿名内部类引用局部变量的同时,在匿名内部类内部对这个变量进行储存,但是如果这个局部变量的值或者储存的地址在方法中一直变化,让堆内存中对象的属性值也不断变化就会消耗资源。因此就使用final来保证局部变量在方法内是不变的。
内部类可能导致的内存泄漏问题
- 如果一个匿名内部类没有被任何引用持有,那么匿名内部类对象用完就有机会被回收。
- 如果内部类仅仅只是在外部类中被引用,当外部类的不再被引用时,外部类和内部类就可以都被GC回收。
- 如果当内部类的引用被外部类以外的其他类引用时,就会造成内部类和外部类无法被GC回收的情况,即使外部类没有被引用,因为内部类持有指向外部类的引用)。
可以理解为内部类和外部类在一定程度上是一体的(匿名内部类除外),因为对于非静态内部类来说,如果内部类对象不被回收,外部类对象肯定也不能被回收,因为它被内部类对象引用。对于静态内部类来说,其引用着外部类,所以和非静态内部类一样。局部内部类在外部类的方法中就更不用说了。
因此解决这种可能存在的内存泄漏就是在回收外部类的时候,一定先把内部类回收掉。
在Android 中 Hanlder 作为内部类使用的时候其对象被系统主线程的 Looper 持有(当然这里也可是子线程手动创建的 Looper)掌管的消息队列 MessageQueue 中的 Hanlder 发送的 Message 持有,当消息队列中有大量消息处理的需要处理,或者延迟消息需要执行的时候,创建该 Handler 的 Activity 已经退出了,但是因为Message还在引用着Handler,导致创建Handler的Avtivity对象不能够被回收,就造成了内存泄漏。
那么 Hanlder 何时会被释放,当消息队列处理完 Hanlder 携带的 message 的时候就会调用 msg.recycleUnchecked()释放Message所持有的Handler引用。
在 Android 中要想处理 Hanlder 内存泄漏可以从两个方面着手:
- 在关闭Activity/Fragment 的 onDestry,取消还在排队的Message:
- 将 Hanlder 创建为静态内部类并采用软引用方式
private static class MyHandler extends Handler {
private final WeakReference<MainActivity> mActivity;
public MyHandler(MainActivity activity) {
mActivity = new WeakReference<MainActivity>(activity);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = mActivity.get();
if (activity == null || activity.isFinishing()) {
return;
}
}
}
网友评论