内部类可以细分为 4 种:
成员内部类
局部内部类
匿名内部类
静态内部类
这几种内部类通常我们对它只是有个模糊的印象,面试中如果被问到会突然感觉很难区分,这是因为我们平时没有对他们的特点进行总结以及对他们的使用场景进行归纳。
下面我们一起来看看这几种内部类的区别吧:
1.成员内部类
成员内部类是最普通的内部类,必须依托外部类的对象才能实例化出来,所以不能再成员内部类中声明静态的变量或者方法。
实例化栗子:
new Outer().new Inner();
同时在编译的时候,JVM 默认会生成一个带外部类形参的构造方法,也就是成员内部类天然持有外部类的引用,所以它能访问外部类的所有方法和成员变量。
举个栗子好了
public class Outer {
public int num = 10;
//内部类
class Inner {
public int num = 20;
public void show() {
int num = 30;
//访问局部变量
System.out.println(num);
//访问内部类的成员变量
System.out.println(this.num);
//访问外部类的成员变量
System.out.println(Outer.this.num);
}
}
}
2.静态内部类
使用 static
关键字修饰的内部类就是静态内部类,静态内部类和外部类没有任何关系,可以看作是和外部类平级的类。
静态内部类很干净,没有持有外部类的引用,我们要访问外部类的成员只能 new
一个外部类的对象。否则只能访问外部类的静态属性和静态方法,同理外部类只能访问内部类的静态属性和静态方法。
在不需要访问外部类成员变量的情况下,多数使用静态内部类。比如 RecyclerView
的 Adapter
里面的 ViewHolder
,经常声明为 Adapter
的静态内部类。
再举个粒子好了
public class OutClass3 {
private String name;
private int age;
public static class InnerStaticClass {
private String name;
public String getName() {
//返回内部类的成员变量
return name;
}
public int getAge() {
//构造外部类对象
return new OutClass3().age;
}
}
}
3.局部内部类
局部内部类是指在代码块或者方法中创建的类。
它和成员内部类的区别就是:局部内部类的作用域只能在其所在的代码块或者方法内,在其它地方是无法创建该类的对象。
使用场景不多。
举个梨子好了
public class OutClass4 {
private String className = "OutClass";
//代码块的局部内部类
{
class PartClassOne {
private void method() {
System.out.println("PartClassOne " + className);
}
}
new PartClassOne().method();
}
//方法中的局部内部类
public void testMethod() {
class PartClassTwo {
private void method() {
System.out.println("PartClassTwo " + className);
}
}
new PartClassTwo().method();
}
}
局部内部类也持有了外部类的引用。不过是出了它们声明的作用域后,就再也无法访问它们,可以把局部内部类理解为作用域很小的成员内部类。
4.匿名内部类
匿名内部类用得最多的地方就是回调,而且匿名内部类访问的变量必须声明为 final
。
一起看看
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
});
new OnClickListener(){ ...... }
这个就是匿名内部类,一样持有外部引用。
5.内存泄漏分析
经过前面的介绍我们知道,四种内部类中除了静态内部类,只要访问外部类的成员/方法,就会持有外部类的引用。
当内部类持有外部类的引用,同时生命周期比外部类要长(比如执行耗时任务、被其他长生命周期对象持有),就会导致外部类该被回收时无法被回收,也就是内存泄漏问题。
一个 Android 开发中常见的内部类导致内存泄露的例子:
public class MainActivity extends AppCompatActivity {
public final int LOGIN_SUCCESS = 1;
private Context mContext;
private boolean isLongTimeNoMsg;
@SuppressWarnings("HandlerLeak")
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
isLongTimeNoMsg = false;
switch (msg.what) {
case LOGIN_SUCCESS: {/
break;
}
//...
}
}
这个 Handler
持有外部类的引用,它发送的 runnable
对象,会被进一步包装为 message
对象,放入消息队列,在被执行、回收之前会一致持有引用,导致无法释放。
解决办法就是使用弱引用或者干脆将 Handler 设计为静态内部类。