一、内部类基础
- 成员内部类
先看一段代码
public class Tigger{
String name;
public static void main(String[] args){
Fox fox = new Fox();
Tigger tigger = new Tigger();
tigger.name = "大哥";
Tigger.PersianCat cat = tigger.new PersianCat();
fox.speak(cat.name);
cat.speak(fox.name);
}
public void speak(String name){
System.out.println("if you touch him,I will kill you,"+name);
}
class PersianCat extends Cat{
String name = "little cat";
public void speak(String who){
miao();
System.out.println("Hi,it is you "+name+"? Talk to "+who);
Tigger.this.speak(who);
}
}
}
class Fox{
String name = "fox";
public void speak(String to){
System.out.println("I am a fox,I will eat you,"+to);
}
}
class Cat{
public void miao(){
System.out.println("喵喵~~");
}
}
执行结果
I am a fox,I will eat you,little cat
喵喵~~
Hi,it is you little cat? Talk to fox
if you touch him,I will kill you,fox
我们看到,成员内部类可以访问外部类的一切成员变量和方法(包括static变量和static方法)。如果内部类出现和外部类相同签名的方法或者同名同类型的成员变量,那么内部类的相关数据的访问会覆盖外部类的相关数据。实际上这个过程有点像继承,并且内部类还可以继承自其他类,这就弥补了java不能多继承的缺点。
- 为什么成员内部类可以访问外部类的一切变量和方法?
我们看下javac Tigger.java生成的class文件
Tigger$PersianCat.class
Tigger.class
Fox.class
Cat.class
我们用javap命令看下Tigger$PersianCat.class文件
class Tigger$PersianCat extends Cat {
java.lang.String name;
final Tigger this$0;
Tigger$PersianCat(Tigger);
Code:
0: aload_0
1: aload_1
2: putfield #1 // Field this$0:LTigger;
5: aload_0
6: invokespecial #2 // Method Cat."<init>":()V
9: aload_0
10: ldc #3 // String little cat
12: putfield #4 // Field name:Ljava/lang/String;
15: return
public void speak(java.lang.String);
Code:
0: aload_0
1: invokevirtual #5 // Method miao:()V
4: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
7: aload_0
8: getfield #4 // Field name:Ljava/lang/String;
11: aload_1
12: invokedynamic #7, 0 // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
17: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
20: aload_0
21: getfield #1 // Field this$0:LTigger;
24: aload_1
25: invokevirtual #9 // Method Tigger.speak:(Ljava/lang/String;)V
28: return
}
我们看到有这样一行代码
final Tigger this$0;
这行代码的意思是,内部类需要持有外部类的一个引用,因此内部类可以访问外部类的一切成员变量和方法。也正因为如此,当我们在外部类以外想new一个内部类对象出来的时候,需要显示的将外部类对象绑定到内部类对象上
Tigger.PersianCat cat = tigger.new PersianCat();
- 内部类可能存在的隐患
通过上面分析,我们知道了,内部类持有了一个外部类的引用,因此容易引发内存泄漏。我们举个例子
public class TestActivity extends Activity {
private static Dog dog ;
class Dog {
public void say(){
System.out.println("I am lovely dog!");
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
dog = new Dog();
}
@Override
protected void onDestroy() {
super.onDestroy();
}
}
一旦TestActivity调用了onCreate方法,那么即使Activity退出,内存也将无法回收。因为静态变量dog持有了Activity的一个引用,除非手动将dog置null,堆上的dog对象才会被回收,进而Activity被回收。
二、静态内部类
- 我们稍微把上面的代码改下
public class Tigger{
String name;
public static void main(String[] args){
Fox fox = new Fox();
Tigger tigger = new Tigger();
tigger.name = "大哥";
Tigger.PersianCat cat = new Tigger.PersianCat();
fox.speak(cat.name);
cat.speak(fox.name);
}
static String elderBrother(){
return "老虎";
}
public void speak(String name){
System.out.println("if you touch him,I will kill you,"+name);
}
static class PersianCat extends Cat{
String name = "little cat";
public void speak(String who){
miao();
System.out.println("if you thouch me,I will call my 大哥 "+elderBrother());
}
}
}
class Fox{
String name = "fox";
public void speak(String to){
System.out.println("I am a fox,I will eat you,"+to);
}
}
class Cat{
public void miao(){
System.out.println("喵喵~~");
}
}
----------------------------------
I am a fox,I will eat you,little cat
喵喵~~
if you thouch me,I will call my 大哥 老虎
我们看到,外部类对静态内部类而言,更多的只像一个命名空间
Compiled from "Tigger.java"
class Tigger$PersianCat extends Cat {
java.lang.String name;
Tigger$PersianCat();
Code:
0: aload_0
1: invokespecial #1 // Method Cat."<init>":()V
4: aload_0
5: ldc #2 // String little cat
7: putfield #3 // Field name:Ljava/lang/String;
10: return
public void speak(java.lang.String);
Code:
0: aload_0
1: invokevirtual #4 // Method miao:()V
4: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
7: invokestatic #6 // Method Tigger.elderBrother:()Ljava/lang/String;
10: invokedynamic #7, 0 // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
15: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
18: return
}
静态内部类不持有外部类的引用。静态内部类可以访问外部类的一切静态变量和方法。
三、匿名内部类
- 看代码
public class MainActivity extends AppCompatActivity {
private static String TAG = "MainActivity";
public Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler = new Handler(){
@Override
public void handleMessage(Message message){
test();
}
};
}
public void test(){
}
}
mHandler所指向的对象的类就是一个匿名类。为什么这么说呢,我们看到编译生成的class文件列表里有这样一个class文件
MainActivity$1.class
我们反编译看下
Compiled from "MainActivity.java"
class com.debug.pluginhost.MainActivity$1 extends android.os.Handler {
final com.debug.pluginhost.MainActivity this$0;
com.debug.pluginhost.MainActivity$1(com.debug.pluginhost.MainActivity);
Code:
0: aload_0
1: aload_1
2: putfield #1 // Field this$0:Lcom/debug/pluginhost/MainActivity;
5: aload_0
6: invokespecial #2 // Method android/os/Handler."<init>":()V
9: return
public void handleMessage(android.os.Message);
Code:
0: aload_0
1: getfield #1 // Field this$0:Lcom/debug/pluginhost/MainActivity;
4: invokevirtual #3 // Method com/debug/pluginhost/MainActivity.test:()V
7: return
}
我们看到,mHandler所指向的对象的类是com.debug.pluginhost.MainActivity$1它继承自android.os.Handler。所以一般说来,匿名内部类用于继承其他类或者实现接口,并不需要增加额外的方法,只是重写父类方法或者实现接口。这里需要注意的地方是,匿名内部类也持有了外部类的一个引用,因此也是存在内存泄漏的安全隐患的。比如常见的Handler引起的内存泄漏。
public class TestActivity extends Activity {
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
System.out.println("===== handle message ====");
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
// 会导致内存泄露,非静态内部类(包括匿名内部类,比如这个 Handler 匿名内部类)会引用外部类对象 this(比如 Activity)
// 当它使用了 postDelayed 的时候,如果 Activity 已经 finish 了,而这个 handler 仍然引用着这个 Activity 就会致使内存泄漏
// 因为这个 handler 会在一段时间内继续被 main Looper 持有,导致引用仍然存在.
handler.sendEmptyMessageDelayed(0x123, 12000);
}
@Override
protected void onDestroy() {
super.onDestroy();
}
}
如果进入TestActivity后立即退出,这段代码肯定会引起内存泄漏。原因是变量handler持有了TestActivity的一个引用,调用
handler.sendEmptyMessageDelayed(0x123, 12000);
后,一个Message消息message又持有了handler的一个引用,message又被MessageQueue对象queue持有,queue又被当前线程的Looper对象持有,Looper对象又被当前线程持有,从而引发内存泄漏。解决的办法之一是
@Override
protected void onDestroy() {
super.onDestroy();
handler.removeCallbacksAndMessages(null);
}
在Activit退出的时候,清除所有未处理的消息和回调。
或者
private Handler handler = new MyHandler(this);
static class MyHandler extends Handler{
WeakReference<TestActivity> reference = null;
@Override
public void handleMessage(Message message){
Log.d("TestActivity","--------------");
reference.get().test();
}
public MyHandler(TestActivity testActivity){
reference = new WeakReference<TestActivity>(testActivity);
}
}
第二种方法有个问题,就是当
handler.sendEmptyMessageDelayed(0x123, 12000);
回调的时候,TestActivity对象可能已经被回收了。如果不使用静态内部了,也不调用
handler.removeCallbacksAndMessages(null);
那么在消息回调之前TestActivity对象是绝对不会被回收的。
四、局部内部类
局部内部类比较少用,我就不说了。
五、总结
关于内部类,我们尽可能使用静态内部类,因为这样可以减少不必要的麻烦。如果静态内部类想访问外部类的数据,我们可以通过持有外部类的一个弱引用对象来达到目的。如下:
public class TestActivity extends Activity {
private MyHandler myHandler = new MyHandler(this);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
}
static class MyHandler extends Handler {
private WeakReference<Activity> mWeakReference;
public MyHandler(Activity activity){
mWeakReference = new WeakReference<Activity>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Toast.makeText(mWeakReference.get(), "xxxx", Toast.LENGTH_LONG).show();
Log.d("xx", mWeakReference.get().getPackageName());
}
}
@Override
protected void onDestroy() {
super.onDestroy();
}