一、內部類基礎
- 成員內部類
先看一段代碼
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("喵喵~~");
}
}
執(zhí)行結果
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方法)。如果內部類出現(xiàn)和外部類相同簽名的方法或者同名同類型的成員變量,那么內部類的相關數據的訪問會覆蓋外部類的相關數據。實際上這個過程有點像繼承,并且內部類還可以繼承自其他類,這就彌補了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();
- 內部類可能存在的隱患
通過上面分析,我們知道了,內部類持有了一個外部類的引用,因此容易引發(fā)內存泄漏。我們舉個例子
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退出,內存也將無法回收。因為靜態(tài)變量dog持有了Activity的一個引用,除非手動將dog置null,堆上的dog對象才會被回收,進而Activity被回收。
二、靜態(tài)內部類
- 我們稍微把上面的代碼改下
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 大哥 老虎
我們看到,外部類對靜態(tài)內部類而言,更多的只像一個命名空間
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
}
靜態(tài)內部類不持有外部類的引用。靜態(tài)內部類可以訪問外部類的一切靜態(tài)變量和方法。
三、匿名內部類
- 看代碼
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。所以一般說來,匿名內部類用于繼承其他類或者實現(xiàn)接口,并不需要增加額外的方法,只是重寫父類方法或者實現(xiàn)接口。這里需要注意的地方是,匿名內部類也持有了外部類的一個引用,因此也是存在內存泄漏的安全隱患的。比如常見的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);
// 會導致內存泄露,非靜態(tài)內部類(包括匿名內部類,比如這個 Handler 匿名內部類)會引用外部類對象 this(比如 Activity)
// 當它使用了 postDelayed 的時候,如果 Activity 已經 finish 了,而這個 handler 仍然引用著這個 Activity 就會致使內存泄漏
// 因為這個 handler 會在一段時間內繼續(xù)被 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對象又被當前線程持有,從而引發(fā)內存泄漏。解決的辦法之一是
@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對象可能已經被回收了。如果不使用靜態(tài)內部了,也不調用
handler.removeCallbacksAndMessages(null);
那么在消息回調之前TestActivity對象是絕對不會被回收的。
四、局部內部類
局部內部類比較少用,我就不說了。
五、總結
關于內部類,我們盡可能使用靜態(tài)內部類,因為這樣可以減少不必要的麻煩。如果靜態(tài)內部類想訪問外部類的數據,我們可以通過持有外部類的一個弱引用對象來達到目的。如下:
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();
}